From 5a32d25fc621639ac3950274b912b361d68e3908 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 21 Sep 2020 15:53:20 +0300 Subject: [PATCH 1/6] Revert to release-7.0.1 --- Makefile | 2 +- Telegram/NotificationService/FetchImage.m | 5 +- Telegram/NotificationService/Serialization.m | 2 +- .../Telegram-iOS/en.lproj/Localizable.strings | 29 - .../en.lproj/Localizable.strings.orig | 5793 --------- .../Sources/AccountContext.swift | 17 +- .../Sources/ChatController.swift | 87 - .../Sources/ChatHistoryLocation.swift | 4 +- .../Sources/GalleryController.swift | 8 - .../AccountContext/Sources/MediaManager.swift | 114 - .../Sources/OpenChatMessage.swift | 15 +- .../Sources/StoredMessageFromSearchPeer.swift | 4 +- .../AvatarNode/Sources/AvatarNode.swift | 17 - submodules/ChatInterfaceState/BUCK | 22 - submodules/ChatInterfaceState/BUILD | 22 - .../Sources/ChatListSearchItemHeader.swift | 198 +- submodules/ChatListUI/BUCK | 9 - submodules/ChatListUI/BUILD | 9 - .../Sources/ChatListController.swift | 67 +- .../Sources/ChatListControllerNode.swift | 45 +- .../ChatListFilterTabContainerNode.swift | 2 +- .../Sources/ChatListSearchContainerNode.swift | 1562 +-- .../ChatListSearchFiltersContainerNode.swift | 306 - .../Sources/ChatListSearchMediaNode.swift | 1135 -- ...tListSearchMessageSelectionPanelNode.swift | 182 - .../ChatListUI/Sources/DateSuggestion.swift | 144 - .../Sources/Node/ChatListItem.swift | 8 +- .../ChatMessageInteractiveMediaBadge/BUCK | 20 - .../ChatMessageInteractiveMediaBadge/BUILD | 20 - .../Sources/ContactsPeerItem.swift | 48 +- .../DeleteChatPeerActionSheetItem.swift | 2 - submodules/Display/Source/NavigationBar.swift | 96 +- .../Display/Source/TabBarController.swift | 4 +- .../Display/Source/ViewController.swift | 3 - submodules/FileMediaResourceStatus/BUCK | 23 - submodules/FileMediaResourceStatus/BUILD | 22 - submodules/GalleryData/BUCK | 31 - submodules/GalleryData/BUILD | 29 - .../GalleryData/Sources/GalleryData.swift | 296 - .../GalleryUI/Sources/GalleryController.swift | 246 +- .../GalleryUI/Sources/GalleryItemNode.swift | 3 - .../Items/UniversalVideoGalleryItem.swift | 10 +- submodules/HashtagSearchUI/BUCK | 1 - submodules/HashtagSearchUI/BUILD | 1 - .../Sources/HashtagSearchController.swift | 26 +- .../Sources/HashtagSearchControllerNode.swift | 2 - .../Sources/InstantPageController.swift | 50 - .../Sources/InstantPageLayout.swift | 3 - .../Sources/ItemListPeerItem.swift | 4 - .../Sources/LegacyChatImport.swift | 2 +- submodules/ListMessageItem/BUCK | 36 - submodules/ListMessageItem/BUILD | 38 - .../Sources/LiveLocationManager.swift | 2 +- submodules/MediaResources/BUCK | 1 - submodules/MediaResources/BUILD | 1 - submodules/MtProtoKit/BUCK | 4 +- submodules/MtProtoKit/BUILD | 4 +- .../MtProtoKit/MTBindKeyMessageService.h | 9 - .../PublicHeaders/MtProtoKit/MTContext.h | 10 +- .../MtProtoKit/MTDatacenterAuthAction.h | 15 +- .../MtProtoKit/MTDatacenterAuthInfo.h | 19 +- .../MtProtoKit/MTMessageService.h | 6 +- .../PublicHeaders/MtProtoKit/MTProto.h | 2 - .../PublicHeaders/MtProtoKit/MTProtoEngine.h | 13 - .../MtProtoKit/MTProtoInstance.h | 12 - .../MtProtoKit/MTProtoPersistenceInterface.h | 14 - .../Sources/MTBindKeyMessageService.m | 156 - submodules/MtProtoKit/Sources/MTContext.m | 181 +- .../Sources/MTDatacenterAuthAction.m | 184 +- .../MtProtoKit/Sources/MTDatacenterAuthInfo.m | 36 +- .../Sources/MTDatacenterAuthMessageService.m | 6 +- .../Sources/MTDiscoverConnectionSignals.m | 7 +- .../MTDiscoverDatacenterAddressAction.m | 6 +- submodules/MtProtoKit/Sources/MTProto.m | 699 +- submodules/MtProtoKit/Sources/MTProtoEngine.m | 52 - .../MtProtoKit/Sources/MTProtoInstance.m | 48 - .../Sources/MTRequestMessageService.m | 18 +- .../Sources/MTResendMessageService.m | 4 +- .../MtProtoKit/Sources/MTTcpTransport.m | 2 +- .../Sources/MTTimeSyncMessageService.m | 4 +- .../Sources/Utils/MTQueueLocalObject.h | 14 - .../Sources/Utils/MTQueueLocalObject.m | 56 - .../Sources/ChannelAdminController.swift | 39 +- .../Sources/ChannelAdminsController.swift | 2 +- ...hannelDiscussionGroupSetupController.swift | 6 +- .../Sources/ChannelInfoController.swift | 6 +- .../ChannelMembersSearchContainerNode.swift | 2 +- .../ChannelMembersSearchControllerNode.swift | 2 +- .../Sources/GroupInfoController.swift | 6 +- .../AdditionalMessageHistoryViewData.swift | 2 - .../Postbox/Sources/ChatListViewState.swift | 2 +- submodules/Postbox/Sources/ChatLocation.swift | 10 +- .../Sources/GlobalMessageTagsView.swift | 4 +- .../Postbox/Sources/IntermediateMessage.swift | 12 +- submodules/Postbox/Sources/Message.swift | 48 +- .../Sources/MessageHistoryHolesView.swift | 37 - .../Postbox/Sources/MessageHistoryTable.swift | 112 +- .../Sources/MessageHistoryTagsTable.swift | 4 - .../Sources/MessageHistoryThreadsTable.swift | 98 - .../Postbox/Sources/MessageHistoryView.swift | 348 +- .../Sources/MessageHistoryViewState.swift | 202 +- .../Sources/MessageOfInterestHolesView.swift | 17 +- submodules/Postbox/Sources/Postbox.swift | 220 +- submodules/Postbox/Sources/ViewTracker.swift | 7 +- submodules/PresentationDataUtils/BUCK | 1 - submodules/PresentationDataUtils/BUILD | 1 - .../Sources/OpenUrl.swift | 62 - .../SearchBarNode/Sources/SearchBarNode.swift | 398 +- .../Sources/SearchBarPlaceholderNode.swift | 4 +- .../Sources/SearchDisplayController.swift | 38 +- .../SearchDisplayControllerContentNode.swift | 9 +- .../Sources/SelectablePeerNode.swift | 3 - .../BubbleSettingsController.swift | 10 +- .../Sources/CachedFaqInstantPage.swift | 6 +- .../SettingsUI/Sources/DebugController.swift | 1 - .../ForwardPrivacyChatPreviewItem.swift | 2 +- .../Sources/Search/SettingsSearchItem.swift | 18 +- .../Sources/SettingsController.swift | 2 +- .../TextSizeSelectionController.swift | 22 +- .../ThemeAccentColorControllerNode.swift | 24 +- .../Themes/ThemeGridSearchColorsItem.swift | 31 +- .../Themes/ThemeGridSearchContentNode.swift | 55 +- .../Themes/ThemePreviewControllerNode.swift | 30 +- .../Themes/ThemeSettingsChatPreviewItem.swift | 4 +- .../Sources/Themes/WallpaperGalleryItem.swift | 4 +- .../SyncCore/Sources/CachedChannelData.swift | 44 +- .../Sources/ReplyMessageAttribute.swift | 24 +- .../Sources/ReplyThreadMessageAttribute.swift | 34 - .../Sources/TelegramChatAdminRights.swift | 2 - submodules/TelegramApi/Sources/Api0.swift | 32 +- submodules/TelegramApi/Sources/Api1.swift | 854 +- submodules/TelegramApi/Sources/Api2.swift | 36 - submodules/TelegramApi/Sources/Api3.swift | 223 +- .../Sources/TelegramBaseController.swift | 6 +- submodules/TelegramCore/Sources/Account.swift | 14 +- .../TelegramCore/Sources/AccountManager.swift | 1 - .../Sources/AccountStateManagementUtils.swift | 106 +- .../Sources/AccountViewTracker.swift | 94 +- .../ApplyMaxReadIndexInteractively.swift | 4 +- .../Sources/ApplyUpdateMessage.swift | 34 +- .../Sources/CachedChannelParticipants.swift | 23 +- .../TelegramCore/Sources/ChannelAdmins.swift | 2 +- .../Sources/ChannelOwnershipTransfer.swift | 2 +- .../Sources/ChatHistoryPreloadManager.swift | 3 +- .../TelegramCore/Sources/DeleteMessages.swift | 18 +- .../Sources/DeleteMessagesInteractively.swift | 2 +- .../TelegramCore/Sources/EnqueueMessage.swift | 49 +- .../Sources/ExportMessageLink.swift | 33 +- .../Sources/HistoryViewStateValidation.swift | 12 +- submodules/TelegramCore/Sources/Holes.swift | 225 +- ...InstallInteractiveReadMessagesAction.swift | 2 +- .../ManageChannelDiscussionGroup.swift | 20 +- .../ManagedAutoremoveMessageOperations.swift | 2 +- ...anagedConsumePersonalMessagesActions.swift | 4 +- .../Sources/ManagedMessageHistoryHoles.swift | 15 +- .../ManagedSecretChatOutgoingOperations.swift | 4 +- ...kAllUnseenPersonalMessagesOperations.swift | 2 +- ...essageContentAsConsumedInteractively.swift | 4 +- .../Sources/MessageReactions.swift | 4 +- .../TelegramCore/Sources/MessageUtils.swift | 6 +- submodules/TelegramCore/Sources/Network.swift | 29 +- .../TelegramCore/Sources/PeerAdmins.swift | 8 +- .../TelegramCore/Sources/PeerUtils.swift | 21 - .../Sources/PendingMessageManager.swift | 20 +- .../PendingMessageUploadedContent.swift | 4 +- ...ecretChatIncomingDecryptedOperations.swift | 30 +- .../Sources/ReplyThreadHistory.swift | 236 - .../Sources/RequestEditMessage.swift | 2 +- .../Sources/RequestUserPhotos.swift | 4 +- .../TelegramCore/Sources/SearchMessages.swift | 33 +- .../TelegramCore/Sources/Serialization.swift | 2 +- .../TelegramCore/Sources/SplitTest.swift | 2 +- .../Sources/StoreMessage_Telegram.swift | 242 +- .../SynchronizeAppLogEventsOperation.swift | 2 +- .../Sources/SynchronizePeerReadState.swift | 36 +- .../Sources/TelegramChannel.swift | 13 - .../UnauthorizedAccountStateManager.swift | 2 +- .../Sources/UpdateCachedPeerData.swift | 2 +- .../Sources/UpdateMessageMedia.swift | 63 +- .../Sources/UpdateMessageService.swift | 23 +- .../Sources/UpdatesApiUtils.swift | 29 +- .../TelegramCore/Sources/WebpagePreview.swift | 1 + .../Sources/ChatPresentationData.swift | 71 - .../Sources/DefaultDayPresentationTheme.swift | 8 +- .../Sources/PresentationStrings.swift | 10467 ++++++++-------- .../PresentationThemeEssentialGraphics.swift | 18 +- .../Resources/PresentationResourceKey.swift | 7 - .../Resources/PresentationResourcesChat.swift | 37 - submodules/TelegramUI/BUCK | 5 - submodules/TelegramUI/BUILD | 5 - .../Contents.json | 12 - .../Search/Arrow.imageset/Contents.json | 12 - .../Search/Arrow.imageset/ic_addresult.pdf | Bin 4138 -> 0 bytes .../Search/Calendar.imageset/Contents.json | 12 - .../ic_search_calendar (2).pdf | Bin 4358 -> 0 bytes .../Chat List/Search/Contents.json | 9 - .../Search/Files.imageset/Contents.json | 12 - .../Search/Files.imageset/ic_search_docs.pdf | Bin 4216 -> 0 bytes .../Search/Links.imageset/Contents.json | 12 - .../Search/Links.imageset/ic_search_links.pdf | Bin 4572 -> 0 bytes .../Search/M_Files.imageset/Contents.json | 12 - .../Search/M_Files.imageset/Files.png | Bin 18218 -> 0 bytes .../Search/M_Links.imageset/Contents.json | 12 - .../Search/M_Links.imageset/Links.png | Bin 17164 -> 0 bytes .../Search/M_Music.imageset/Contents.json | 12 - .../Search/M_Music.imageset/Music.png | Bin 23163 -> 0 bytes .../Search/Media.imageset/Contents.json | 12 - .../Search/Media.imageset/ic_search_media.pdf | Bin 4387 -> 0 bytes .../Search/Music.imageset/Contents.json | 12 - .../Search/Music.imageset/ic_search_music.pdf | Bin 4057 -> 0 bytes .../Search/User.imageset/Contents.json | 12 - .../Search/User.imageset/ic_search_user.pdf | Bin 4285 -> 0 bytes .../Search/Voice.imageset/Contents.json | 12 - .../Search/Voice.imageset/ic_search_voice.pdf | Bin 4187 -> 0 bytes .../Replies.imageset/Contents.json | 12 - .../Replies.imageset/ic_viewreplies.pdf | Bin 4676 -> 0 bytes .../BubbleComments.imageset/Contents.json | 12 - .../ic_leaveacomment (1).pdf | Bin 4385 -> 0 bytes .../BubbleReplies.imageset/Contents.json | 12 - .../BubbleReplies.imageset/Ic_viewinchat.pdf | Bin 4626 -> 0 bytes .../FreeRepliesIcon.imageset/Contents.json | 12 - .../replies_sticker.pdf | Bin 3999 -> 0 bytes .../Message/ReplyCount.imageset/Contents.json | 12 - .../Message/ReplyCount.imageset/replies.pdf | Bin 4117 -> 0 bytes .../Search Bar/Clear.imageset/Clear.pdf} | Bin 4083 -> 4177 bytes .../Search Bar/Clear.imageset/Contents.json | 10 +- .../Clear.imageset/ic_search_clear.pdf | Bin 4127 -> 0 bytes .../Search Bar/Loupe.imageset/Contents.json | 20 +- .../Loupe.imageset/IconSearch@2x.png | Bin 0 -> 964 bytes .../Loupe.imageset/IconSearch@3x.png | Bin 0 -> 1535 bytes .../Loupe.imageset/ic_search_search.pdf | Bin 4046 -> 0 bytes .../Contents.json | 12 - .../ic_search_color.pdf | Bin 4114 -> 0 bytes .../Animations/ChatListNoResults.tgs | Bin 8590 -> 0 bytes .../Resources/PresentationStrings.mapping | Bin 155792 -> 154970 bytes .../TelegramUI/Sources/AccountContext.swift | 39 - .../Sources/AudioWaveformNode.swift | 3 + .../ChatChannelSubscriberInputPanelNode.swift | 18 +- .../TelegramUI/Sources/ChatController.swift | 1713 ++- .../Sources/ChatControllerInteraction.swift | 87 +- .../Sources/ChatControllerNode.swift | 51 +- .../TelegramUI/Sources/ChatEmptyNode.swift | 16 +- .../Sources/ChatHistoryEntriesForView.swift | 53 - .../TelegramUI/Sources/ChatHistoryEntry.swift | 38 +- .../Sources/ChatHistoryGridNode.swift | 513 + .../Sources/ChatHistoryListNode.swift | 139 +- .../ChatHistorySearchContainerNode.swift | 5 +- .../Sources/ChatHistoryViewForLocation.swift | 50 +- submodules/TelegramUI/Sources/ChatInfo.swift | 3 + .../Sources/ChatInfoTitlePanelNode.swift | 2 - .../Sources/ChatInterfaceInputContexts.swift | 1 - .../Sources/ChatInterfaceInputNodes.swift | 2 +- .../Sources/ChatInterfaceState.swift | 129 +- .../ChatInterfaceStateContextMenus.swift | 217 +- .../ChatInterfaceStateInputPanels.swift | 31 +- .../ChatInterfaceStateNavigationButtons.swift | 48 - .../Sources/ChatMediaInputNode.swift | 7 +- .../ChatMessageAnimatedStickerItemNode.swift | 176 +- .../ChatMessageAttachedContentNode.swift | 12 +- .../ChatMessageBubbleContentNode.swift | 8 +- .../Sources/ChatMessageBubbleItemNode.swift | 278 +- .../ChatMessageCommentFooterContentNode.swift | 294 - .../ChatMessageContactBubbleContentNode.swift | 9 +- .../ChatMessageDateAndStatusNode.swift | 122 +- .../ChatMessageFileBubbleContentNode.swift | 2 +- .../ChatMessageInstantVideoItemNode.swift | 86 +- .../ChatMessageInteractiveFileNode.swift | 14 +- ...atMessageInteractiveInstantVideoNode.swift | 10 +- .../ChatMessageInteractiveMediaBadge.swift | 24 +- .../ChatMessageInteractiveMediaNode.swift | 1 - .../TelegramUI/Sources/ChatMessageItem.swift | 106 +- .../Sources/ChatMessageItemView.swift | 1 - .../ChatMessageMapBubbleContentNode.swift | 11 +- .../ChatMessageMediaBubbleContentNode.swift | 9 +- .../ChatMessagePollBubbleContentNode.swift | 51 +- ...atMessageRestrictedBubbleContentNode.swift | 9 +- .../Sources/ChatMessageStickerItemNode.swift | 83 +- .../ChatMessageTextBubbleContentNode.swift | 41 +- .../ChatMessageWebpageBubbleContentNode.swift | 78 +- .../ChatPanelInterfaceInteraction.swift | 7 +- .../ChatPinnedMessageTitlePanelNode.swift | 36 +- .../Sources/ChatPresentationData.swift | 65 + .../ChatPresentationInterfaceState.swift | 1 - .../Sources/ChatRecentActionsController.swift | 4 +- .../ChatRecentActionsControllerNode.swift | 7 +- .../ChatRecentActionsHistoryTransition.swift | 62 +- .../Sources/ChatReplyCountItem.swift | 176 - .../ChatScheduleTimeControllerNode.swift | 1 + .../Sources/ChatSearchInputPanelNode.swift | 4 - .../ChatSearchNavigationContentNode.swift | 21 +- ...ChatSendMessageActionSheetController.swift | 4 +- .../ChatTextInputMediaRecordingButton.swift | 6 +- .../Sources/ChatTextInputPanelNode.swift | 9 - .../TelegramUI/Sources/ChatTitleView.swift | 37 +- .../ContactMultiselectionController.swift | 2 - .../Sources/DeclareEncodables.swift | 2 - .../Sources/DrawingStickersScreen.swift | 1 - .../Sources/EditAccessoryPanelNode.swift | 2 +- .../Sources/FileMediaResourceStatus.swift | 16 +- .../TelegramUI/Sources/GridMessageItem.swift | 1 - .../Sources/ListMessageDateHeader.swift | 10 +- .../Sources/ListMessageFileItemNode.swift | 214 +- .../Sources/ListMessageHoleItem.swift | 0 .../Sources/ListMessageItem.swift | 68 +- .../Sources/ListMessageNode.swift | 13 +- .../Sources/ListMessageSnippetItemNode.swift | 199 +- .../Sources/ManageSharedAccountInfo.swift | 2 +- .../TelegramUI/Sources/MediaManager.swift | 1 - .../Sources/MediaPlaybackStoredState.swift | 0 .../Sources/NavigateToChatController.swift | 4 - .../TelegramUI/Sources/OpenChatMessage.swift | 283 +- .../TelegramUI/Sources/OpenResolvedUrl.swift | 5 - .../OverlayAudioPlayerController.swift | 6 +- ...wift => OverlayPlayerControllerNode.swift} | 14 +- .../PeerInfo/Panes/PeerInfoListPaneNode.swift | 8 +- .../Panes/PeerInfoVisualMediaPaneNode.swift | 2 - .../Sources/PeerInfo/PeerInfoData.swift | 4 +- .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 6 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 16 +- .../PeerMediaCollectionController.swift | 971 ++ .../PeerMediaCollectionControllerNode.swift | 546 + .../PeerMediaCollectionInterfaceState.swift | 1 - .../Sources/PeerMessagesMediaPlaylist.swift | 371 +- .../Sources/PeerSelectionControllerNode.swift | 5 +- .../PreparedChatHistoryViewTransition.swift | 1 - .../Sources/ReplyAccessoryPanelNode.swift | 2 +- .../Sources/SharedAccountContext.swift | 11 +- .../TelegramAccountAuxiliaryMethods.swift | 1 - .../TelegramUI/Sources/TextLinkHandling.swift | 5 - .../ChannelMemberCategoryListContext.swift | 2 +- .../UrlHandling/Sources/UrlHandling.swift | 45 +- .../UrlWhitelist/Sources/UrlWhitelist.swift | 56 - .../WalletUI/Resources/WalletStrings.mapping | Bin 8422 -> 8422 bytes .../WalletUI/Sources/WalletStrings.swift | 494 +- submodules/WebsiteType/BUCK | 3 - submodules/WebsiteType/BUILD | 1 - .../WebsiteType/Sources/WebsiteType.swift | 19 - 337 files changed, 12048 insertions(+), 24332 deletions(-) delete mode 100644 Telegram/Telegram-iOS/en.lproj/Localizable.strings.orig delete mode 100644 submodules/ChatInterfaceState/BUCK delete mode 100644 submodules/ChatInterfaceState/BUILD delete mode 100644 submodules/ChatListUI/Sources/ChatListSearchFiltersContainerNode.swift delete mode 100644 submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift delete mode 100644 submodules/ChatListUI/Sources/ChatListSearchMessageSelectionPanelNode.swift delete mode 100644 submodules/ChatListUI/Sources/DateSuggestion.swift delete mode 100644 submodules/ChatMessageInteractiveMediaBadge/BUCK delete mode 100644 submodules/ChatMessageInteractiveMediaBadge/BUILD delete mode 100644 submodules/FileMediaResourceStatus/BUCK delete mode 100644 submodules/FileMediaResourceStatus/BUILD delete mode 100644 submodules/GalleryData/BUCK delete mode 100644 submodules/GalleryData/BUILD delete mode 100644 submodules/GalleryData/Sources/GalleryData.swift delete mode 100644 submodules/ListMessageItem/BUCK delete mode 100644 submodules/ListMessageItem/BUILD delete mode 100644 submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTBindKeyMessageService.h delete mode 100644 submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoEngine.h delete mode 100644 submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoInstance.h delete mode 100644 submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoPersistenceInterface.h delete mode 100644 submodules/MtProtoKit/Sources/MTBindKeyMessageService.m delete mode 100644 submodules/MtProtoKit/Sources/MTProtoEngine.m delete mode 100644 submodules/MtProtoKit/Sources/MTProtoInstance.m delete mode 100644 submodules/MtProtoKit/Sources/Utils/MTQueueLocalObject.h delete mode 100644 submodules/MtProtoKit/Sources/Utils/MTQueueLocalObject.m delete mode 100644 submodules/Postbox/Sources/MessageHistoryThreadsTable.swift delete mode 100644 submodules/PresentationDataUtils/Sources/OpenUrl.swift delete mode 100644 submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift delete mode 100644 submodules/TelegramCore/Sources/ReplyThreadHistory.swift delete mode 100644 submodules/TelegramPresentationData/Sources/ChatPresentationData.swift delete mode 100644 submodules/TelegramUI/Images.xcassets/Avatar/RepliesMessagesIcon.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Arrow.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Arrow.imageset/ic_addresult.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Calendar.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Calendar.imageset/ic_search_calendar (2).pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Files.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Files.imageset/ic_search_docs.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Links.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Links.imageset/ic_search_links.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Files.png delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Links.png delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Music.png delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Media.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Media.imageset/ic_search_media.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Music.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Music.imageset/ic_search_music.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/User.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/User.imageset/ic_search_user.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Voice.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Voice.imageset/ic_search_voice.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/ic_viewreplies.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/BubbleComments.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/BubbleComments.imageset/ic_leaveacomment (1).pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/BubbleReplies.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/BubbleReplies.imageset/Ic_viewinchat.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/FreeRepliesIcon.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/FreeRepliesIcon.imageset/replies_sticker.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/replies.pdf rename submodules/TelegramUI/Images.xcassets/{Avatar/RepliesMessagesIcon.imageset/repliesavatar.pdf => Components/Search Bar/Clear.imageset/Clear.pdf} (74%) delete mode 100644 submodules/TelegramUI/Images.xcassets/Components/Search Bar/Clear.imageset/ic_search_clear.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Components/Search Bar/Loupe.imageset/IconSearch@2x.png create mode 100644 submodules/TelegramUI/Images.xcassets/Components/Search Bar/Loupe.imageset/IconSearch@3x.png delete mode 100644 submodules/TelegramUI/Images.xcassets/Components/Search Bar/Loupe.imageset/ic_search_search.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Settings/WallpaperSearchColorIcon.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Settings/WallpaperSearchColorIcon.imageset/ic_search_color.pdf delete mode 100644 submodules/TelegramUI/Resources/Animations/ChatListNoResults.tgs create mode 100644 submodules/TelegramUI/Sources/ChatHistoryGridNode.swift rename submodules/{ChatInterfaceState => TelegramUI}/Sources/ChatInterfaceState.swift (84%) delete mode 100644 submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift rename submodules/{ChatMessageInteractiveMediaBadge => TelegramUI}/Sources/ChatMessageInteractiveMediaBadge.swift (95%) create mode 100644 submodules/TelegramUI/Sources/ChatPresentationData.swift delete mode 100644 submodules/TelegramUI/Sources/ChatReplyCountItem.swift rename submodules/{FileMediaResourceStatus => TelegramUI}/Sources/FileMediaResourceStatus.swift (80%) rename submodules/{ListMessageItem => TelegramUI}/Sources/ListMessageDateHeader.swift (89%) rename submodules/{ListMessageItem => TelegramUI}/Sources/ListMessageFileItemNode.swift (81%) rename submodules/{ListMessageItem => TelegramUI}/Sources/ListMessageHoleItem.swift (100%) rename submodules/{ListMessageItem => TelegramUI}/Sources/ListMessageItem.swift (61%) rename submodules/{ListMessageItem => TelegramUI}/Sources/ListMessageNode.swift (56%) rename submodules/{ListMessageItem => TelegramUI}/Sources/ListMessageSnippetItemNode.swift (73%) rename submodules/{MediaResources => TelegramUI}/Sources/MediaPlaybackStoredState.swift (100%) rename submodules/TelegramUI/Sources/{OverlayAudioPlayerControllerNode.swift => OverlayPlayerControllerNode.swift} (94%) create mode 100644 submodules/TelegramUI/Sources/PeerMediaCollectionController.swift create mode 100644 submodules/TelegramUI/Sources/PeerMediaCollectionControllerNode.swift diff --git a/Makefile b/Makefile index 625c56dc0f..c781f082f2 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ include Utils.makefile -APP_VERSION="7.1" +APP_VERSION="7.0.1" CORE_COUNT=$(shell sysctl -n hw.logicalcpu) CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1) diff --git a/Telegram/NotificationService/FetchImage.m b/Telegram/NotificationService/FetchImage.m index 6da9bb2e90..68d971fc1e 100644 --- a/Telegram/NotificationService/FetchImage.m +++ b/Telegram/NotificationService/FetchImage.m @@ -108,8 +108,7 @@ dispatch_block_t fetchImage(BuildConfig *buildConfig, AccountProxyConnection * _ apiEnvironment = [apiEnvironment withUpdatedSocksProxySettings:[[MTSocksProxySettings alloc] initWithIp:proxyConnection.host port:(uint16_t)proxyConnection.port username:proxyConnection.username password:proxyConnection.password secret:proxyConnection.secret]]; } - MTContext *context = [[MTContext alloc] initWithSerialization:serialization encryptionProvider:[[EmptyEncryptionProvider alloc] init] apiEnvironment:apiEnvironment isTestingEnvironment:account.isTestingEnvironment useTempAuthKeys:true]; - context.tempKeyExpiration = 10 * 60 * 60; + MTContext *context = [[MTContext alloc] initWithSerialization:serialization encryptionProvider:[[EmptyEncryptionProvider alloc] init] apiEnvironment:apiEnvironment isTestingEnvironment:account.isTestingEnvironment useTempAuthKeys:false]; NSDictionary *seedAddressList = @{}; @@ -154,7 +153,7 @@ dispatch_block_t fetchImage(BuildConfig *buildConfig, AccountProxyConnection * _ for (NSNumber *datacenterId in account.datacenters) { AccountDatacenterInfo *info = account.datacenters[datacenterId]; - [context updateAuthInfoForDatacenterWithId:[datacenterId intValue] authInfo:[[MTDatacenterAuthInfo alloc] initWithAuthKey:info.masterKey.data authKeyId:info.masterKey.keyId saltSet:@[] authKeyAttributes:@{}] selector:MTDatacenterAuthInfoSelectorPersistent]; + [context updateAuthInfoForDatacenterWithId:[datacenterId intValue] authInfo:[[MTDatacenterAuthInfo alloc] initWithAuthKey:info.masterKey.data authKeyId:info.masterKey.keyId saltSet:@[] authKeyAttributes:@{} mainTempAuthKey:nil mediaTempAuthKey:nil]]; } MTProto *mtProto = [[MTProto alloc] initWithContext:context datacenterId:datacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0]; diff --git a/Telegram/NotificationService/Serialization.m b/Telegram/NotificationService/Serialization.m index f137bbe5df..ddfa890a93 100644 --- a/Telegram/NotificationService/Serialization.m +++ b/Telegram/NotificationService/Serialization.m @@ -3,7 +3,7 @@ @implementation Serialization - (NSUInteger)currentLayer { - return 119; + return 118; } - (id _Nullable)parseMessage:(NSData * _Nullable)data { diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 74585672ff..2f1cba8cc0 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -2607,7 +2607,6 @@ Unused sets are archived when you add more."; "Channel.AdminLog.CanInviteUsers" = "Add Users"; "Channel.AdminLog.CanPinMessages" = "Pin Messages"; "Channel.AdminLog.CanAddAdmins" = "Add New Admins"; -"Channel.AdminLog.CanBeAnonymous" = "Remain Anonymous"; "Channel.AdminLog.CanEditMessages" = "Edit Messages"; "Channel.AdminLog.MessageToggleInvitesOn" = "%@ enabled group invites"; @@ -5742,31 +5741,3 @@ Any member of this group will be able to see messages in the channel."; "AccessDenied.VideoCallCamera" = "Telegram needs access to your camera to make video calls.\n\nPlease go to Settings > Privacy > Camera and set Telegram to ON."; "Call.AccountIsLoggedOnCurrentDevice" = "Sorry, you can't call %@ because that account is logged in to Telegram on the device you're using for the call."; - -"ChatList.Search.FilterMedia" = "Media"; -"ChatList.Search.FilterLinks" = "Links"; -"ChatList.Search.FilterFiles" = "Files"; -"ChatList.Search.FilterMusic" = "Music"; -"ChatList.Search.FilterVoice" = "Voice"; - -"ChatList.Search.NoResults" = "No Results"; -"ChatList.Search.NoResultsQueryDescription" = "There were no results for \"%@\".\nTry a new search."; -"ChatList.Search.NoResultsDescription" = "There were no results.\nTry a new search."; - -"ChatList.Search.NoResultsFilter" = "Nothing Yet"; -"ChatList.Search.NoResultsFitlerMedia" = "Photos and videos from all your chats will be shown here."; -"ChatList.Search.NoResultsFitlerLinks" = "Links from all your chats will be shown here."; -"ChatList.Search.NoResultsFitlerFiles" = "Files from all your chats will be shown here."; -"ChatList.Search.NoResultsFitlerMusic" = "Music from all your chats will be shown here."; -"ChatList.Search.NoResultsFitlerVoice" = "Voice and video messages from all your chats will be shown here."; - -"ChatList.Search.Messages_0" = "%@ messages"; -"ChatList.Search.Messages_1" = "%@ message"; -"ChatList.Search.Messages_2" = "%@ messages"; -"ChatList.Search.Messages_3_10" = "%@ messages"; -"ChatList.Search.Messages_many" = "%@ messages"; -"ChatList.Search.Messages_any" = "%@ messages"; - -"Conversation.InputTextAnonymousPlaceholder" = "Send anonymously"; - -"DialogList.Replies" = "Replies"; diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings.orig b/Telegram/Telegram-iOS/en.lproj/Localizable.strings.orig deleted file mode 100644 index 9d490177c0..0000000000 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings.orig +++ /dev/null @@ -1,5793 +0,0 @@ -// Notifications -"PUSH_MESSAGE_TEXT" = "%1$@|%2$@"; -"PUSH_MESSAGE_NOTEXT" = "%1$@|sent you a message"; -"PUSH_MESSAGE_PHOTO" = "%1$@|sent you a photo"; -"PUSH_MESSAGE_PHOTO_SECRET" = "%1$@|sent you a self-destructing photo"; -"PUSH_MESSAGE_VIDEO" = "%1$@|sent you a video"; -"PUSH_MESSAGE_VIDEO_SECRET" = "%1$@|sent you a self-destructing video"; -"PUSH_MESSAGE_ROUND" = "%1$@|sent you a video message"; -"PUSH_MESSAGE_CONTACT" = "%1$@|shared a contact %2$@ with you"; -"PUSH_MESSAGE_GEO" = "%1$@|sent you a map"; -"PUSH_MESSAGE_GEOLIVE" = "%1$@|started sharing their live location"; -"PUSH_MESSAGE_DOC" = "%1$@|sent you a file"; -"PUSH_MESSAGE_AUDIO" = "%1$@|sent you a voice message"; -"PUSH_MESSAGE_GIF" = "%1$@|sent you a GIF"; -"PUSH_ENCRYPTED_MESSAGE" = "You have a new message%1$@"; -"PUSH_LOCKED_MESSAGE" = "You have a new message%1$@"; -"PUSH_MESSAGE_SCREENSHOT" = "%1$@|took a screenshot!"; -"PUSH_ENCRYPTION_REQUEST" = "New encryption request%1$@"; -"PUSH_ENCRYPTION_ACCEPT" = "Your encryption request was accepted%1$@"; - -"PUSH_MESSAGE_POLL" = "%1$@|sent you a poll %2$@"; -"PUSH_CHANNEL_MESSAGE_POLL" = "%1$@|posted a poll %2$@"; -"PUSH_PINNED_POLL" = "%1$@|pinned a poll"; - -"PUSH_MESSAGE_QUIZ" = "%1$@|sent you a quiz %2$@"; -"PUSH_CHANNEL_MESSAGE_QUIZ" = "%1$@|posted a quiz %2$@"; -"PUSH_PINNED_QUIZ" = "%1$@|pinned a quiz"; - -"PUSH_CHAT_MESSAGE_TEXT" = "%2$@|%1$@: %3$@"; -"PUSH_CHAT_MESSAGE_NOTEXT" = "%2$@|%1$@ sent a message"; -"PUSH_CHAT_MESSAGE_PHOTO" = "%2$@|%1$@ sent a photo"; -"PUSH_CHAT_MESSAGE_VIDEO" = "%2$@|%1$@ sent a video"; -"PUSH_CHAT_MESSAGE_ROUND" = "%2$@|%1$@ sent a video message"; -"PUSH_CHAT_MESSAGE_CONTACT" = "%2$@|%1$@ shared a contact %3$@"; -"PUSH_CHAT_MESSAGE_GEO" = "%2$@|%1$@ sent a map"; -"PUSH_CHAT_MESSAGE_GEOLIVE" = "%2$@|%1$@ started sharing their live location"; -"PUSH_CHAT_MESSAGE_DOC" = "%2$@|%1$@ sent a file"; -"PUSH_CHAT_MESSAGE_AUDIO" = "%2$@|%1$@ sent a voice message"; -"PUSH_CHAT_MESSAGE_GIF" = "%2$@|%1$@ sent a GIF"; -"PUSH_CHAT_CREATED" = "%2$@|%1$@ invited you to the group"; -"PUSH_CHAT_TITLE_EDITED" = "%2$@|%1$@ edited the group's name"; -"PUSH_CHAT_PHOTO_EDITED" = "%2$@|%1$@ edited the group's photo"; -"PUSH_CHAT_ADD_MEMBER" = "%2$@|%1$@ invited %3$@ to the group"; -"PUSH_CHAT_ADD_YOU" = "%2$@|%1$@ invited you to the group"; -"PUSH_CHAT_DELETE_YOU" = "%2$@|%1$@ removed you from the group"; -"PUSH_CHAT_DELETE_MEMBER" = "%2$@|%1$@ removed %3$@ from the group"; -"PUSH_CHAT_LEFT" = "%2$@|%1$@ left the group"; -"PUSH_CHAT_RETURNED" = "%2$@|%1$@ returned to the group"; - -"PUSH_MESSAGE_STICKER" = "%1$@|sent you a %2$@sticker"; -"PUSH_CHAT_MESSAGE_STICKER" = "%2$@|%1$@ sent a %3$@sticker"; - -"PUSH_CONTACT_JOINED" = "%1$@|joined Telegram!"; - -"PUSH_CHANNEL_MESSAGE_TEXT" = "%1$@|%2$@"; -"PUSH_CHANNEL_MESSAGE_NOTEXT" = "%1$@|posted a message"; -"PUSH_CHANNEL_MESSAGE_PHOTO" = "%1$@|posted a photo"; -"PUSH_CHANNEL_MESSAGE_VIDEO" = "%1$@|posted a video"; -"PUSH_CHANNEL_MESSAGE_ROUND" = "%1$@|posted a video message"; -"PUSH_CHANNEL_MESSAGE_DOC" = "%1$@|posted a document"; -"PUSH_CHANNEL_MESSAGE_STICKER" = "%1$@|posted a %2$@sticker"; -"PUSH_CHANNEL_MESSAGE_AUDIO" = "%1$@|posted a voice message"; -"PUSH_CHANNEL_MESSAGE_CONTACT" = "%1$@|posted a %2$@ contact"; -"PUSH_CHANNEL_MESSAGE_GEO" = "%1$@|posted a map"; -"PUSH_CHANNEL_MESSAGE_GEOLIVE" = "%1$@|posted a live location"; -"PUSH_CHANNEL_MESSAGE_GIF" = "%1$@|posted a GIF"; - -"PUSH_PINNED_TEXT" = "%1$@ pinned \"%2$@\" "; -"PUSH_PINNED_NOTEXT" = "%1$@ pinned a message"; -"PUSH_PINNED_PHOTO" = "%1$@ pinned a photo"; -"PUSH_PINNED_VIDEO" = "%1$@ pinned a video"; -"PUSH_PINNED_ROUND" = "%1$@ pinned a video message"; -"PUSH_PINNED_DOC" = "%1$@ pinned a file"; -"PUSH_PINNED_STICKER" = "%1$@ pinned a %2$@sticker"; -"PUSH_PINNED_AUDIO" = "%1$@ pinned a voice message"; -"PUSH_PINNED_GEO" = "%1$@ pinned a map"; -"PUSH_PINNED_GEOLIVE" = "%1$@ pinned a live location"; -"PUSH_PINNED_GIF" = "%1$@ pinned a GIF"; - -"PUSH_MESSAGE_GAME" = "%1$@|invited you to play %2$@"; -"PUSH_CHANNEL_MESSAGE_GAME" = "%1$@|invited you to play %2$@"; -"PUSH_CHAT_MESSAGE_GAME" = "%2$@|%1$@ invited the group to play %3$@"; -"PUSH_PINNED_GAME" = "%1$@ pinned a game"; - -"PUSH_MESSAGE_TEXT" = "%1$@|%2$@"; -"PUSH_MESSAGE_NOTEXT" = "%1$@|sent you a message"; -"PUSH_MESSAGE_PHOTO" = "%1$@|sent you a photo"; -"PUSH_MESSAGE_PHOTO_SECRET" = "%1$@|sent you a self-destructing photo"; -"PUSH_MESSAGE_VIDEO" = "%1$@|sent you a video"; -"PUSH_MESSAGE_VIDEO_SECRET" = "%1$@|sent you a self-destructing video"; -"PUSH_MESSAGE_SCREENSHOT" = "%1$@|took a screenshot"; -"PUSH_MESSAGE_ROUND" = "%1$@|sent you a video message"; -"PUSH_MESSAGE_DOC" = "%1$@|sent you a file"; -"PUSH_MESSAGE_STICKER" = "%1$@|sent you a %2$@sticker"; -"PUSH_MESSAGE_AUDIO" = "%1$@|sent you a voice message"; -"PUSH_MESSAGE_CONTACT" = "%1$@|shared a contact with you"; -"PUSH_MESSAGE_GEO" = "%1$@|sent you a map"; -"PUSH_MESSAGE_GEOLIVE" = "%1$@|started sharing their live location"; -"PUSH_MESSAGE_POLL" = "%1$@|sent you a poll"; -"PUSH_MESSAGE_QUIZ" = "%1$@|sent you a quiz"; -"PUSH_MESSAGE_GIF" = "%1$@|sent you a GIF"; -"PUSH_MESSAGE_GAME" = "%1$@|invited you to play %2$@"; -"PUSH_MESSAGE_INVOICE" = "%1$@|sent you an invoice for %2$@"; -"PUSH_MESSAGE_FWD" = "%1$@|forwarded you a message"; -"PUSH_MESSAGE_FWDS_1" = "%1$@|forwarded you a message"; -"PUSH_MESSAGE_FWDS_any" = "%1$@|forwarded you %2$d messages"; -"PUSH_MESSAGE_PHOTO" = "%1$@|sent you a photo"; -"PUSH_MESSAGE_PHOTOS_1" = "%1$@|sent you a photo"; -"PUSH_MESSAGE_PHOTOS_any" = "%1$@|sent you %2$d photos"; -"PUSH_MESSAGE_VIDEO" = "%1$@|sent you a video"; -"PUSH_MESSAGE_VIDEOS_1" = "%1$@|sent you a video"; -"PUSH_MESSAGE_VIDEOS_any" = "%1$@|sent you %2$d videos"; -"PUSH_MESSAGE_ROUND" = "%1$@|sent you a video message"; -"PUSH_MESSAGE_ROUNDS_1" = "%1$@|sent you a video message"; -"PUSH_MESSAGE_ROUNDS_any" = "%1$@|sent you %2$d video messages"; -"PUSH_MESSAGE" = "%1$@|sent you a message"; -"PUSH_MESSAGES_1" = "%1$@|sent you a message"; -"PUSH_MESSAGES_any" = "%1$@|sent you %2$d messages"; -"PUSH_ALBUM" = "%1$@|sent you an album"; - -"PUSH_CHANNEL_MESSAGE_TEXT" = "%1$@|%2$@"; -"PUSH_CHANNEL_MESSAGE_NOTEXT" = "%1$@|posted a message"; -"PUSH_CHANNEL_MESSAGE_PHOTO" = "%1$@|posted a photo"; -"PUSH_CHANNEL_MESSAGE_VIDEO" = "%1$@|posted a video"; -"PUSH_CHANNEL_MESSAGE_ROUND" = "%1$@|posted a video message"; -"PUSH_CHANNEL_MESSAGE_DOC" = "%1$@|posted a file"; -"PUSH_CHANNEL_MESSAGE_STICKER" = "%1$@|posted a %2$@sticker"; -"PUSH_CHANNEL_MESSAGE_AUDIO" = "%1$@|posted a voice message"; -"PUSH_CHANNEL_MESSAGE_CONTACT" = "%1$@|posted a contact"; -"PUSH_CHANNEL_MESSAGE_GEO" = "%1$@|posted a map"; -"PUSH_CHANNEL_MESSAGE_GEOLIVE" = "%1$@|posted a live location"; -"PUSH_CHANNEL_MESSAGE_POLL" = "%1$@|posted a poll"; -"PUSH_CHANNEL_MESSAGE_QUIZ" = "%1$@|posted a quiz"; -"PUSH_CHANNEL_MESSAGE_GIF" = "%1$@|posted a GIF"; -"PUSH_CHANNEL_MESSAGE_GAME" = "%1$@|invited you to play %2$@"; -"PUSH_CHANNEL_MESSAGE_FWD" = "%1$@|posted a forwarded message"; -"PUSH_CHANNEL_MESSAGE_FWDS_1" = "%1$@|posted a forwarded message"; -"PUSH_CHANNEL_MESSAGE_FWDS_any" = "%1$@|posted %2$d forwarded messages"; -"PUSH_CHANNEL_MESSAGE_PHOTO" = "%1$@|posted a photo"; -"PUSH_CHANNEL_MESSAGE_PHOTOS_1" = "%1$@|posted a photo"; -"PUSH_CHANNEL_MESSAGE_PHOTOS_any" = "%1$@|posted %2$d photos"; -"PUSH_CHANNEL_MESSAGE_VIDEO" = "%1$@|posted a video"; -"PUSH_CHANNEL_MESSAGE_VIDEOS_1" = "%1$@|posted a video"; -"PUSH_CHANNEL_MESSAGE_VIDEOS_any" = "%1$@|posted %2$d videos"; -"PUSH_CHANNEL_MESSAGE_ROUND" = "%1$@|posted a video message"; -"PUSH_CHANNEL_MESSAGE_ROUNDS_1" = "%1$@|posted a video message"; -"PUSH_CHANNEL_MESSAGE_ROUNDS_any" = "%1$@|posted %2$d video messages"; -"PUSH_CHANNEL_MESSAGE" = "%1$@|posted a message"; -"PUSH_CHANNEL_MESSAGES_1" = "%1$@|posted a message"; -"PUSH_CHANNEL_MESSAGES_any" = "%1$@|posted %2$d messages"; -"PUSH_CHANNEL_ALBUM" = "%1$@|posted an album"; - -"PUSH_CHAT_MESSAGE_TEXT" = "%2$@|%1$@:%3$@"; -"PUSH_CHAT_MESSAGE_NOTEXT" = "%2$@|%1$@ sent a message to the group"; -"PUSH_CHAT_MESSAGE_VIDEO" = "%2$@|%1$@ sent a video "; -"PUSH_CHAT_MESSAGE_ROUND" = "%2$@|%1$@ sent a video message"; -"PUSH_CHAT_MESSAGE_DOC" = "%2$@|%1$@ sent a file"; -"PUSH_CHAT_MESSAGE_STICKER" = "%2$@|%1$@ sent a %3$@sticker"; -"PUSH_CHAT_MESSAGE_AUDIO" = "%2$@|%1$@ sent a voice message"; -"PUSH_CHAT_MESSAGE_CONTACT" = "%2$@|%1$@ shared a contact"; -"PUSH_CHAT_MESSAGE_GEO" = "%2$@|%1$@ sent a map"; -"PUSH_CHAT_MESSAGE_GEOLIVE" = "%2$@|%1$@ started sharing their live location"; -"PUSH_CHAT_MESSAGE_POLL" = "%2$@|%1$@ sent a poll %3$@ to the group"; -"PUSH_CHAT_MESSAGE_QUIZ" = "%2$@|%1$@ sent a quiz %3$@ to the group"; -"PUSH_CHAT_MESSAGE_GIF" = "%2$@|%1$@ sent a GIF"; -"PUSH_CHAT_MESSAGE_GAME" = "%2$@|%1$@ invited the group to play %3$@"; -"PUSH_CHAT_MESSAGE_INVOICE" = "%2$@|%1$@ sent an invoice for %3$@"; -"PUSH_CHAT_CREATED" = "%2$@|%1$@ invited you to the group"; -"PUSH_CHAT_TITLE_EDITED" = "%2$@|%1$@ edited the group\'s name"; -"PUSH_CHAT_PHOTO_EDITED" = "%2$@|%1$@ edited the group\'s photo"; -"PUSH_CHAT_ADD_MEMBER" = "%2$@|%1$@ invited %3$@ to the group"; -"PUSH_CHAT_ADD_YOU" = "%2$@|%1$@ invited you to the group"; -"PUSH_CHAT_DELETE_MEMBER" = "%2$@|%1$@ kicked %3$@ from the group"; -"PUSH_CHAT_DELETE_YOU" = "%2$@|%1$@ kicked you from the group "; -"PUSH_CHAT_LEFT" = "%2$@|%1$@ has left the group"; -"PUSH_CHAT_RETURNED" = "%2$@|%1$@ has returned to the group"; -"PUSH_CHAT_JOINED" = "%2$@|%1$@ has joined the group"; -"PUSH_CHAT_MESSAGE_FWD" = "%2$@|%1$@ forwarded a message"; -"PUSH_CHAT_MESSAGE_FWDS_1" = "%2$@|%1$@ forwarded a message"; -"PUSH_CHAT_MESSAGE_FWDS_any" = "%2$@|%1$@ forwarded %3$d messages"; -"PUSH_CHAT_MESSAGE_PHOTO" = "%2$@|%1$@ sent a photo"; -"PUSH_CHAT_MESSAGE_PHOTOS_1" = "%2$@|%1$@ sent a photo"; -"PUSH_CHAT_MESSAGE_PHOTOS_any" = "%2$@|%1$@ sent %3$d photos"; -"PUSH_CHAT_MESSAGE_VIDEO" = "%2$@|%1$@ sent a video"; -"PUSH_CHAT_MESSAGE_VIDEOS_1" = "%2$@|%1$@ sent a video"; -"PUSH_CHAT_MESSAGE_VIDEOS_any" = "%2$@|%1$@ sent %3$d videos"; -"PUSH_CHAT_MESSAGE_ROUND" = "%2$@|%1$@ sent a video message"; -"PUSH_CHAT_MESSAGE_ROUNDS_1" = "%2$@|%1$@ sent a video message"; -"PUSH_CHAT_MESSAGE_ROUNDS_any" = "%2$@|%1$@ sent %3$d video messages"; -"PUSH_CHAT_MESSAGE" = "%2$@|%1$@ sent a message"; -"PUSH_CHAT_MESSAGES_1" = "%2$@|%1$@ sent a message"; -"PUSH_CHAT_MESSAGES_any" = "%2$@|%1$@ sent %3$d messages"; -"PUSH_CHAT_ALBUM" = "%2$@|%1$@ sent an album"; - -"PUSH_PINNED_TEXT" = "%1$@|pinned \"%2$@\" "; -"PUSH_PINNED_NOTEXT" = "%1$@|pinned a message"; -"PUSH_PINNED_PHOTO" = "%1$@|pinned a photo"; -"PUSH_PINNED_VIDEO" = "%1$@|pinned a video"; -"PUSH_PINNED_ROUND" = "%1$@|pinned a video message"; -"PUSH_PINNED_DOC" = "%1$@|pinned a file"; -"PUSH_PINNED_STICKER" = "%1$@|pinned a %2$@sticker"; -"PUSH_PINNED_AUDIO" = "%1$@|pinned a voice message"; -"PUSH_PINNED_CONTACT" = "%1$@|pinned a %2$@ contact"; -"PUSH_PINNED_GEO" = "%1$@|pinned a map"; -"PUSH_PINNED_GEOLIVE" = "%1$@|pinned a live location"; -"PUSH_PINNED_POLL" = "|%1$@|pinned a poll %2$@"; -"PUSH_PINNED_QUIZ" = "|%1$@|pinned a quiz %2$@"; -"PUSH_PINNED_GAME" = "%1$@|pinned a game"; -"PUSH_PINNED_INVOICE" = "%1$@|pinned an invoice"; -"PUSH_PINNED_GIF" = "%1$@|pinned a GIF"; - -"PUSH_CONTACT_JOINED" = "%1$@|joined Telegram!"; - -"PUSH_AUTH_UNKNOWN" = "New login|from unrecognized device %1$@"; -"PUSH_AUTH_REGION" = "New login|from unrecognized device %1$@, location: %2$@"; - -"PUSH_PHONE_CALL_REQUEST" = "%1$@|is calling you!"; -"PUSH_VIDEO_CALL_REQUEST" = "%1$@|is calling you!"; -"PUSH_PHONE_CALL_MISSED" = "%1$@|You missed a call"; -"PUSH_VIDEO_CALL_MISSED" = "%1$@|You missed a video call"; - -"PUSH_MESSAGE_GAME_SCORE" = "%1$@ scored %3$@ in game %2$@"; -"PUSH_MESSAGE_VIDEOS" = "%1$@ sent you %2$@ videos"; -"PUSH_MESSAGE_CHANNEL_MESSAGE_GAME_SCORE" = "%1$@ scored %3$@ in game %2$@"; -"PUSH_CHANNEL_MESSAGE_VIDEOS" = "%1$@ posted %2$@ videos"; -"PUSH_PINNED_GAME_SCORE" = "%1$@ pinned a game score"; -"PUSH_CHAT_MESSAGE_GAME_SCORE" = "%1$@ scored %4$@ in game %3$@ in the group %2$@"; -"PUSH_CHAT_MESSAGE_VIDEOS" = "%1$@ sent %3$@ videos to the group %2$@"; - -"PUSH_REMINDER_TITLE" = "🗓 Reminder"; -"PUSH_SENDER_YOU" = "📅 You"; - -"LOCAL_MESSAGE_FWDS" = "%1$@ forwarded you %2$d messages"; -"LOCAL_CHANNEL_MESSAGE_FWDS" = "%1$@ posted %2$d forwarded messages"; -"LOCAL_CHAT_MESSAGE_FWDS" = "%1$@ forwarded %2$d messages"; - -// Common -"Common.OK" = "OK"; -"Common.Cancel" = "Cancel"; -"Common.Edit" = "Edit"; -"Common.edit" = "edit"; -"Common.Done" = "Done"; -"Common.Next" = "Next"; -"Common.Delete" = "Delete"; -"Common.Create" = "Create"; -"Common.Back" = "Back"; -"Common.Close" = "Close"; -"Common.Yes" = "Yes"; -"Common.No" = "No"; -"Common.TakePhotoOrVideo" = "Take Photo or Video"; -"Common.TakePhoto" = "Take Photo"; -"Common.ChoosePhoto" = "Choose Photo"; -"Common.of" = "of"; -"Common.Search" = "Search"; -"Common.More" = "More"; -"Common.Select" = "Select"; -"Items.NOfM" = "%1$@ of %2$@"; - -// State -"State.Connecting" = "Connecting..."; -"State.connecting" = "connecting..."; -"State.ConnectingToProxy" = "Connecting to Proxy..."; -"State.ConnectingToProxyInfo" = "tap here for settings"; -"State.Updating" = "Updating..."; -"State.WaitingForNetwork" = "Waiting for network"; - -"ChatState.Connecting" = "connecting..."; -"ChatState.ConnectingToProxy" = "connecting to proxy..."; -"ChatState.Updating" = "updating..."; -"ChatState.WaitingForNetwork" = "waiting for network..."; - -// Presence -"Presence.online" = "online"; - -// Date -"Month.GenJanuary" = "January"; -"Month.GenFebruary" = "February"; -"Month.GenMarch" = "March"; -"Month.GenApril" = "April"; -"Month.GenMay" = "May"; -"Month.GenJune" = "June"; -"Month.GenJuly" = "July"; -"Month.GenAugust" = "August"; -"Month.GenSeptember" = "September"; -"Month.GenOctober" = "October"; -"Month.GenNovember" = "November"; -"Month.GenDecember" = "December"; -"Month.ShortJanuary" = "Jan"; -"Month.ShortFebruary" = "Feb"; -"Month.ShortMarch" = "Mar"; -"Month.ShortApril" = "Apr"; -"Month.ShortMay" = "May"; -"Month.ShortJune" = "Jun"; -"Month.ShortJuly" = "Jul"; -"Month.ShortAugust" = "Aug"; -"Month.ShortSeptember" = "Sep"; -"Month.ShortOctober" = "Oct"; -"Month.ShortNovember" = "Nov"; -"Month.ShortDecember" = "Dec"; -"Weekday.ShortMonday" = "Mon"; -"Weekday.ShortTuesday" = "Tue"; -"Weekday.ShortWednesday" = "Wed"; -"Weekday.ShortThursday" = "Thu"; -"Weekday.ShortFriday" = "Fri"; -"Weekday.ShortSaturday" = "Sat"; -"Weekday.ShortSunday" = "Sun"; -"Weekday.Today" = "Today"; -"Weekday.Yesterday" = "Yesterday"; - -"Time.TodayAt" = "today at %@"; -"Time.YesterdayAt" = "yesterday at %@"; - -"LastSeen.JustNow" = "last seen just now"; -"LastSeen.MinutesAgo_0" = "last seen %@ minutes ago"; //three to ten -"LastSeen.MinutesAgo_1" = "last seen 1 minute ago"; //one -"LastSeen.MinutesAgo_2" = "last seen 2 minutes ago"; //two -"LastSeen.MinutesAgo_3_10" = "last seen %@ minutes ago"; //three to ten -"LastSeen.MinutesAgo_many" = "last seen %@ minutes ago"; // more than ten -"LastSeen.MinutesAgo_any" = "last seen %@ minutes ago"; // more than ten -"LastSeen.HoursAgo_0" = "last seen %@ hours ago"; -"LastSeen.HoursAgo_1" = "last seen 1 hour ago"; -"LastSeen.HoursAgo_2" = "last seen 2 hours ago"; -"LastSeen.HoursAgo_3_10" = "last seen %@ hours ago"; -"LastSeen.HoursAgo_any" = "last seen %@ hours ago"; -"LastSeen.HoursAgo_many" = "last seen %@ hours ago"; -"LastSeen.HoursAgo_0" = "last seen %@ hours ago"; -"LastSeen.YesterdayAt" = "last seen yesterday at %@"; -"LastSeen.AtDate" = "last seen %@"; -"LastSeen.TodayAt" = "last seen today at %@"; -"LastSeen.Lately" = "last seen recently"; -"LastSeen.WithinAWeek" = "last seen within a week"; -"LastSeen.WithinAMonth" = "last seen within a month"; -"LastSeen.ALongTimeAgo" = "last seen a long time ago"; -"LastSeen.Offline" = "offline"; - -"Date.DialogDateFormat" = "{month} {day}"; -"Date.ChatDateHeader" = "%1$@ %2$@"; -"Date.ChatDateHeaderYear" = "%1$@ %2$@, %3$@"; - -// Tour -"Tour.Title1" = "Telegram"; -"Tour.Text1" = "The world's **fastest** messaging app.\nIt is **free** and **secure**."; - -"Tour.Title2" = "Fast"; -"Tour.Text2" = "**Telegram** delivers messages\nfaster than any other application."; - -"Tour.Title3" = "Powerful"; -"Tour.Text3" = "**Telegram** has no limits on\nthe size of your chats and media."; - -"Tour.Title4" = "Secure"; -"Tour.Text4" = "**Telegram** keeps your messages\nsafe from hacker attacks."; - -"Tour.Title5" = "Cloud-Based"; -"Tour.Text5" = "**Telegram** lets you access your\nmessages from multiple devices."; - -"Tour.Title6" = "Free"; -"Tour.Text6" = "**Telegram** is free forever. No ads.\nNo subscription fees."; - -"Tour.StartButton" = "Start Messaging"; - -// Login -"Login.PhoneAndCountryHelp" = "Please confirm your country code and enter your phone number."; -"Login.CodeSentInternal" = "We've sent the code to the **Telegram** app on your other device"; -"Login.HaveNotReceivedCodeInternal" = "Haven't received the code?"; -"Login.CodeSentSms" = "We have sent you an SMS with the code"; -"Login.Code" = "Code"; -"Login.WillCallYou" = "Telegram will call you in %@"; -"Login.CallRequestState2" = "Requesting a call from Telegram..."; -"Login.CallRequestState3" = "Telegram dialed your number\n[Didn't get the code?]"; -"Login.EmailNotConfiguredError" = "Please set up an email account."; -"Login.EmailCodeSubject" = "%@, no code"; -"Login.EmailCodeBody" = "My phone number is:\n%@\nI can't get an activation code for Telegram."; -"Login.UnknownError" = "An error occurred. Please try again later"; -"Login.InvalidCodeError" = "You have entered an invalid code. Please try again."; -"Login.NetworkError" = "Please check your internet connection and try again."; -"Login.CodeExpiredError" = "Code expired. Please try again."; -"Login.CodeFloodError" = "Limit exceeded. Please try again later."; -"Login.InvalidPhoneError" = "Invalid phone number. Please try again."; -"Login.InvalidFirstNameError" = "Invalid first name. Please try again."; -"Login.InvalidLastNameError" = "Invalid last name. Please try again."; - -"Login.InvalidPhoneEmailSubject" = "Invalid phone number: %@"; -"Login.InvalidPhoneEmailBody" = "I'm trying to use my mobile phone number: %1$@\nBut Telegram says it's invalid. Please help.\n\nApp version: %2$@\nOS version: %3$@\nLocale: %4$@\nMNC: %5$@"; - -"Login.PhoneBannedEmailSubject" = "Banned phone number: %@"; -"Login.PhoneBannedEmailBody" = "I'm trying to use my mobile phone number: %1$@\nBut Telegram says it's banned. Please help.\n\nApp version: %2$@\nOS version: %3$@\nLocale: %4$@\nMNC: %5$@"; - -"Login.PhoneGenericEmailSubject" = "Telegram iOS error: %@"; -"Login.PhoneGenericEmailBody" = "I'm trying to use my mobile phone number: %1$@\nBut Telegram shows an error. Please help.\n\nError: %2$@\nApp version: %3$@\nOS version: %4$@\nLocale: %5$@\nMNC: %6$@"; - - -"Login.PhoneTitle" = "Your Phone"; -"Login.PhonePlaceholder" = "Your phone number"; -"Login.CountryCode" = "Country Code"; -"Login.InvalidCountryCode" = "Invalid Country Code"; - -"Login.InfoTitle" = "Your Info"; -"Login.InfoAvatarAdd" = "add"; -"Login.InfoAvatarPhoto" = "photo"; -"Login.InfoFirstNamePlaceholder" = "First Name"; -"Login.InfoLastNamePlaceholder" = "Last Name"; -"Login.InfoDeletePhoto" = "Delete Photo"; -"Login.InfoHelp" = "Enter your name and add a profile picture."; - -// Login.SelectCountry -"Login.SelectCountry.Title" = "Country"; - -// Dialog List -"DialogList.TabTitle" = "Chats"; -"DialogList.Title" = "Chats"; -"DialogList.SearchLabel" = "Search for messages or users"; -"DialogList.NoMessagesTitle" = "You have no conversations yet"; -"DialogList.NoMessagesText" = "Start messaging by pressing the pencil button in the top right corner or go to the Contacts section."; -"DialogList.SingleTypingSuffix" = "%@ is typing"; -"DialogList.SingleRecordingAudioSuffix" = "%@ is recording audio"; -"DialogList.SingleUploadingPhotoSuffix" = "%@ is sending photo"; -"DialogList.SingleUploadingVideoSuffix" = "%@ is sending video"; -"DialogList.SingleRecordingVideoMessageSuffix" = "%@ is recording video"; -"DialogList.SingleUploadingFileSuffix" = "%@ is sending file"; -"DialogList.MultipleTypingSuffix" = "%d are typing"; -"DialogList.Typing" = "typing"; -"DialogList.ClearHistoryConfirmation" = "Clear History"; -"DialogList.DeleteConversationConfirmation" = "Delete and Exit"; -"DialogList.AwaitingEncryption" = "Waiting for %@ to get online..."; -"DialogList.EncryptionRejected" = "Secret chat cancelled"; -"DialogList.EncryptionProcessing" = "Exchanging encryption keys..."; -"DialogList.EncryptedChatStartedOutgoing" = "%@ joined your secret chat."; -"DialogList.EncryptedChatStartedIncoming" = "%@ created a secret chat."; - -// Compose -"Compose.TokenListPlaceholder" = "Whom would you like to message?"; -"Compose.NewMessage" = "New Message"; -"Compose.NewGroup" = "New Group"; -"Compose.NewGroupTitle" = "New Group"; -"Compose.NewEncryptedChat" = "New Secret Chat"; -"Compose.NewEncryptedChatTitle" = "New Secret Chat"; -"Compose.Create" = "Create"; - -// Contacts -"Contacts.TabTitle" = "Contacts"; -"Contacts.Title" = "Contacts"; -"Contacts.FailedToSendInvitesMessage" = "An error occurred."; -"Contacts.AccessDeniedError" = "Telegram does not have access to your contacts"; -"Contacts.AccessDeniedHelpLandscape" = "Please go to your %@ Settings — Privacy — Contacts.\nThen select ON for Telegram."; -"Contacts.AccessDeniedHelpPortrait" = "Please go to your %@ Settings — Privacy — Contacts. Then select ON for Telegram."; -"Contacts.AccessDeniedHelpON" = "ON"; -"Contacts.InviteToTelegram" = "Invite to Telegram"; -"Contacts.InviteFriends" = "Invite Friends"; -"Contacts.SelectAll" = "Select All"; - -// Conversation -"Conversation.InputTextPlaceholder" = "Message"; -"Conversation.typing" = "typing"; -"Conversation.MessageDeliveryFailed" = "Your message was not sent. Tap \"Resend\" to send this message."; -"Conversation.MessageDialogEdit" = "Edit"; -"Conversation.MessageDialogRetry" = "Resend"; -"Conversation.MessageDialogRetryAll" = "Resend %1$d Messages"; -"Conversation.MessageDialogDelete" = "Delete"; -"Conversation.LinkDialogOpen" = "Open"; -"Conversation.LinkDialogCopy" = "Copy"; -"Conversation.ForwardTitle" = "Forward"; -"Conversation.ForwardChats" = "Chats"; -"Conversation.ForwardContacts" = "Contacts"; -"Conversation.StatusKickedFromGroup" = "you were removed from the group"; -"Conversation.StatusLeftGroup" = "you have left the group"; -"Conversation.StatusTyping" = "typing"; -"Conversation.Call" = "Call"; -"Conversation.Mute" = "Mute"; -"ChatList.Mute" = "Mute"; -"Conversation.TitleMute" = "Mute"; -"Conversation.Unmute" = "Unmute"; -"ChatList.Unmute" = "Unmute"; -"Conversation.TitleUnmute" = "Unmute"; -"Conversation.Edit" = "Edit"; -"Conversation.Info" = "Info"; -"Conversation.Search" = "Search"; -"Conversation.Unblock" = "Unblock"; -"Conversation.ClearAll" = "Delete All"; -"Conversation.Location" = "Location"; -"Conversation.Contact" = "Contact"; -"Conversation.BlockUser" = "Block User"; -"Conversation.UnblockUser" = "Unblock User"; -"Conversation.UnsupportedMedia" = "This message is not supported on your version of Telegram. Update the app to view:\nhttps://telegram.org/update"; -"Conversation.EncryptionWaiting" = "Waiting for %@ to get online..."; -"Conversation.EncryptionProcessing" = "Exchanging encryption keys..."; -"Conversation.EmptyPlaceholder" = "No messages here yet..."; -"Conversation.EncryptedPlaceholderTitleIncoming" = "%@ invited you to join a secret chat."; -"Conversation.EncryptedPlaceholderTitleOutgoing" = "You have invited %@ to join a secret chat."; -"Conversation.EncryptedDescriptionTitle" = "Secret chats:"; -"Conversation.EncryptedDescription1" = "Use end-to-end encryption"; -"Conversation.EncryptedDescription2" = "Leave no trace on our servers"; -"Conversation.EncryptedDescription3" = "Have a self-destruct timer"; -"Conversation.EncryptedDescription4" = "Do not allow forwarding"; -"Conversation.ContextMenuCopy" = "Copy"; -"Conversation.ContextMenuDelete" = "Delete"; -"Conversation.ContextMenuForward" = "Forward"; -"Conversation.ContextMenuMore" = "More..."; - -"Conversation.StatusMembers_0" = "%@ members"; -"Conversation.StatusMembers_1" = "1 member"; -"Conversation.StatusMembers_2" = "2 members"; -"Conversation.StatusMembers_3_10" = "%@ members"; -"Conversation.StatusMembers_many" = "%@ members"; -"Conversation.StatusMembers_any" = "%@ members"; - -"Conversation.StatusOnline_1" = "1 online"; -"Conversation.StatusOnline_2" = "2 online"; -"Conversation.StatusOnline_3_10" = "%@ online"; -"Conversation.StatusOnline_any" = "%@ online"; -"Conversation.StatusOnline_many" = "%@ online"; -"Conversation.StatusOnline_0" = "%@ online"; - -"Conversation.UnreadMessages" = "Unread Messages"; - -// Notification -"Notification.RenamedChat" = "%@ renamed group"; -"Notification.RenamedChannel" = "Channel renamed"; -"Notification.ChangedGroupPhoto" = "%@ changed group photo"; -"Notification.RemovedGroupPhoto" = "%@ removed group photo"; -"Notification.JoinedChat" = "%@ joined the group"; -"Notification.JoinedChannel" = "%@ joined the channel"; -"Notification.Invited" = "%@ invited %@"; -"Notification.InvitedMultiple" = "%@ invited %@"; -"Notification.LeftChat" = "%@ left the group"; -"Notification.LeftChannel" = "%@ left the channel"; -"Notification.Kicked" = "%@ removed %@"; -"Notification.CreatedChat" = "%@ created a group"; -"Notification.CreatedChannel" = "Channel created"; -"Notification.CreatedChatWithTitle" = "%@ created the group \"%@\" "; -"Notification.Joined" = "%@ joined Telegram"; -"Notification.ChangedGroupName" = "%@ changed group name to \"%@\" "; -"Notification.NewAuthDetected" = "%1$@,\nWe detected a login into your account from a new device on %2$@, %3$@ at %4$@\n\nDevice: %5$@\nLocation: %6$@\n\nIf this wasn't you, you can go to Settings — Privacy and Security — Sessions and terminate that session.\n\nIf you think that somebody logged in to your account against your will, you can enable two-step verification in Privacy and Security settings.\n\nSincerely,\nThe Telegram Team"; -"Notification.MessageLifetimeChanged" = "%1$@ set the self-destruct timer to %2$@"; -"Notification.MessageLifetimeChangedOutgoing" = "You set the self-destruct timer to %1$@"; -"Notification.MessageLifetimeRemoved" = "%1$@ disabled the self-destruct timer"; -"Notification.MessageLifetimeRemovedOutgoing" = "You disabled the self-destruct timer"; -"Notification.MessageLifetime2s" = "2 seconds"; -"Notification.MessageLifetime5s" = "5 seconds"; -"Notification.MessageLifetime1m" = "1 minute"; -"Notification.MessageLifetime1h" = "1 hour"; -"Notification.MessageLifetime1d" = "1 day"; -"Notification.MessageLifetime1w" = "1 week"; - -"Notification.Exceptions.AlwaysOn" = "Always On"; -"Notification.Exceptions.AlwaysOff" = "Always Off"; -"Notification.Exceptions.MutedUntil" = "Muted until %@"; - -"Notification.Exceptions.AddException" = "Add an Exception"; -"Notification.Exceptions.NewException" = "New Exception"; -"Notification.Exceptions.NewException.NotificationHeader" = "NOTIFICATIONS"; -"Notification.Exceptions.Sound" = "Sound: %@"; - - - -// Message -"Message.Photo" = "Photo"; -"Message.Video" = "Video"; -"Message.Location" = "Location"; -"Message.Contact" = "Contact"; -"Message.File" = "File"; -"Message.Sticker" = "Sticker"; -"Message.StickerText" = "Sticker %@"; -"Message.Audio" = "Voice Message"; -"Message.ForwardedMessage" = "Forwarded Message\nFrom: %@"; -"Message.Animation" = "GIF"; -"Message.Game" = "Game"; - -// Conversation Profile -"ConversationProfile.ErrorCreatingConversation" = "An error occurred"; -"ConversationProfile.UnknownAddMemberError" = "An unexpected error has occurred. Our wizards have been notified and will fix the problem soon. Sorry."; -"ConversationProfile.UsersTooMuchError" = "Sorry, this group is full. You cannot add any more members here."; - -"ConversationProfile.LeaveDeleteAndExit" = "Delete and Exit"; -"Group.LeaveGroup" = "Leave Group"; - -"Conversation.Megabytes" = "%.1f MB"; -"Conversation.Kilobytes" = "%d KB"; -"Conversation.Bytes" = "%d B"; -"Conversation.ShareMyContactInfo" = "Share My Contact Info"; -"Conversation.AddContact" = "Add Contact"; -"Conversation.SendMessage" = "Send Message"; -"Conversation.EncryptionCanceled" = "Secret chat cancelled"; -"Conversation.DeleteManyMessages" = "Delete Messages"; -"Conversation.SlideToCancel" = "Slide to cancel"; -"Conversation.ApplyLocalization" = "Apply Localization"; -"Conversation.OpenFile" = "Open File"; - -// Media Picker -"MediaPicker.Send" = "Send"; -"SearchImages.Title" = "Albums"; -"MediaPicker.CameraRoll" = "Camera Roll"; -"SearchImages.NoImagesFound" = "No images found"; - -// User Profile -"Profile.CreateEncryptedChatError" = "An error occurred."; -"Profile.CreateEncryptedChatOutdatedError" = "Cannot create a secret chat with %@.\n%@ is using an older version of Telegram and needs to update first."; -"Profile.CreateNewContact" = "Create New Contact"; -"Profile.AddToExisting" = "Add to Existing Contact"; -"Profile.EncryptionKey" = "Encryption Key"; -"Profile.MessageLifetimeForever" = "Off"; -"Profile.MessageLifetime2s" = "2s"; -"Profile.MessageLifetime5s" = "5s"; -"Profile.MessageLifetime1m" = "1m"; -"Profile.MessageLifetime1h" = "1h"; -"Profile.MessageLifetime1d" = "1d"; -"Profile.MessageLifetime1w" = "1w"; -"Profile.ShareContactButton" = "Share Contact"; - -// User Info -"UserInfo.Title" = "Info"; -"UserInfo.FirstNamePlaceholder" = "First Name"; -"UserInfo.LastNamePlaceholder" = "Last Name"; -"UserInfo.GenericPhoneLabel" = "mobile"; -"UserInfo.SendMessage" = "Send Message"; -"UserInfo.AddContact" = "Add Contact"; -"UserInfo.ShareContact" = "Share Contact"; -"UserInfo.StartSecretChat" = "Start Secret Chat"; -"UserInfo.StartSecretChatConfirmation" = "Are you sure you want to start a secret chat with %@?"; -"UserInfo.StartSecretChatStart" = "Start"; -"UserInfo.DeleteContact" = "Delete Contact"; -"UserInfo.CreateNewContact" = "Create New Contact"; -"UserInfo.AddToExisting" = "Add to Existing"; -"UserInfo.AddPhone" = "add phone"; -"UserInfo.NotificationsEnabled" = "Enabled"; -"UserInfo.NotificationsDisabled" = "Disabled"; -"UserInfo.NotificationsEnable" = "Enable"; -"UserInfo.NotificationsDisable" = "Disable"; -"UserInfo.Invite" = "Invite to Telegram"; - -// New Contact -"NewContact.Title" = "New Contact"; - -// Phone Label -"PhoneLabel.Title" = "Label"; - -// Secret Chat -"SecretChat.Title" = "Secret Chat"; - -// Group Info -"GroupInfo.Title" = "Group Info"; -"GroupInfo.GroupNamePlaceholder" = "Group Name"; -"GroupInfo.BroadcastListNamePlaceholder" = "List Name"; -"GroupInfo.SetGroupPhoto" = "Set Group Photo"; -"GroupInfo.SetGroupPhotoStop" = "Stop"; -"GroupInfo.SetGroupPhotoDelete" = "Delete Photo"; -"GroupInfo.Notifications" = "Notifications"; -"GroupInfo.Sound" = "Sound"; -"GroupInfo.SetSound" = "Set Sound"; -"GroupInfo.SharedMedia" = "Shared Media"; -"GroupInfo.SharedMediaNone" = "None"; -"GroupInfo.DeleteAndExit" = "Delete and Exit"; -"GroupInfo.DeleteAndExitConfirmation" = "You will not be able to join this group again."; -"GroupInfo.ParticipantCount_1" = "1 MEMBER"; -"GroupInfo.ParticipantCount_2" = "2 MEMBERS"; -"GroupInfo.ParticipantCount_3_10" = "%@ MEMBERS"; -"GroupInfo.ParticipantCount_any" = "%@ MEMBERS"; -"GroupInfo.ParticipantCount_many" = "%@ MEMBERS"; -"GroupInfo.ParticipantCount_0" = "%@ MEMBERS"; -"GroupInfo.AddParticipant" = "Add Member"; -"GroupInfo.AddParticipantTitle" = "Contacts"; -"GroupInfo.AddParticipantConfirmation" = "Add %@ to the group?"; -"GroupInfo.LeftStatus" = "You have left the group"; - -// Encryption Key -"EncryptionKey.Title" = "Encryption Key"; -"EncryptionKey.Description" = "This image and text were derived from the encryption key for this secret chat with %1$@.\n\n If they look the same on %2$@'s device, end-to-end encryption is guaranteed.\n\nLearn more at telegram.org"; - -// Conversation media -"ConversationMedia.Title" = "Media"; - -// Preview -"Preview.DeletePhoto" = "Delete Photo"; -"Preview.SaveToCameraRoll" = "Save to Camera Roll"; - -// Map -"Map.ChooseLocationTitle" = "Location"; -"Map.Map" = "Map"; -"Map.Satellite" = "Satellite"; -"Map.Hybrid" = "Hybrid"; -"Map.GetDirections" = "Get Directions"; -"Map.OpenInGoogleMaps" = "Open in Google Maps"; - -// Web -"Web.Error" = "Couldn't load page"; -"Web.OpenExternal" = "Open in Safari"; - -// Document -"Document.TargetConfirmationFormat" = "Send file ({size}) to {target}?"; - -// Dialog List -"DialogList.You" = "You"; - -// Settings -"Settings.SetProfilePhoto" = "Set Profile Photo"; -"Settings.Logout" = "Log Out"; -"Settings.Title" = "Settings"; -"Settings.NotificationsAndSounds" = "Notifications and Sounds"; -"Settings.ChatSettings" = "Data and Storage"; -"Settings.BlockedUsers" = "Blocked Users"; -"Settings.ChatBackground" = "Chat Background"; -"Settings.Support" = "Ask a Question"; -"Settings.FAQ" = "Telegram FAQ"; -"Settings.FAQ_URL" = "https://telegram.org/faq#general"; -"Settings.FAQ_Intro" = "Please note that Telegram Support is done by volunteers. We try to respond as quickly as possible, but it may take a while.\n\nPlease take a look at the Telegram FAQ: it has important troubleshooting tips and answers to most questions."; -"Settings.FAQ_Button" = "FAQ"; -"Settings.SaveIncomingPhotos" = "Save Incoming Photos"; - -// Notifications and Sounds -"Notifications.Title" = "Notifications"; -"Notifications.MessageNotifications" = "MESSAGE NOTIFICATIONS"; -"Notifications.MessageNotificationsAlert" = "Alert"; -"Notifications.MessageNotificationsPreview" = "Message Preview"; -"Notifications.MessageNotificationsSound" = "Sound"; -"Notifications.MessageNotificationsHelp" = "You can set custom notifications for specific users on their Info page."; -"Notifications.MessageNotificationsExceptionsHelp" = "Set custom notifications for specific users."; - - - -"Notifications.GroupNotifications" = "GROUP NOTIFICATIONS"; -"Notifications.GroupNotificationsAlert" = "Alert"; -"Notifications.GroupNotificationsPreview" = "Message Preview"; -"Notifications.GroupNotificationsSound" = "Sound"; -"Notifications.GroupNotificationsHelp" = "You can set custom notifications for specific groups on the Group Info page."; -"Notifications.GroupNotificationsExceptionsHelp" = "Set custom notifications for specific groups."; - -"Notifications.ChannelNotifications" = "CHANNEL NOTIFICATIONS"; -"Notifications.ChannelNotificationsAlert" = "Alert"; -"Notifications.ChannelNotificationsPreview" = "Message Preview"; -"Notifications.ChannelNotificationsSound" = "Sound"; -"Notifications.ChannelNotificationsHelp" = "You can set custom notifications for specific channels on the Channel Info page."; -"Notifications.ChannelNotificationsExceptionsHelp" = "Set custom notifications for specific channels."; - -"Notifications.TextTone" = "Text Tone"; -"Notifications.AlertTones" = "ALERT TONES"; -"Notifications.ClassicTones" = "CLASSIC"; - -"Notifications.InAppNotifications" = "IN-APP NOTIFICATIONS"; -"Notifications.InAppNotificationsSounds" = "In-App Sounds"; -"Notifications.InAppNotificationsVibrate" = "In-App Vibrate"; -"Notifications.InAppNotificationsPreview" = "In-App Preview"; - -"Notifications.Reset" = "Reset"; -"Notifications.ResetAllNotifications" = "Reset All Notifications"; -"Notifications.ResetAllNotificationsHelp" = "Undo all custom notification settings for all your contacts and groups."; - -// Chat Settings -"ChatSettings.Title" = "Data and Storage"; -"ChatSettings.Appearance" = "APPEARANCE"; -"ChatSettings.TextSize" = "Text Size"; -"ChatSettings.TextSizeUnits" = "pt"; -"ChatSettings.AutomaticPhotoDownload" = "AUTOMATIC PHOTO DOWNLOAD"; -"ChatSettings.AutomaticAudioDownload" = "AUTOMATIC AUDIO DOWNLOAD"; -"ChatSettings.PrivateChats" = "Private Chats"; -"ChatSettings.Groups" = "Groups"; -"ChatSettings.Cache" = "Storage Usage"; - -// Usage -"Cache.Title" = "Storage Usage"; -"Cache.ClearCache" = "Clear Cache"; -"Cache.KeepMedia" = "Keep Media"; -"Cache.Help" = "Photos, videos and other files from cloud chats that you have **not accessed** during this period will be removed from this device to save disk space.\n\nAll media will stay in the Telegram cloud and can be re-downloaded if you need it again."; - -// Blocked Users -"BlockedUsers.Title" = "Blocked"; -"BlockedUsers.SelectUserTitle" = "Block User"; -"BlockedUsers.BlockUser" = "Block User..."; -"BlockedUsers.BlockTitle" = "Block"; -"BlockedUsers.LeavePrefix" = "Leave "; -"BlockedUsers.Info" = "Blocked users can't send you messages or add you to groups. They will not see your profile pictures, online and last seen status."; -"BlockedUsers.AddNew" = "Add New..."; -"BlockedUsers.Unblock" = "Unblock"; - -// Wallpaper -"Wallpaper.Title" = "Chat Background"; -"Wallpaper.PhotoLibrary" = "Photo Library"; -"Wallpaper.Set" = "Set"; -"Wallpaper.Wallpaper" = "Wallpaper"; - -"Notification.SecretChatMessageScreenshot" = "%@ took a screenshot!"; -"Notification.SecretChatScreenshot" = "Screenshot taken!"; - -"BroadcastListInfo.AddRecipient" = "Add Recipient"; - -"Settings.LogoutConfirmationTitle" = "Log out?"; -"Settings.LogoutConfirmationText" = "\nNote that you can seamlessly use Telegram on all your devices at once.\n\nRemember, logging out kills all your Secret Chats."; - -"Login.PadPhoneHelp" = "\nYou can use your main mobile number to log in to Telegram on all devices.\nDon't use your iPad's SIM number here — we'll need to send you an SMS.\n\nIs this number correct?\n{number}"; -"Login.PadPhoneHelpTitle" = "Your Number"; - -"MessageTimer.Custom" = "Custom"; - -"MessageTimer.Forever" = "Forever"; - -"MessageTimer.Seconds_1" = "%@ second"; -"MessageTimer.Seconds_2" = "%@ seconds"; -"MessageTimer.Seconds_3_10" = "%@ seconds"; -"MessageTimer.Seconds_any" = "%@ seconds"; -"MessageTimer.Seconds_many" = "%@ seconds"; -"MessageTimer.Seconds_0" = "%@ seconds"; -"MessageTimer.Minutes_1" = "%@ minute"; -"MessageTimer.Minutes_2" = "%@ minutes"; -"MessageTimer.Minutes_3_10" = "%@ minutes"; -"MessageTimer.Minutes_any" = "%@ minutes"; -"MessageTimer.Minutes_many" = "%@ minutes"; -"MessageTimer.Minutes_0" = "%@ minutes"; -"MessageTimer.Hours_1" = "%@ hour"; -"MessageTimer.Hours_2" = "%@ hours"; -"MessageTimer.Hours_3_10" = "%@ hours"; -"MessageTimer.Hours_any" = "%@ hours"; -"MessageTimer.Hours_many" = "%@ hours"; -"MessageTimer.Hours_0" = "%@ hours"; -"MessageTimer.Days_1" = "%@ day"; -"MessageTimer.Days_2" = "%@ days"; -"MessageTimer.Days_3_10" = "%@ days"; -"MessageTimer.Days_any" = "%@ days"; -"MessageTimer.Days_many" = "%@ days"; -"MessageTimer.Days_0" = "%@ days"; -"MessageTimer.Weeks_1" = "%@ week"; -"MessageTimer.Weeks_2" = "%@ weeks"; -"MessageTimer.Weeks_3_10" = "%@ weeks"; -"MessageTimer.Weeks_any" = "%@ weeks"; -"MessageTimer.Weeks_many" = "%@ weeks"; -"MessageTimer.Weeks_0" = "%@ weeks"; -"MessageTimer.Months_1" = "%@ month"; -"MessageTimer.Months_2" = "%@ months"; -"MessageTimer.Months_3_10" = "%@ months"; -"MessageTimer.Months_any" = "%@ months"; -"MessageTimer.Months_many" = "%@ months"; -"MessageTimer.Months_0" = "%@ months"; -"MessageTimer.Years_1" = "%@ year"; -"MessageTimer.Years_2" = "%@ years"; -"MessageTimer.Years_3_10" = "%@ years"; -"MessageTimer.Years_any" = "%@ years"; -"MessageTimer.Months_many" = "%@ years"; - -"MessageTimer.ShortSeconds_1" = "%@s"; -"MessageTimer.ShortSeconds_2" = "%@s"; -"MessageTimer.ShortSeconds_3_10" = "%@s"; -"MessageTimer.ShortSeconds_any" = "%@s"; -"MessageTimer.ShortSeconds_many" = "%@s"; -"MessageTimer.ShortSeconds_0" = "%@s"; -"MessageTimer.ShortMinutes_1" = "%@m"; -"MessageTimer.ShortMinutes_2" = "%@m"; -"MessageTimer.ShortMinutes_3_10" = "%@m"; -"MessageTimer.ShortMinutes_any" = "%@m"; -"MessageTimer.ShortMinutes_many" = "%@m"; -"MessageTimer.ShortMinutes_0" = "%@m"; -"MessageTimer.ShortHours_1" = "%@h"; -"MessageTimer.ShortHours_2" = "%@h"; -"MessageTimer.ShortHours_3_10" = "%@h"; -"MessageTimer.ShortHours_any" = "%@h"; -"MessageTimer.ShortHours_many" = "%@h"; -"MessageTimer.ShortHours_0" = "%@h"; -"MessageTimer.ShortDays_1" = "%@d"; -"MessageTimer.ShortDays_2" = "%@d"; -"MessageTimer.ShortDays_3_10" = "%@d"; -"MessageTimer.ShortDays_any" = "%@d"; -"MessageTimer.ShortDays_many" = "%@d"; -"MessageTimer.ShortDays_0" = "%@d"; -"MessageTimer.ShortWeeks_1" = "%@w"; -"MessageTimer.ShortWeeks_2" = "%@w"; -"MessageTimer.ShortWeeks_3_10" = "%@w"; -"MessageTimer.ShortWeeks_any" = "%@w"; -"MessageTimer.ShortWeeks_many" = "%@w"; -"MessageTimer.ShortWeeks_0" = "%@w"; - -"Activity.UploadingPhoto" = "sending photo"; -"Activity.UploadingVideo" = "sending video"; -"Activity.UploadingDocument" = "sending file"; -"Activity.RecordingAudio" = "recording audio"; -"Activity.RecordingVideoMessage" = "recording video"; - -"Compatibility.SecretMediaVersionTooLow" = "%@ is using an older version of Telegram, so secret photos will be shown in compatibility mode.\n\nOnce %@ updates Telegram, photos with timers for 1 minute or less will start working in 'Tap and hold to view' mode, and you will be notified whenever the other party takes a screenshot."; - -"Contacts.GlobalSearch" = "Global Search"; -"Profile.Username" = "username"; -"Settings.Username" = "Username"; -"Settings.UsernameEmpty" = "Add"; - -"Username.Title" = "Username"; -"Username.Placeholder" = "Your Username"; -"Username.Help" = "You can choose a username on **Telegram**. If you do, other people will be able to find you by this username and contact you without knowing your phone number.\n\nYou can use **a-z**, **0-9** and underscores. Minimum length is **5** characters."; -"Username.InvalidTooShort" = "A username must have at least 5 characters."; -"Username.InvalidStartsWithNumber" = "Sorry, a username can't start with a number."; -"Username.InvalidCharacters" = "Sorry, this username is invalid."; -"Username.InvalidTaken" = "Sorry, this username is already taken."; - -"Username.CheckingUsername" = "Checking username..."; -"Username.UsernameIsAvailable" = "%@ is available."; - -"WebSearch.Images" = "Images"; -"WebSearch.GIFs" = "GIFs"; -"WebSearch.RecentSectionTitle" = "Recent"; -"WebSearch.RecentSectionClear" = "Clear"; - -"Settings.PrivacySettings" = "Privacy and Security"; - -"UserCount_1" = "1 user"; -"UserCount_2" = "2 users"; -"UserCount_3_10" = "%@ users"; -"UserCount_any" = "%@ users"; -"UserCount_many" = "%@ users"; -"UserCount_0" = "%@ users"; - -"PrivacySettings.Title" = "Privacy and Security"; - -"PrivacySettings.PrivacyTitle" = "PRIVACY"; -"PrivacySettings.LastSeen" = "Last Seen"; -"PrivacySettings.LastSeenTitle" = "Last Seen"; -"PrivacySettings.LastSeenEverybody" = "Everybody"; -"PrivacySettings.LastSeenContacts" = "My Contacts"; -"PrivacySettings.LastSeenNobody" = "Nobody"; - -"PrivacySettings.LastSeenEverybodyMinus" = "Everybody (-%@)"; -"PrivacySettings.LastSeenContactsPlus" = "My Contacts (+%@)"; -"PrivacySettings.LastSeenContactsMinus" = "My Contacts (-%@)"; -"PrivacySettings.LastSeenContactsMinusPlus" = "My Contacts (-%@, +%@)"; -"PrivacySettings.LastSeenNobodyPlus" = "Nobody (+%@)"; - -"PrivacySettings.SecurityTitle" = "SECURITY"; - -"PrivacySettings.DeleteAccountTitle" = "DELETE MY ACCOUNT"; -"PrivacySettings.DeleteAccountIfAwayFor" = "If Away For"; -"PrivacySettings.DeleteAccountHelp" = "If you do not log in at least once within this period, your account will be deleted along with all groups, messages and contacts."; - -"PrivacyLastSeenSettings.Title" = "Last Seen"; -"PrivacyLastSeenSettings.CustomHelp" = "Important: you won't be able to see Last Seen times for people with whom you don't share your Last Seen time. Approximate last seen will be shown instead (recently, within a week, within a month)."; -"PrivacyLastSeenSettings.AlwaysShareWith" = "Always Share With"; -"PrivacyLastSeenSettings.NeverShareWith" = "Never Share With"; -"PrivacyLastSeenSettings.CustomShareSettingsHelp" = "These settings will override the values above."; - -"PrivacyLastSeenSettings.CustomShareSettings.Delete" = "Delete"; -"PrivacyLastSeenSettings.AlwaysShareWith.Title" = "Always Share"; -"PrivacyLastSeenSettings.AlwaysShareWith.Placeholder" = "Always share with users..."; -"PrivacyLastSeenSettings.NeverShareWith.Title" = "Never Share"; -"PrivacyLastSeenSettings.NeverShareWith.Placeholder" = "Never share with users..."; -"PrivacyLastSeenSettings.EmpryUsersPlaceholder" = "Add Users"; -"PrivacyLastSeenSettings.AddUsers_1" = "Add 1 user to this list?"; -"PrivacyLastSeenSettings.AddUsers_2" = "Add 2 users to this list?"; -"PrivacyLastSeenSettings.AddUsers_3_10" = "Add %@ users to this list?"; -"PrivacyLastSeenSettings.AddUsers_any" = "Add %@ users to this list?"; -"PrivacyLastSeenSettings.AddUsers_many" = "Add %@ users to this list?"; -"PrivacyLastSeenSettings.AddUsers_0" = "Add %@ users to this list?"; - -// Photo Editor -"PhotoEditor.DiscardChanges" = "Discard Changes"; - -"PhotoEditor.Original" = "Original"; - -"PhotoEditor.CropReset" = "RESET"; -"PhotoEditor.CropAuto" = "AUTO"; -"PhotoEditor.CropAspectRatioOriginal" = "Original"; -"PhotoEditor.CropAspectRatioSquare" = "Square"; - -"PhotoEditor.EnhanceTool" = "Enhance"; -"PhotoEditor.ExposureTool" = "Brightness"; -"PhotoEditor.ContrastTool" = "Contrast"; -"PhotoEditor.WarmthTool" = "Warmth"; -"PhotoEditor.SaturationTool" = "Saturation"; -"PhotoEditor.HighlightsTool" = "Highlights"; -"PhotoEditor.ShadowsTool" = "Shadows"; -"PhotoEditor.VignetteTool" = "Vignette"; -"PhotoEditor.GrainTool" = "Grain"; -"PhotoEditor.SharpenTool" = "Sharpen"; - -"PhotoEditor.BlurToolOff" = "Off"; -"PhotoEditor.BlurToolRadial" = "Radial"; -"PhotoEditor.BlurToolLinear" = "Linear"; - -"PhotoEditor.Set" = "Set"; -"PhotoEditor.Skip" = "Skip"; - -// Camera -"Camera.PhotoMode" = "PHOTO"; -"Camera.VideoMode" = "VIDEO"; -"Camera.SquareMode" = "SQUARE"; -"Camera.FlashOff" = "Off"; -"Camera.FlashOn" = "On"; -"Camera.FlashAuto" = "Auto"; -"Camera.Retake" = "Retake"; - -"Settings.PhoneNumber" = "Change Number"; - -"PhoneNumberHelp.Help" = "You can change your Telegram number here. Your account and all your cloud data — messages, media, contacts, etc. will be moved to the new number.\n\n**Important:** all your Telegram contacts will get your **new number** added to their address book, provided they had your old number and you haven't blocked them in Telegram."; -"PhoneNumberHelp.Alert" = "All your Telegram contacts will get your new number added to their address book, provided they had your old number and you haven't blocked them in Telegram."; -"PhoneNumberHelp.ChangeNumber" = "Change Number"; - -"ChangePhoneNumberNumber.Title" = "Change Number"; -"ChangePhoneNumberNumber.NewNumber" = "NEW NUMBER"; -"ChangePhoneNumberNumber.Help" = "We will send an SMS with a confirmation code to your new number."; -"ChangePhoneNumberNumber.NumberPlaceholder" = "Enter your new number"; - -"ChangePhoneNumberCode.Code" = "YOUR CODE"; -"ChangePhoneNumberCode.CodePlaceholder" = "Code"; -"ChangePhoneNumberCode.Help" = "We have sent you an SMS with the code"; -"ChangePhoneNumberCode.CallTimer" = "Telegram will call you in %@"; -"ChangePhoneNumberCode.RequestingACall" = "Requesting a call from Telegram..."; -"ChangePhoneNumberCode.Called" = "Telegram dialed your number"; - -"LoginPassword.Title" = "Your Password"; -"LoginPassword.PasswordPlaceholder" = "Password"; -"LoginPassword.InvalidPasswordError" = "Invalid password. Please try again."; -"LoginPassword.FloodError" = "Limit exceeded. Please try again later."; -"LoginPassword.ForgotPassword" = "Forgot password?"; -"LoginPassword.PasswordHelp" = "Two-Step verification enabled. Your account is protected with an additional password."; -"LoginPassword.ResetAccount" = "Reset Account"; - -"QuickSend.Photos_1" = "Send 1 Photo"; -"QuickSend.Photos_2" = "Send 2 Photos"; -"QuickSend.Photos_3_10" = "Send %@ Photos"; -"QuickSend.Photos_any" = "Send %@ Photos"; -"QuickSend.Photos_many" = "Send %@ Photos"; -"QuickSend.Photos_0" = "Send %@ Photos"; - -"Share.Title" = "Share"; -"Forward.ConfirmMultipleFiles_1" = "Send 1 file to {target}?"; -"Forward.ConfirmMultipleFiles_2" = "Send 2 files to {target}?"; -"Forward.ConfirmMultipleFiles_3_10" = "Send %@ files to {target}?"; -"Forward.ConfirmMultipleFiles_any" = "Send %@ files to {target}?"; -"Forward.ConfirmMultipleFiles_many" = "Send %@ files to {target}?"; -"Forward.ConfirmMultipleFiles_0" = "Send %@ files to {target}?"; - -"Notification.Reply" = "Reply"; -"Notification.Mute1h" = "Mute for 1 hour"; -"Notification.Mute1hMin" = "Mute for 1h"; -"Conversation.ContextMenuShare" = "Share"; -"Conversation.ContextMenuLookUp" = "Look Up"; - -"SharedMedia.TitleAll" = "Shared Media"; - -"SharedMedia.Photo_1" = "1 photo"; -"SharedMedia.Photo_2" = "2 photos"; -"SharedMedia.Photo_3_10" = "%@ photos"; -"SharedMedia.Photo_any" = "%@ photos"; -"SharedMedia.Photo_many" = "%@ photos"; -"SharedMedia.Photo_0" = "%@ photos"; - -"SharedMedia.Video_1" = "1 video"; -"SharedMedia.Video_2" = "2 videos"; -"SharedMedia.Video_3_10" = "%@ videos"; -"SharedMedia.Video_any" = "%@ videos"; -"SharedMedia.Video_many" = "%@ videos"; -"SharedMedia.Video_0" = "%@ videos"; - -"SharedMedia.File_1" = "1 file"; -"SharedMedia.File_2" = "2 files"; -"SharedMedia.File_3_10" = "%@ files"; -"SharedMedia.File_any" = "%@ files"; -"SharedMedia.File_many" = "%@ files"; -"SharedMedia.File_0" = "%@ files"; - -"SharedMedia.Generic_1" = "1 media file"; -"SharedMedia.Generic_2" = "2 media files"; -"SharedMedia.Generic_3_10" = "%@ media files"; -"SharedMedia.Generic_any" = "%@ media files"; -"SharedMedia.Generic_many" = "%@ media files"; -"SharedMedia.Generic_0" = "%@ media files"; - -"FileSize.B" = "%@ B"; -"FileSize.KB" = "%@ KB"; -"FileSize.MB" = "%@ MB"; -"FileSize.GB" = "%@ GB"; - -"DownloadingStatus" = "Downloading %@ of %@"; - -"Time.MonthOfYear_m1" = "January %@"; -"Time.MonthOfYear_m2" = "February %@"; -"Time.MonthOfYear_m3" = "March %@"; -"Time.MonthOfYear_m4" = "April %@"; -"Time.MonthOfYear_m5" = "May %@"; -"Time.MonthOfYear_m6" = "June %@"; -"Time.MonthOfYear_m7" = "July %@"; -"Time.MonthOfYear_m8" = "August %@"; -"Time.MonthOfYear_m9" = "September %@"; -"Time.MonthOfYear_m10" = "October %@"; -"Time.MonthOfYear_m11" = "November %@"; -"Time.MonthOfYear_m12" = "December %@"; - -"Time.PreciseDate_m1" = "Jan %1$@, %2$@ at %3$@"; -"Time.PreciseDate_m2" = "Feb %1$@, %2$@ at %3$@"; -"Time.PreciseDate_m3" = "Mar %1$@, %2$@ at %3$@"; -"Time.PreciseDate_m4" = "Apr %1$@, %2$@ at %3$@"; -"Time.PreciseDate_m5" = "May %1$@, %2$@ at %3$@"; -"Time.PreciseDate_m6" = "Jun %1$@, %2$@ at %3$@"; -"Time.PreciseDate_m7" = "Jul %1$@, %2$@ at %3$@"; -"Time.PreciseDate_m8" = "Aug %1$@, %2$@ at %3$@"; -"Time.PreciseDate_m9" = "Sep %1$@, %2$@ at %3$@"; -"Time.PreciseDate_m10" = "Oct %1$@, %2$@ at %3$@"; -"Time.PreciseDate_m11" = "Nov %1$@, %2$@ at %3$@"; -"Time.PreciseDate_m12" = "Dec %1$@, %2$@ at %3$@"; - -"MuteFor.Hours_1" = "Mute for 1 hour"; -"MuteFor.Hours_2" = "Mute for 2 hours"; -"MuteFor.Hours_3_10" = "Mute for %@ hours"; -"MuteFor.Hours_any" = "Mute for %@ hours"; -"MuteFor.Hours_many" = "Mute for %@ hours"; -"MuteFor.Hours_0" = "Mute for %@ hours"; - -"MuteFor.Days_1" = "Mute for 1 day"; -"MuteFor.Days_2" = "Mute for 2 days"; -"MuteFor.Days_3_10" = "Mute for %@ days"; -"MuteFor.Days_any" = "Mute for %@ days"; -"MuteFor.Days_many" = "Mute for %@ days"; -"MuteFor.Days_0" = "Mute for %@ days"; - -"MuteExpires.Minutes_1" = "in 1 minute"; -"MuteExpires.Minutes_2" = "in 2 minutes"; -"MuteExpires.Minutes_3_10" = "in %@ minutes"; -"MuteExpires.Minutes_any" = "in %@ minutes"; -"MuteExpires.Minutes_many" = "in %@ minutes"; -"MuteExpires.Minutes_0" = "in %@ minutes"; - -"MuteExpires.Hours_1" = "in 1 hour"; -"MuteExpires.Hours_2" = "in 2 hours"; -"MuteExpires.Hours_3_10" = "in %@ hours"; -"MuteExpires.Hours_any" = "in %@ hours"; -"MuteExpires.Hours_many" = "in %@ hours"; -"MuteExpires.Hours_0" = "in %@ hours"; - -"MuteExpires.Days_1" = "in 1 day"; -"MuteExpires.Days_2" = "in 2 days"; -"MuteExpires.Days_3_10" = "in %@ days"; -"MuteExpires.Days_any" = "in %@ days"; -"MuteExpires.Days_many" = "in %@ days"; -"MuteExpires.Days_0" = "in %@ days"; - -"SharedMedia.EmptyTitle" = "No media files yet"; -"SharedMedia.EmptyText" = "Share photos and videos in this chat\n — or this paperclip stays unhappy."; -"SharedMedia.EmptyFilesText" = "You can send and receive\nfiles of any type up to 1.5 GB each\nand access them anywhere."; - -"ShareFileTip.Title" = "Sharing Files"; -"ShareFileTip.Text" = "You can share **uncompressed** media files from your Camera Roll here.\n\nTo share files of any other type, open them on your %@ (e.g. in your browser), tap **Open in...** or the action button and choose Telegram."; -"ShareFileTip.CloseTip" = "Close Tip"; - -"DialogList.SearchSectionDialogs" = "Chats and Contacts"; -"DialogList.SearchSectionGlobal" = "Global Search"; -"DialogList.SearchSectionMessages" = "Messages"; - -"Username.LinkHint" = "This link opens a chat with you in Telegram:[\nhttps://t.me/%@]"; -"Username.LinkCopied" = "Copied link to clipboard"; - -"SharedMedia.DeleteItemsConfirmation_1" = "Delete media file?"; -"SharedMedia.DeleteItemsConfirmation_2" = "Delete 2 media files?"; -"SharedMedia.DeleteItemsConfirmation_3_10" = "Delete %@ media files?"; -"SharedMedia.DeleteItemsConfirmation_any" = "Delete %@ media files?"; -"SharedMedia.DeleteItemsConfirmation_many" = "Delete %@ media files?"; -"SharedMedia.DeleteItemsConfirmation_0" = "Delete %@ media files?"; - -"PrivacySettings.Passcode" = "Passcode Lock"; -"PasscodeSettings.Title" = "Passcode Lock"; -"PasscodeSettings.TurnPasscodeOn" = "Turn Passcode On"; -"PasscodeSettings.TurnPasscodeOff" = "Turn Passcode Off"; -"PasscodeSettings.ChangePasscode" = "Change Passcode"; -"PasscodeSettings.Help" = "When you set up an additional passcode, a lock icon will appear on the chats page. Tap it to lock and unlock the app.\n\nNote: if you forget the passcode, you'll need to delete and reinstall the app. All secret chats will be lost."; -"PasscodeSettings.UnlockWithTouchId" = "Unlock with Touch ID"; -"PasscodeSettings.SimplePasscode" = "Simple Passcode"; -"PasscodeSettings.SimplePasscodeHelp" = "A simple passcode is a 4 digit number."; -"PasscodeSettings.EncryptData" = "Encrypt Local Database"; -"PasscodeSettings.EncryptDataHelp" = "Experimental feature, use with caution. Encrypt your local Telegram data, using a derivative of your passcode as the key."; - -"EnterPasscode.EnterTitle" = "Enter your Telegram Passcode"; -"EnterPasscode.ChangeTitle" = "Change Passcode"; -"EnterPasscode.EnterPasscode" = "Enter your Telegram Passcode"; -"EnterPasscode.EnterNewPasscodeNew" = "Enter a passcode"; -"EnterPasscode.EnterNewPasscodeChange" = "Enter your new passcode"; -"EnterPasscode.RepeatNewPasscode" = "Re-enter your new passcode"; -"EnterPasscode.EnterCurrentPasscode" = "Enter your current passcode"; -"EnterPasscode.TouchId" = "Unlock Telegram"; - -"DialogList.PasscodeLockHelp" = "Tap to lock Telegram"; - -"PasscodeSettings.AutoLock" = "Auto-Lock"; -"PasscodeSettings.AutoLock.Disabled" = "Disabled"; -"PasscodeSettings.AutoLock.IfAwayFor_1minute" = "If away for 1 min"; -"PasscodeSettings.AutoLock.IfAwayFor_5minutes" = "If away for 5 min"; -"PasscodeSettings.AutoLock.IfAwayFor_1hour" = "If away for 1 hour"; -"PasscodeSettings.AutoLock.IfAwayFor_5hours" = "If away for 5 hours"; - -"PasscodeSettings.FailedAttempts_1" = "1 Failed Passcode Attempt"; -"PasscodeSettings.FailedAttempts_2" = "2 Failed Passcode Attempts"; -"PasscodeSettings.FailedAttempts_3_10" = "%@ Failed Passcode Attempts"; -"PasscodeSettings.FailedAttempts_any" = "%@ Failed Passcode Attempt"; -"PasscodeSettings.FailedAttempts_many" = "%@ Failed Passcode Attempts"; -"PasscodeSettings.FailedAttempts_0" = "%@ Failed Passcode Attempts"; -"PasscodeSettings.TryAgainIn1Minute" = "Try again in 1 minute"; - -"AccessDenied.Title" = "Please Allow Access"; - -"AccessDenied.Contacts" = "Telegram messaging is based on your existing contact list.\n\nPlease go to Settings > Privacy > Contacts and set Telegram to ON."; - -"AccessDenied.VoiceMicrophone" = "Telegram needs access to your microphone to send voice messages.\n\nPlease go to Settings > Privacy > Microphone and set Telegram to ON."; - -"AccessDenied.VideoMicrophone" = "Telegram needs access to your microphone to record sound in videos recording.\n\nPlease go to Settings > Privacy > Microphone and set Telegram to ON."; - -"AccessDenied.MicrophoneRestricted" = "Microphone access is restricted for Telegram.\n\nPlease go to Settings > General > Restrictions > Microphone and set Telegram to ON."; - - -"AccessDenied.Camera" = "Telegram needs access to your camera to take photos and videos.\n\nPlease go to Settings > Privacy > Camera and set Telegram to ON."; - -"AccessDenied.CameraRestricted" = "Camera access is restricted for Telegram.\n\nPlease go to Settings > General > Restrictions > Camera and set Telegram to ON."; - -"AccessDenied.CameraDisabled" = "Camera access is globally restricted on your phone.\n\nPlease go to Settings > General > Restrictions and set Camera to ON"; - -"AccessDenied.PhotosAndVideos" = "Telegram needs access to your photo library to send photos and videos.\n\nPlease go to Settings > Privacy > Photos and set Telegram to ON."; - -"AccessDenied.SaveMedia" = "Telegram needs access to your photo library to save photos and videos.\n\nPlease go to Settings > Privacy > Photos and set Telegram to ON."; - -"AccessDenied.PhotosRestricted" = "Photo access is restricted for Telegram.\n\nPlease go to Settings > General > Restrictions > Photos and set Telegram to ON."; - -"AccessDenied.LocationDenied" = "Telegram needs access to your location so that you can share it with your contacts.\n\nPlease go to Settings > Privacy > Location Services and set Telegram to ON."; - -"AccessDenied.LocationDisabled" = "Telegram needs access to your location so that you can share it with your contacts.\n\nPlease go to Settings > Privacy > Location Services and set it to ON."; - -"AccessDenied.LocationTracking" = "Telegram needs access to your location to show you on the map.\n\nPlease go to Settings > Privacy > Location Services and set it to ON."; - -"AccessDenied.Settings" = "Settings"; - -"WebSearch.RecentClearConfirmation" = "Are you sure you want to clear recent images?"; - -"FeatureDisabled.Oops" = "Oops"; - -"Conversation.ContextMenuReply" = "Reply"; - -"ForwardedMessages_1" = "Forwarded message"; -"ForwardedMessages_2" = "2 forwarded messages"; -"ForwardedMessages_3_10" = "%@ forwarded messages"; -"ForwardedMessages_any" = "%@ forwarded messages"; -"ForwardedMessages_many" = "%@ forwarded messages"; -"ForwardedMessages_0" = "%@ forwarded messages"; - -"ForwardedFiles_1" = "Forwarded file"; -"ForwardedFiles_2" = "2 forwarded files"; -"ForwardedFiles_3_10" = "%@ forwarded files"; -"ForwardedFiles_any" = "%@ forwarded files"; -"ForwardedFiles_many" = "%@ forwarded files"; -"ForwardedFiles_0" = "%@ forwarded files"; - -"ForwardedStickers_1" = "Forwarded sticker"; -"ForwardedStickers_2" = "2 forwarded stickers"; -"ForwardedStickers_3_10" = "%@ forwarded stickers"; -"ForwardedStickers_any" = "%@ forwarded stickers"; -"ForwardedStickers_many" = "%@ forwarded stickers"; -"ForwardedStickers_0" = "%@ forwarded stickers"; - -"ForwardedPhotos_1" = "Forwarded photo"; -"ForwardedPhotos_2" = "2 forwarded photos"; -"ForwardedPhotos_3_10" = "%@ forwarded photos"; -"ForwardedPhotos_any" = "%@ forwarded photos"; -"ForwardedPhotos_many" = "%@ forwarded photos"; -"ForwardedPhotos_0" = "%@ forwarded photos"; - -"ForwardedVideos_1" = "Forwarded video"; -"ForwardedVideos_2" = "2 forwarded videos"; -"ForwardedVideos_3_10" = "%@ forwarded videos"; -"ForwardedVideos_any" = "%@ forwarded videos"; -"ForwardedVideos_many" = "%@ forwarded videos"; -"ForwardedVideos_0" = "%@ forwarded videos"; - -"ForwardedAudios_1" = "Forwarded audio"; -"ForwardedAudios_2" = "2 forwarded audios"; -"ForwardedAudios_3_10" = "%@ forwarded audios"; -"ForwardedAudios_any" = "%@ forwarded audios"; -"ForwardedAudios_many" = "%@ forwarded audios"; -"ForwardedAudios_0" = "%@ forwarded audios"; - -"ForwardedLocations_1" = "Forwarded location"; -"ForwardedLocations_2" = "2 forwarded locations"; -"ForwardedLocations_3_10" = "%@ forwarded locations"; -"ForwardedLocations_any" = "%@ forwarded locations"; -"ForwardedLocations_many" = "%@ forwarded locations"; -"ForwardedLocations_0" = "%@ forwarded locations"; - -"ForwardedGifs_1" = "Forwarded GIF"; -"ForwardedGifs_2" = "2 forwarded GIFs"; -"ForwardedGifs_3_10" = "%@ forwarded GIFs"; -"ForwardedGifs_any" = "%@ forwarded GIFs"; -"ForwardedGifs_many" = "%@ forwarded GIFs"; -"ForwardedGifs_0" = "%@ forwarded GIFs"; - -"ForwardedContacts_1" = "Forwarded contact"; -"ForwardedContacts_2" = "2 forwarded contacts"; -"ForwardedContacts_3_10" = "%@ forwarded contacts"; -"ForwardedContacts_any" = "%@ forwarded contacts"; -"ForwardedContacts_many" = "%@ forwarded contacts"; -"ForwardedContacts_0" = "%@ forwarded contacts"; - -"ForwardedAuthors2" = "%@, %@"; -"ForwardedAuthorsOthers_1" = "%@ and 1 other"; -"ForwardedAuthorsOthers_2" = "%@ and 2 others"; -"ForwardedAuthorsOthers_3_10" = "%@ and %@ others"; -"ForwardedAuthorsOthers_any" = "%@ and %@ others"; -"ForwardedAuthorsOthers_many" = "%@ and %@ others"; -"ForwardedAuthorsOthers_0" = "%@ and %@ others"; - -"PrivacySettings.TwoStepAuth" = "Two-Step Verification"; -"TwoStepAuth.Title" = "Two-Step Verification"; -"TwoStepAuth.SetPassword" = "Set Additional Password"; -"TwoStepAuth.SetPasswordHelp" = "You can set a password that will be required when you log in on a new device in addition to the code you get in the SMS."; -"TwoStepAuth.SetupPasswordTitle" = "Your Password"; - -"TwoStepAuth.SetupHintTitle" = "Password Hint"; -"TwoStepAuth.SetupHint" = "Please create a hint for your password:"; - -"TwoStepAuth.ChangePassword" = "Change Password"; -"TwoStepAuth.RemovePassword" = "Turn Password Off"; -"TwoStepAuth.SetupEmail" = "Set Recovery E-Mail"; -"TwoStepAuth.ChangeEmail" = "Change Recovery E-Mail"; -"TwoStepAuth.PendingEmailHelp" = "Your recovery e-mail %@ is not yet active and pending confirmation."; -"TwoStepAuth.GenericHelp" = "You have enabled Two-Step verification.\nYou'll need the password you set up here to log in to your Telegram account."; - -"TwoStepAuth.ConfirmationTitle" = "Two-Step Verification"; -"TwoStepAuth.ConfirmationText" = "Please check your e-mail and click on the validation link to complete Two-Step Verification setup. Be sure to check the spam folder as well."; -"TwoStepAuth.ConfirmationAbort" = "Abort Two-Step Verification Setup"; - -"TwoStepAuth.SetupPasswordEnterPasswordNew" = "Enter a password:"; -"TwoStepAuth.SetupPasswordEnterPasswordChange" = "Please enter your new password:"; -"TwoStepAuth.SetupPasswordConfirmPassword" = "Please re-enter your password:"; -"TwoStepAuth.SetupPasswordConfirmFailed" = "Passwords don't match. Please try again."; - -"TwoStepAuth.EnterPasswordTitle" = "Password"; -"TwoStepAuth.EnterPasswordPassword" = "Password"; -"TwoStepAuth.EnterPasswordHint" = "Hint: %@"; -"TwoStepAuth.EnterPasswordHelp" = "You have enabled Two-Step Verification, so your account is protected with an additional password."; -"TwoStepAuth.EnterPasswordInvalid" = "Invalid password. Please try again."; -"TwoStepAuth.EnterPasswordForgot" = "Forgot password?"; - -"TwoStepAuth.EmailTitle" = "Recovery E-Mail"; -"TwoStepAuth.EmailSkip" = "Skip"; -"TwoStepAuth.EmailSkipAlert" = "No, seriously.\n\nIf you forget your password, you will lose access to your Telegram account. There will be no way to restore it."; -"TwoStepAuth.Email" = "E-Mail"; -"TwoStepAuth.EmailPlaceholder" = "Your E-Mail"; -"TwoStepAuth.EmailHelp" = "Please add your valid e-mail. It is the only way to recover a forgotten password."; -"TwoStepAuth.EmailInvalid" = "Invalid e-mail address. Please try again."; -"TwoStepAuth.EmailSent" = "We have sent you an e-mail to confirm your address."; -"TwoStepAuth.PasswordSet" = "Your password for Two-Step Verification is now active."; -"TwoStepAuth.PasswordRemoveConfirmation" = "Are you sure you want to disable your password?"; -"TwoStepAuth.EmailCodeExpired" = "This confirmation code has expired. Please try again."; - -"TwoStepAuth.RecoveryUnavailable" = "Since you haven't provided a recovery e-mail when setting up your password, your remaining options are either to remember your password or to reset your account."; -"TwoStepAuth.RecoveryFailed" = "Your remaining options are either to remember your password or to reset your account."; -"TwoStepAuth.ResetAccountHelp" = "You will lose all your chats and messages, along with any media and files you've shared, if you proceed with resetting your account."; -"TwoStepAuth.ResetAccountConfirmation" = "You will lose all your chats and messages, along with any media and files you've shared, if you proceed with resetting your account."; - -"TwoStepAuth.RecoveryTitle" = "E-Mail Code"; -"TwoStepAuth.RecoveryCode" = "Code"; -"TwoStepAuth.RecoveryCodeHelp" = "Please check your e-mail and enter the 6-digit code we've sent there to deactivate your cloud password."; -"TwoStepAuth.RecoveryCodeInvalid" = "Invalid code. Please try again."; -"TwoStepAuth.RecoveryCodeExpired" = "We have sent you a new 6-digit code."; -"TwoStepAuth.RecoveryEmailUnavailable" = "Having trouble accessing your e-mail %@?"; - -"TwoStepAuth.FloodError" = "Limit exceeded. Please try again later."; - -"Conversation.FilePhotoOrVideo" = "Photo or Video"; -"Conversation.FileICloudDrive" = "iCloud Drive"; -"Conversation.FileDropbox" = "Dropbox"; - -"Conversation.FileOpenIn" = "Open in..."; -"Conversation.FileHowToText" = "To share files of any type, open them on your %@ (e.g. in your browser), tap **Open in...** or the action button and choose Telegram."; - -"Map.LocationTitle" = "Location"; -"Map.OpenInMaps" = "Open in Maps"; -"Map.OpenInHereMaps" = "Open in HERE Maps"; -"Map.OpenInYandexMaps" = "Open in Yandex Maps"; -"Map.OpenInYandexNavigator" = "Open in Yandex Navigator"; -"Map.OpenIn" = "Open In"; - -"Map.SendThisLocation" = "Send This Location"; -"Map.SendMyCurrentLocation" = "Send My Current Location"; -"Map.Locating" = "Locating..."; -"Map.ChooseAPlace" = "Or choose a place"; -"Map.AccurateTo" = "Accurate to %@"; -"Map.Search" = "Search places nearby"; -"Map.ShowPlaces" = "Show places"; -"Map.LoadError" = "An error occurred. Please try again."; -"Map.LocatingError" = "Failed to locate"; -"Map.Unknown" = "Unknown location"; - -"Map.DistanceAway" = "%@ away"; -"Map.ETAMinutes_0" = "%@ min"; -"Map.ETAMinutes_1" = "%@ min"; -"Map.ETAMinutes_2" = "%@ min"; -"Map.ETAMinutes_3_10" = "%@ min"; -"Map.ETAMinutes_any" = "%@ min"; -"Map.ETAMinutes_many" = "%@ min"; -"Map.ETAMinutes_0" = "%@ min"; -"Map.ETAHours_1" = "%@ h"; -"Map.ETAHours_2" = "%@ h"; -"Map.ETAHours_3_10" = "%@ h"; -"Map.ETAHours_any" = "%@ h"; -"Map.ETAHours_many" = "%@ h"; - -"ChangePhone.ErrorOccupied" = "The number %@ is already connected to a Telegram account. Please delete that account before migrating to the new number."; - -"AccessDenied.LocationTracking" = "Telegram needs access to your location to show you on the map.\n\nPlease go to Settings > Privacy > Location Services and set it to ON."; - -"PrivacySettings.AuthSessions" = "Active Sessions"; -"AuthSessions.Title" = "Active Sessions"; -"AuthSessions.CurrentSession" = "CURRENT SESSION"; -"AuthSessions.TerminateOtherSessions" = "Terminate all other sessions"; -"AuthSessions.TerminateOtherSessionsHelp" = "Logs out all devices except for this one."; -"AuthSessions.TerminateSession" = "Terminate session"; -"AuthSessions.OtherSessions" = "ACTIVE SESSIONS"; -"AuthSessions.EmptyTitle" = "No other sessions"; -"AuthSessions.EmptyText" = "You can log in to Telegram from other mobile, tablet and desktop devices, using the same phone number. All your data will be instantly synchronized."; -"AuthSessions.AppUnofficial" = "(ID: %@)"; - -"WebPreview.GettingLinkInfo" = "Getting Link Info..."; - -"Preview.OpenInInstagram" = "Open in Instagram"; - -"MediaPicker.AddCaption" = "Add a caption..."; - -"GroupInfo.InviteByLink" = "Invite to Group via Link"; - -"GroupInfo.InviteLink.Title" = "Invite Link"; -"GroupInfo.InviteLink.LinkSection" = "LINK"; -"GroupInfo.InviteLink.Help" = "Anyone who has Telegram installed will be able to join your group by following this link."; -"GroupInfo.InviteLink.CopyLink" = "Copy Link"; -"GroupInfo.InviteLink.RevokeLink" = "Revoke Link"; -"GroupInfo.InviteLink.ShareLink" = "Share Link"; -"GroupInfo.InviteLink.RevokeAlert.Text" = "Are you sure you want to revoke this link? Once you do, no one will be able to join the group using it."; -"GroupInfo.InviteLink.RevokeAlert.Revoke" = "Revoke"; -"GroupInfo.InviteLink.RevokeAlert.Success" = "The previous invite link is now inactive. A new invite link has just been generated."; -"GroupInfo.InviteLink.CopyAlert.Success" = "Link copied to clipboard."; - -"UserInfo.ShareMyContactInfo" = "Share My Contact Info"; - -"GroupInfo.InvitationLinkAcceptChannel" = "Do you want to join the channel \"%@\"?"; -"GroupInfo.InvitationLinkDoesNotExist" = "Sorry, this group does not seem to exist."; -"GroupInfo.InvitationLinkGroupFull" = "Sorry, this group is already full."; - -"Core.ServiceUserStatus" = "Service Notifications"; - -"Notification.JoinedGroupByLink" = "%@ joined the group via invite link"; - -"ChatSettings.Other" = "OTHER"; -"ChatSettings.Stickers" = "Stickers"; - -"StickerPacksSettings.Title" = "Stickers"; -"StickerPacksSettings.ShowStickersButton" = "Show Stickers Tab"; -"StickerPacksSettings.ShowStickersButtonHelp" = "A sticker icon will appear in the input field."; - -"StickerPacksSettings.StickerPacksSection" = "STICKER SETS"; -"StickerPacksSettings.ManagingHelp" = "Artists are welcome to add their own sticker sets using our @stickers bot.\n\nTap on a sticker to view and add the whole set."; - -"StickerPack.BuiltinPackName" = "Great Minds"; -"StickerPack.StickerCount_1" = "1 sticker"; -"StickerPack.StickerCount_2" = "2 stickers"; -"StickerPack.StickerCount_3_10" = "%@ stickers"; -"StickerPack.StickerCount_any" = "%@ stickers"; -"StickerPack.StickerCount_many" = "%@ stickers"; -"StickerPack.StickerCount_0" = "%@ stickers"; - -"StickerPack.AddStickerCount_1" = "Add 1 Sticker"; -"StickerPack.AddStickerCount_2" = "Add 2 Stickers"; -"StickerPack.AddStickerCount_3_10" = "Add %@ Stickers"; -"StickerPack.AddStickerCount_any" = "Add %@ Stickers"; -"StickerPack.AddStickerCount_many" = "Add %@ Stickers"; -"StickerPack.AddStickerCount_0" = "Add %@ Stickers"; - -"Conversation.ContextMenuStickerPackAdd" = "Add Stickers"; -"Conversation.ContextMenuStickerPackInfo" = "Info"; - -"MediaPicker.Nof" = "%@ of"; - -"UserInfo.ShareBot" = "Share"; -"UserInfo.InviteBotToGroup" = "Add To Group"; -"Profile.BotInfo" = "about"; - -"Target.SelectGroup" = "Choose Group"; -"Target.InviteToGroupConfirmation" = "Add the bot to \"%@\"?"; -"Target.InviteToGroupErrorAlreadyInvited" = "The bot is already a member of the group."; -"Bot.GenericBotStatus" = "bot"; -"Bot.GenericSupportStatus" = "support"; -"Bot.DescriptionTitle" = "What can this bot do?"; -"Bot.GroupStatusReadsHistory" = "has access to messages"; -"Bot.GroupStatusDoesNotReadHistory" = "has no access to messages"; -"Bot.Start" = "Start"; -"UserInfo.BotSettings" = "Settings"; -"UserInfo.BotHelp" = "Help"; - -"Contacts.SearchLabel" = "Search for contacts or usernames"; -"ChatSearch.SearchPlaceholder" = "Search"; - -"WatchRemote.NotificationText" = "Open this notification on your phone to view the message from your Apple Watch"; -"WatchRemote.AlertTitle" = "Message from your Apple Watch"; -"WatchRemote.AlertText" = "Open the message here?"; -"WatchRemote.AlertOpen" = "Open"; - -"Conversation.SearchPlaceholder" = "Search this chat"; -"Conversation.SearchNoResults" = "No Results"; - -"GroupInfo.AddUserLeftError" = "Sorry, if a person left a group, only a mutual contact can bring them back (they need to have your phone number, and you need theirs)."; - -"DialogList.SearchSectionRecent" = "Recent"; - -"DialogList.DeleteBotConfirmation" = "Delete"; -"DialogList.DeleteBotConversationConfirmation" = "Delete and Stop"; -"Bot.Stop" = "Stop Bot"; -"Bot.Unblock" = "Restart Bot"; - -"Login.PhoneNumberHelp" = "Help"; -"Login.EmailPhoneSubject" = "Invalid number %@"; -"Login.EmailPhoneBody" = "I'm trying to use my mobile phone number: %@\nBut Telegram says it's invalid. Please help.\nAdditional Info: %@, %@."; - -"SharedMedia.TitleLink" = "Shared Links"; -"SharedMedia.EmptyLinksText" = "All links shared in this chat will appear here."; - -"SharedMedia.Link_1" = "1 link"; -"SharedMedia.Link_2" = "2 links"; -"SharedMedia.Link_3_10" = "%@ links"; -"SharedMedia.Link_any" = "%@ links"; -"SharedMedia.Link_many" = "%@ links"; -"SharedMedia.Link_0" = "%@ links"; - -"Compose.NewChannel" = "New Channel"; -"GroupInfo.ChannelListNamePlaceholder" = "Channel Name"; - -"Channel.MessagePhotoUpdated" = "Channel photo updated"; -"Channel.MessagePhotoRemoved" = "Channel photo removed"; -"Channel.MessageTitleUpdated" = "Channel renamed to \"%@\" "; -"Channel.TitleInfo" = "Channel Info"; - -"Channel.UpdatePhotoItem" = "Set Channel Photo"; - -"Channel.LinkItem" = "share link"; -"Channel.Edit.AboutItem" = "Description"; -"Channel.Edit.LinkItem" = "Link"; - -"Channel.Username.Title" = "Link"; -"Channel.Username.Help" = "You can choose a channel name on **Telegram**. If you do, other people will be able to find your channel by this name.\n\nYou can use **a-z**, **0-9** and underscores. Minimum length is **5** characters."; -"Channel.Username.LinkHint" = "This link opens your channel in Telegram:[\nhttps://t.me/%@]"; -"Channel.Username.InvalidTooShort" = "Channel names must have at least 5 characters."; -"Channel.Username.InvalidStartsWithNumber" = "Channel names can't start with a number."; -"Channel.Username.InvalidCharacters" = "Sorry, this name is invalid."; -"Channel.Username.InvalidTaken" = "Sorry, this name is already taken."; -"Channel.Username.CheckingUsername" = "Checking name..."; -"Channel.Username.UsernameIsAvailable" = "%@ is available."; - -"Channel.LeaveChannel" = "Leave Channel"; - -"Channel.About.Title" = "Description"; - -"Channel.About.Placeholder" = "Description (Optional)"; -"Channel.About.Help" = "You can provide an optional description for your channel."; -"Group.About.Help" = "You can provide an optional description for your group."; - -"Channel.Status" = "channel"; -"Group.Status" = "group"; - -"Compose.NewChannel.Members" = "MEMBERS"; - -"ChannelInfo.ConfirmLeave" = "Leave Channel"; -"Channel.JoinChannel" = "Join"; -"Forward.ChannelReadOnly" = "Sorry, you can't post to this channel."; - -"Channel.ErrorAccessDenied" = "Sorry, this channel is private."; -"Group.ErrorAccessDenied" = "Sorry, this group is private."; -"Conversation.InputTextBroadcastPlaceholder" = "Broadcast"; - -"Channel.NotificationLoading" = "Loading..."; - -"Compose.ChannelTokenListPlaceholder" = "Search for contacts or usernames"; -"Compose.GroupTokenListPlaceholder" = "Search for contacts or usernames"; - -"Compose.ChannelMembers" = "Members"; - -"Channel.Setup.TypeHeader" = "CHANNEL TYPE"; -"Channel.Setup.TypePrivate" = "Private"; -"Channel.Setup.TypePublic" = "Public"; -"Channel.Setup.TypePublicHelp" = "Public channels can be found in search, anyone can join them."; -"Channel.Setup.TypePrivateHelp" = "Private channels can only be joined via an invite link."; - -"Channel.Setup.Title" = "Channel"; - -"Channel.Username.CreatePublicLinkHelp" = "People can share this link with others and find your channel using Telegram search."; -"Channel.Username.CreatePrivateLinkHelp" = "People can join your channel by following this link. You can revoke the link at any time."; - -"Channel.Setup.PublicNoLink" = "Please choose a link for your public channel, so that people can find it in search and share with others.\n\nIf you're not interested, we suggest creating a private channel instead."; - -"Channel.Edit.PrivatePublicLinkAlert" = "Please note that if you choose a public link for your channel, anyone will be able to find it in search and join.\n\nDo not create this link if you want your channel to stay private."; - -"Channel.Info.Description" = "description"; - -"Channel.Info.Management" = "Admins"; -"Channel.Info.Banned" = "Blacklist"; -"Channel.Info.Members" = "Members"; - -"Channel.Members.AddMembers" = "Add Subscribers"; -"Channel.Members.AddMembersHelp" = "Only channel admins can see this list."; -"Channel.Members.Title" = "Members"; -"Channel.BlackList.Title" = "Blacklist"; -"Channel.Management.Title" = "Admins"; -"Channel.Management.LabelCreator" = "Creator"; -"Channel.Management.LabelEditor" = "Admin"; - -"Channel.Management.AddModerator" = "Add Admin"; -"Channel.Management.AddModeratorHelp" = "You can add admins to help you manage your channel."; - -"Channel.Members.InviteLink" = "Invite via Link"; - -"Channel.Management.ErrorNotMember" = "%@ hasn't joined the channel yet. Do you want to invite them?"; - -"Channel.Moderator.AccessLevelRevoke" = "Dismiss Admin"; - -"Channel.Moderator.Title" = "Admin"; - -"Notification.ChannelInviter" = "%@ invited you to this channel"; -"Notification.ChannelInviterSelf" = "You joined this channel"; - -"Notification.GroupInviter" = "%@ invited you to this group"; -"Notification.GroupInviterSelf" = "You joined this group"; - -"ChannelInfo.DeleteChannel" = "Delete Channel"; -"ChannelInfo.DeleteChannelConfirmation" = "Wait! Deleting this channel will remove all members and all messages will be lost. Delete the channel anyway?"; - -"ChannelInfo.ChannelForbidden" = "Sorry, the channel \"%@\" is no longer accessible."; -"ChannelInfo.AddParticipantConfirmation" = "Add %@ to the channel?"; - -"PhotoEditor.FadeTool" = "Fade"; -"PhotoEditor.TintTool" = "Tint"; -"PhotoEditor.ShadowsTint" = "Shadows"; -"PhotoEditor.HighlightsTint" = "Highlights"; -"PhotoEditor.CurvesTool" = "Curves"; -"PhotoEditor.CurvesAll" = "All"; -"PhotoEditor.CurvesRed" = "Red"; -"PhotoEditor.CurvesGreen" = "Green"; -"PhotoEditor.CurvesBlue" = "Blue"; - -"Channel.ErrorAddBlocked" = "Sorry, you can't add this user to channels."; -"Channel.ErrorAddTooMuch" = "Sorry, you can only add the first 200 members to a channel. Note that an unlimited number of people may join via the channel's link."; - -"ChannelIntro.Title" = "What is a Channel?"; -"ChannelIntro.Text" = "Channels are a new tool for\nbroadcasting your messages\nto large audiences."; -"ChannelIntro.CreateChannel" = "Create Channel"; - -"ShareMenu.Send" = "Send"; - -"Conversation.ReportSpam" = "Report Spam"; -"Conversation.ReportSpamAndLeave" = "Report Spam and Leave"; -"Conversation.ReportSpamConfirmation" = "Are you sure you want to report spam from this user?"; -"Conversation.ReportSpamGroupConfirmation" = "Are you sure you want to report spam from this group?"; -"Conversation.ReportSpamChannelConfirmation" = "Are you sure you want to report spam from this channel?"; -"SharedMedia.EmptyMusicText" = "All music shared in this chat will appear here."; - -"ChatSettings.AutoPlayAnimations" = "Autoplay GIFs"; - -"GroupInfo.ChatAdmins" = "Add Admins"; - -"ChatAdmins.Title" = "Chat Admins"; -"ChatAdmins.AllMembersAreAdmins" = "All Members Are Admins"; -"ChatAdmins.AllMembersAreAdminsOnHelp" = "All members can add new members, edit name and photo of the group."; -"ChatAdmins.AllMembersAreAdminsOffHelp" = "Only admins can add and remove members, edit name and photo of the group."; -"ChatAdmins.AdminLabel" = "admin"; - -"Group.MessagePhotoUpdated" = "Group photo updated"; -"Group.MessagePhotoRemoved" = "Group photo removed"; - -"Group.UpgradeNoticeHeader" = "MEMBERS LIMIT REACHED"; - -"Group.UpgradeNoticeText1" = "To go over the limit and get additional features, upgrade to a supergroup:"; -"Group.UpgradeNoticeText2" = "• Supergroups can get up to {supergroup_member_limit} members\n• New members see the entire chat history\n• Admins delete messages for everyone\n• Notifications are muted by default"; -"GroupInfo.UpgradeButton" = "Upgrade to supergroup"; -"Group.UpgradeConfirmation" = "Warning: this action is irreversible. It is not possible to downgrade a supergroup to a regular group."; - -"Notification.GroupActivated" = "Group deactivated"; - -"GroupInfo.DeactivatedStatus" = "Group Deactivated"; - -"Notification.RenamedGroup" = "Group renamed"; - -"Group.ErrorAddTooMuchBots" = "Sorry, you've reached the maximum number of bots for this group."; -"Group.ErrorAddTooMuchAdmins" = "Sorry, you've reached the maximum number of admins for this group."; -"Group.ErrorAddBlocked" = "Sorry, you can't add this user to groups."; -"Group.ErrorNotMutualContact" = "Sorry, you can only add mutual contacts to groups at the moment."; - -"Conversation.SendMessageErrorFlood" = "Sorry, you can only send messages to mutual contacts at the moment."; -"Generic.ErrorMoreInfo" = "More Info"; - -"ChannelInfo.DeleteGroup" = "Delete Group"; -"ChannelInfo.DeleteGroupConfirmation" = "Wait! Deleting this group will remove all members and all messages will be lost. Delete the group anyway?"; - -"ReportPeer.Report" = "Report"; - -"ReportPeer.ReasonSpam" = "Spam"; -"ReportPeer.ReasonViolence" = "Violence"; -"ReportPeer.ReasonPornography" = "Pornography"; -"ReportPeer.ReasonChildAbuse" = "Child Abuse"; -"ReportPeer.ReasonOther" = "Other"; - -"ReportPeer.AlertSuccess" = "Thank you!\nYour report will be reviewed by our team very soon."; - -"Login.TermsOfServiceLabel" = "By signing up,\nyou agree to the [Terms of Service]."; -"Login.TermsOfServiceHeader" = "Terms of Service"; - -"ReportPeer.ReasonOther.Placeholder" = "Description"; -"ReportPeer.ReasonOther.Title" = "Report"; -"ReportPeer.ReasonOther.Send" = "Send"; - -"Group.Management.AddModeratorHelp" = "You can add admins to help you manage your group."; - -"Watch.AppName" = "Telegram"; -"Watch.Compose.AddContact" = "Choose Contact"; -"Watch.Compose.CreateMessage" = "Create Message"; -"Watch.Compose.CurrentLocation" = "Current Location"; -"Watch.Compose.Send" = "Send"; -"Watch.Contacts.NoResults" = "No matching\ncontacts found"; -"Watch.ChatList.NoConversationsTitle" = "No Conversations"; -"Watch.ChatList.NoConversationsText" = "To start messaging,\npress firmly, then tap\nNew Message"; -"Watch.ChatList.Compose" = "New Message"; - -"Watch.Conversation.Reply" = "Reply"; -"Watch.Conversation.Unblock" = "Unblock"; -"Watch.Conversation.UserInfo" = "Info"; -"Watch.Conversation.GroupInfo" = "Group Info"; -"Watch.Bot.Restart" = "Restart"; - -"Watch.UserInfo.Title" = "Info"; -"Watch.UserInfo.Service" = "service notifications"; - -"Watch.UserInfo.Block" = "Block"; -"Watch.UserInfo.Unblock" = "Unblock"; -"Watch.UserInfo.Mute_1" = "Mute for 1 hour"; -"Watch.UserInfo.Mute_2" = "Mute for 2 hours"; -"Watch.UserInfo.Mute_3_10" = "Mute for %@ hours"; -"Watch.UserInfo.Mute_any" = "Mute for %@ hours"; -"Watch.UserInfo.Mute_many" = "Mute for %@ hours"; -"Watch.UserInfo.Mute_0" = "Mute for %@ hours"; -"Watch.UserInfo.MuteTitle" = "Mute"; -"Watch.UserInfo.Unmute" = "Unmute"; - -"Watch.GroupInfo.Title" = "Group Info"; -"Watch.ChannelInfo.Title" = "Channel Info"; - -"Watch.Message.ForwardedFrom" = "Forwarded from"; - -"Watch.Notification.Joined" = "Joined Telegram"; - -"Watch.MessageView.Title" = "Message"; -"Watch.MessageView.Forward" = "Forward"; -"Watch.MessageView.Reply" = "Reply"; -"Watch.MessageView.ViewOnPhone" = "View On Phone"; - -"Watch.PhotoView.Title" = "Photo"; - -"Watch.Stickers.Recents" = "Recents"; -"Watch.Stickers.RecentPlaceholder" = "Your most frequently used stickers will appear here"; -"Watch.Stickers.StickerPacks" = "Sticker Sets"; - -"Watch.Location.Current" = "Current Location"; -"Watch.Location.Access" = "Allow Telegram to access location on your phone"; - -"Watch.AuthRequired" = "Log in to Telegram on your phone to get started"; - -"Watch.NoConnection" = "No Connection"; -"Watch.ConnectionDescription" = "Your Watch needs to be connected for the app to work"; - -"Watch.Time.ShortTodayAt" = "Today %@"; -"Watch.Time.ShortYesterdayAt" = "Yesterday %@"; -"Watch.Time.ShortWeekdayAt" = "%1$@ %2$@"; -"Watch.Time.ShortFullAt" = "%1$@ %2$@"; - -"Watch.LastSeen.JustNow" = "just now"; -"Watch.LastSeen.MinutesAgo_1" = "1 minute ago"; -"Watch.LastSeen.MinutesAgo_2" = "2 minutes ago"; -"Watch.LastSeen.MinutesAgo_3_10" = "%@ minutes ago"; -"Watch.LastSeen.MinutesAgo_any" = "%@ minutes ago"; -"Watch.LastSeen.MinutesAgo_many" = "%@ minutes ago"; -"Watch.LastSeen.MinutesAgo_0" = "%@ minutes ago"; -"Watch.LastSeen.HoursAgo_1" = "1 hour ago"; -"Watch.LastSeen.HoursAgo_2" = "2 hours ago"; -"Watch.LastSeen.HoursAgo_3_10" = "%@ hours ago"; -"Watch.LastSeen.HoursAgo_any" = "%@ hours ago"; -"Watch.LastSeen.HoursAgo_many" = "%@ hours ago"; -"Watch.LastSeen.HoursAgo_0" = "%@ hours ago"; -"Watch.LastSeen.YesterdayAt" = "yesterday at %@"; -"Watch.LastSeen.AtDate" = "%@"; -"Watch.LastSeen.Lately" = "recently"; -"Watch.LastSeen.WithinAWeek" = "within a week"; -"Watch.LastSeen.WithinAMonth" = "within a month"; -"Watch.LastSeen.ALongTimeAgo" = "a long time ago"; - -"Watch.Suggestion.OK" = "OK"; -"Watch.Suggestion.Thanks" = "Thanks!"; -"Watch.Suggestion.WhatsUp" = "What's up?"; -"Watch.Suggestion.TalkLater" = "Talk later?"; -"Watch.Suggestion.CantTalk" = "Can't talk now..."; -"Watch.Suggestion.HoldOn" = "Hold on a sec..."; -"Watch.Suggestion.BRB" = "BRB"; -"Watch.Suggestion.OnMyWay" = "I'm on my way."; -"Cache.Photos" = "Photos"; -"Cache.Videos" = "Videos"; -"Cache.Music" = "Music"; -"Cache.Files" = "Files"; -"Cache.Clear" = "Clear (%@)"; -"Cache.ClearNone" = "Clear"; -"Cache.ClearProgress" = "Please Wait..."; -"Cache.ClearEmpty" = "Empty"; -"Cache.ByPeerHeader" = "CHATS"; -"Cache.Indexing" = "Telegram is calculating current cache size.\nThis can take a few minutes."; - -"ExplicitContent.AlertTitle" = "Sorry"; -"ExplicitContent.AlertChannel" = "You can't access this channel because it violates App Store rules."; - -"StickerSettings.ContextHide" = "Archive"; - -"Conversation.LinkDialogSave" = "Save"; -"Conversation.GifTooltip" = "Tap here to access saved GIFs"; - -"AttachmentMenu.PhotoOrVideo" = "Photo or Video"; -"AttachmentMenu.File" = "File"; - -"AttachmentMenu.SendPhoto_1" = "Send 1 Photo"; -"AttachmentMenu.SendPhoto_2" = "Send 2 Photos"; -"AttachmentMenu.SendPhoto_3_10" = "Send %@ Photos"; -"AttachmentMenu.SendPhoto_any" = "Send %@ Photos"; -"AttachmentMenu.SendPhoto_many" = "Send %@ Photos"; -"AttachmentMenu.SendPhoto_0" = "Send %@ Photos"; - -"AttachmentMenu.SendVideo_1" = "Send 1 Video"; -"AttachmentMenu.SendVideo_2" = "Send 2 Videos"; -"AttachmentMenu.SendVideo_3_10" = "Send %@ Videos"; -"AttachmentMenu.SendVideo_any" = "Send %@ Videos"; -"AttachmentMenu.SendVideo_many" = "Send %@ Videos"; -"AttachmentMenu.SendVideo_0" = "Send %@ Videos"; - -"AttachmentMenu.SendGif_1" = "Send 1 GIF"; -"AttachmentMenu.SendGif_2" = "Send 2 GIFs"; -"AttachmentMenu.SendGif_3_10" = "Send %@ GIFs"; -"AttachmentMenu.SendGif_any" = "Send %@ GIFs"; -"AttachmentMenu.SendGif_many" = "Send %@ GIFs"; -"AttachmentMenu.SendGif_0" = "Send %@ GIFs"; - -"AttachmentMenu.SendItem_1" = "Send 1 Item"; -"AttachmentMenu.SendItem_2" = "Send 2 Items"; -"AttachmentMenu.SendItem_3_10" = "Send %@ Items"; -"AttachmentMenu.SendItem_any" = "Send %@ Items"; -"AttachmentMenu.SendItem_many" = "Send %@ Items"; -"AttachmentMenu.SendItem_0" = "Send %@ Items"; - -"AttachmentMenu.SendAsFile" = "Send as File"; -"AttachmentMenu.SendAsFiles" = "Send as Files"; - -"Conversation.Processing" = "Processing..."; - -"Conversation.MessageViaUser" = "via %@"; - -"CreateGroup.SoftUserLimitAlert" = "You will be able to add more users after you finish creating the group and convert it to a supergroup."; - -"Privacy.GroupsAndChannels" = "Groups"; -"Privacy.GroupsAndChannels.WhoCanAddMe" = "WHO CAN ADD ME TO GROUP CHATS"; -"Privacy.GroupsAndChannels.CustomHelp" = "You can restrict who can add you to groups and channels with granular precision."; -"Privacy.GroupsAndChannels.AlwaysAllow" = "Always Allow"; -"Privacy.GroupsAndChannels.NeverAllow" = "Never Allow"; -"Privacy.GroupsAndChannels.CustomShareHelp" = "These users will or will not be able to add you to groups and channels regardless of the settings above."; - -"Privacy.GroupsAndChannels.AlwaysAllow.Title" = "Always Allow"; -"Privacy.GroupsAndChannels.AlwaysAllow.Placeholder" = "Always allow..."; -"Privacy.GroupsAndChannels.NeverAllow.Title" = "Never Allow"; -"Privacy.GroupsAndChannels.NeverAllow.Placeholder" = "Never allow..."; - -"Privacy.GroupsAndChannels.InviteToGroupError" = "Sorry, you cannot add %@ to groups because of %@'s privacy settings."; -"Privacy.GroupsAndChannels.InviteToChannelError" = "Sorry, you cannot add %@ to channels because of %@'s privacy settings."; -"Privacy.GroupsAndChannels.InviteToChannelMultipleError" = "Sorry, you can't create a group with these users due to their privacy settings."; - -"ChannelMembers.WhoCanAddMembers" = "Who can add members"; -"ChannelMembers.WhoCanAddMembers.AllMembers" = "All Members"; -"ChannelMembers.WhoCanAddMembers.Admins" = "Only Admins"; -"ChannelMembers.WhoCanAddMembersAllHelp" = "Everybody can add new members."; -"ChannelMembers.WhoCanAddMembersAdminsHelp" = "Only admins can add new members."; - -"ChannelMembers.GroupAdminsTitle" = "GROUP ADMINS"; -"ChannelMembers.ChannelAdminsTitle" = "CHANNEL ADMINS"; -"MusicPlayer.VoiceNote" = "Voice Message"; - -"PrivacyLastSeenSettings.WhoCanSeeMyTimestamp" = "WHO CAN SEE MY TIMESTAMP"; - -"PrivacyLastSeenSettings.GroupsAndChannelsHelp" = "Change who can add you to groups and channels."; -"MusicPlayer.VoiceNote" = "Voice Message"; - -"Watch.Microphone.Access" = "Allow Telegram to access the microphone on your phone"; - -"Settings.AppleWatch" = "Apple Watch"; -"AppleWatch.Title" = "Apple Watch"; -"AppleWatch.ReplyPresets" = "REPLY PRESETS"; -"AppleWatch.ReplyPresetsHelp" = "You can select one of these default replies when you compose or reply to a message, or you can change them to anything you like."; - -"KeyCommand.FocusOnInputField" = "Write Message"; -"KeyCommand.Find" = "Search"; -"KeyCommand.ScrollUp" = "Scroll Up"; -"KeyCommand.ScrollDown" = "Scroll Down"; -"KeyCommand.NewMessage" = "New Message"; -"KeyCommand.JumpToPreviousChat" = "Jump to Previous Chat"; -"KeyCommand.JumpToNextChat" = "Jump to Next Chat"; -"KeyCommand.JumpToPreviousUnreadChat" = "Jump to Previous Unread Chat"; -"KeyCommand.JumpToNextUnreadChat" = "Jump to Next Unread Chat"; -"KeyCommand.SendMessage" = "Send Message"; -"KeyCommand.ChatInfo" = "Chat Info"; - -"Conversation.SecretLinkPreviewAlert" = "Would you like to enable extended link previews in Secret Chats? Note that link previews are generated on Telegram servers."; -"Conversation.SecretChatContextBotAlert" = "Please note that inline bots are provided by third-party developers. For the bot to work, the symbols you type after the bot's username are sent to the respective developer."; - -"Map.OpenInWaze" = "Open in Waze"; - -"ShareMenu.CopyShareLink" = "Copy Link"; - -"Channel.SignMessages" = "Sign Messages"; -"Channel.SignMessages.Help" = "Add names of the admins to the messages they post."; - -"Channel.EditMessageErrorGeneric" = "Sorry, you can't edit this message."; - -"Conversation.InputTextSilentBroadcastPlaceholder" = "Silent Broadcast"; -"Conversation.SilentBroadcastTooltipOn" = "Members will be notified when you post"; -"Conversation.SilentBroadcastTooltipOff" = "Members will not be notified when you post"; - -"Settings.About" = "Bio"; -"GroupInfo.LabelAdmin" = "admin"; - -"Conversation.Pin" = "Pin"; -"Conversation.Unpin" = "Unpin"; -"Conversation.Report" = "Report Spam"; -"Conversation.PinnedMessage" = "Pinned Message"; - -"Conversation.Moderate.Delete" = "Delete Message"; -"Conversation.Moderate.Ban" = "Ban User"; -"Conversation.Moderate.Report" = "Report Spam"; -"Conversation.Moderate.DeleteAllMessages" = "Delete All From %@"; - -"Group.Username.InvalidTooShort" = "Group names must have at least 5 characters."; -"Group.Username.InvalidStartsWithNumber" = "Group names can't start with a number."; - -"Notification.PinnedTextMessage" = "%@ pinned \"%@\" "; -"Notification.PinnedPhotoMessage" = "%@ pinned a photo"; -"Notification.PinnedVideoMessage" = "%@ pinned a video"; -"Notification.PinnedRoundMessage" = "%@ pinned a video message"; -"Notification.PinnedAudioMessage" = "%@ pinned a voice message"; -"Notification.PinnedDocumentMessage" = "%@ pinned a file"; -"Notification.PinnedAnimationMessage" = "%@ pinned a GIF"; -"Notification.PinnedStickerMessage" = "%@ pinned a sticker"; -"Notification.PinnedLocationMessage" = "%@ pinned a map"; -"Notification.PinnedContactMessage" = "%@ pinned a contact"; -"Notification.PinnedDeletedMessage" = "%@ pinned deleted message"; -"Notification.PinnedPollMessage" = "%@ pinned a poll"; -"Notification.PinnedQuizMessage" = "%@ pinned a quiz"; - -"Message.PinnedTextMessage" = "pinned \"%@\" "; -"Message.PinnedPhotoMessage" = "pinned photo"; -"Message.PinnedVideoMessage" = "pinned video"; -"Message.PinnedAudioMessage" = "pinned voice message"; -"Message.PinnedDocumentMessage" = "pinned file"; -"Message.PinnedAnimationMessage" = "pinned GIF"; -"Message.PinnedStickerMessage" = "pinned sticker"; -"Message.PinnedLocationMessage" = "pinned location"; -"Message.PinnedContactMessage" = "pinned contact"; - -"Notification.PinnedMessage" = "pinned message"; - -"GroupInfo.ConvertToSupergroup" = "Convert to Supergroup"; - -"ConvertToSupergroup.Title" = "Supergroup"; -"ConvertToSupergroup.HelpTitle" = "**In supergroups:**"; -"ConvertToSupergroup.HelpText" = "• New members can see the full message history\n• Deleted messages will disappear for all members\n• Admins can pin important messages\n• Creator can set a public link for the group"; - -"ConvertToSupergroup.Note" = "**Note**: this action can't be undone."; - -"GroupInfo.GroupType" = "Group Type"; - -"Group.Setup.TypeHeader" = "GROUP TYPE"; -"Group.Setup.TypePublicHelp" = "Public groups can be found in search, chat history is available to everyone and anyone can join."; -"Group.Setup.TypePrivateHelp" = "Private groups can only be joined if you were invited or have an invite link."; - -"Group.Username.CreatePublicLinkHelp" = "People can share this link with others and find your group using Telegram search."; -"Group.Username.CreatePrivateLinkHelp" = "People can join your group by following this link. You can revoke the link at any time."; - -"Conversation.PinMessageAlertGroup" = "Pin this message and notify all members of the group?"; -"Conversation.PinMessageAlert.OnlyPin" = "Only Pin"; - -"Conversation.UnpinMessageAlert" = "Would you like to unpin this message?"; - -"Settings.About.Title" = "Bio"; -"Settings.About.Help" = "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco."; - -"Profile.About" = "bio"; - -"Conversation.StatusKickedFromChannel" = "you were removed from the channel"; - -"Generic.OpenHiddenLinkAlert" = "Open %@?"; - -"Resolve.ErrorNotFound" = "Sorry, this user doesn't seem to exist."; - -"StickerPack.Share" = "Share"; -"StickerPack.Send" = "Send Sticker"; - -"StickerPack.RemoveStickerCount_1" = "Remove 1 Sticker"; -"StickerPack.RemoveStickerCount_2" = "Remove 2 Stickers"; -"StickerPack.RemoveStickerCount_3_10" = "Remove %@ Stickers"; -"StickerPack.RemoveStickerCount_any" = "Remove %@ Stickers"; -"StickerPack.RemoveStickerCount_many" = "Remove %@ Stickers"; -"StickerPack.RemoveStickerCount_0" = "Remove %@ Stickers"; - -"StickerPack.HideStickers" = "Hide Stickers"; -"StickerPack.ShowStickers" = "Show Stickers"; - -"ShareMenu.ShareTo" = "Share to"; -"ShareMenu.SelectChats" = "Select chats"; -"ShareMenu.Comment" = "Add a comment..."; - -"MediaPicker.Videos" = "Videos"; - -"Coub.TapForSound" = "Tap for sound"; - -"Preview.SaveGif" = "Save GIF"; -"Preview.DeleteGif" = "Delete GIF"; -"Preview.CopyAddress" = "Copy Address"; - -"Conversation.ShareBotLocationConfirmationTitle" = "Share Your Location?"; -"Conversation.ShareBotLocationConfirmation" = "This will send your current location to the bot."; - -"Conversation.ShareBotContactConfirmationTitle" = "Share Your Phone Number?"; -"Conversation.ShareBotContactConfirmation" = "The bot will know your phone number. This can be useful for integration with other services."; - -"Conversation.ShareInlineBotLocationConfirmation" = "This bot would like to know your location each time you send it a request. This can be used to provide location-specific results."; - -"StickerPack.ErrorNotFound" = "Sorry, this sticker set doesn't seem to exist."; - -"Camera.TapAndHoldForVideo" = "Tap and hold for video"; - -"DialogList.RecentTitlePeople" = "People"; - -"Conversation.MessageEditedLabel" = "edited"; -"Conversation.EditingMessagePanelTitle" = "Edit Message"; - -"DialogList.Draft" = "Draft"; -"Embed.PlayingInPIP" = "This video is playing in Picture in Picture"; - -"StickerPacksSettings.FeaturedPacks" = "Trending Stickers"; -"FeaturedStickerPacks.Title" = "Trending Stickers"; - -"Invitation.JoinGroup" = "Join Group"; -"Invitation.Members_1" = "1 member:"; -"Invitation.Members_2" = "2 members:"; -"Invitation.Members_3_10" = "%@ members:"; -"Invitation.Members_any" = "%@ members:"; -"Invitation.Members_many" = "%@ members:"; -"Invitation.Members_0" = "%@ members:"; - -"StickerPacksSettings.ArchivedPacks" = "Archived Stickers"; -"StickerPacksSettings.ArchivedPacks.Info" = "You can have up to 200 sticker sets installed.\nUnused stickers are archived when you add more."; - -"Conversation.CloudStorageInfo.Title" = "Your Cloud Storage"; -"Conversation.ClousStorageInfo.Description1" = "• Forward messages here to save them"; -"Conversation.ClousStorageInfo.Description2" = "• Send media and files to store them"; -"Conversation.ClousStorageInfo.Description3" = "• Access this chat from any device"; -"Conversation.ClousStorageInfo.Description4" = "• Use search to quickly find things"; - -"Conversation.CloudStorage.ChatStatus" = "chat with yourself"; - -"ArchivedPacksAlert.Title" = "Some of your older sticker sets have been archived. You can reactivate them in the Sticker Settings."; - -"StickerSettings.ContextInfo" = "If you archive a sticker set, you can quickly restore it later from the Archived Stickers section."; - -"Contacts.TopSection" = "CONTACTS"; - -"Login.ResetAccountProtected.Title" = "Reset Account"; -"Login.ResetAccountProtected.Text" = "Since the account %@ is active and protected by a password, we will delete it in 1 week for security purposes.\n\nYou can cancel this process at any time."; -"Login.ResetAccountProtected.TimerTitle" = "You'll be able to reset your account in:"; -"Login.ResetAccountProtected.Reset" = "Reset"; -"Login.ResetAccountProtected.LimitExceeded" = "Your recent attempts to reset this account have been cancelled by its active user. Please try again in 7 days."; - -"Login.CodeSentCall" = "We are calling your phone to dictate a code."; - -"Login.WillSendSms" = "Telegram will send you an SMS in %@"; -"Login.SmsRequestState2" = "Requesting an SMS from Telegram..."; -"Login.SmsRequestState3" = "Telegram sent you an SMS\n[Didn't get the code?]"; - -"CancelResetAccount.Title" = "Cancel Account Reset"; -"CancelResetAccount.TextSMS" = "Somebody with access to your phone number %@ has requested to delete your Telegram account and reset your 2-Step Verification password.\n\nIf it wasn't you, please enter the code we've just sent you via SMS to your number."; - -"CancelResetAccount.Success" = "The deletion process was cancelled for your account %@."; -"MediaPicker.MomentsDateRangeSameMonthYearFormat" = "{month} {day1} – {day2}, {year}"; - -"Paint.Clear" = "Clear All"; -"Paint.ClearConfirm" = "Clear Painting"; -"Paint.Delete" = "Delete"; -"Paint.Edit" = "Edit"; -"Paint.Duplicate" = "Duplicate"; -"Paint.Stickers" = "Stickers"; -"Paint.RecentStickers" = "Recent"; -"Paint.Masks" = "Masks"; - -"Paint.Outlined" = "Outlined"; -"Paint.Regular" = "Regular"; - -"MediaPicker.VideoMuteDescription" = "Sound is now muted, so the video will autoplay and loop like a GIF."; - - -"Group.Username.RemoveExistingUsernamesInfo" = "Sorry, you have reserved too many public usernames. You can revoke the link from one of your older groups or channels, or create a private entity instead."; - -"ServiceMessage.GameScoreExtended_1" = "{name} scored %@ in {game}"; -"ServiceMessage.GameScoreExtended_2" = "{name} scored %@ in {game}"; -"ServiceMessage.GameScoreExtended_3_10" = "{name} scored %@ in {game}"; -"ServiceMessage.GameScoreExtended_any" = "{name} scored %@ in {game}"; -"ServiceMessage.GameScoreExtended_many" = "{name} scored %@ in {game}"; -"ServiceMessage.GameScoreExtended_0" = "{name} scored %@ in {game}"; - -"ServiceMessage.GameScoreSelfExtended_1" = "You scored %@ in {game}"; -"ServiceMessage.GameScoreSelfExtended_2" = "You scored %@ in {game}"; -"ServiceMessage.GameScoreSelfExtended_3_10" = "You scored %@ in {game}"; -"ServiceMessage.GameScoreSelfExtended_any" = "You scored %@ in {game}"; -"ServiceMessage.GameScoreSelfExtended_many" = "You scored %@ in {game}"; -"ServiceMessage.GameScoreSelfExtended_0" = "You scored %@ in {game}"; - -"ServiceMessage.GameScoreSimple_1" = "{name} scored %@"; -"ServiceMessage.GameScoreSimple_2" = "{name} scored %@"; -"ServiceMessage.GameScoreSimple_3_10" = "{name} scored %@"; -"ServiceMessage.GameScoreSimple_any" = "{name} scored %@"; -"ServiceMessage.GameScoreSimple_many" = "{name} scored %@"; -"ServiceMessage.GameScoreSimple_0" = "{name} scored %@"; - -"ServiceMessage.GameScoreSelfSimple_1" = "You scored %@"; -"ServiceMessage.GameScoreSelfSimple_2" = "You scored %@"; -"ServiceMessage.GameScoreSelfSimple_3_10" = "You scored %@"; -"ServiceMessage.GameScoreSelfSimple_any" = "You scored %@"; -"ServiceMessage.GameScoreSelfSimple_many" = "You scored %@"; -"ServiceMessage.GameScoreSelfSimple_0" = "You scored %@"; - -"Notification.GameScoreExtended_1" = "scored %@ in {game}"; -"Notification.GameScoreExtended_2" = "scored %@ in {game}"; -"Notification.GameScoreExtended_3_10" = "scored %@ in {game}"; -"Notification.GameScoreExtended_any" = "scored %@ in {game}"; -"Notification.GameScoreExtended_many" = "scored %@ in {game}"; -"Notification.GameScoreExtended_0" = "scored %@ in {game}"; - -"Notification.GameScoreSelfExtended_1" = "scored %@ in {game}"; -"Notification.GameScoreSelfExtended_2" = "scored %@ in {game}"; -"Notification.GameScoreSelfExtended_3_10" = "scored %@ in {game}"; -"Notification.GameScoreSelfExtended_any" = "scored %@ in {game}"; -"Notification.GameScoreSelfExtended_many" = "scored %@ in {game}"; -"Notification.GameScoreSelfExtended_0" = "scored %@ in {game}"; - -"Notification.GameScoreSimple_1" = "scored %@"; -"Notification.GameScoreSimple_2" = "scored %@"; -"Notification.GameScoreSimple_3_10" = "scored %@"; -"Notification.GameScoreSimple_any" = "scored %@"; -"Notification.GameScoreSimple_many" = "scored %@"; -"Notification.GameScoreSimple_0" = "scored %@"; - -"Notification.GameScoreSelfSimple_1" = "scored %@"; -"Notification.GameScoreSelfSimple_2" = "scored %@"; -"Notification.GameScoreSelfSimple_3_10" = "scored %@"; -"Notification.GameScoreSelfSimple_any" = "scored %@"; -"Notification.GameScoreSelfSimple_many" = "scored %@"; -"Notification.GameScoreSelfSimple_0" = "scored %@"; - -"Stickers.Install" = "ADD"; -"Stickers.Installed" = "ADDED"; - -"MaskStickerSettings.Title" = "Masks"; -"MaskStickerSettings.Info" = "You can add masks to photos and videos you send. To do this, open the photo editor before sending a photo or video."; - -"StickerPack.Add" = "Add"; -"StickerPack.AddMaskCount_1" = "Add 1 Mask"; -"StickerPack.AddMaskCount_2" = "Add 2 Masks"; -"StickerPack.AddMaskCount_3_10" = "Add %@ Masks"; -"StickerPack.AddMaskCount_any" = "Add %@ Masks"; -"StickerPack.AddMaskCount_many" = "Add %@ Masks"; -"StickerPack.AddMaskCount_0" = "Add %@ Masks"; - -"StickerPack.RemoveMaskCount_1" = "Remove 1 Mask"; -"StickerPack.RemoveMaskCount_2" = "Remove 2 Masks"; -"StickerPack.RemoveMaskCount_3_10" = "Remove %@ Masks"; -"StickerPack.RemoveMaskCount_any" = "Remove %@ Masks"; -"StickerPack.RemoveMaskCount_many" = "Remove %@ Masks"; -"StickerPack.RemoveMaskCount_0" = "Remove %@ Masks"; - -"Conversation.BotInteractiveUrlAlert" = "Allow %@ to pass your Telegram name and id (not your phone number) to pages you open with this bot?"; -"StickerPacksSettings.ArchivedMasks" = "Archived Masks"; -"StickerSettings.MaskContextInfo" = "If you archive a set of masks, you can quickly restore it later from the Archived Masks section."; -"StickerPacksSettings.ArchivedMasks.Info" = "You can have up to 200 sets of masks. -Unused sets are archived when you add more."; - -"CloudStorage.Title" = "Cloud Storage"; - -"Widget.AuthRequired" = "Log in to Telegram"; -"Widget.NoUsers" = "Start messaging to see your friends here"; - -"ShareMenu.CopyShareLinkGame" = "Copy link to game"; - -"Message.PinnedGame" = "pinned a game"; -"Message.AuthorPinnedGame" = "%@ pinned a game"; - -"Target.ShareGameConfirmationPrivate" = "Share the game with %@?"; -"Target.ShareGameConfirmationGroup" = "Share the game with \"%@\"?"; - -"Activity.PlayingGame" = "playing game"; -"Activity.UploadingVideoMessage" = "sending video"; - -"DialogList.SinglePlayingGameSuffix" = "%@ is playing a game"; - -"UserInfo.GroupsInCommon" = "Groups In Common"; -"Conversation.InstantPagePreview" = "INSTANT VIEW"; - -"StickerPack.ViewPack" = "View Sticker Set"; -"InstantPage.AuthorAndDateTitle" = "By %1$@ • %2$@"; -"InstantPage.FeedbackButton" = "Leave feedback about this preview"; -"Conversation.JumpToDate" = "Jump To Date"; -"Conversation.AddToReadingList" = "Add to Reading List"; - -"AccessDenied.CallMicrophone" = "Telegram needs access to your microphone for voice calls.\n\nPlease go to Settings > Privacy > Microphone and set Telegram to ON."; - -"Call.EncryptionKey.Title" = "Encryption Key"; - -"Application.Name" = "Telegram"; -"DialogList.Pin" = "Pin"; -"DialogList.Unpin" = "Unpin"; -"DialogList.PinLimitError" = "Sorry, you can pin no more than %@ chats to the top."; -"DialogList.UnknownPinLimitError" = "Sorry, you can't pin any more chats to the top."; - -"Conversation.DeleteMessagesForMe" = "Delete for me"; -"Conversation.DeleteMessagesFor" = "Delete for me and %@"; -"Conversation.DeleteMessagesForEveryone" = "Delete for everyone"; - -"NetworkUsageSettings.Title" = "Network Usage"; -"NetworkUsageSettings.Cellular" = "Cellular"; -"NetworkUsageSettings.Wifi" = "Wi-Fi"; - -"NetworkUsageSettings.GeneralDataSection" = "MESSAGES"; -"NetworkUsageSettings.MediaImageDataSection" = "PHOTOS"; -"NetworkUsageSettings.MediaVideoDataSection" = "VIDEOS"; -"NetworkUsageSettings.MediaAudioDataSection" = "AUDIO"; -"NetworkUsageSettings.MediaDocumentDataSection" = "DOCUMENTS"; -"NetworkUsageSettings.TotalSection" = "TOTAL BYTES"; -"NetworkUsageSettings.BytesSent" = "Bytes Sent"; -"NetworkUsageSettings.BytesReceived" = "Bytes Received"; - -"NetworkUsageSettings.ResetStats" = "Reset Statistics"; -"NetworkUsageSettings.ResetStatsConfirmation" = "Do you want to reset your usage statistics?"; -"NetworkUsageSettings.CellularUsageSince" = "Cellular usage since %@"; -"NetworkUsageSettings.WifiUsageSince" = "Wi-Fi usage since %@"; - -"Settings.CallSettings" = "Voice Calls"; - -"Calls.TabTitle" = "Calls"; -"Calls.All" = "All"; -"Calls.Missed" = "Missed"; - -"CallSettings.Title" = "Voice Calls"; -"CallSettings.RecentCalls" = "Recent Calls"; -"CallSettings.TabIcon" = "Show Calls Tab"; -"CallSettings.TabIconDescription" = "A call icon will appear in the tab bar."; -"CallSettings.UseLessData" = "Use Less Data"; -"CallSettings.Never" = "Never"; -"CallSettings.OnMobile" = "On Mobile Network"; -"CallSettings.Always" = "Always"; -"CallSettings.UseLessDataLongDescription" = "Using less data may improve your experience on bad networks, but will slightly decrease audio quality."; - -"Calls.CallTabTitle" = "Calls Tab"; -"Calls.CallTabDescription" = "You can add a Calls Tab to the tab bar."; -"Calls.NotNow" = "Not Now"; -"Calls.AddTab" = "Add Tab"; -"Calls.NewCall" = "New Call"; - -"Calls.RatingTitle" = "Please rate the quality\nof your Telegram call"; -"Calls.SubmitRating" = "Submit"; - -"Call.Seconds_1" = "%@ second"; -"Call.Seconds_2" = "%@ seconds"; -"Call.Seconds_3_10" = "%@ seconds"; -"Call.Seconds_any" = "%@ seconds"; -"Call.Seconds_many" = "%@ seconds"; -"Call.Seconds_0" = "%@ seconds"; -"Call.Minutes_1" = "%@ minute"; -"Call.Minutes_2" = "%@ minutes"; -"Call.Minutes_3_10" = "%@ minutes"; -"Call.Minutes_any" = "%@ minutes"; -"Call.Minutes_many" = "%@ minutes"; -"Call.Minutes_0" = "%@ minutes"; - -"Call.ShortSeconds_1" = "%@ sec"; -"Call.ShortSeconds_2" = "%@ sec"; -"Call.ShortSeconds_3_10" = "%@ sec"; -"Call.ShortSeconds_any" = "%@ sec"; -"Call.ShortSeconds_many" = "%@ sec"; -"Call.ShortSeconds_0" = "%@ sec"; -"Call.ShortMinutes_1" = "%@ min"; -"Call.ShortMinutes_2" = "%@ min"; -"Call.ShortMinutes_3_10" = "%@ min"; -"Call.ShortMinutes_any" = "%@ min"; -"Call.ShortMinutes_many" = "%@ min"; -"Call.ShortMinutes_0" = "%@ min"; - -"Notification.CallTimeFormat" = "%1$@ (%2$@)"; // 1 - type, 2 - duration -"Notification.CallOutgoing" = "Outgoing Call"; -"Notification.VideoCallOutgoing" = "Outgoing Video Call"; -"Notification.CallIncoming" = "Incoming Call"; -"Notification.VideoCallIncoming" = "Incoming Video Call"; -"Notification.CallMissed" = "Missed Call"; -"Notification.VideoCallMissed" = "Missed Video Call"; -"Notification.CallCanceled" = "Cancelled Call"; -"Notification.VideoCallCanceled" = "Cancelled Video Call"; -"Notification.CallOutgoingShort" = "Outgoing"; -"Notification.CallIncomingShort" = "Incoming"; -"Notification.CallMissedShort" = "Missed"; -"Notification.CallCanceledShort" = "Cancelled"; -"Notification.CallFormat" = "%1$@, %2$@"; // 1 - time, 2 - duration - - - -"Call.ConnectionErrorTitle" = "Unable to Call"; -"Call.ConnectionErrorMessage" = "Please check your internet connection and try again."; - -"Call.CallAgain" = "Call Again"; - -"Login.PhoneFloodError" = "Sorry, you have deleted and re-created your account too many times recently. Please wait for a few days before signing up again."; - -"Checkout.Title" = "Checkout"; -"Checkout.TotalAmount" = "Total"; -"Checkout.TotalPaidAmount" = "Total Paid"; -"Checkout.PaymentMethod" = "Payment Method"; -"Checkout.ShippingMethod" = "Shipping Method"; -"Checkout.ShippingAddress" = "Shipping Information"; -"Checkout.Name" = "Name"; -"Checkout.Email" = "E-Mail"; -"Checkout.Phone" = "Phone"; -"Checkout.PayPrice" = "Pay %@"; -"Checkout.PayNone" = "Pay"; - -"Checkout.PaymentMethod.Title" = "Payment Method"; -"Checkout.PaymentMethod.New" = "New Card..."; - -"Checkout.NewCard.Title" = "New Card"; -"Checkout.NewCard.PaymentCard" = "PAYMENT CARD"; -"Checkout.NewCard.SaveInfo" = "Save Payment Information"; -"Checkout.NewCard.SaveInfoEnableHelp" = "You can save your payment information for future use.\nPlease [turn on Two-Step Verification] to enable this."; -"Checkout.NewCard.SaveInfoHelp" = "You can save your payment information for future use."; -"Checkout.NewCard.CardholderNameTitle" = "CARDHOLDER"; -"Checkout.NewCard.CardholderNamePlaceholder" = "Cardholder Name"; -"Checkout.NewCard.PostcodeTitle" = "BILLING ADDRESS"; -"Checkout.NewCard.PostcodePlaceholder" = "Zip Code"; - -"Checkout.ShippingOption.Title" = "Shipping Method"; - -"Checkout.ErrorProviderAccountInvalid" = "This bot can't accept payments at the moment. Please try again later."; -"Checkout.ErrorProviderAccountTimeout" = "This bot can't process payments at the moment. Please try again later."; -"Checkout.ErrorInvoiceAlreadyPaid" = "You have already paid for this item."; - -"Checkout.ErrorGeneric" = "An error occurred while processing your payment. Your card has not been billed."; -"Checkout.ErrorPaymentFailed" = "Payment failed. Your card has not been billed."; -"Checkout.ErrorPrecheckoutFailed" = "The bot couldn't process your payment. Your card has not been billed."; - -"CheckoutInfo.Title" = "Shipping Information"; -"CheckoutInfo.ShippingInfoTitle" = "SHIPPING ADDRESS"; -"CheckoutInfo.ShippingInfoAddress1" = "Address 1"; -"CheckoutInfo.ShippingInfoAddress1Placeholder" = "Address"; -"CheckoutInfo.ShippingInfoAddress2" = "Address 2"; -"CheckoutInfo.ShippingInfoAddress2Placeholder" = "Address"; -"CheckoutInfo.ShippingInfoState" = "State"; -"CheckoutInfo.ShippingInfoStatePlaceholder" = "State"; -"CheckoutInfo.ShippingInfoCity" = "City"; -"CheckoutInfo.ShippingInfoCityPlaceholder" = "City"; -"CheckoutInfo.ShippingInfoCountry" = "Country"; -"CheckoutInfo.ShippingInfoCountryPlaceholder" = "Country"; -"CheckoutInfo.ShippingInfoPostcode" = "Postcode"; -"CheckoutInfo.ShippingInfoPostcodePlaceholder" = "Postcode"; -"CheckoutInfo.ReceiverInfoTitle" = "RECEIVER"; -"CheckoutInfo.ReceiverInfoName" = "Name"; -"CheckoutInfo.ReceiverInfoNamePlaceholder" = "Name Surname"; -"CheckoutInfo.ReceiverInfoEmail" = "Email"; -"CheckoutInfo.ReceiverInfoEmailPlaceholder" = "Email"; -"CheckoutInfo.ReceiverInfoPhone" = "Phone"; -"CheckoutInfo.SaveInfo" = "Save Info"; -"CheckoutInfo.SaveInfoHelp" = "You can save your shipping information for future use."; -"CheckoutInfo.Pay" = "Pay"; - -"Checkout.Receipt.Title" = "Receipt"; - -"Message.ReplyActionButtonShowReceipt" = "Show Receipt"; -"Message.InvoiceLabel" = "INVOICE"; - -"CheckoutInfo.ErrorShippingNotAvailable" = "Shipping to the selected country is not available."; -"CheckoutInfo.ErrorPostcodeInvalid" = "Please enter a valid postcode."; -"CheckoutInfo.ErrorStateInvalid" = "Please enter a valid state."; -"CheckoutInfo.ErrorCityInvalid" = "Please enter a valid city."; -"CheckoutInfo.ErrorNameInvalid" = "Please enter a valid name."; -"CheckoutInfo.ErrorEmailInvalid" = "Please enter a valid e-mail address."; -"CheckoutInfo.ErrorPhoneInvalid" = "Please enter a valid phone number."; - -"Checkout.WebConfirmation.Title" = "Complete Payment"; -"Checkout.PasswordEntry.Title" = "Payment Confirmation"; -"Checkout.PasswordEntry.Pay" = "Pay"; -"Checkout.PasswordEntry.Text" = "Your card %@ is on file. To pay with this card, please enter your 2-Step-Verification password."; - -"Checkout.SavePasswordTimeout" = "Would you like to save your password for %@?"; -"Checkout.SavePasswordTimeoutAndTouchId" = "Would you like to save your password for %@ and use Touch ID instead?"; -"Checkout.PayWithTouchId" = "Pay with Touch ID"; -"Checkout.EnterPassword" = "Enter Password"; - -"Your_card_has_expired" = "Your card has expired."; - -/* Error when the card was declined by the credit card networks */ -"Your_card_was_declined" = "Your card was declined."; - -/* Error when the card's expiration month is not valid */ -"Your_cards_expiration_month_is_invalid" ="You've entered an invalid expiration month."; - -/* Error when the card's expiration year is not valid */ -"Your_cards_expiration_year_is_invalid" ="You've entered an invalid expiration year."; - -/* Error when the card number is not valid */ -"Your_cards_number_is_invalid" = "You've entered an invalid card number."; - -/* Error when the card's CVC is not valid */ -"Your_cards_security_code_is_invalid" = "You've entered an invalid security code."; - -"MESSAGE_INVOICE" = "%1$@ sent you an invoice for %2$@"; -"CHAT_MESSAGE_INVOICE" = "%1$@ sent an invoice for %3$@ to the group %2$@"; -"PINNED_INVOICE" = "%1$@ pinned an invoice"; - -"Message.PinnedInvoice" = "pinned an invoice"; - -"User.DeletedAccount" = "Deleted Account"; - -"Settings.SaveEditedPhotos" = "Save Edited Photos"; - -"Message.PaymentSent" = "Payment: %@"; -"Notification.PaymentSent" = "You have just successfully transferred {amount} to {name} for {title}"; - -"Common.NotNow" = "Not Now"; - -"Calls.RatingFeedback" = "Write a comment..."; - -"Call.StatusIncoming" = "Telegram Audio..."; -"Call.IncomingVoiceCall" = "Incoming Voice Call"; -"Call.IncomingVideoCall" = "Incoming Video Call"; -"Call.StatusRequesting" = "Contacting..."; -"Call.StatusWaiting" = "Waiting..."; -"Call.StatusRinging" = "Ringing..."; -"Call.StatusConnecting" = "Connecting..."; -"Call.StatusOngoing" = "Telegram Audio %@"; -"Call.StatusEnded" = "Call Ended"; -"Call.StatusFailed" = "Call Failed"; -"Call.StatusBusy" = "Busy"; -"Call.Accept" = "Accept"; -"Call.Decline" = "Decline"; - -"Call.StatusBar" = "Touch to return to call %@"; - -"Call.ParticipantVersionOutdatedError" = "%@'s app does not support calls. They need to update their app before you can call them."; -"Call.ParticipantVideoVersionOutdatedError" = "%@'s app does not support video calls. They need to update their app before you can call them."; - -"Privacy.Calls" = "Voice Calls"; - -"Privacy.Calls.WhoCanCallMe" = "WHO CAN CALL ME"; -"Privacy.Calls.CustomHelp" = "You can restrict who can call you with granular precision."; -"Privacy.Calls.AlwaysAllow" = "Always Allow"; -"Privacy.Calls.NeverAllow" = "Never Allow"; -"Privacy.Calls.CustomShareHelp" = "These users will or will not be able to call you regardless of the settings above."; - -"Privacy.Calls.AlwaysAllow.Title" = "Always Allow"; -"Privacy.Calls.AlwaysAllow.Placeholder" = "Always allow..."; -"Privacy.Calls.NeverAllow.Title" = "Never Allow"; -"Privacy.Calls.NeverAllow.Placeholder" = "Never allow..."; - -"PhotoEditor.QualityTool" = "Quality"; -"PhotoEditor.QualityVeryLow" = "Very Low"; -"PhotoEditor.QualityLow" = "Low"; -"PhotoEditor.QualityMedium" = "Medium"; -"PhotoEditor.QualityHigh" = "High"; -"PhotoEditor.QualityVeryHigh" = "Very High"; - -"Settings.SaveEditedPhotos" = "Save Edited Photos"; - -"Calls.NoCallsPlaceholder" = "Your recent calls will appear here"; -"Calls.NoMissedCallsPlacehoder" = "You have no missed calls"; - -"Call.CallInProgressTitle" = "Call in Progress"; -"Call.CallInProgressMessage" = "Finish call with %1$@ and start a new one with %2$@?"; -"Call.ExternalCallInProgressMessage" = "Please finish the current call first."; - -"Call.Message" = "Message"; - -"UserInfo.TapToCall" = "Tap to make an end-to-end encrypted call"; -"Call.GroupFormat" = "%1$@ (%2$@)"; - -"NetworkUsageSettings.CallDataSection" = "CALLS"; - -"Call.PrivacyErrorMessage" = "Sorry, %@ doesn't accept calls."; - -"Notification.CallBack" = "Call Back"; - -"Call.AudioRouteSpeaker" = "Speaker"; -"Call.AudioRouteHeadphones" = "Headphones"; -"Call.AudioRouteHide" = "Hide"; - -"Call.PhoneCallInProgressMessage" = "You can’t place a Telegram call if you’re already on a phone call."; -"Call.RecordingDisabledMessage" = "Please end your call before recording a voice message."; - -"Call.EmojiDescription" = "If these emoji are the same on %@'s screen, this call is 100%% secure."; - -"Message.VideoMessage" = "Video Message"; - -"Conversation.HoldForAudio" = "Hold to record audio. Tap to switch to video."; -"Conversation.HoldForVideo" = "Hold to record video. Tap to switch to audio."; - -"UserInfo.TelegramCall" = "Telegram Call"; -"UserInfo.PhoneCall" = "Phone Call"; - -"SharedMedia.CategoryMedia" = "Media"; -"SharedMedia.CategoryDocs" = "Docs"; -"SharedMedia.CategoryLinks" = "Links"; -"SharedMedia.CategoryOther" = "Audio"; - -"AccessDenied.VideoMessageCamera" = "Telegram needs access to your camera to send video messages.\n\nPlease go to Settings > Privacy > Camera and set Telegram to ON."; -"AccessDenied.VideoMessageMicrophone" = "Telegram needs access to your microphone to send video messages.\n\nPlease go to Settings > Privacy > Microphone and set Telegram to ON."; - -"ChatSettings.AutomaticVideoMessageDownload" = "AUTOMATIC VIDEO MESSAGE DOWNLOAD"; - -"ForwardedVideoMessages_1" = "Forwarded video message"; -"ForwardedVideoMessages_2" = "2 forwarded video messages"; -"ForwardedVideoMessages_3_10" = "%@ forwarded video messages"; -"ForwardedVideoMessages_any" = "%@ forwarded video messages"; -"ForwardedVideoMessages_many" = "%@ forwarded video messages"; -"ForwardedVideoMessages_0" = "%@ forwarded video messages"; - -"Conversation.DiscardVoiceMessageTitle" = "Discard Voice Message"; -"Conversation.DiscardVoiceMessageDescription" = "Are you sure you want to stop recording and discard\nyour voice message?"; -"Conversation.DiscardVoiceMessageAction" = "Discard"; - -"Message.ForwardedMessageShort" = "Forwarded From\n%@"; - -"Checkout.LiabilityAlertTitle" = "Warning"; -"Checkout.LiabilityAlert" = "Neither Telegram, nor %1$@ will have access to your credit card information. Credit card details will be handled only by the payment system, %2$@.\n\nPayments will go directly to the developer of %1$@. Telegram cannot provide any guarantees, so proceed at your own risk. In case of problems, please contact the developer of %1$@ or your bank."; - -"Settings.AppLanguage" = "Language"; -"Settings.AppLanguage.Unofficial" = "UNOFFICIAL"; - -"InstantPage.AutoNightTheme" = "Auto-Night Theme"; - -"Privacy.PaymentsTitle" = "PAYMENTS"; -"Privacy.PaymentsClearInfo" = "Clear payment & shipping info"; -"Privacy.PaymentsClearInfoHelp" = "You can delete your shipping info and instruct all payment providers to remove your saved credit cards. Note that Telegram never stores your credit card data."; -"Privacy.PaymentsClear.PaymentInfo" = "Payment Info"; -"Privacy.PaymentsClear.ShippingInfo" = "Shipping Info"; - -"Channel.EditAdmin.PermissionsHeader" = "WHAT CAN THIS ADMIN DO?"; -"Channel.EditAdmin.PermissionChangeInfo" = "Change Channel Info"; -"Group.EditAdmin.PermissionChangeInfo" = "Change Group Info"; -"Channel.EditAdmin.PermissionPostMessages" = "Post Messages"; -"Channel.EditAdmin.PermissionEditMessages" = "Edit Messages"; -"Channel.EditAdmin.PermissionDeleteMessages" = "Delete Messages"; -"Channel.EditAdmin.PermissionBanUsers" = "Ban Users"; -"Channel.EditAdmin.PermissionInviteSubscribers" = "Add Subscribers"; -"Channel.EditAdmin.PermissionInviteMembers" = "Add Members"; -"Channel.EditAdmin.PermissionInviteViaLink" = "Invite Users via Link"; -"Channel.EditAdmin.PermissionPinMessages" = "Pin Messages"; -"Channel.EditAdmin.PermissionAddAdmins" = "Add New Admins"; - -"Channel.EditAdmin.PermissinAddAdminOn" = "This Admin will be able to add new admins with the same (or more limited) permissions."; -"Channel.EditAdmin.PermissinAddAdminOff" = "This Admin will not be able to add new admins."; - -"Login.ContinueWithLocalization" = "Continue with English"; -"Localization.LanguageName" = "English"; -"Localization.ChooseLanguage" = "Choose Your Language"; -"Localization.EnglishLanguageName" = "English"; -"Localization.LanguageOther" = "Other"; -"Localization.LanguageCustom" = "Custom"; - -"Channel.BanUser.Title" = "Ban User"; -"Channel.BanUser.PermissionsHeader" = "User Restrictions"; -"Channel.BanUser.PermissionReadMessages" = "Can Read Messages"; -"Channel.BanUser.PermissionSendMessages" = "Can Send Messages"; -"Channel.BanUser.PermissionSendMedia" = "Can Send Media"; -"Channel.BanUser.PermissionSendStickersAndGifs" = "Can Send Stickers & GIFs"; -"Channel.BanUser.PermissionEmbedLinks" = "Can Embed Links"; -"Channel.BanUser.PermissionSendPolls" = "Send Polls"; -"Channel.BanUser.PermissionChangeGroupInfo" = "Change Group Info"; -"Channel.BanUser.PermissionAddMembers" = "Add Members"; -"Channel.BanUser.Unban" = "Unban"; - -"Channel.BanUser.BlockFor" = "Block For"; - -"Channel.BanList.BlockedTitle" = "BLOCKED"; -"Channel.BanList.RestrictedTitle" = "RESTRICTED"; - -"Group.Info.AdminLog" = "Recent Actions"; -"Channel.AdminLog.InfoPanelTitle" = "What Is This?"; -"Channel.AdminLog.InfoPanelAlertTitle" = "What is the event log?"; -"Channel.AdminLog.InfoPanelAlertText" = "This is a list of all service actions taken by the group's members and admins in the last 48 hours."; -"Channel.AdminLog.InfoPanelChannelAlertText" = "This is a list of all service actions taken by the channel's admins in the last 48 hours."; - -"Channel.AdminLog.BanReadMessages" = "Read Messages"; -"Channel.AdminLog.BanSendMessages" = "Send Messages"; -"Channel.AdminLog.BanSendMedia" = "Send Media"; -"Channel.AdminLog.BanSendStickersAndGifs" = "Send Stickers & GIFs"; -"Channel.AdminLog.BanEmbedLinks" = "Embed Links"; -"Channel.AdminLog.MessageRestricted" = "%@ changed restrictions for %@ (%@)"; -"Channel.AdminLog.MessageAdmin" = "%@ changed privileges for %@ (%@)"; -"Channel.AdminLog.ChangeInfo" = "Change Info"; -"Channel.AdminLog.PinMessages" = "Pin Messages"; -"Channel.AdminLog.AddMembers" = "Add Members"; -"Channel.AdminLog.SendPolls" = "Send Polls"; - -"Channel.AdminLog.CanChangeInfo" = "Change Info"; -"Channel.AdminLog.CanSendMessages" = "Post Messages"; -"Channel.AdminLog.CanDeleteMessages" = "Delete Messages"; -"Channel.AdminLog.CanBanUsers" = "Ban Users"; -"Channel.AdminLog.CanInviteUsers" = "Add Users"; -"Channel.AdminLog.CanPinMessages" = "Pin Messages"; -"Channel.AdminLog.CanAddAdmins" = "Add New Admins"; -"Channel.AdminLog.CanBeAnonymous" = "Remain Anonymous"; -"Channel.AdminLog.CanEditMessages" = "Edit Messages"; - -"Channel.AdminLog.MessageToggleInvitesOn" = "%@ enabled group invites"; -"Channel.AdminLog.MessageToggleInvitesOff" = "%@ disabled group invites"; - -"Channel.AdminLog.MessageUnpinned" = "%@ unpinned message"; - -"Channel.AdminLog.MessageToggleSignaturesOn" = "%@ enabled signatures"; -"Channel.AdminLog.MessageToggleSignaturesOff" = "%@ disabled signatures"; - -"Channel.AdminLog.MessageChangedGroupUsername" = "%@ changed group link:"; -"Channel.AdminLog.MessageChangedChannelUsername" = "%@ changed channel link:"; -"Channel.AdminLog.MessageRemovedGroupUsername" = "%@ removed group link"; -"Channel.AdminLog.MessageRemovedChannelUsername" = "%@ removed channel link"; - -"Channel.AdminLog.MessageChangedGroupAbout" = "%@ edited group description"; -"Channel.AdminLog.MessageChangedChannelAbout" = "%@ edited channel description"; - -"Channel.AdminLog.MessageEdited" = "%@ edited message:"; -"Channel.AdminLog.CaptionEdited" = "%@ edited caption:"; -"Channel.AdminLog.MessageDeleted" = "%@ deleted message:"; -"Channel.AdminLog.MessagePinned" = "%@ pinned message:"; - -"Channel.AdminLog.MessageInvitedName" = "invited %1$@"; -"Channel.AdminLog.MessageInvitedNameUsername" = "invited %1$@ (%2$@)"; -"Channel.AdminLog.MessageKickedName" = "banned %1$@"; -"Channel.AdminLog.MessageKickedNameUsername" = "banned %1$@ (%2$@)"; -"Channel.AdminLog.MessageUnkickedName" = "unbanned %1$@"; -"Channel.AdminLog.MessageUnkickedNameUsername" = "unbanned %1$@ (%2$@)"; -"Channel.AdminLog.MessageRestrictedName" = "changed restrictions for %1$@"; -"Channel.AdminLog.MessageRestrictedNameUsername" = "changed restrictions for %1$@ (%2$@)"; -"Channel.AdminLog.MessagePromotedName" = "changed privileges for %1$@"; -"Channel.AdminLog.MessagePromotedNameUsername" = "changed privileges for %1$@ (%2$@)"; -"Channel.AdminLog.MessageRestrictedUntil" = "until %@"; -"Channel.AdminLog.MessageRestrictedForever" = "indefinitely"; -"Channel.AdminLog.MessageRestrictedNewSetting" = "now: %@"; - -"Channel.AdminLog.MessagePreviousMessage" = "Original message"; -"Channel.AdminLog.MessagePreviousCaption" = "Original caption"; -"Channel.AdminLog.MessagePreviousLink" = "Previous link"; -"Channel.AdminLog.MessagePreviousDescription" = "Previous description"; - -"Contacts.MemberSearchSectionTitleGroup" = "Group Members"; - -"Channel.AdminLog.TitleAllEvents" = "All Actions"; -"Channel.AdminLog.TitleSelectedEvents" = "Selected Actions"; -"Channel.AdminLogFilter.Title" = "Filter"; -"Channel.AdminLogFilter.EventsTitle" = "ACTIONS"; -"Channel.AdminLogFilter.EventsAll" = "All Actions"; -"Channel.AdminLogFilter.EventsRestrictions" = "New Restrictions"; -"Channel.AdminLogFilter.EventsAdmins" = "New Admins"; -"Channel.AdminLogFilter.EventsNewMembers" = "New Members"; -"Channel.AdminLogFilter.EventsInfo" = "Group Info"; -"Channel.AdminLogFilter.ChannelEventsInfo" = "Channel Info"; -"Channel.AdminLogFilter.EventsDeletedMessages" = "Deleted Messages"; -"Channel.AdminLogFilter.EventsEditedMessages" = "Edited Messages"; -"Channel.AdminLogFilter.EventsPinned" = "Pinned Messages"; -"Channel.AdminLogFilter.EventsLeaving" = "Members Removed"; -"Channel.AdminLogFilter.AdminsTitle" = "ADMINS"; -"Channel.AdminLogFilter.AdminsAll" = "All Admins"; - -"Group.ErrorSendRestrictedStickers" = "Sorry, the admins of this group have restricted you from sending stickers."; -"Group.ErrorSendRestrictedMedia" = "Sorry, the admins of this group have restricted you from sending media."; - -"SharedMedia.ViewInChat" = "View in Chat"; - -"Channel.Info.BlackList" = "Blacklist"; - -"Channel.Management.PromotedBy" = "Promoted by %@"; -"DialogList.LanguageTooltip" = "You can change the language later in Settings"; - -"Contacts.PhoneNumber" = "Phone Number"; -"Contacts.AddPhoneNumber" = "Add %@"; -"Contacts.ShareTelegram" = "Share Telegram"; - -"Conversation.ViewChannel" = "VIEW CHANNEL"; -"Conversation.ViewGroup" = "VIEW GROUP"; - -"GroupInfo.ActionPromote" = "Promote"; -"GroupInfo.ActionRestrict" = "Restrict"; - -"Conversation.RestrictedTextTimed" = "The admins of this group have restricted you from writing here until %@."; -"Conversation.RestrictedText" = "The admins of this group have restricted you from writing here."; -"Conversation.DefaultRestrictedText" = "Writing messages isn’t allowed in this group."; - -"Conversation.RestrictedInlineTimed" = "The admins of this group have restricted you from posting inline content here until %@."; -"Conversation.RestrictedInline" = "The admins of this group have restricted you from posting inline content here."; -"Conversation.DefaultRestrictedInline" = "Posting inline content isn’t allowed in this group."; - -"Conversation.RestrictedMediaTimed" = "The admins of this group have restricted you from posting media content here until %@."; -"Conversation.RestrictedMedia" = "The admins of this group have restricted you from posting media content here."; -"Conversation.DefaultRestrictedMedia" = "Posting media content isn’t allowed in this group."; - -"Conversation.RestrictedStickersTimed" = "The admins of this group have restricted you from posting stickers here until %@."; -"Conversation.RestrictedStickers" = "The admins of this group have restricted you from posting stickers here."; -"Conversation.DefaultRestrictedStickers" = "Posting stickers isn’t allowed in this group."; - -"ChatSettings.ConnectionType.Title" = "CONNECTION TYPE"; -"ChatSettings.ConnectionType.UseProxy" = "Use Proxy"; -"ChatSettings.ConnectionType.UseSocks5" = "SOCKS5"; - -"SocksProxySetup.Title" = "Proxy"; - -"SocksProxySetup.TypeNone" = "Disabled"; -"SocksProxySetup.TypeSocks" = "SOCKS5"; - -"SocksProxySetup.Connection" = "CONNECTION"; -"SocksProxySetup.Hostname" = "Server"; -"SocksProxySetup.Port" = "Port"; - -"SocksProxySetup.Credentials" = "CREDENTIALS (OPTIONAL)"; -"SocksProxySetup.Username" = "Username"; -"SocksProxySetup.Password" = "Password"; - -"Channel.AdminLog.EmptyTitle" = "No actions here yet"; -"Channel.AdminLog.EmptyText" = "No service actions were taken by the channel members and admins in the last 48 hours."; -"Group.AdminLog.EmptyText" = "No service actions were taken by the group's members and admins in the last 48 hours."; -"Broadcast.AdminLog.EmptyText" = "No service actions were taken by the channel's admins in the last 48 hours."; - -"Channel.AdminLog.EmptyFilterTitle" = "No actions found"; -"Channel.AdminLog.EmptyFilterQueryText" = "No recent actions that contain '%@' have been found."; -"Channel.AdminLog.EmptyFilterText" = "No recent actions that match your query have been found."; - -"Channel.AdminLog.EmptyMessageText" = "Empty"; - -"Camera.Title" = "Take Photo or Video"; - -"Channel.Members.AddAdminErrorNotAMember" = "Sorry, you can't add this user as an admin because they are not a member of this group and you are not allowed to invite them."; - -"Channel.Members.AddAdminErrorBlacklisted" = "Sorry, you can't add this user as an admin because they are in the blacklist and you can't unban them."; - -"Channel.Members.AddBannedErrorAdmin" = "Sorry, you can't ban this user because they are an admin in this group and you are not allowed to demote them."; - -"Group.Members.AddMemberBotErrorNotAllowed" = "Sorry, you don't have the necessary permissions to add bots to this group."; - -"Privacy.Calls.P2P" = "Peer-to-Peer"; -"Privacy.Calls.P2PHelp" = "Disabling peer-to-peer will relay all calls through Telegram servers to avoid revealing your IP address, but will slightly decrease audio quality."; - -"Privacy.Calls.Integration" = "iOS Call Integration"; -"Privacy.Calls.IntegrationHelp" = "iOS Call Integration shows Telegram calls on the lock screen and in the system's call history. If iCloud sync is enabled, your call history is shared with Apple."; - -"Call.ReportPlaceholder" = "What went wrong?"; -"Call.ReportIncludeLog" = "Send technical information"; -"Call.ReportIncludeLogDescription" = "This won't reveal the contents of your conversation, but will help us fix the issue sooner."; -"Call.ReportSkip" = "Skip"; -"Call.ReportSend" = "Send"; - -"Channel.EditAdmin.CannotEdit" = "You cannot edit the rights of this admin."; -"Call.RateCall" = "Rate This Call"; -"Call.ShareStats" = "Share Statistics"; - -"Settings.ApplyProxyAlert" = "Are you sure you want to enable this proxy?\nServer: %1$@\nPort: %2$@\n\nYou can change your proxy server later it in the Settings (Data and Storage)."; -"Settings.ApplyProxyAlertCredentials" = "Are you sure you want to enable this proxy?\nServer: %1$@\nPort: %2$@\nUsername: %3$@\nPassword: %4$@\n\nYou can change your proxy server later it in the Settings (Data and Storage)."; -"Settings.ApplyProxyAlertEnable" = "Enable"; - -"Channel.Management.RestrictedBy" = "Restricted by %@"; - -"Stickers.FrequentlyUsed" = "Recently Used"; - -"Contacts.ImportersCount_1" = "1 contact on Telegram"; -"Contacts.ImportersCount_2" = "2 contacts on Telegram"; -"Contacts.ImportersCount_3_10" = "%@ contacts on Telegram"; -"Contacts.ImportersCount_any" = "%@ contacts on Telegram"; -"Contacts.ImportersCount_many" = "%@ contacts on Telegram"; -"Contacts.ImportersCount_0" = "%@ contacts on Telegram"; - -"Conversation.ContextMenuBan" = "Restrict"; - -"SocksProxySetup.UseForCalls" = "Use for calls"; -"SocksProxySetup.UseForCallsHelp" = "Proxy servers may degrade the quality of your calls."; - -"InviteText.URL" = "https://telegram.org/dl"; -"InviteText.SingleContact" = "Hey, I'm using Telegram to chat. Join me! Download it here: %@"; -"InviteText.ContactsCountText_1" = "Hey, I'm using Telegram to chat. Join me! Download it here: {url}"; -"InviteText.ContactsCountText_2" = "Hey, I'm using Telegram to chat – and so are 2 of our other contacts. Join us! Download it here: {url}"; -"InviteText.ContactsCountText_3_10" = "Hey, I'm using Telegram to chat – and so are %@ of our other contacts. Join us! Download it here: {url}"; -"InviteText.ContactsCountText_any" = "Hey, I'm using Telegram to chat – and so are %@ of our other contacts. Join us! Download it here: {url}"; -"InviteText.ContactsCountText_many" = "Hey, I'm using Telegram to chat – and so are %@ of our other contacts. Join us! Download it here: {url}"; -"InviteText.ContactsCountText_0" = "Hey, I'm using Telegram to chat. Join me! Download it here: {url}"; - -"Invite.LargeRecipientsCountWarning" = "Please note that it may take some time for your device to send all of these invitations"; - -"Contacts.InviteSearchLabel" = "Search for contacts"; - -"Message.ImageExpired" = "Photo has expired"; -"Message.VideoExpired" = "Video has expired"; - -"SecretImage.Title" = "Disappearing Photo"; -"SecretVideo.Title" = "Disappearing Video"; -"SecretGif.Title" = "Disappearing GIF"; -"SecretTimer.ImageDescription" = "If you set a timer, the photo will self-destruct after it was viewed."; -"SecretTimer.VideoDescription" = "If you set a timer, the video will self-destruct after it was viewed."; - -"PhotoEditor.TiltShift" = "Tilt Shift"; - -"Notification.SecretChatMessageScreenshotSelf" = "You took a screenshot!"; - -"Settings.AboutEmpty" = "Add"; - -"SecretImage.NotViewedYet" = "%@ hasn't opened this photo yet"; -"SecretVideo.NotViewedYet" = "%@ hasn't played this video yet"; -"SecretGIF.NotViewedYet" = "%@ hasn't played this GIF yet"; - -"UserInfo.About.Placeholder" = "Bio"; - -"Call.StatusNoAnswer" = "No Answer"; - -"Conversation.SearchByName.Prefix" = "from: "; -"Conversation.SearchByName.Placeholder" = "Search Members"; - -"Login.PhoneBannedError" = "Your phone was banned."; - -"Clipboard.SendPhoto" = "Send Photo"; - -"HashtagSearch.AllChats" = "All Chats"; - -"Stickers.AddToFavorites" = "Add to Favorites"; -"Stickers.RemoveFromFavorites" = "Remove from Favorites"; - -"Channel.Info.Stickers" = "Group Sticker Set"; -"Channel.Stickers.Placeholder" = "stickerset"; -"Channel.Stickers.YourStickers" = "CHOOSE FROM YOUR STICKERS"; - -"Stickers.FavoriteStickers" = "Favorite Stickers"; -"Stickers.GroupStickers" = "Group Stickers"; -"Stickers.GroupChooseStickerPack" = "CHOOSE STICKER SET"; -"Stickers.GroupStickersHelp" = "You can choose a set that will be available to all group members when they are chatting in this group."; - -"Channel.AdminLog.MessageChangedGroupStickerPack" = "%@ changed group sticker set"; -"Channel.AdminLog.MessageRemovedGroupStickerPack" = "%@ removed group sticker set"; - -"Conversation.ContextMenuCopyLink" = "Copy Link"; - -"Channel.Stickers.Searching" = "Searching..."; -"Channel.Stickers.NotFound" = "No such sticker set found"; -"Channel.Stickers.NotFoundHelp" = "Try again or choose from the list below"; -"Channel.Stickers.CreateYourOwn" = "You can create your own custom sticker set using @stickers bot."; - -"MediaPicker.TimerTooltip" = "You can now set a self-destruct timer"; - -"UserInfo.BlockConfirmation" = "Block %@?"; - -"FastTwoStepSetup.Title" = "Password & Email"; -"FastTwoStepSetup.PasswordSection" = "PASSWORD"; -"FastTwoStepSetup.PasswordPlaceholder" = "Enter a password"; -"FastTwoStepSetup.PasswordConfirmationPlaceholder" = "Re-enter your password"; -"FastTwoStepSetup.PasswordHelp" = "Please create a password to protect your payment info. You'll be asked to enter it when you log in."; -"FastTwoStepSetup.EmailSection" = "RECOVERY E-MAIL"; -"FastTwoStepSetup.EmailPlaceholder" = "Your E-Mail"; -"FastTwoStepSetup.EmailHelp" = "Please add your valid e-mail. It is the only way to recover a forgotten password."; - -"Conversation.ViewMessage" = "VIEW MESSAGE"; - -"GroupInfo.GroupHistory" = "History For New Members"; -"GroupInfo.GroupHistoryVisible" = "Visible"; -"GroupInfo.GroupHistoryHidden" = "Hidden"; - -"Group.Setup.HistoryTitle" = "Chat History Settings"; -"Group.Setup.HistoryHeader" = "HISTORY FOR NEW MEMBERS"; -"Group.Setup.HistoryVisible" = "Visible"; -"Group.Setup.HistoryHidden" = "Hidden"; - -"Group.Setup.HistoryVisibleHelp" = "New members will see messages that were sent before they joined."; -"Group.Setup.HistoryHiddenHelp" = "New members won't see earlier messages."; -"Group.Setup.BasicHistoryHiddenHelp" = "New members won't see more than 100 previous messages."; - - -"Channel.AdminLog.MessageGroupPreHistoryVisible" = "%@ made the group history visible for new members"; -"Channel.AdminLog.MessageGroupPreHistoryHidden" = "%@ made the group history hidden from new members"; - -"Map.PullUpForPlaces" = "PULL UP TO SEE PLACES NEARBY"; -"Map.ShareLiveLocation" = "Share My Live Location for..."; -"Map.ShareLiveLocationHelp" = "Updated in real time as you move"; -"Map.StopLiveLocation" = "Stop Sharing Location"; -"Map.Directions" = "Directions"; -"Map.DirectionsDriveEta" = "%@ drive"; -"Map.Location" = "Location"; -"Map.YouAreHere" = "you are here"; -"Map.LiveLocationShowAll" = "Show All"; - -"Map.LiveLocationTitle" = "Live Location"; -"Map.LiveLocationPrivateDescription" = "Choose for how long %@ will see your accurate location."; -"Map.LiveLocationGroupDescription" = "Choose for how long people in this chat will see your accurate location."; -"Map.LiveLocationFor15Minutes" = "for 15 minutes"; -"Map.LiveLocationFor1Hour" = "for 1 hour"; -"Map.LiveLocationFor8Hours" = "for 8 hours"; -"Map.LiveLocationShortHour" = "%@h"; - -"Message.LiveLocation" = "Live Location"; -"Conversation.LiveLocation" = "Live Location"; - -"Conversation.LiveLocationYou" = "You"; -"Conversation.LiveLocationYouAnd" = "*You* and %@"; -"Conversation.LiveLocationMembersCount_1" = "1 member"; -"Conversation.LiveLocationMembersCount_2" = "2 members"; -"Conversation.LiveLocationMembersCount_3_10" = "%@ members"; -"Conversation.LiveLocationMembersCount_any" = "%@ members"; -"Conversation.LiveLocationMembersCount_many" = "%@ members"; -"Conversation.LiveLocationMembersCount_0" = "%@ members"; - -"Conversation.Admin" = "admin"; - -"LiveLocationUpdated.JustNow" = "updated just now"; -"LiveLocationUpdated.MinutesAgo_0" = "updated %@ minutes ago"; //three to ten -"LiveLocationUpdated.MinutesAgo_1" = "updated 1 minute ago"; //one -"LiveLocationUpdated.MinutesAgo_2" = "updated 2 minutes ago"; //two -"LiveLocationUpdated.MinutesAgo_3_10" = "updated %@ minutes ago"; //three to ten -"LiveLocationUpdated.MinutesAgo_many" = "updated %@ minutes ago"; // more than ten -"LiveLocationUpdated.MinutesAgo_any" = "updated %@ minutes ago"; // more than ten -"LiveLocationUpdated.TodayAt" = "updated at %@"; -"LiveLocationUpdated.YesterdayAt" = "updated yesterday at %@"; - -"LiveLocation.MenuChatsCount_1" = "You are sharing Live Location with 1 chat."; -"LiveLocation.MenuChatsCount_2" = "You are sharing Live Location with 2 chats."; -"LiveLocation.MenuChatsCount_3_10" = "You are sharing Live Location with %@ chats."; -"LiveLocation.MenuChatsCount_any" = "You are sharing Live Location with %@ chats."; -"LiveLocation.MenuChatsCount_many" = "You are sharing Live Location with %@ chats."; -"LiveLocation.MenuChatsCount_0" = "You are sharing Live Location with %@ chats."; -"LiveLocation.MenuStopAll" = "Stop All"; - -"DialogList.LiveLocationSharingTo" = "sharing with %@"; -"DialogList.LiveLocationChatsCount_1" = "sharing with 1 chat"; -"DialogList.LiveLocationChatsCount_2" = "sharing with 2 chats"; -"DialogList.LiveLocationChatsCount_3_10" = "sharing with %@ chats"; -"DialogList.LiveLocationChatsCount_any" = "sharing with %@ chats"; -"DialogList.LiveLocationChatsCount_many" = "sharing with %@ chats"; -"DialogList.LiveLocationChatsCount_0" = "sharing with %@ chats"; - -"Notification.PinnedLiveLocationMessage" = "%@ pinned a live location"; -"Message.PinnedLiveLocationMessage" = "pinned live location"; - -"NotificationSettings.ContactJoined" = "New Contacts"; - -"AccessDenied.LocationAlwaysDenied" = "If you'd like to share your Live Location with friends, Telegram needs location access when the app is in the background.\n\nPlease go to Settings > Privacy > Location Services and set Telegram to Always."; - -"UserInfo.UnblockConfirmation" = "Unblock %@?"; - -"Login.BannedPhoneSubject" = "Banned phone number: %@"; -"Login.BannedPhoneBody" = "I'm trying to use my mobile phone number: %@\nBut Telegram says it's banned. Please help."; - -"Conversation.StopLiveLocation" = "Stop Sharing"; - -"Settings.SavedMessages" = "Saved Messages"; -"Conversation.SavedMessages" = "Saved Messages"; -"DialogList.SavedMessages" = "Saved Messages"; - -"MediaPicker.TapToUngroupDescription" = "Tap to send media separately"; -"MediaPicker.GroupDescription" = "Group media into one message"; -"MediaPicker.UngroupDescription" = "Show media as separate messages"; - -"EditProfile.Title" = "Edit Profile"; -"EditProfile.NameAndPhotoHelp" = "Enter your name and add an optional profile photo."; - -"Settings.SetUsername" = "Set Username"; - -"DialogList.SearchSubtitleFormat" = "%1$@, %2$@"; - -"Media.ShareThisPhoto" = "This Photo"; -"Media.SharePhoto_1" = "%@ Photo"; -"Media.SharePhoto_2" = "All %@ Photos"; -"Media.SharePhoto_3_10" = "All %@ Photos"; -"Media.SharePhoto_any" = "All %@ Photos"; -"Media.SharePhoto_many" = "All %@ Photos"; -"Media.SharePhoto_0" = "All %@ Photos"; - -"Media.ShareThisVideo" = "This Video"; -"Media.ShareVideo_1" = "%@ Video"; -"Media.ShareVideo_2" = "All %@ Videos"; -"Media.ShareVideo_3_10" = "All %@ Videos"; -"Media.ShareVideo_any" = "All %@ Videos"; -"Media.ShareVideo_many" = "All %@ Videos"; -"Media.ShareVideo_0" = "All %@ Videos"; - -"Media.ShareItem_1" = "%@ Item"; -"Media.ShareItem_2" = "All %@ Items"; -"Media.ShareItem_3_10" = "All %@ Items"; -"Media.ShareItem_any" = "All %@ Items"; -"Media.ShareItem_many" = "All %@ Items"; -"Media.ShareItem_0" = "All %@ Items"; - -"Settings.ViewPhoto" = "View Photo"; - -"DialogList.SavedMessagesTooltip" = "You can find your Saved Messages in Settings"; - -"PasscodeSettings.UnlockWithFaceId" = "Unlock with Face ID"; -"Checkout.SavePasswordTimeoutAndFaceId" = "Would you like to save your password for %@ and use Face ID instead?"; -"Checkout.PayWithFaceId" = "Pay with Face ID"; - -"Conversation.StatusSubscribers_0" = "%@ subscribers"; -"Conversation.StatusSubscribers_1" = "%@ subscriber"; -"Conversation.StatusSubscribers_2" = "%@ subscribers"; -"Conversation.StatusSubscribers_3_10" = "%@ subscribers"; -"Conversation.StatusSubscribers_many" = "%@ subscribers"; -"Conversation.StatusSubscribers_any" = "%@ subscribers"; - -"DialogList.SavedMessagesHelp" = "Forward messages here for quick access"; - -"PrivacySettings.PasscodeAndTouchId" = "Passcode & Touch ID"; -"PrivacySettings.PasscodeAndFaceId" = "Passcode & Face ID"; - -"TwoStepAuth.AdditionalPassword" = "Additional Password"; - -"PasscodeSettings.HelpTop" = "When you set up an additional passcode, a lock icon will appear on the chats page. Tap it to lock and unlock the app."; -"PasscodeSettings.HelpBottom" = "Note: if you forget the passcode, you'll need to delete and reinstall the app. All secret chats will be lost."; - -"Channel.Setup.TypePublicHelp" = "Public channels can be found in search, channel history is available to everyone and anyone can join."; -"Channel.Setup.TypePrivateHelp" = "Private channels can only be joined if you were invited or have an invite link."; -"Group.Username.InvalidTooShort" = "Group names must have at least 5 characters."; -"Group.Username.InvalidStartsWithNumber" = "Group names can't start with a number."; -"Group.Username.CreatePublicLinkHelp" = "People can share this link with others and find your group using Telegram search."; -"Channel.TypeSetup.Title" = "Channel Type"; - -"Group.Setup.TypePrivate" = "Private"; -"Group.Setup.TypePublic" = "Public"; - -"Channel.Info.Subscribers" = "Subscribers"; -"Channel.Subscribers.Title" = "Subscribers"; -"Conversation.InfoGroup" = "Group"; - -"Privacy.PaymentsClearInfoDoneHelp" = "Payment & shipping info cleared."; - -"InfoPlist.NSContactsUsageDescription" = "Telegram will continuously upload your contacts to its heavily encrypted cloud servers to let you connect with your friends across all your devices."; -"InfoPlist.NSLocationWhenInUseUsageDescription" = "When you send your location to your friends, Telegram needs access to show them a map."; -"InfoPlist.NSCameraUsageDescription" = "We need this so that you can take and share photos and videos, as well as make video calls."; -"InfoPlist.NSPhotoLibraryUsageDescription" = "We need this so that you can share photos and videos from your photo library."; -"InfoPlist.NSPhotoLibraryAddUsageDescription" = "We need this so that you can save photos and videos to your photo library."; -"InfoPlist.NSMicrophoneUsageDescription" = "We need this so that you can record and share voice messages and videos with sound."; -"InfoPlist.NSSiriUsageDescription" = "You can use Siri to send messages."; -"InfoPlist.NSLocationAlwaysAndWhenInUseUsageDescription" = "When you choose to share your Live Location with friends in a chat, Telegram needs background access to your location to keep them updated for the duration of the live sharing."; -"InfoPlist.NSLocationAlwaysUsageDescription" = "When you choose to share your live location with friends in a chat, Telegram needs background access to your location to keep them updated for the duration of the live sharing. You also need this to send locations from an Apple Watch."; -"InfoPlist.NSLocationWhenInUseUsageDescription" = "When you send your location to your friends, Telegram needs access to show them a map."; -"InfoPlist.NSFaceIDUsageDescription" = "You can use Face ID to unlock the app."; - -"Privacy.Calls.P2PNever" = "Never"; -"Privacy.Calls.P2PContacts" = "My Contacts"; -"Privacy.Calls.P2PAlways" = "Always"; - -"ChatSettings.AutoDownloadTitle" = "AUTO-DOWNLOAD MEDIA"; -"ChatSettings.AutoDownloadEnabled" = "Auto-Download Media"; -"ChatSettings.AutoDownloadPhotos" = "Photos"; -"ChatSettings.AutoDownloadVideos" = "Videos"; -"ChatSettings.AutoDownloadDocuments" = "Documents"; -"ChatSettings.AutoDownloadVoiceMessages" = "Voice Messages"; -"ChatSettings.AutoDownloadVideoMessages" = "Video Messages"; -"ChatSettings.AutoDownloadReset" = "Reset Auto-Download Settings"; - -"AutoDownloadSettings.Title" = "Auto-Download"; - -"AutoDownloadSettings.PhotosTitle" = "Photos"; -"AutoDownloadSettings.VideosTitle" = "Videos"; -"AutoDownloadSettings.DocumentsTitle" = "Documents"; -"AutoDownloadSettings.VoiceMessagesTitle" = "Voice Messages"; -"AutoDownloadSettings.VideoMessagesTitle" = "Video Messages"; - -"AutoDownloadSettings.Cellular" = "CELLULAR"; -"AutoDownloadSettings.WiFi" = "WI-FI"; -"AutoDownloadSettings.Contacts" = "Contacts"; -"AutoDownloadSettings.PrivateChats" = "Other Private Chats"; -"AutoDownloadSettings.GroupChats" = "Group Chats"; -"AutoDownloadSettings.Channels" = "Channels"; -"AutoDownloadSettings.LimitBySize" = "LIMIT BY SIZE"; -"AutoDownloadSettings.UpTo" = "up to %@"; -"AutoDownloadSettings.Unlimited" = "unlimited"; - -"AutoDownloadSettings.Reset" = "Reset"; -"AutoDownloadSettings.ResetHelp" = "Undo all custom auto-download settings."; - -"SaveIncomingPhotosSettings.Title" = "Save Incoming Photos"; -"SaveIncomingPhotosSettings.From" = "SAVE INCOMING PHOTOS FROM"; - -"Channel.AdminLog.ChannelEmptyText" = "No service actions were taken by the channel's subscribers and admins in the last 48 hours."; -"Channel.AdminLogFilter.EventsNewSubscribers" = "New Subscribers"; -"Channel.AdminLogFilter.EventsLeavingSubscribers" = "Subscribers Removed"; - -"Conversation.ClearPrivateHistory" = "This will delete all messages and media in this chat from your Telegram cloud. Your chat partner will still have them."; -"Conversation.ClearGroupHistory" = "This will delete all messages and media in this chat from your Telegram cloud. Other members of the group will still have them."; -"Conversation.ClearSecretHistory" = "This will delete all messages and media in this chat for both you and your chat partner."; -"Conversation.ClearSelfHistory" = "This will delete all messages and media in this chat from your Telegram cloud."; - -"MediaPicker.LivePhotoDescription" = "The live photo will be sent as a GIF."; - -"Settings.Appearance" = "Appearance"; -"Appearance.Title" = "Appearance"; -"Appearance.Preview" = "CHAT PREVIEW"; -"Appearance.ColorTheme" = "COLOR THEME"; -"Appearance.ThemeDayClassic" = "Day Classic"; -"Appearance.ThemeDay" = "Day"; -"Appearance.ThemeNight" = "Night"; -"Appearance.ThemeNightBlue" = "Night Blue"; -"Appearance.PreviewReplyAuthor" = "Lucio"; -"Appearance.PreviewReplyText" = "Reinhardt, we need to find you some..."; -"Appearance.PreviewIncomingText" = "Ah you kids today with techno music! Enjoy the classics, like Hasselhoff!"; -"Appearance.PreviewOutgoingText" = "I can't take you seriously right now. Sorry.."; -"Appearance.AccentColor" = "Accent Color"; -"Appearance.PickAccentColor" = "Pick an Accent Color"; - -"Appearance.AutoNightTheme" = "Auto-Night Theme"; -"Appearance.AutoNightThemeDisabled" = "Disabled"; - -"AutoNightTheme.Title" = "Auto-Night Theme"; -"AutoNightTheme.Disabled" = "Disabled"; -"AutoNightTheme.Scheduled" = "Scheduled"; -"AutoNightTheme.Automatic" = "Automatic"; - -"AutoNightTheme.ScheduleSection" = "SCHEDULE"; -"AutoNightTheme.UseSunsetSunrise" = "Use Location Sunset & Sunrise"; -"AutoNightTheme.ScheduledFrom" = "From"; -"AutoNightTheme.ScheduledTo" = "To"; - -"AutoNightTheme.UpdateLocation" = "Update Location"; -"AutoNightTheme.LocationHelp" = "Calculating sunset & sunrise times requires a one-time check of your approximate location. Note that this location is stored locally on your device only.\n\nSunset: %@\nSunrise: %@"; -"AutoNightTheme.NotAvailable" = "N/A"; - -"AutoNightTheme.AutomaticSection" = "BRIGHTNESS THRESHOLD"; -"AutoNightTheme.AutomaticHelp" = "Switch to night theme when brightness is %@%% or less. Auto-brightness should be enabled for this feature to work correctly."; - -"AutoNightTheme.PreferredTheme" = "PREFERRED THEME"; - -"AuthSessions.Sessions" = "Sessions"; -"AuthSessions.LoggedIn" = "Websites"; -"AuthSessions.LogOutApplications" = "Disconnect All Websites"; -"AuthSessions.LogOutApplicationsHelp" = "You can log in on websites that support signing in with Telegram."; -"AuthSessions.LoggedInWithTelegram" = "CONNECTED WEBSITES"; -"AuthSessions.LogOut" = "Disconnect"; -"AuthSessions.Message" = "You allowed this bot to message you when you logged in on %@."; - -"Conversation.ContextMenuReport" = "Report"; - -"Stickers.Search" = "Search Stickers"; -"Stickers.NoStickersFound" = "No Stickers Found"; - -"Camera.Discard" = "Discard All"; - -"Stickers.SuggestStickers" = "Suggest Stickers by Emoji"; -"Stickers.SuggestAll" = "All Sets"; -"Stickers.SuggestAdded" = "My Sets"; -"Stickers.SuggestNone" = "None"; - -"Settings.Proxy" = "Proxy"; -"Settings.ProxyDisabled" = "Disabled"; -"Settings.ProxyConnecting" = "Connecting..."; -"Settings.ProxyConnected" = "Connected"; - -"SocksProxySetup.UseProxy" = "Use Proxy"; -"SocksProxySetup.SavedProxies" = "SAVED PROXIES"; -"SocksProxySetup.AddProxy" = "Add Proxy"; -"SocksProxySetup.SaveProxy" = "Save Proxy"; -"SocksProxySetup.ConnectAndSave" = "Connect Proxy"; -"SocksProxySetup.AddProxyTitle" = "Add Proxy"; -"SocksProxySetup.ProxyDetailsTitle" = "Proxy Details"; -"SocksProxySetup.ProxyStatusChecking" = "checking..."; -"SocksProxySetup.ProxyStatusPing" = "%@ ms ping"; -"SocksProxySetup.ProxyStatusUnavailable" = "unavailable"; -"SocksProxySetup.ProxyStatusConnecting" = "connecting"; -"SocksProxySetup.ProxyStatusConnected" = "connected"; - -"SocksProxySetup.ProxyType" = "TYPE"; -"SocksProxySetup.ProxySocks5" = "SOCKS5"; -"SocksProxySetup.ProxyTelegram" = "MTProto"; -"SocksProxySetup.HostnamePlaceholder" = "Server"; -"SocksProxySetup.PortPlaceholder" = "Port"; -"SocksProxySetup.UsernamePlaceholder" = "Username"; -"SocksProxySetup.PasswordPlaceholder" = "Password"; -"SocksProxySetup.Secret" = "Secret"; -"SocksProxySetup.SecretPlaceholder" = "Secret"; -"SocksProxySetup.RequiredCredentials" = "CREDENTIALS"; - -"SocksProxySetup.Connecting" = "Connecting..."; -"SocksProxySetup.FailedToConnect" = "Failed to connect"; - -"SocksProxySetup.ProxyEnabled" = "Proxy\nEnabled"; - -"DialogList.AdLabel" = "Proxy Sponsor"; -"DialogList.AdNoticeAlert" = "The proxy you are using displays a sponsored channel in your chat list."; -"SocksProxySetup.AdNoticeHelp" = "This proxy may display a sponsored channel in your chat list. This doesn't reveal your Telegram traffic."; - -"SocksProxySetup.ShareProxyList" = "Share Proxy List"; - -"Privacy.SecretChatsTitle" = "SECRET CHATS"; -"Privacy.SecretChatsLinkPreviews" = "Link Previews"; -"Privacy.SecretChatsLinkPreviewsHelp" = "Link previews will be generated on Telegram servers. We do not store data about the links you send."; - -"Privacy.ContactsTitle" = "CONTACTS"; -"Privacy.ContactsSync" = "Sync Contacts"; -"Privacy.ContactsSyncHelp" = "Turn on to continuously sync contacts from this device with your account."; -"Privacy.ContactsReset" = "Delete Synced Contacts"; -"Privacy.ContactsResetConfirmation" = "This will remove your contacts from the Telegram servers.\nIf 'Sync Contacts' is enabled, contacts will be re-synced."; - -"Login.TermsOfServiceDecline" = "Decline"; -"Login.TermsOfServiceAgree" = "Agree & Continue"; - -"Login.TermsOfService.ProceedBot" = "Please agree and proceed to %@."; - -"Login.TermsOfServiceSignupDecline" = "We're very sorry, but this means you can't sign up for Telegram.\n\nUnlike others, we don't use your data for ad targeting or other commercial purposes. Telegram only stores the information it needs to function as a feature-rich cloud service. You can adjust how we use your data (e.g., delete synced contacts) in Privacy & Security settings.\n\nBut if you're generally not OK with Telegram's modest needs, it won't be possible for us to provide this service."; - -"UserInfo.BotPrivacy" = "Privacy Policy"; - -"PrivacyPolicy.Title" = "Privacy Policy and Terms of Service"; -"PrivacyPolicy.Decline" = "Decline"; -"PrivacyPolicy.Accept" = "Agree & Continue"; - -"PrivacyPolicy.AgeVerificationTitle" = "Age Verification"; -"PrivacyPolicy.AgeVerificationMessage" = "Tap Agree to confirm that you are %@ or over."; -"PrivacyPolicy.AgeVerificationAgree" = "Agree"; - -"PrivacyPolicy.DeclineTitle" = "Decline"; -"PrivacyPolicy.DeclineMessage" = "We're very sorry, but this means we must part ways here. Unlike others, we don't use your data for ad targeting or other commercial purposes. Telegram only stores the information it needs to function as a feature-rich cloud service. You can adjust how we use your data (e.g., delete synced contacts) in Privacy & Security settings.\n\nBut if you're generally not OK with Telegram's modest needs, it won't be possible for us to provide this service."; -"PrivacyPolicy.DeclineDeclineAndDelete" = "Decline and Delete"; - -"PrivacyPolicy.DeclineLastWarning" = "Warning, this will irreversibly delete your Telegram account along with all the data you store in the Telegram cloud.\n\nWe will provide a tool to download your data before June, 23 – so you may want to wait a little before deleting."; -"PrivacyPolicy.DeclineDeleteNow" = "Delete Now"; - -"Settings.Passport" = "Telegram Passport"; - -"Passport.Title" = "Passport"; - -"Passport.RequestHeader" = "%@ requests access to your personal data to sign you up for their services."; - -"Passport.InfoTitle" = "What is Telegram Passport?"; -"Passport.InfoText" = "With **Telegram Passport** you can easily sign up for websites and services that require identity verification.\n\nYour information, personal data, and documents are protected by end-to-end encryption. Nobody, including Telegram, can access them without your permission."; -"Passport.InfoLearnMore" = "Learn More"; -"Passport.InfoFAQ_URL" = "https://telegram.org/faq#passport"; - -"Passport.PassportInformation" = "PASSPORT INFORMATION"; -"Passport.RequestedInformation" = "REQUESTED INFORMATION"; -"Passport.FieldIdentity" = "Identity Document"; -"Passport.FieldIdentityDetailsHelp" = "Fill in your personal details"; -"Passport.FieldIdentityUploadHelp" = "Upload a scan of your passport or other ID"; -"Passport.FieldIdentitySelfieHelp" = "Take a selfie with your document"; -"Passport.FieldAddress" = "Residential Address"; -"Passport.FieldAddressHelp" = "Please provide your address"; -"Passport.FieldAddressUploadHelp" = "Upload proof of your address"; -"Passport.FieldPhone" = "Phone Number"; -"Passport.FieldPhoneHelp" = "Provide your contact phone number"; -"Passport.FieldEmail" = "Email Address"; -"Passport.FieldEmailHelp" = "Provide your contact email address"; -"Passport.PrivacyPolicy" = "You accept the [%1$@ Privacy Policy] and allow their @%2$@ to send you messages."; -"Passport.AcceptHelp" = "You are sending your documents directly to %1$@ and allowing their @%2$@ to send you messages."; -"Passport.Authorize" = "Authorize"; - -"Passport.DeletePassport" = "Delete Telegram Passport"; -"Passport.DeletePassportConfirmation" = "Are you sure you want to delete your Telegram Passport? All details will be lost."; - -"Passport.PasswordHelp" = "Please enter your Telegram Password\nto decrypt your data"; -"Passport.PasswordPlaceholder" = "Enter your password"; -"Passport.InvalidPasswordError" = "Invalid password. Please try again."; -"Passport.FloodError" = "Limit exceeded. Please try again later."; -"Passport.UpdateRequiredError" = "Sorry, your Telegram app is out of date and can’t handle this request. Please update Telegram."; - -"Passport.ForgottenPassword" = "Forgotten Password"; -"Passport.PasswordReset" = "All documents uploaded to your Telegram Passport will be lost. You will be able to upload new documents."; - -"Passport.PasswordDescription" = "Please create a password to secure your personal data with end-to-end encryption.\n\nThis password will also be required whenever you log in to Telegram on a new device."; -"Passport.PasswordCreate" = "Create a Password"; -"Passport.PasswordCompleteSetup" = "Complete Password Setup"; -"Passport.PasswordNext" = "Next"; - -"Passport.DeletePersonalDetails" = "Delete Personal Details"; -"Passport.DeletePersonalDetailsConfirmation" = "Are you sure you want to delete personal details?"; - -"Passport.DeleteAddress" = "Delete Address"; -"Passport.DeleteAddressConfirmation" = "Are you sure you want to delete address?"; - -"Passport.DeleteDocument" = "Delete Document"; -"Passport.DeleteDocumentConfirmation" = "Are you sure you want to delete this document? All details will be lost."; - -"Passport.Scans" = "SCANS"; -"Passport.Scans.Upload" = "Upload Scan"; -"Passport.Scans.UploadNew" = "Upload Additional Scan"; -"Passport.Scans.ScanIndex" = "Scan %@"; - -"Passport.Identity.TypePersonalDetails" = "Personal Details"; -"Passport.Identity.TypePassport" = "Passport"; -"Passport.Identity.TypePassportUploadScan" = "Upload a scan of your passport"; -"Passport.Identity.TypeInternalPassport" = "Internal Passport"; -"Passport.Identity.TypeInternalPassportUploadScan" = "Upload a scan of your internal passport"; -"Passport.Identity.TypeIdentityCard" = "Identity Card"; -"Passport.Identity.TypeIdentityCardUploadScan" = "Upload a scan of your identity card"; -"Passport.Identity.TypeDriversLicense" = "Driver's License"; -"Passport.Identity.TypeDriversLicenseUploadScan" = "Upload a scan of your driver's license"; - -"Passport.Identity.AddPersonalDetails" = "Add Personal Details"; -"Passport.Identity.AddPassport" = "Add Passport"; -"Passport.Identity.AddInternalPassport" = "Add Internal Passport"; -"Passport.Identity.AddIdentityCard" = "Add Identity Card"; -"Passport.Identity.AddDriversLicense" = "Add Driver's License"; - -"Passport.Identity.EditPersonalDetails" = "Edit Personal Details"; -"Passport.Identity.EditPassport" = "Edit Passport"; -"Passport.Identity.EditInternalPassport" = "Edit Internal Passport"; -"Passport.Identity.EditIdentityCard" = "Edit Identity Card"; -"Passport.Identity.EditDriversLicense" = "Edit Driver's License"; - -"Passport.Identity.DocumentDetails" = "DOCUMENT DETAILS"; -"Passport.Identity.Name" = "First Name"; -"Passport.Identity.NamePlaceholder" = "First Name"; -"Passport.Identity.MiddleName" = "Middle Name"; -"Passport.Identity.MiddleNamePlaceholder" = "Middle Name"; -"Passport.Identity.Surname" = "Last Name"; -"Passport.Identity.SurnamePlaceholder" = "Last Name"; -"Passport.Identity.DateOfBirth" = "Date of Birth"; -"Passport.Identity.DateOfBirthPlaceholder" = "Date of Birth"; -"Passport.Identity.Gender" = "Gender"; -"Passport.Identity.GenderPlaceholder" = "Gender"; -"Passport.Identity.GenderMale" = "Male"; -"Passport.Identity.GenderFemale" = "Female"; -"Passport.Identity.Country" = "Citizenship"; -"Passport.Identity.CountryPlaceholder" = "Citizenship"; -"Passport.Identity.ResidenceCountry" = "Residence"; -"Passport.Identity.ResidenceCountryPlaceholder" = "Residence"; -"Passport.Identity.DocumentNumber" = "Document #"; -"Passport.Identity.DocumentNumberPlaceholder" = "Document Number"; -"Passport.Identity.IssueDate" = "Issue Date"; -"Passport.Identity.IssueDatePlaceholder" = "Issue Date"; -"Passport.Identity.ExpiryDate" = "Expiry Date"; -"Passport.Identity.ExpiryDatePlaceholder" = "Expiry Date"; -"Passport.Identity.ExpiryDateNone" = "None"; -"Passport.Identity.DoesNotExpire" = "Does Not Expire"; - -"Passport.Identity.FilesTitle" = "REQUESTED FILES"; -"Passport.Identity.ScansHelp" = "The document must contain your photograph, first and last name, date of birth, document number, country of issue, and expiry date."; -"Passport.Identity.FilesView" = "View"; -"Passport.Identity.FilesUploadNew" = "Upload New"; -"Passport.Identity.MainPage" = "Main Page"; -"Passport.Identity.MainPageHelp" = "Upload a main page photo of the document"; -"Passport.Identity.FrontSide" = "Front Side"; -"Passport.Identity.FrontSideHelp" = "Upload a front side photo of the document"; -"Passport.Identity.ReverseSide" = "Reverse Side"; -"Passport.Identity.ReverseSideHelp" = "Upload a reverse side photo of the document"; -"Passport.Identity.Selfie" = "Selfie"; -"Passport.Identity.SelfieHelp" = "Upload a selfie holding this document"; -"Passport.Identity.Translation" = "Translation"; -"Passport.Identity.TranslationHelp" = "Upload a translation of this document"; - -"Passport.Address.TypeResidentialAddress" = "Residential Address"; -"Passport.Address.TypePassportRegistration" = "Passport Registration"; -"Passport.Address.TypeUtilityBill" = "Utility Bill"; -"Passport.Address.TypeBankStatement" = "Bank Statement"; -"Passport.Address.TypeRentalAgreement" = "Tenancy Agreement"; -"Passport.Address.TypeTemporaryRegistration" = "Temporary Registration"; - -"Passport.Address.AddResidentialAddress" = "Add Residential Address"; -"Passport.Address.AddPassportRegistration" = "Add Passport Registration"; -"Passport.Address.AddUtilityBill" = "Add Utility Bill"; -"Passport.Address.AddBankStatement" = "Add Bank Statement"; -"Passport.Address.AddRentalAgreement" = "Add Tenancy Agreement"; -"Passport.Address.AddTemporaryRegistration" = "Add Temporary Registration"; - -"Passport.Address.EditResidentialAddress" = "Edit Residential Address"; -"Passport.Address.EditPassportRegistration" = "Edit Passport Registration"; -"Passport.Address.EditUtilityBill" = "Edit Utility Bill"; -"Passport.Address.EditBankStatement" = "Edit Bank Statement"; -"Passport.Address.EditRentalAgreement" = "Edit Tenancy Agreement"; -"Passport.Address.EditTemporaryRegistration" = "Edit Temporary Registration"; - -"Passport.Address.Address" = "ADDRESS"; -"Passport.Address.Street" = "Street"; -"Passport.Address.Street1Placeholder" = "Street and number, P.O. box"; -"Passport.Address.Street2Placeholder" = "Apt., suite, unit, building, floor"; -"Passport.Address.Postcode" = "Postcode"; -"Passport.Address.PostcodePlaceholder" = "Postcode"; -"Passport.Address.City" = "City"; -"Passport.Address.CityPlaceholder" = "City"; -"Passport.Address.Region" = "Region"; -"Passport.Address.RegionPlaceholder" = "State / Province / Region"; -"Passport.Address.Country" = "Country"; -"Passport.Address.CountryPlaceholder" = "Country"; - -"Passport.Address.ScansHelp" = "The document must contain your first and last name, your residential address, a stamp / barcode / QR code / logo, and issue date, no more than 3 months ago."; - -"Passport.Phone.Title" = "Phone Number"; -"Passport.Phone.UseTelegramNumber" = "Use %@"; -"Passport.Phone.UseTelegramNumberHelp" = "Use the same phone number as on Telegram."; -"Passport.Phone.EnterOtherNumber" = "OR ENTER NEW PHONE NUMBER"; -"Passport.Phone.Help" = "Note: You will receive a confirmation code on the phone number you provide."; -"Passport.Phone.Delete" = "Delete Phone Number"; - -"Passport.Email.Title" = "Email"; -"Passport.Email.UseTelegramEmail" = "Use %@"; -"Passport.Email.UseTelegramEmailHelp" = "Use the same address as on Telegram."; -"Passport.Email.EnterOtherEmail" = "OR ENTER NEW EMAIL ADDRESS"; -"Passport.Email.EmailPlaceholder" = "Enter your email address"; -"Passport.Email.Help" = "Note: You will receive a confirmation code to the email address you provide."; -"Passport.Email.Delete" = "Delete Email Address"; -"Passport.Email.CodeHelp" = "Please enter the confirmation code we've just sent to %@"; - -"Notification.PassportValuesSentMessage" = "%1$@ received the following documents: %2$@"; -"Notification.PassportValuePersonalDetails" = "personal details"; -"Notification.PassportValueProofOfIdentity" = "proof of identity"; -"Notification.PassportValueAddress" = "your address"; -"Notification.PassportValueProofOfAddress" = "proof of address"; -"Notification.PassportValuePhone" = "phone number"; -"Notification.PassportValueEmail" = "email address"; - -"FastTwoStepSetup.HintSection" = "HINT"; -"FastTwoStepSetup.HintPlaceholder" = "Enter a hint"; -"FastTwoStepSetup.HintHelp" = "Please create an optional hint for your password."; - -"Passport.DiscardMessageTitle" = "Discard Changes"; -"Passport.DiscardMessageDescription" = "Are you sure you want to discard all changes?"; -"Passport.DiscardMessageAction" = "Discard"; - -"Passport.ScanPassport" = "Scan Your Passport"; -"Passport.ScanPassportHelp" = "Scan your passport or identity card with machine-readable zone to fill personal details automatically."; - -"TwoStepAuth.PasswordRemovePassportConfirmation" = "Are you sure you want to disable your password?\n\nWarning! All data saved in your Telegram Passport will be lost!"; - -"Application.Update" = "Update"; - -"Conversation.EditingMessagePanelMedia" = "Tap to edit media"; -"Conversation.EditingMessageMediaChange" = "Change Photo or Video"; -"Conversation.EditingMessageMediaEditCurrentPhoto" = "Edit Current Photo"; -"Conversation.EditingMessageMediaEditCurrentVideo" = "Edit Current Video"; - -"Conversation.InputTextCaptionPlaceholder" = "Caption"; - -"Conversation.ViewContactDetails" = "VIEW CONTACT"; - -"DialogList.Read" = "Read"; -"DialogList.Unread" = "Unread"; - -"ContactInfo.Title" = "Contact Info"; -"ContactInfo.PhoneLabelHome" = "home"; -"ContactInfo.PhoneLabelWork" = "work"; -"ContactInfo.PhoneLabelMobile" = "mobile"; -"ContactInfo.PhoneLabelMain" = "main"; -"ContactInfo.PhoneLabelHomeFax" = "home fax"; -"ContactInfo.PhoneLabelWorkFax" = "work fax"; -"ContactInfo.PhoneLabelPager" = "pager"; -"ContactInfo.PhoneLabelOther" = "other"; -"ContactInfo.URLLabelHomepage" = "homepage"; -"ContactInfo.BirthdayLabel" = "birthday"; -"ContactInfo.Job" = "job"; - -"UserInfo.NotificationsDefault" = "Default"; -"UserInfo.NotificationsDefaultSound" = "Default (%@)"; - -"DialogList.ProxyConnectionIssuesTooltip" = "Can’t connect to your preferred proxy.\nTap to change settings."; - -"Conversation.TapAndHoldToRecord" = "Tap and hold to record"; - -"Privacy.TopPeers" = "Suggest Frequent Contacts"; -"Privacy.TopPeersHelp" = "Display people you message frequently at the top of the search section for quick access."; -"Privacy.TopPeersWarning" = "This will delete all data about the people you message frequently as well the inline bots you are likely to use."; -"Privacy.TopPeersDelete" = "Delete"; - -"Conversation.EditingCaptionPanelTitle" = "Edit Caption"; - -"Passport.CorrectErrors" = "Tap to correct errors"; - -"Passport.NotLoggedInMessage" = "Please log in to your account to use Telegram Passport"; - -"Update.Title" = "Telegram Update"; -"Update.AppVersion" = "Telegram %@"; -"Update.UpdateApp" = "Update Telegram"; -"Update.Skip" = "Skip"; - -"ReportPeer.ReasonCopyright" = "Copyright"; - -"PrivacySettings.DataSettings" = "Data Settings"; -"PrivacySettings.DataSettingsHelp" = "Control which of your data is stored in the cloud and used by Telegram to enable advanced features."; - -"PrivateDataSettings.Title" = "Data Settings"; -"Privacy.ChatsTitle" = "CHATS"; -"Privacy.DeleteDrafts" = "Delete All Cloud Drafts"; - -"UserInfo.NotificationsDefaultEnabled" = "Default (Enabled)"; -"UserInfo.NotificationsDefaultDisabled" = "Default (Disabled)"; - -"Notifications.MessageNotificationsExceptions" = "Exceptions"; -"Notifications.GroupNotificationsExceptions" = "Exceptions"; - -"Notifications.ExceptionsNone" = "None"; -"Notifications.Exceptions_1" = "%@ chat"; -"Notifications.Exceptions_2" = "%@ chats"; -"Notifications.Exceptions_3_10" = "%@ chats"; -"Notifications.Exceptions_any" = "%@ chats"; -"Notifications.Exceptions_many" = "%@ chats"; -"Notifications.Exceptions_0" = "%@ chats"; - -"Notifications.ExceptionMuteExpires.Minutes_1" = "In 1 minute"; -"Notifications.ExceptionMuteExpires.Minutes_2" = "In 2 minutes"; -"Notifications.ExceptionMuteExpires.Minutes_3_10" = "In %@ minutes"; -"Notifications.ExceptionMuteExpires.Minutes_any" = "In %@ minutes"; -"Notifications.ExceptionMuteExpires.Minutes_many" = "In %@ minutes"; -"Notifications.ExceptionMuteExpires.Minutes_0" = "In %@ minutes"; - -"Notifications.ExceptionMuteExpires.Hours_1" = "In 1 hour"; -"Notifications.ExceptionMuteExpires.Hours_2" = "In 2 hours"; -"Notifications.ExceptionMuteExpires.Hours_3_10" = "In %@ hours"; -"Notifications.ExceptionMuteExpires.Hours_any" = "In %@ hours"; -"Notifications.ExceptionMuteExpires.Hours_many" = "In %@ hours"; -"Notifications.ExceptionMuteExpires.Hours_0" = "In %@ hours"; - -"Notifications.ExceptionMuteExpires.Days_1" = "In 1 day"; -"Notifications.ExceptionMuteExpires.Days_2" = "In 2 days"; -"Notifications.ExceptionMuteExpires.Days_3_10" = "In %@ days"; -"Notifications.ExceptionMuteExpires.Days_any" = "In %@ days"; -"Notifications.ExceptionMuteExpires.Days_many" = "In %@ days"; -"Notifications.ExceptionMuteExpires.Days_0" = "In %@ days"; - -"Notifications.ExceptionsTitle" = "Exceptions"; -"Notifications.ExceptionsChangeSound" = "Change Sound (%@)"; -"Notifications.ExceptionsDefaultSound" = "Default"; -"Notifications.ExceptionsMuted" = "Muted"; -"Notifications.ExceptionsUnmuted" = "Unmuted"; -"Notifications.AddExceptionTitle" = "Add Exception"; - -"Notifications.ExceptionsMessagePlaceholder" = "This section will list all private chats with non-default notification settings."; -"Notifications.ExceptionsGroupPlaceholder" = "This section will list all groups and channels with non-default notification settings."; - -"Passport.Identity.LatinNameHelp" = "Enter your name using the Latin alphabet"; -"Passport.Identity.NativeNameTitle" = "YOUR NAME IN %@"; -"Passport.Identity.NativeNameGenericTitle" = "NAME IN DOCUMENT LANGUAGE"; -"Passport.Identity.NativeNameHelp" = "Your name in the language of the country that issued the document."; -"Passport.Identity.NativeNameGenericHelp" = "Your name in the language of the country (%@) that issued the document."; - -"Passport.Identity.Translations" = "TRANSLATION"; -"Passport.Identity.TranslationsHelp" = "Upload scans of verified translation of the document."; -"Passport.FieldIdentityTranslationHelp" = "Upload a translation of your document"; -"Passport.FieldAddressTranslationHelp" = "Upload a translation of your document"; - -"Passport.FieldOneOf.Or" = "%1$@ or %2$@"; -"Passport.Identity.UploadOneOfScan" = "Upload a scan of your %@"; -"Passport.Address.UploadOneOfScan" = "Upload a scan of your %@"; - -"Passport.Address.TypeUtilityBillUploadScan" = "Upload a scan of your utiliity bill"; -"Passport.Address.TypeBankStatementUploadScan" = "Upload a scan of your bank statement"; -"Passport.Address.TypeRentalAgreementUploadScan" = "Upload a scan of your tenancy agreement"; -"Passport.Address.TypePassportRegistrationUploadScan" = "Upload a scan of your passport registration"; -"Passport.Address.TypeTemporaryRegistrationUploadScan" = "Upload a scan of your temporary registration"; - -"Passport.Identity.OneOfTypePassport" = "passport"; -"Passport.Identity.OneOfTypeInternalPassport" = "internal passport"; -"Passport.Identity.OneOfTypeIdentityCard" = "identity card"; -"Passport.Identity.OneOfTypeDriversLicense" = "driver's license"; - -"Passport.Address.OneOfTypePassportRegistration" = "passport registration"; -"Passport.Address.OneOfTypeUtilityBill" = "utility bill"; -"Passport.Address.OneOfTypeBankStatement" = "bank statement"; -"Passport.Address.OneOfTypeRentalAgreement" = "tenancy agreement"; -"Passport.Address.OneOfTypeTemporaryRegistration" = "temporary registration"; - -"Passport.FieldOneOf.Delimeter" = ", "; -"Passport.FieldOneOf.FinalDelimeter" = " or "; - -"Passport.Scans_1" = "%@ scan"; -"Passport.Scans_2" = "%@ scans"; -"Passport.Scans_3_10" = "%@ scans"; -"Passport.Scans_any" = "%@ scans"; -"Passport.Scans_many" = "%@ scans"; -"Passport.Scans_0" = "%@ scans"; - -"NotificationsSound.None" = "None"; -"NotificationsSound.Note" = "Note"; -"NotificationsSound.Aurora" = "Aurora"; -"NotificationsSound.Bamboo" = "Bamboo"; -"NotificationsSound.Chord" = "Chord"; -"NotificationsSound.Circles" = "Circles"; -"NotificationsSound.Complete" = "Complete"; -"NotificationsSound.Hello" = "Hello"; -"NotificationsSound.Input" = "Input"; -"NotificationsSound.Keys" = "Keys"; -"NotificationsSound.Popcorn" = "Popcorn"; -"NotificationsSound.Pulse" = "Pulse"; -"NotificationsSound.Synth" = "Synth"; - -"NotificationsSound.Tritone" = "Tri-tone"; -"NotificationsSound.Tremolo" = "Tremolo"; -"NotificationsSound.Alert" = "Alert"; -"NotificationsSound.Bell" = "Bell"; -"NotificationsSound.Calypso" = "Calypso"; -"NotificationsSound.Chime" = "Chime"; -"NotificationsSound.Glass" = "Glass"; -"NotificationsSound.Telegraph" = "Telegraph"; - -"Settings.CopyPhoneNumber" = "Copy Phone Number"; -"Settings.CopyUsername" = "Copy Username"; - -"Passport.Language.ar" = "Arabic"; -"Passport.Language.az" = "Azerbaijani"; -"Passport.Language.bg" = "Bulgarian"; -"Passport.Language.bn" = "Bangla"; -"Passport.Language.cs" = "Czech"; -"Passport.Language.da" = "Danish"; -"Passport.Language.de" = "German"; -"Passport.Language.dv" = "Divehi"; -"Passport.Language.dz" = "Dzongkha"; -"Passport.Language.el" = "Greek"; -"Passport.Language.en" = "English"; -"Passport.Language.es" = "Spanish"; -"Passport.Language.et" = "Estonian"; -"Passport.Language.fa" = "Persian"; -"Passport.Language.fr" = "French"; -"Passport.Language.he" = "Hebrew"; -"Passport.Language.hr" = "Croatian"; -"Passport.Language.hu" = "Hungarian"; -"Passport.Language.hy" = "Armenian"; -"Passport.Language.id" = "Indonesian"; -"Passport.Language.is" = "Icelandic"; -"Passport.Language.it" = "Italian"; -"Passport.Language.ja" = "Japanese"; -"Passport.Language.ka" = "Georgian"; -"Passport.Language.km" = "Khmer"; -"Passport.Language.ko" = "Korean"; -"Passport.Language.lo" = "Lao"; -"Passport.Language.lt" = "Lithuanian"; -"Passport.Language.lv" = "Latvian"; -"Passport.Language.mk" = "Macedonian"; -"Passport.Language.mn" = "Mongolian"; -"Passport.Language.ms" = "Malay"; -"Passport.Language.my" = "Burmese"; -"Passport.Language.ne" = "Nepali"; -"Passport.Language.nl" = "Dutch"; -"Passport.Language.pl" = "Polish"; -"Passport.Language.pt" = "Portuguese"; -"Passport.Language.ro" = "Romanian"; -"Passport.Language.ru" = "Russian"; -"Passport.Language.sk" = "Slovak"; -"Passport.Language.sl" = "Slovenian"; -"Passport.Language.th" = "Thai"; -"Passport.Language.tk" = "Turkmen"; -"Passport.Language.tr" = "Turkish"; -"Passport.Language.uk" = "Ukrainian"; -"Passport.Language.uz" = "Uzbek"; -"Passport.Language.vi" = "Vietnamese"; - -"Conversation.EmptyGifPanelPlaceholder" = "You have no saved GIFs yet.\nEnter @gif to search."; -"DialogList.MultipleTyping" = "%@ and %@"; -"Contacts.NotRegisteredSection" = "Phonebook"; - -"SocksProxySetup.PasteFromClipboard" = "Paste From Clipboard"; - -"Share.AuthTitle" = "Log in to Telegram"; -"Share.AuthDescription" = "Open Telegram and log in to share."; - -"Notifications.DisplayNamesOnLockScreen" = "Names on lock-screen"; -"Notifications.DisplayNamesOnLockScreenInfoWithLink" = "Display names in notifications when the device is locked. To disable, make sure that \"Show Previews\" is also set to \"When Unlocked\" or \"Never\" in [iOS Settings]"; - -"Notifications.Badge" = "BADGE COUNTER"; -"Notifications.Badge.IncludeMutedChats" = "Include Muted Chats"; -"Notifications.Badge.IncludePublicGroups" = "Include Public Groups"; -"Notifications.Badge.IncludeChannels" = "Include Channels"; -"Notifications.Badge.CountUnreadMessages" = "Count Unread Messages"; -"Notifications.Badge.CountUnreadMessages.InfoOff" = "Switch on to show the number of unread messages instead of chats."; -"Notifications.Badge.CountUnreadMessages.InfoOn" = "Switch off to show the number of unread chats instead of messages."; - -"Appearance.ReduceMotion" = "Reduce Motion"; -"Appearance.ReduceMotionInfo" = "Disable animations in message bubbles and in the chats list."; - -"Appearance.Animations" = "ANIMATIONS"; - -"Weekday.Monday" = "Monday"; -"Weekday.Tuesday" = "Tuesday"; -"Weekday.Wednesday" = "Wednesday"; -"Weekday.Thursday" = "Thursday"; -"Weekday.Friday" = "Friday"; -"Weekday.Saturday" = "Saturday"; -"Weekday.Sunday" = "Sunday"; - -"Watch.Message.Call" = "Call"; -"Watch.Message.Game" = "Game"; -"Watch.Message.Invoice" = "Invoice"; -"Watch.Message.Poll" = "Poll"; -"Watch.Message.Unsupported" = "Unsupported Message"; - -"Notifications.ExceptionsResetToDefaults" = "Reset to Defaults"; - -"AuthSessions.IncompleteAttempts" = "INCOMPLETE LOGIN ATTEMPTS"; -"AuthSessions.IncompleteAttemptsInfo" = "These devices have no access to your account. The code was entered correctly, but no correct password was given."; - -"AuthSessions.Terminate" = "Terminate"; - -"ApplyLanguage.ChangeLanguageAlreadyActive" = "The language %1$@ is already active."; -"ApplyLanguage.ChangeLanguageTitle" = "Change Language?"; -"ApplyLanguage.ChangeLanguageUnofficialText" = "You are about to apply a custom language pack **%1$@** that is %2$@% complete.\n\nThis will translate the entire interface. You can suggest corrections in the [translation panel]().\n\nYou can change your language back at any time in Settings."; -"ApplyLanguage.ChangeLanguageOfficialText" = "You are about to apply a language pack **%1$@**.\n\nThis will translate the entire interface. You can suggest corrections in the [translation panel]().\n\nYou can change your language back at any time in Settings."; -"ApplyLanguage.ChangeLanguageAction" = "Change"; -"ApplyLanguage.ApplyLanguageAction" = "Change"; -"ApplyLanguage.UnsufficientDataTitle" = "Insufficient Data"; -"ApplyLanguage.UnsufficientDataText" = "Unfortunately, this custom language pack (%1$@) doesn't contain data for Telegram iOS. You can contribute to this language pack using the [translations platform]()"; -"ApplyLanguage.LanguageNotSupportedError" = "Sorry, this language doesn't seem to exist."; -"ApplyLanguage.ApplySuccess" = "Language changed"; - -"TextFormat.Bold" = "Bold"; -"TextFormat.Italic" = "Italic"; -"TextFormat.Monospace" = "Monospace"; - -"TwoStepAuth.SetupPasswordTitle" = "Create a Password"; -"TwoStepAuth.SetupPasswordDescription" = "Please create a password which will be used to protect your data."; -"TwoStepAuth.ChangePassword" = "Change Password"; -"TwoStepAuth.ChangePasswordDescription" = "Please enter a new password which will be used to protect your data."; -"TwoStepAuth.ReEnterPasswordTitle" = "Re-enter your Password"; -"TwoStepAuth.ReEnterPasswordDescription" = "Please confirm your password."; -"TwoStepAuth.AddHintTitle" = "Add a Hint"; -"TwoStepAuth.AddHintDescription" = "You can create an optional hint for your password."; -"TwoStepAuth.HintPlaceholder" = "Hint"; -"TwoStepAuth.RecoveryEmailTitle" = "Recovery Email"; -"TwoStepAuth.RecoveryEmailAddDescription" = "Please add your valid e-mail. It is the only way to recover a forgotten password."; -"TwoStepAuth.RecoveryEmailChangeDescription" = "Please enter your new recovery email. It is the only way to recover a forgotten password."; -"TwoStepAuth.ChangeEmail" = "Change Email"; -"TwoStepAuth.ConfirmEmailDescription" = "Please enter the code we've just emailed at %1$@."; -"TwoStepAuth.ConfirmEmailCodePlaceholder" = "Code"; -"TwoStepAuth.ConfirmEmailResendCode" = "Resend Code"; - -"TwoStepAuth.SetupPendingEmail" = "Your recovery email %@ needs to be confirmed and is not yet active.\n\nPlease check your email and enter the confirmation code to complete Two-Step Verification setup. Be sure to check the spam folder as well."; -"TwoStepAuth.SetupResendEmailCode" = "Resend Code"; -"TwoStepAuth.SetupResendEmailCodeAlert" = "The code has been sent. Please check your e-mail. Be sure to check the spam folder as well."; -"TwoStepAuth.EnterEmailCode" = "Enter Code"; - -"TwoStepAuth.EnabledSuccess" = "Two-Step verification\nis enabled."; -"TwoStepAuth.DisableSuccess" = "Two-Step verification\nis disabled."; -"TwoStepAuth.PasswordChangeSuccess" = "Your password\nhas been changed."; -"TwoStepAuth.EmailAddSuccess" = "Your recovery e-mail\nhas been added."; -"TwoStepAuth.EmailChangeSuccess" = "Your recovery e-mail\nhas been changed."; - -"Conversation.SendMessageErrorGroupRestricted" = "Sorry, you are currently restricted from posting to public groups."; - -"InstantPage.TapToOpenLink" = "Tap to open the link:"; -"InstantPage.RelatedArticleAuthorAndDateTitle" = "%1$@ • %2$@"; - -"AuthCode.Alert" = "Your login code is %@. Enter it in the Telegram app where you are trying to log in.\n\nDo not give this code to anyone."; -"Login.CheckOtherSessionMessages" = "Check your Telegram messages"; -"Login.SendCodeViaSms" = "Send the code as an SMS"; -"Login.CancelPhoneVerification" = "Do you want to stop the phone number verification process?"; -"Login.CancelPhoneVerificationStop" = "Stop"; -"Login.CancelPhoneVerificationContinue" = "Continue"; -"Login.CodeExpired" = "Code expired, please login again."; -"Login.CancelSignUpConfirmation" = "Do you want to stop the registration process?"; - -"Passcode.AppLockedAlert" = "Telegram\nLocked"; - -"ChatList.ReadAll" = "Read All"; -"ChatList.Read" = "Read"; -"ChatList.DeleteConfirmation_1" = "Delete"; -"ChatList.DeleteConfirmation_2" = "Delete 2 Chats"; -"ChatList.DeleteConfirmation_3_10" = "Delete %@ Chats"; -"ChatList.DeleteConfirmation_any" = "Delete %@ Chats"; -"ChatList.DeleteConfirmation_many" = "Delete %@ Chats"; -"ChatList.DeleteConfirmation_0" = "Delete %@ Chats"; - -"Username.TooManyPublicUsernamesError" = "Sorry, you have reserved too many public usernames."; -"Group.Username.RevokeExistingUsernamesInfo" = "You can revoke the link from one of your older groups or channels, or create a private group instead."; -"Channel.Username.RevokeExistingUsernamesInfo" = "You can revoke the link from one of your older groups or channels, or create a private channel instead."; - -"InstantPage.Reference" = "Reference"; - -"Permissions.Skip" = "Skip"; - -"Permissions.ContactsTitle.v0" = "Sync Your Contacts"; -"Permissions.ContactsText.v0" = "See who's on Telegram and switch seamlessly, without having to \"add\" your friends."; -"Permissions.ContactsAllow.v0" = "Allow Access"; -"Permissions.ContactsAllowInSettings.v0" = "Allow in Settings"; - -"Permissions.NotificationsTitle.v0" = "Turn ON Notifications"; -"Permissions.NotificationsText.v0" = "Don't miss important messages from your friends and coworkers."; -"Permissions.NotificationsUnreachableText.v0" = "Please note that you partly disabled message notifications in your Settings."; -"Permissions.NotificationsAllow.v0" = "Turn Notifications ON"; -"Permissions.NotificationsAllowInSettings.v0" = "Turn ON in Settings"; - -"Permissions.CellularDataTitle.v0" = "Enable Cellular Data"; -"Permissions.CellularDataText.v0" = "Don't worry, Telegram keeps network usage to a minimum. You can further control this in Settings > Data and Storage."; -"Permissions.CellularDataAllowInSettings.v0" = "Turn ON in Settings"; - -"Permissions.SiriTitle.v0" = "Turn ON Siri"; -"Permissions.SiriText.v0" = "Use Siri to send messages and make calls."; -"Permissions.SiriAllow.v0" = "Turn Siri ON"; -"Permissions.SiriAllowInSettings.v0" = "Turn ON in Settings"; - -"Permissions.PrivacyPolicy" = "Privacy Policy"; - -"Contacts.PermissionsTitle" = "Access to Contacts"; -"Contacts.PermissionsText" = "Please allow Telegram access to your phonebook to seamlessly find all your friends."; -"Contacts.PermissionsAllow" = "Allow Access"; -"Contacts.PermissionsAllowInSettings" = "Allow in Settings"; -"Contacts.PermissionsSuppressWarningTitle" = "Keep contacts disabled?"; -"Contacts.PermissionsSuppressWarningText" = "You won't know when your friends join Telegram and become available to chat. We recommend enabling access to contacts in Settings."; -"Contacts.PermissionsKeepDisabled" = "Keep Disabled"; -"Contacts.PermissionsEnable" = "Enable"; - -"Notifications.PermissionsTitle" = "Turn ON Notifications"; -"Notifications.PermissionsText" = "Don't miss important messages from your friends and coworkers."; -"Notifications.PermissionsUnreachableTitle" = "Check Notification Settings"; -"Notifications.PermissionsUnreachableText" = "Please note that you partly disabled message notifications in your Settings."; -"Notifications.PermissionsAllow" = "Turn Notifications ON"; -"Notifications.PermissionsAllowInSettings" = "Turn ON in Settings"; -"Notifications.PermissionsOpenSettings" = "Open Settings"; -"Notifications.PermissionsSuppressWarningTitle" = "Keep notifications disabled?"; -"Notifications.PermissionsSuppressWarningText" = "You may miss important messages on Telegram due to your current settings.\n\nFor better results, enable alerts or banners and try muting certain chats or chat types in Telegram settings."; -"Notifications.PermissionsKeepDisabled" = "Keep Disabled"; -"Notifications.PermissionsEnable" = "Enable"; - -"ChatSettings.DownloadInBackground" = "Background Download"; -"ChatSettings.DownloadInBackgroundInfo" = "The app will continue downloading media files for a limited time."; - -"Cache.ServiceFiles" = "Service Files"; - -"SharedMedia.SearchNoResults" = "No Results"; -"SharedMedia.SearchNoResultsDescription" = "There were no results for \"%@\".\nTry a new search."; - -"MessagePoll.LabelAnonymous" = "Anonymous Poll"; -"MessagePoll.LabelClosed" = "Final Results"; -"MessagePoll.NoVotes" = "No votes"; -"MessagePoll.VotedCount_0" = "%@ votes"; -"MessagePoll.VotedCount_1" = "1 vote"; -"MessagePoll.VotedCount_2" = "2 votes"; -"MessagePoll.VotedCount_3_10" = "%@ votes"; -"MessagePoll.VotedCount_many" = "%@ votes"; -"MessagePoll.VotedCount_any" = "%@ votes"; -"AttachmentMenu.Poll" = "Poll"; -"Conversation.PinnedPoll" = "Pinned Poll"; -"Conversation.PinnedQuiz" = "Pinned Quiz"; - -"CreatePoll.Title" = "New Poll"; -"CreatePoll.Create" = "Send"; -"CreatePoll.TextHeader" = "QUESTION"; -"CreatePoll.TextPlaceholder" = "Ask a question"; -"CreatePoll.OptionsHeader" = "POLL OPTIONS"; -"CreatePoll.OptionPlaceholder" = "Option"; -"CreatePoll.AddOption" = "Add an Option"; - -"CreatePoll.AddMoreOptions_0" = "You can add %@ more options."; -"CreatePoll.AddMoreOptions_1" = "You can add 1 more option."; -"CreatePoll.AddMoreOptions_2" = "You can add 2 more options."; -"CreatePoll.AddMoreOptions_3_10" = "You can add %@ more options."; -"CreatePoll.AddMoreOptions_many" = "You can add %@ more options."; -"CreatePoll.AddMoreOptions_any" = "You can add %@ more options."; -"CreatePoll.AllOptionsAdded" = "You have added the maximum number of options."; - -"CreatePoll.CancelConfirmation" = "Are you sure you want to discard this poll?"; - -"ForwardedPolls_1" = "Forwarded poll"; -"ForwardedPolls_2" = "2 forwarded polls"; -"ForwardedPolls_3_10" = "%@ forwarded polls"; -"ForwardedPolls_any" = "%@ forwarded polls"; -"ForwardedPolls_many" = "%@ forwarded polls"; -"ForwardedPolls_0" = "%@ forwarded polls"; - -"Conversation.UnvotePoll" = "Retract Vote"; -"Conversation.StopPoll" = "Stop Poll"; -"Conversation.StopPollConfirmationTitle" = "If you stop this poll now, nobody will be able to vote in it anymore. This action cannot be undone."; -"Conversation.StopPollConfirmation" = "Stop Poll"; - -"AttachmentMenu.WebSearch" = "Web Search"; - -"Conversation.UnsupportedMediaPlaceholder" = "This message is not supported on your version of Telegram. Please update to the latest version."; -"Conversation.UpdateTelegram" = "UPDATE TELEGRAM"; - -"Cache.LowDiskSpaceText" = "Your phone has run out of available storage. Please free some space to download or upload media."; - -"Contacts.SortBy" = "Sort by:"; -"Contacts.SortByName" = "Name"; -"Contacts.SortByPresence" = "Last Seen Time"; -"Contacts.SortedByName" = "Sorted by Name"; -"Contacts.SortedByPresence" = "Sorted by Last Seen Time"; - -"NotificationSettings.ContactJoinedInfo" = "Receive push notifications when one of your contacts becomes available on Telegram."; - -"GroupInfo.Permissions" = "Permissions"; -"GroupInfo.Permissions.Title" = "Permissions"; -"GroupInfo.Permissions.SectionTitle" = "WHAT CAN MEMBERS OF THIS GROUP DO?"; -"GroupInfo.Permissions.Removed" = "Removed Users"; -"GroupInfo.Permissions.Exceptions" = "EXCEPTIONS"; -"GroupInfo.Permissions.AddException" = "Add Exception"; -"GroupInfo.Permissions.SearchPlaceholder" = "Search Exceptions"; - -"GroupInfo.Administrators" = "Administrators"; -"GroupInfo.Administrators.Title" = "Administrators"; - -"GroupPermission.NoSendMessages" = "no messages"; -"GroupPermission.NoSendMedia" = "no media"; -"GroupPermission.NoSendGifs" = "no GIFs"; -"GroupPermission.NoSendPolls" = "no polls"; -"GroupPermission.NoSendLinks" = "no links"; -"GroupPermission.NoChangeInfo" = "no info"; -"GroupPermission.NoAddMembers" = "no add"; -"GroupPermission.NoPinMessages" = "no pin"; - -"GroupPermission.Title" = "Exception"; -"GroupPermission.NewTitle" = "New Exception"; -"GroupPermission.SectionTitle" = "WHAT CAN THIS MEMBER DO?"; -"GroupPermission.Duration" = "Duration"; -"GroupPermission.AddedInfo" = "Exception added by %1$@ %2$@"; -"GroupPermission.Delete" = "Delete Exception"; -"GroupPermission.ApplyAlertText" = "You have changed this user's rights in %@.\nApply Changes?"; -"GroupPermission.ApplyAlertAction" = "Apply"; -"GroupPermission.AddSuccess" = "Exception Added"; -"GroupPermission.NotAvailableInPublicGroups" = "This permission is not available in public groups."; -"GroupPermission.AddMembersNotAvailable" = "You don't have persmission to add members."; - -"Channel.EditAdmin.PermissionEnabledByDefault" = "This option is permitted for all members in Group Permissions."; - -"GroupPermission.EditingDisabled" = "You cannot edit restrictions of this user."; -"GroupPermission.PermissionDisabledByDefault" = "This option is disabled for all members in Group Permissions."; - -"Channel.Management.RemovedBy" = "Removed by %@"; - -"GroupRemoved.Title" = "Removed Users"; -"GroupRemoved.Remove" = "Remove User"; -"GroupRemoved.RemoveInfo" = "Users removed from the group by admins cannot rejoin it via invite links."; -"ChannelRemoved.RemoveInfo" = "Users removed from the channel by admins cannot rejoin it via invite links."; -"GroupRemoved.UsersSectionTitle" = "REMOVED USERS"; -"GroupRemoved.ViewUserInfo" = "View User Info"; -"GroupRemoved.AddToGroup" = "Add To Group"; -"GroupRemoved.DeleteUser" = "Delete"; - -"EmptyGroupInfo.Title" = "You have created a group"; -"EmptyGroupInfo.Subtitle" = "Groups can have:"; -"EmptyGroupInfo.Line1" = "Up to %@ members"; -"EmptyGroupInfo.Line2" = "Persistent chat history"; -"EmptyGroupInfo.Line3" = "Public links such as t.me/title"; -"EmptyGroupInfo.Line4" = "Admins with different rights"; - -"WallpaperPreview.Title" = "Background Preview"; -"WallpaperPreview.PreviewTopText" = "Press Set to apply the background"; -"WallpaperPreview.PreviewBottomText" = "Enjoy the view"; -"WallpaperPreview.SwipeTopText" = "Swipe left or right to preview more backgrounds"; -"WallpaperPreview.SwipeBottomText" = "Backgrounds for the god of backgrounds!"; -"WallpaperPreview.SwipeColorsTopText" = "Swipe left or right to see more colors"; -"WallpaperPreview.SwipeColorsBottomText" = "Salmon is a fish, not a color"; -"WallpaperPreview.CustomColorTopText" = "Use sliders to adjust color"; -"WallpaperPreview.CustomColorBottomText" = "Something to match your curtains"; -"WallpaperPreview.CropTopText" = "Pinch and pan to adjust background"; -"WallpaperPreview.CropBottomText" = "Pinch me, I'm dreaming"; -"WallpaperPreview.Motion" = "Motion"; -"WallpaperPreview.Blurred" = "Blurred"; -"WallpaperPreview.Pattern" = "Pattern"; - -"Wallpaper.Search" = "Search Backgrounds"; -"Wallpaper.SearchShort" = "Search"; -"Wallpaper.SetColor" = "Set a Color"; -"Wallpaper.SetCustomBackground" = "Choose from Gallery"; -"Wallpaper.SetCustomBackgroundInfo" = "You can set a custom background image and share it with your friends."; - -"Wallpaper.DeleteConfirmation_1" = "Delete Background"; -"Wallpaper.DeleteConfirmation_2" = "Delete 2 Backgrounds"; -"Wallpaper.DeleteConfirmation_3_10" = "Delete %@ Backgrounds"; -"Wallpaper.DeleteConfirmation_any" = "Delete %@ Backgrounds"; -"Wallpaper.DeleteConfirmation_many" = "Delete %@ Backgrounds"; -"Wallpaper.DeleteConfirmation_0" = "Delete %@ Backgrounds"; - -"WallpaperColors.Title" = "Set a Color"; -"WallpaperColors.SetCustomColor" = "Set Custom Color"; - -"WallpaperSearch.ColorTitle" = "SEARCH BY COLOR"; -"WallpaperSearch.Recent" = "RECENT"; -"WallpaperSearch.ColorPrefix" = "color: "; -"WallpaperSearch.ColorBlue" = "Blue"; -"WallpaperSearch.ColorRed" = "Red"; -"WallpaperSearch.ColorOrange" = "Orange"; -"WallpaperSearch.ColorYellow" = "Yellow"; -"WallpaperSearch.ColorGreen" = "Green"; -"WallpaperSearch.ColorTeal" = "Teal"; -"WallpaperSearch.ColorPurple" = "Purple"; -"WallpaperSearch.ColorPink" = "Pink"; -"WallpaperSearch.ColorBrown" = "Brown"; -"WallpaperSearch.ColorBlack" = "Black"; -"WallpaperSearch.ColorGray" = "Gray"; -"WallpaperSearch.ColorWhite" = "White"; - -"Channel.AdminLog.DefaultRestrictionsUpdated" = "changed default permissions"; -"Channel.AdminLog.PollStopped" = "%@ stopped poll"; - -"ChatList.DeleteChat" = "Delete Chat"; -"ChatList.DeleteChatConfirmation" = "Are you sure you want to delete chat\nwith %@?"; -"ChatList.DeleteSecretChatConfirmation" = "Are you sure you want to delete secret chat\nwith %@?"; -"ChatList.LeaveGroupConfirmation" = "Are you sure you want to leave %@?"; -"ChatList.DeleteSavedMessagesConfirmation" = "Are you sure you want to delete\nSaved Messages?"; - -"Undo.Undo" = "Undo"; -"Undo.ChatDeleted" = "Chat deleted"; -"Undo.ChatCleared" = "Chat cleared"; -"Undo.ChatClearedForBothSides" = "Chat cleared for both sides"; -"Undo.SecretChatDeleted" = "Secret Chat deleted"; -"Undo.LeftChannel" = "Left channel"; -"Undo.LeftGroup" = "Left group"; -"Undo.DeletedChannel" = "Deleted channel"; -"Undo.DeletedGroup" = "Deleted group"; - -"AccessDenied.Wallpapers" = "Telegram needs access to your photo library to set a custom chat background.\n\nPlease go to Settings > Privacy > Photos and set Telegram to ON."; - -"Conversation.ChatBackground" = "Chat Background"; -"Conversation.ViewBackground" = "VIEW BACKGROUND"; - -"SocksProxySetup.ShareQRCodeInfo" = "Your friends can add this proxy by scanning this code with phone or in-app camera."; -"SocksProxySetup.ShareQRCode" = "Share QR Code"; -"SocksProxySetup.ShareLink" = "Share Lisnk"; - -"CallFeedback.Title" = "Call Feedback"; -"CallFeedback.WhatWentWrong" = "WHAT WENT WRONG?"; -"CallFeedback.ReasonEcho" = "I heard my own voice"; -"CallFeedback.ReasonNoise" = "I heard background noise"; -"CallFeedback.ReasonInterruption" = "The other side kept disappearing"; -"CallFeedback.ReasonDistortedSpeech" = "Speech was distorted"; -"CallFeedback.ReasonSilentLocal" = "I couldn't hear the other side"; -"CallFeedback.ReasonSilentRemote" = "The other side couldn't hear me"; -"CallFeedback.ReasonDropped" = "Call ended unexpectedly"; -"CallFeedback.VideoReasonDistorted" = "Video was distorted"; -"CallFeedback.VideoReasonLowQuality" = "Video was pixelated"; -"CallFeedback.AddComment" = "Add an optional comment"; -"CallFeedback.IncludeLogs" = "Include technical information"; -"CallFeedback.IncludeLogsInfo" = "This won't reveal the contents of your conversation, but will help us fix the issue sooner."; -"CallFeedback.Send" = "Send"; -"CallFeedback.Success" = "Thanks for\nyour feedback"; - -"Settings.AddAccount" = "Add Account"; -"WebSearch.SearchNoResults" = "No Results"; -"WebSearch.SearchNoResultsDescription" = "There were no results for \"%@\".\nTry a new search."; - -"WallpaperPreview.PatternIntensity" = "Pattern Intensity"; - -"Message.Wallpaper" = "Chat Background"; - -"Wallpaper.ResetWallpapers" = "Reset Chat Backgrounds"; -"Wallpaper.ResetWallpapersInfo" = "Remove all uploaded chat backgrounds and restore pre-installed backgrounds for all themes."; -"Wallpaper.ResetWallpapersConfirmation" = "Reset Chat Backgrounds"; - -"Proxy.TooltipUnavailable" = "The proxy may be unavailable. Try selecting another one."; - -"SocksProxySetup.Status" = "Status"; -"Login.PhoneNumberAlreadyAuthorized" = "This account is already logged in from this app."; - -"Login.PhoneNumberAlreadyAuthorizedSwitch" = "Switch"; - -"Call.AnsweringWithAccount" = "Answering as %@"; - -"AutoDownloadSettings.CellularTitle" = "Using Cellular"; -"AutoDownloadSettings.WifiTitle" = "Using Wi-Fi"; -"AutoDownloadSettings.AutoDownload" = "Auto-Download Media"; -"AutoDownloadSettings.MediaTypes" = "TYPES OF MEDIA"; -"AutoDownloadSettings.Photos" = "Photos"; -"AutoDownloadSettings.Videos" = "Videos"; -"AutoDownloadSettings.Files" = "Files"; -"AutoDownloadSettings.VoiceMessagesInfo" = "Voice messages are tiny and always downloaded automatically."; -"AutoDownloadSettings.ResetSettings" = "Reset Auto-Download Settings"; -"AutoDownloadSettings.AutodownloadPhotos" = "AUTO-DOWNLOAD PHOTOS"; -"AutoDownloadSettings.AutodownloadVideos" = "AUTO-DOWNLOAD VIDEOS AND GIFS"; -"AutoDownloadSettings.AutodownloadFiles" = "AUTO-DOWNLOAD FILES AND MUSIC"; -"AutoDownloadSettings.MaxVideoSize" = "MAXIMUM VIDEO SIZE"; -"AutoDownloadSettings.MaxFileSize" = "MAXIMUM FILE SIZE"; -"AutoDownloadSettings.DataUsage" = "DATA USAGE"; -"AutoDownloadSettings.DataUsageLow" = "Low"; -"AutoDownloadSettings.DataUsageMedium" = "Medium"; -"AutoDownloadSettings.DataUsageHigh" = "High"; -"AutoDownloadSettings.DataUsageCustom" = "Custom"; -"AutoDownloadSettings.OnForAll" = "On for all chats"; -"AutoDownloadSettings.OnFor" = "On for %@"; -"AutoDownloadSettings.TypeContacts" = "Contacts"; -"AutoDownloadSettings.TypePrivateChats" = "PM"; -"AutoDownloadSettings.TypeGroupChats" = "Groups"; -"AutoDownloadSettings.TypeChannels" = "Channels"; -"AutoDownloadSettings.UpToForAll" = "Up to %@ for all chats"; -"AutoDownloadSettings.UpToFor" = "Up to %1$@ for %2$@"; -"AutoDownloadSettings.OffForAll" = "Off for all chats"; -"AutoDownloadSettings.Delimeter" = ", "; -"AutoDownloadSettings.LastDelimeter" = " and "; -"AutoDownloadSettings.PreloadVideo" = "Preload Larger Videos"; -"AutoDownloadSettings.PreloadVideoInfo" = "Preload first seconds of videos larger than %@ for instant playback."; - -"ChatSettings.AutoDownloadUsingCellular" = "Using Cellular"; -"ChatSettings.AutoDownloadUsingWiFi" = "Using Wi-Fi"; -"ChatSettings.AutoPlayTitle" = "AUTO-PLAY MEDIA"; -"ChatSettings.AutoPlayGifs" = "GIFs"; -"ChatSettings.AutoPlayVideos" = "Videos"; - -"ChatSettings.AutoDownloadSettings.TypePhoto" = "Photos"; -"ChatSettings.AutoDownloadSettings.TypeVideo" = "Videos (%@)"; -"ChatSettings.AutoDownloadSettings.TypeFile" = "Files (%@)"; -"ChatSettings.AutoDownloadSettings.OffForAll" = "Disabled"; -"ChatSettings.AutoDownloadSettings.Delimeter" = ", "; - -"LogoutOptions.Title" = "Log out"; -"LogoutOptions.AlternativeOptionsSection" = "ALTERNATIVE OPTIONS"; -"LogoutOptions.AddAccountTitle" = "Add another account"; -"LogoutOptions.AddAccountText" = "Set up multiple phone numbers and easily switch between them."; -"LogoutOptions.SetPasscodeTitle" = "Set a Passcode"; -"LogoutOptions.SetPasscodeText" = "Lock the app with a passcode so that others can't open it."; -"LogoutOptions.ClearCacheTitle" = "Clear Cache"; -"LogoutOptions.ClearCacheText" = "Free up disk space on your device; your media will stay in the cloud."; -"LogoutOptions.ChangePhoneNumberTitle" = "Change Phone Number"; -"LogoutOptions.ChangePhoneNumberText" = "Move your contacts, groups, messages and media to a new number."; -"LogoutOptions.ContactSupportTitle" = "Contact Support"; -"LogoutOptions.ContactSupportText" = "Tell us about any issues; logging out doesn't usually help."; -"LogoutOptions.LogOut" = "Log Out"; -"LogoutOptions.LogOutInfo" = "Remember, logging out kills all your Secret Chats."; -"LogoutOptions.LogOutWalletInfo" = "Logging out will cancel all your secret chats and unlink your Gram wallet from the app."; - -"GroupPermission.PermissionGloballyDisabled" = "This permission is disabled in this group."; - -"ChannelInfo.Stats" = "Statistics"; - -"Conversation.PressVolumeButtonForSound" = "Press volume button\nto unmute the video"; - -"ChatList.SelectedChats_1" = "%@ Chat Selected"; -"ChatList.SelectedChats_2" = "%@ Chats Selected"; -"ChatList.SelectedChats_3_10" = "%@ Chats Selected"; -"ChatList.SelectedChats_any" = "%@ Chats Selected"; -"ChatList.SelectedChats_many" = "%@ Chats Selected"; -"ChatList.SelectedChats_0" = "%@ Chats Selected"; - -"NotificationSettings.ShowNotificationsFromAccountsSection" = "SHOW NOTIFICATIONS FROM"; -"NotificationSettings.ShowNotificationsAllAccounts" = "All Accounts"; -"NotificationSettings.ShowNotificationsAllAccountsInfoOn" = "Turn this off if you want to receive notifications only from your active account."; -"NotificationSettings.ShowNotificationsAllAccountsInfoOff" = "Turn this on if you want to receive notifications from all your accounts."; - -"Gif.Search" = "Search GIFs"; -"Gif.NoGifsFound" = "No GIFs Found"; -"Gif.NoGifsPlaceholder" = "You have no saved GIFs yet."; - -"Privacy.ProfilePhoto" = "Profile Photo"; -"Privacy.Forwards" = "Forwarded Messages"; - -"Privacy.ProfilePhoto.WhoCanSeeMyPhoto" = "WHO CAN SEE MY PROFILE PHOTO"; -"Privacy.ProfilePhoto.CustomHelp" = "You can restrict who can see your profile photo with granular precision."; -"Privacy.ProfilePhoto.AlwaysShareWith.Title" = "Always Share With"; -"Privacy.ProfilePhoto.NeverShareWith.Title" = "Never Share With"; - -"Privacy.Forwards.WhoCanForward" = "WHO CAN ADD LINK TO MY ACCOUNT WHEN FORWARDING MY MESSAGES"; -"Privacy.Forwards.CustomHelp" = "When forwarded to other chats, messages you send will not link back to your account."; -"Privacy.Forwards.AlwaysAllow.Title" = "Always Allow"; -"Privacy.Forwards.NeverAllow.Title" = "Never Allow"; - -"Conversation.ContextMenuCancelSending" = "Cancel Sending"; - -"Conversation.ForwardAuthorHiddenTooltip" = "The account was hidden by the user"; - -"Privacy.Forwards.Preview" = "PREVIEW"; -"Privacy.Forwards.PreviewMessageText" = "Reinhardt, we need to find you some new music."; -"Privacy.Forwards.AlwaysLink" = "Link to your account"; -"Privacy.Forwards.LinkIfAllowed" = "Link if allowed by settings below"; -"Privacy.Forwards.NeverLink" = "Not a link to your account"; - -"Chat.UnsendMyMessagesAlertTitle" = "Unsending will also delete messages you sent on %@'s side."; -"Chat.UnsendMyMessages" = "Unsend My Messages"; - -"Chat.DeleteMessagesConfirmation_1" = "Delete message"; -"Chat.DeleteMessagesConfirmation_any" = "Delete %@ messages"; - -"Settings.Search" = "Search Settings"; - -"SettingsSearch.FAQ" = "FAQ"; - -"SettingsSearch.Synonyms.EditProfile.Title" = " "; -"SettingsSearch.Synonyms.EditProfile.Bio" = " "; -"SettingsSearch.Synonyms.EditProfile.PhoneNumber" = " "; -"SettingsSearch.Synonyms.EditProfile.Username" = " "; -"SettingsSearch.Synonyms.EditProfile.AddAccount" = " "; -"SettingsSearch.Synonyms.EditProfile.Logout" = " "; - -"SettingsSearch.Synonyms.Calls.Title" = " "; -"SettingsSearch.Synonyms.Calls.CallTab" = " "; - -"SettingsSearch.Synonyms.Stickers.Title" = " "; -"SettingsSearch.Synonyms.Stickers.SuggestStickers" = " "; -"SettingsSearch.Synonyms.Stickers.FeaturedPacks" = " "; -"SettingsSearch.Synonyms.Stickers.ArchivedPacks" = " "; -"SettingsSearch.Synonyms.Stickers.Masks" = " "; - -"SettingsSearch.Synonyms.Notifications.Title" = " "; -"SettingsSearch.Synonyms.Notifications.MessageNotificationsAlert" = " "; -"SettingsSearch.Synonyms.Notifications.MessageNotificationsPreview" = " "; -"SettingsSearch.Synonyms.Notifications.MessageNotificationsSound" = " "; -"SettingsSearch.Synonyms.Notifications.MessageNotificationsExceptions" = " "; -"SettingsSearch.Synonyms.Notifications.GroupNotificationsAlert" = " "; -"SettingsSearch.Synonyms.Notifications.GroupNotificationsPreview" = " "; -"SettingsSearch.Synonyms.Notifications.GroupNotificationsSound" = " "; -"SettingsSearch.Synonyms.Notifications.GroupNotificationsExceptions" = " "; -"SettingsSearch.Synonyms.Notifications.ChannelNotificationsAlert" = " "; -"SettingsSearch.Synonyms.Notifications.ChannelNotificationsPreview" = " "; -"SettingsSearch.Synonyms.Notifications.ChannelNotificationsSound" = " "; -"SettingsSearch.Synonyms.Notifications.ChannelNotificationsExceptions" = " "; -"SettingsSearch.Synonyms.Notifications.InAppNotificationsSound" = " "; -"SettingsSearch.Synonyms.Notifications.InAppNotificationsVibrate" = " "; -"SettingsSearch.Synonyms.Notifications.InAppNotificationsPreview" = " "; -"SettingsSearch.Synonyms.Notifications.DisplayNamesOnLockScreen" = " "; -"SettingsSearch.Synonyms.Notifications.BadgeIncludeMutedChats" = " "; -"SettingsSearch.Synonyms.Notifications.BadgeIncludeMutedPublicGroups" = " "; -"SettingsSearch.Synonyms.Notifications.BadgeIncludeMutedChannels" = " "; -"SettingsSearch.Synonyms.Notifications.BadgeCountUnreadMessages" = " "; -"SettingsSearch.Synonyms.Notifications.ContactJoined" = " "; -"SettingsSearch.Synonyms.Notifications.ResetAllNotifications" = " "; - -"SettingsSearch.Synonyms.Privacy.Title" = " "; -"SettingsSearch.Synonyms.Privacy.BlockedUsers" = " "; -"SettingsSearch.Synonyms.Privacy.LastSeen" = " "; -"SettingsSearch.Synonyms.Privacy.ProfilePhoto" = " "; -"SettingsSearch.Synonyms.Privacy.Forwards" = " "; -"SettingsSearch.Synonyms.Privacy.Calls" = " "; -"SettingsSearch.Synonyms.Privacy.GroupsAndChannels" = " "; -"SettingsSearch.Synonyms.Privacy.Passcode" = " "; -"SettingsSearch.Synonyms.Privacy.PasscodeAndTouchId" = " "; -"SettingsSearch.Synonyms.Privacy.PasscodeAndFaceId" = " "; -"SettingsSearch.Synonyms.Privacy.TwoStepAuth" = "Password"; -"SettingsSearch.Synonyms.Privacy.AuthSessions" = " "; -"SettingsSearch.Synonyms.Privacy.DeleteAccountIfAwayFor" = " "; - -"SettingsSearch.Synonyms.Privacy.Data.Title" = " "; -"SettingsSearch.Synonyms.Privacy.Data.ContactsReset" = " "; -"SettingsSearch.Synonyms.Privacy.Data.ContactsSync" = " "; -"SettingsSearch.Synonyms.Privacy.Data.TopPeers" = " "; -"SettingsSearch.Synonyms.Privacy.Data.DeleteDrafts" = " "; -"SettingsSearch.Synonyms.Privacy.Data.ClearPaymentsInfo" = " "; -"SettingsSearch.Synonyms.Privacy.Data.SecretChatLinkPreview" = " "; - -"SettingsSearch.Synonyms.Data.Title" = " "; -"SettingsSearch.Synonyms.Data.Storage.Title" = "Cache"; -"SettingsSearch.Synonyms.Data.Storage.KeepMedia" = " "; -"SettingsSearch.Synonyms.Data.Storage.ClearCache" = " "; -"SettingsSearch.Synonyms.Data.NetworkUsage" = " "; -"SettingsSearch.Synonyms.Data.AutoDownloadUsingCellular" = " "; -"SettingsSearch.Synonyms.Data.AutoDownloadUsingWifi" = " "; -"SettingsSearch.Synonyms.Data.AutoDownloadReset" = " "; -"SettingsSearch.Synonyms.Data.AutoplayGifs" = " "; -"SettingsSearch.Synonyms.Data.AutoplayVideos" = " "; -"SettingsSearch.Synonyms.Data.CallsUseLessData" = " "; -"SettingsSearch.Synonyms.Data.SaveIncomingPhotos" = " "; -"SettingsSearch.Synonyms.Data.SaveEditedPhotos" = " "; -"SettingsSearch.Synonyms.Data.DownloadInBackground" = " "; - -"SettingsSearch.Synonyms.Proxy.Title" = "SOCKS5\nMTProto"; -"SettingsSearch.Synonyms.Proxy.AddProxy" = " "; -"SettingsSearch.Synonyms.Proxy.UseForCalls" = " "; - -"SettingsSearch.Synonyms.Appearance.Title" = " "; -"SettingsSearch.Synonyms.Appearance.TextSize" = " "; -"SettingsSearch.Synonyms.Appearance.ChatBackground" = "Wallpaper"; -"SettingsSearch.Synonyms.Appearance.ChatBackground.SetColor" = " "; -"SettingsSearch.Synonyms.Appearance.ChatBackground.Custom" = " "; -"SettingsSearch.Synonyms.Appearance.AutoNightTheme" = " "; -"SettingsSearch.Synonyms.Appearance.ColorTheme" = " "; -"SettingsSearch.Synonyms.Appearance.LargeEmoji" = " "; -"SettingsSearch.Synonyms.Appearance.Animations" = "Animations"; - -"SettingsSearch.Synonyms.SavedMessages" = " "; -"SettingsSearch.Synonyms.AppLanguage" = " "; -"SettingsSearch.Synonyms.Passport" = " "; -"SettingsSearch.Synonyms.Watch" = "Apple Watch"; -"SettingsSearch.Synonyms.Support" = "Support"; -"SettingsSearch.Synonyms.FAQ" = " "; - -"ChatList.DeleteForCurrentUser" = "Delete just for me"; -"ChatList.DeleteForEveryone" = "Delete for me and %@"; -"ChatList.DeleteForEveryoneConfirmationTitle" = "Warning!"; -"ChatList.DeleteForEveryoneConfirmationText" = "This will **delete all messages** in this chat for **both participants**."; -"ChatList.DeleteForEveryoneConfirmationAction" = "Delete All"; - -"ChatList.DeleteSavedMessagesConfirmationTitle" = "Warning!"; -"ChatList.DeleteSavedMessagesConfirmationText" = "This will **delete all messages** in this chat."; -"ChatList.DeleteSavedMessagesConfirmationAction" = "Delete All"; - -"ChatList.ClearChatConfirmation" = "Are you sure you want to delete all\nmessages in the chat with %@?"; - -"Settings.CheckPhoneNumberTitle" = "Is %@ still your number?"; -"Settings.CheckPhoneNumberText" = "Keep your number up to date to ensure you can always log in to Telegram. [Learn more]()"; -"Settings.KeepPhoneNumber" = "Keep %@"; -"Settings.ChangePhoneNumber" = "Change Number"; - -"Undo.ChatDeletedForBothSides" = "Chat deleted for both sides"; - -"AppUpgrade.Running" = "Optimizing Telegram... -This may take a while, depending on the size of the database. Please keep the app open until the process is finished. - -Sorry for the inconvenience."; - -"Call.Mute" = "mute"; -"Call.Camera" = "camera"; -"Call.Flip" = "flip"; -"Call.End" = "end"; -"Call.Speaker" = "speaker"; - -"MemberSearch.BotSection" = "BOTS"; - -"Conversation.PrivateMessageLinkCopied" = "This link will only work for members of this chat."; -"Conversation.ErrorInaccessibleMessage" = "Unfortunately, you can't access this message. You are not a member of the chat where it was posted."; - -"Stickers.ClearRecent" = "Clear Recent Stickers"; - -"Appearance.Other" = "Other"; -"Appearance.LargeEmoji" = "Large Emoji"; - -"ChatList.ArchiveAction" = "Archive"; -"ChatList.UnarchiveAction" = "Unarchive"; -"ChatList.HideAction" = "Hide"; -"ChatList.UnhideAction" = "Pin"; - -"ChatList.UndoArchiveTitle" = "Chat archived"; -"ChatList.UndoArchiveMultipleTitle" = "Chats archived"; -"ChatList.UndoArchiveText1" = "Hide the archive by swiping left on it."; -"ChatList.UndoArchiveHiddenTitle" = "Archive hidden"; -"ChatList.UndoArchiveHiddenText" = "Swipe down to see archive."; -"ChatList.UndoArchiveRevealedTitle" = "Archive pinned"; -"ChatList.UndoArchiveRevealedText" = "Swipe left on the archive to hide it."; -"ChatList.ArchivedChatsTitle" = "Archived Chats"; - -"PasscodeSettings.PasscodeOptions" = "Passcode Options"; -"PasscodeSettings.DoNotMatch" = "Passcodes don't match. Please try again."; - -"Conversation.PrivateChannelTooltip" = "This channel is private"; - -"PasscodeSettings.PasscodeOptions" = "Passcode Options"; -"PasscodeSettings.AlphanumericCode" = "Custom Alphanumeric Code"; -"PasscodeSettings.4DigitCode" = "4-Digit Numeric Code"; -"PasscodeSettings.6DigitCode" = "6-Digit Numeric Code"; - -"Conversation.ScamWarning" = "⚠️ Warning: Many users reported this account as a scam. Please be careful, especially if it asks you for money."; - -"Conversation.ClearChatConfirmation" = "Warning, this will delete your **entire chat history** with %@."; - -"ArchivedChats.IntroTitle1" = "This is your archive"; -"ArchivedChats.IntroText1" = "Chats with enabled notifications get unarchived when new notifications arrive."; -"ArchivedChats.IntroTitle2" = "Muted Chats"; -"ArchivedChats.IntroText2" = "Muted chats stay archived when new messages arrive."; -"ArchivedChats.IntroTitle3" = "Pinned Chats"; -"ArchivedChats.IntroText3" = "You can pin up to 100 archived chats to the top."; - -"UserInfo.ScamUserWarning" = "⚠️ Warning: Many users reported this user as a scam. Please be careful, especially if it asks you for money."; -"UserInfo.ScamBotWarning" = "⚠️ Warning: Many users reported this user as a scam. Please be careful, especially if it asks you for money."; -"ChannelInfo.ScamChannelWarning" = "⚠️ Warning: Many users reported this channel as a scam. Please be careful, especially if it asks you for money."; -"GroupInfo.ScamGroupWarning" = "⚠️ Warning: Many users reported this group as a scam. Please be careful, especially if it asks you for money."; - -"Privacy.AddNewPeer" = "Add Users or Groups"; -"PrivacyPhoneNumberSettings.WhoCanSeeMyPhoneNumber" = "WHO CAN SEE MY PHONE NUMBER"; -"PrivacyPhoneNumberSettings.CustomHelp" = "Users who already have your number saved in the contacts will also see it on Telegram."; -"PrivacyPhoneNumberSettings.CustomDisabledHelp" = "Users who add your number to their contacts will see it on Telegram only if they are your contacts."; - -"PrivacyPhoneNumberSettings.DiscoveryHeader" = "WHO CAN FIND ME BY MY NUMBER"; - -"Privacy.PhoneNumber" = "Phone Number"; -"PrivacySettings.PhoneNumber" = "Phone Number"; -"Contacts.SearchUsersAndGroupsLabel" = "Search for users and groups"; - -"PrivacySettings.PasscodeOff" = "Off"; -"PrivacySettings.PasscodeOn" = "On"; - -"UserInfo.BlockConfirmationTitle" = "Do you want to block %@ from messaging and calling you on Telegram?"; -"UserInfo.BlockActionTitle" = "Block %@"; -"ReportSpam.DeleteThisChat" = "Delete this Chat"; - -"PrivacySettings.BlockedPeersEmpty" = "None"; - -"Channel.DiscussionGroup" = "Discussion"; -"Group.LinkedChannel" = "Linked Channel"; -"Channel.DiscussionGroupAdd" = "Add"; -"Channel.DiscussionGroupInfo" = "Add group chat for comments."; -"Channel.DiscussionGroup.Header" = "Select a group chat for discussion that will be displayed in your channel."; -"Channel.DiscussionGroup.HeaderSet" = "A link to %@ is shown to all subscribers in the bottom panel."; -"Channel.DiscussionGroup.HeaderGroupSet" = "%@ is linking the group as it's discussion board."; -"Channel.DiscussionGroup.HeaderLabel" = "Discuss"; -"Channel.DiscussionGroup.Create" = "Create New Group"; -"Channel.DiscussionGroup.PrivateGroup" = "private group"; -"Channel.DiscussionGroup.Info" = "Everything you post in the channel will be forwarded to this group."; -"Channel.DiscussionGroup.LinkGroup" = "Link Group"; -"Channel.DiscussionGroup.UnlinkGroup" = "Unlink Group"; -"Channel.DiscussionGroup.UnlinkChannel" = "Unlink Channel"; -"Channel.DiscussionGroup.PublicChannelLink" = "Do you want to make %1$@ the discussion board for %2$@?"; -"Channel.DiscussionGroup.PrivateChannelLink" = "Do you want to make %1$@ the discussion board for %2$@? - -Any member of this group will be able to see messages in the channel."; -"Channel.DiscussionGroup.MakeHistoryPublic" = "Warning: If you set this private group as the disccussion group for your channel, all channel subscribers will be able to access the group. \"Chat history for new members\" will be switched to Visible."; -"Channel.DiscussionGroup.MakeHistoryPublicProceed" = "Proceed"; - -"Channel.DiscussionGroup.SearchPlaceholder" = "Search"; - -"Channel.AdminLog.MessageChangedLinkedGroup" = "%1$@ made %2$@ the discussion group for this channel."; -"Channel.AdminLog.MessageChangedLinkedChannel" = "%1$@ linked this group to %2$@"; -"Channel.AdminLog.MessageChangedUnlinkedGroup" = "%1$@ removed the discussion group %2$@"; -"Channel.AdminLog.MessageChangedUnlinkedChannel" = "%1$@ unlinked this group from %2$@"; - -"Conversation.OpenBotLinkTitle" = "Open Link"; -"Conversation.OpenBotLinkText" = "Do you want to open\n**%@**?"; -"Conversation.OpenBotLinkLogin" = "Log in to **%1$@** as %2$@"; -"Conversation.OpenBotLinkAllowMessages" = "Allow **%@** to send me messages"; -"Conversation.OpenBotLinkOpen" = "Open"; - -"TextFormat.Link" = "Link"; -"TextFormat.Strikethrough" = "Strikethrough"; -"TextFormat.Underline" = "Underline"; - -"TextFormat.AddLinkTitle" = "Add Link"; -"TextFormat.AddLinkText" = "The link will be displayed as \"%@\"."; -"TextFormat.AddLinkPlaceholder" = "URL"; - -"Channel.AddBotErrorHaveRights" = "Bots can only be added as administrators."; -"Channel.AddBotAsAdmin" = "Make Admin"; -"Channel.AddBotErrorNoRights" = "Sorry, bots can only be added to channels as administrators."; - -"Appearance.AppIcon" = "App Icon"; -"Appearance.AppIconDefault" = "Default"; -"Appearance.AppIconDefaultX" = "Default X"; -"Appearance.AppIconClassic" = "Classic"; -"Appearance.AppIconClassicX" = "Classic X"; -"Appearance.AppIconFilled" = "Filled"; -"Appearance.AppIconFilledX" = "Filled X"; - -"Appearance.ThemeCarouselClassic" = "Classic"; -"Appearance.ThemeCarouselDay" = "Day"; -"Appearance.ThemeCarouselNightBlue" = "Night Blue"; -"Appearance.ThemeCarouselNight" = "Monochrome"; - -"Notification.Exceptions.DeleteAll" = "Delete All"; -"Notification.Exceptions.DeleteAllConfirmation" = "Are you sure you want to delete all exceptions?"; -"Notification.Exceptions.Add" = "Add"; -"Exceptions.AddToExceptions" = "ADD TO EXCEPTIONS"; - -"Notification.Exceptions.NewException.MessagePreviewHeader" = "MESSAGE PREVIEW"; -"Notification.Exceptions.PreviewAlwaysOn" = "Show Preview"; -"Notification.Exceptions.PreviewAlwaysOff" = "Hide Preview"; -"Notification.Exceptions.RemoveFromExceptions" = "Remove from Exceptions"; -"Conversation.Block" = "Block"; -"Conversation.BlockUser" = "Block User"; -"Conversation.ShareMyPhoneNumber" = "Share My Phone Number"; -"Conversation.ShareMyPhoneNumberConfirmation" = "Are you sure you want to share your phone number %1$@ with %2$@?"; -"Conversation.AddToContacts" = "Add to Contacts"; -"Conversation.AddNameToContacts" = "Add %@ to Contacts"; - -"AddContact.ContactWillBeSharedAfterMutual" = "Phone number will be visible once %1$@ adds you as a contact."; -"AddContact.SharedContactException" = "Share My Phone Number"; -"AddContact.SharedContactExceptionInfo" = "You can make your phone visible to %@."; -"AddContact.StatusSuccess" = "%@ is now in your contacts list."; -"Conversation.ShareMyPhoneNumber.StatusSuccess" = "%@ can now see your phone number."; - -"Group.EditAdmin.TransferOwnership" = "Transfer Group Ownership"; -"Channel.EditAdmin.TransferOwnership" = "Transfer Channel Ownership"; - -"OwnershipTransfer.SecurityCheck" = "Security Check"; -"OwnershipTransfer.SecurityRequirements" = "Ownership transfers are available if:\n\n• 2-Step verification was enabled for your account more than **7 days** ago.\n\n• You have logged in on this device more than **24 hours** ago."; -"OwnershipTransfer.ComeBackLater" = "\n\nPlease come back later."; -"OwnershipTransfer.SetupTwoStepAuth" = "Enable 2-Step Verification"; - -"Channel.OwnershipTransfer.Title" = "Transfer Channel Ownership"; -"Channel.OwnershipTransfer.DescriptionInfo" = "This will transfer the full **owner rights** for **%1$@** to **%2$@**.\n\nYou will no longer be considered the creator of the channel. The new owner will be free to remove any of your admin privileges or even ban you."; -"Group.OwnershipTransfer.Title" = "Transfer Group Ownership"; -"Group.OwnershipTransfer.DescriptionInfo" = "This will transfer the full **owner rights** for **%1$@** to **%2$@**.\n\nYou will no longer be considered the creator of the group. The new owner will be free to remove any of your admin privileges or even ban you."; -"Channel.OwnershipTransfer.ChangeOwner" = "Change Owner"; - -"Channel.OwnershipTransfer.ErrorPublicChannelsTooMuch" = "Sorry, the target user has too many public groups or channels already. Please ask them to make one of their existing groups or channels private first."; -"Group.OwnershipTransfer.ErrorLocatedGroupsTooMuch" = "Sorry, the target user has too many location-based groups already. Please ask them to delete or transfer one of their existing ones first."; - -"Group.OwnershipTransfer.ErrorAdminsTooMuch" = "Sorry, this group has too many admins and the new owner can't be added. Please remove one of the existing admins first."; -"Channel.OwnershipTransfer.ErrorAdminsTooMuch" = "Sorry, this channel has too many admins and the new owner can't be added. Please remove one of the existing admins first."; - -"Group.OwnershipTransfer.ErrorPrivacyRestricted" = "Sorry, this user is not a member of this group and their privacy settings prevent you from adding them manually."; -"Channel.OwnershipTransfer.ErrorPrivacyRestricted" = "Sorry, this user is not a member of this channel and their privacy settings prevent you from adding them manually."; - -"Channel.OwnershipTransfer.EnterPassword" = "Enter Password"; -"Channel.OwnershipTransfer.EnterPasswordText" = "Please enter your 2-Step Verification password to complete the transfer."; -"Channel.OwnershipTransfer.PasswordPlaceholder" = "Password"; - -"Channel.OwnershipTransfer.TransferCompleted" = "**%1$@** is now the owner of **%2$@**"; - -"Contacts.AddPeopleNearby" = "Add People Nearby"; - -"PeopleNearby.Title" = "People Nearby"; -"PeopleNearby.Description" = "Ask your friend nearby to open this page to exchange phone numbers."; -"PeopleNearby.Users" = "People Nearby"; -"PeopleNearby.UsersEmpty" = "Looking for users around you..."; -"PeopleNearby.Groups" = "Groups Nearby"; -"PeopleNearby.CreateGroup" = "Create a Group Here"; -"PeopleNearby.NoMembers" = "no members"; - -"Channel.Management.LabelOwner" = "Owner"; -"Channel.Management.LabelAdministrator" = "Administrator"; -"ContactInfo.PhoneNumberHidden" = "Hidden"; - -"Common.ActionNotAllowedError" = "Sorry, you are not allowed to do this."; - -"Group.Location.Title" = "Location"; -"Group.Location.ChangeLocation" = "Change Location"; -"Group.Location.Info" = "People can find your group using People Nearby section."; - -"Channel.AdminLog.MessageTransferedName" = "transferred ownership to %1$@"; -"Channel.AdminLog.MessageTransferedNameUsername" = "transferred ownership to %1$@ (%2$@)"; - -"Channel.AdminLog.MessageChangedGroupGeoLocation" = "changed group location to \"%@\""; - -"Map.SetThisLocation" = "Set This Location"; - -"Permissions.PeopleNearbyTitle.v0" = "People Nearby"; -"Permissions.PeopleNearbyText.v0" = "Use this section to quickly add people near you and discover nearby group chats.\n\nPlease allow location access\nto start using this feature."; -"Permissions.PeopleNearbyAllow.v0" = "Allow Access"; -"Permissions.PeopleNearbyAllowInSettings.v0" = "Allow in Settings"; - -"Conversation.ReportGroupLocation" = "Group unrelated to location?"; -"ReportGroupLocation.Title" = "Report Unrelated Group"; -"ReportGroupLocation.Text" = "Please tell us if this group is not related to this location."; -"ReportGroupLocation.Report" = "Report"; - -"LocalGroup.Title" = "Create a Local Group"; -"LocalGroup.Text" = "Anyone close to this location (neighbors, co-workers, fellow students, event attendees, visitors of a venue) will see your group in the People Nearby section."; -"LocalGroup.ButtonTitle" = "Start Group"; -"LocalGroup.IrrelevantWarning" = "If you start an unrelated group at this location, you may get restricted in creating new location-based groups."; - -"GroupInfo.Location" = "Location"; -"GroupInfo.PublicLink" = "Public Link"; -"GroupInfo.PublicLinkAdd" = "Add"; - -"Group.PublicLink.Title" = "Public Link"; -"Group.PublicLink.Placeholder" = "link"; -"Group.PublicLink.Info" = "People can share this link with others and find your group using Telegram search.\n\nYou can use **a-z**, **0-9** and underscores. Minimum length is **5** characters."; - -"CreateGroup.ErrorLocatedGroupsTooMuch" = "Sorry, you have too many location-based groups already. Please delete one of your existing ones first."; - -"GroupInfo.LabelOwner" = "owner"; - -"Activity.RemindAboutGroup" = "Send message to %@"; -"Activity.RemindAboutUser" = "Send message to %@"; -"Activity.RemindAboutChannel" = "Read %@"; - -"CreateGroup.ChannelsTooMuch" = "Sorry, you are a member of too many groups and channels. Please leave some before creating a new one."; -"Join.ChannelsTooMuch" = "Sorry, you are a member of too many groups and channels. Please leave some before joining one."; -"Invite.ChannelsTooMuch" = "Sorry, the target user is a member of too many groups and channels. Please ask them to leave some first."; - -"Appearance.TintAllColors" = "Tint All Colors"; - -"Contacts.DeselectAll" = "Deselect All"; - -"Channel.TooMuchBots" = "Sorry, there are already too many bots in this group. Please remove some of the bots you're not using first."; -"Channel.BotDoesntSupportGroups" = "Sorry, this bot is telling us it doesn't want to be added to groups. You can't add this bot unless its developers change their mind."; - -"StickerPacksSettings.AnimatedStickers" = "Loop Animated Stickers"; -"StickerPacksSettings.AnimatedStickersInfo" = "Animated stickers will play in chat continuously."; -"GroupInfo.Permissions.SlowmodeHeader" = "SLOWMODE"; -"GroupInfo.Permissions.SlowmodeInfo" = "Members will be restricted to send one message per this interval."; -"Channel.AdminLog.DisabledSlowmode" = "%@ disabled slowmode"; -"Channel.AdminLog.SetSlowmode" = "%1$@ set slowmode to %2$@"; - -"GroupInfo.Permissions.EditingDisabled" = "You cannot edit this permission."; - -"Chat.SlowmodeTooltip" = "Slowmode is enabled. You can send\nyour next message in %@."; -"Chat.SlowmodeTooltipPending" = "Slowmode is enabled. You can't send more than one message at once."; -"Chat.AttachmentLimitReached" = "You can't select more items."; -"Chat.SlowmodeAttachmentLimitReached" = "Slowmode is enabled. You can't select more items."; -"Chat.AttachmentMultipleFilesDisabled" = "Slowmode is enabled. You can't send multiple files at once."; -"Chat.AttachmentMultipleForwardDisabled" = "Slowmode is enabled. You can't forward multiple messages at once."; -"Chat.MultipleTextMessagesDisabled" = "Slowmode is enabled. You can't send multiple messages at once."; -"Share.MultipleMessagesDisabled" = "Slowmode is enabled. You can't send multiple messages at once."; -"Chat.SlowmodeSendError" = "Slowmode is enabled."; -"StickerPacksSettings.AnimatedStickersInfo" = "Animated stickers in a chat will play continuously."; - -"Conversation.Owner" = "owner"; - -"Group.EditAdmin.RankTitle" = "CUSTOM TITLE"; -"Group.EditAdmin.RankInfo" = "A title that will be shown instead of '%@'."; -"Group.EditAdmin.RankOwnerPlaceholder" = "owner"; -"Group.EditAdmin.RankAdminPlaceholder" = "admin"; - -"Conversation.SendMessage.SendSilently" = "Send Without Sound"; -"Conversation.SendMessage.ScheduleMessage" = "Schedule Message"; - -"Appearance.ThemeCarouselTintedNight" = "Tinted Night"; -"Appearance.ThemeCarouselNewNight" = "Night"; - -"Channel.AdminLog.MessageRankName" = "changed custom title for %1$@:\n%2$@"; -"Channel.AdminLog.MessageRankUsername" = "changed custom title for %1$@ (%2$@):\n%3$@"; -"Channel.AdminLog.MessageRank" = "changed custom title:\n%1$@"; - -"VoiceOver.Editing.ClearText" = "Clear text"; -"VoiceOver.Recording.StopAndPreview" = "Stop and preview"; -"VoiceOver.Media.PlaybackRate" = "Playback rate"; -"VoiceOver.Media.PlaybackRateNormal" = "Normal"; -"VoiceOver.Media.PlaybackRateFast" = "Fast"; -"VoiceOver.Media.PlaybackRateChange" = "Double tap to change"; -"VoiceOver.Media.PlaybackStop" = "Stop playback"; -"VoiceOver.Media.PlaybackPlay" = "Play"; -"VoiceOver.Media.PlaybackPause" = "Pause"; -"VoiceOver.Navigation.Compose" = "Compose"; -"VoiceOver.Navigation.Search" = "Search"; -"VoiceOver.Navigation.ProxySettings" = "Proxy settings"; -"VoiceOver.DiscardPreparedContent" = "Discard"; -"VoiceOver.AttachMedia" = "Send media"; -"VoiceOver.Chat.RecordPreviewVoiceMessage" = "Preview voice message"; -"VoiceOver.Chat.RecordModeVoiceMessage" = "Voice message"; -"VoiceOver.Chat.RecordModeVoiceMessageInfo" = "Double tap and hold to record voice message. Slide up to pin recording, slide left to cancel. Double tap to switch to video."; -"VoiceOver.Chat.RecordModeVideoMessage" = "Video message"; -"VoiceOver.Chat.RecordModeVideoMessageInfo" = "Double tap and hold to record video message. Slide up to pin recording, slide left to cancel. Double tap to switch to audio."; -"VoiceOver.Chat.Message" = "Message"; -"VoiceOver.Chat.YourMessage" = "Your message"; -"VoiceOver.Chat.ReplyFrom" = "Reply to message from: %@"; -"VoiceOver.Chat.Reply" = "Reply to message"; -"VoiceOver.Chat.ReplyToYourMessage" = "Reply to your message"; -"VoiceOver.Chat.ForwardedFrom" = "Forwarded from: %@"; -"VoiceOver.Chat.ForwardedFromYou" = "Forwarded from you"; -"VoiceOver.Chat.PhotoFrom" = "Photo, from: %@"; -"VoiceOver.Chat.Photo" = "Photo"; -"VoiceOver.Chat.YourPhoto" = "Your photo"; -"VoiceOver.Chat.VoiceMessageFrom" = "Voice message, from: %@"; -"VoiceOver.Chat.VoiceMessage" = "Voice message"; -"VoiceOver.Chat.YourVoiceMessage" = "Your voice message"; -"VoiceOver.Chat.MusicFrom" = "Music file, from: %@"; -"VoiceOver.Chat.Music" = "Music message"; -"VoiceOver.Chat.YourMusic" = "Your music message"; -"VoiceOver.Chat.VideoFrom" = "Video, from: %@"; -"VoiceOver.Chat.Video" = "Video"; -"VoiceOver.Chat.YourVideo" = "Your video"; -"VoiceOver.Chat.VideoMessageFrom" = "Video message, from: %@"; -"VoiceOver.Chat.VideoMessage" = "Video message"; -"VoiceOver.Chat.YourVideoMessage" = "Your video message"; -"VoiceOver.Chat.FileFrom" = "File, from: %@"; -"VoiceOver.Chat.File" = "File"; -"VoiceOver.Chat.YourFile" = "Your file"; -"VoiceOver.Chat.ContactFrom" = "Shared contact, from: %@"; -"VoiceOver.Chat.Contact" = "Shared contact"; -"VoiceOver.Chat.ContactPhoneNumberCount_1" = "%@ phone number"; -"VoiceOver.Chat.ContactPhoneNumberCount_any" = "%@ phone numbers"; -"VoiceOver.Chat.ContactPhoneNumber" = "Phone number"; -"VoiceOver.Chat.ContactEmailCount_1" = "%@ email address"; -"VoiceOver.Chat.ContactEmailCount_any" = "%@ email addresses"; -"VoiceOver.Chat.ContactEmail" = "Email"; -"VoiceOver.Chat.ContactOrganization" = "Organization: %@"; -"VoiceOver.Chat.YourContact" = "Your shared contact"; -"VoiceOver.Chat.AnonymousPollFrom" = "Anonymous poll, from: %@"; -"VoiceOver.Chat.AnonymousPoll" = "Anonymous poll"; -"VoiceOver.Chat.YourAnonymousPoll" = "Your Anonymous poll"; -"VoiceOver.Chat.PollOptionCount_1" = "%@ option:"; -"VoiceOver.Chat.PollOptionCount_any" = "%@ options:"; -"VoiceOver.Chat.PollVotes_1" = "%@ vote"; -"VoiceOver.Chat.PollVotes_any" = "%@ votes"; -"VoiceOver.Chat.PollNoVotes" = "No votes"; -"VoiceOver.Chat.PollFinalResults" = "Final results"; -"VoiceOver.Chat.OptionSelected" = "selected"; -"VoiceOver.Chat.PagePreview" = "Page preview"; -"VoiceOver.Chat.Title" = "Title: %@"; -"VoiceOver.Chat.Caption" = "Caption: %@"; -"VoiceOver.Chat.Duration" = "Duration: %@"; -"VoiceOver.Chat.Size" = "Size: %@"; -"VoiceOver.Chat.MusicTitle" = "%1$@, by %2$@"; -"VoiceOver.Chat.PlayHint" = "Double tap to play"; -"VoiceOver.Chat.OpenHint" = "Double tap to open"; -"VoiceOver.Chat.OpenLinkHint" = "Double tap to open link"; -"VoiceOver.Chat.SeenByRecipient" = "Seen by recipient"; -"VoiceOver.Chat.SeenByRecipients" = "Seen by recipients"; -"VoiceOver.Chat.Selected" = "Selected"; -"VoiceOver.MessageContextDelete" = "Delete"; -"VoiceOver.MessageContextReport" = "Report"; -"VoiceOver.MessageContextForward" = "Forward"; -"VoiceOver.MessageContextShare" = "Share"; -"VoiceOver.MessageContextSend" = "Send"; -"VoiceOver.MessageContextReply" = "Reply"; -"VoiceOver.MessageContextOpenMessageMenu" = "Open message menu"; - -"ProxyServer.VoiceOver.Active" = "Active"; - -"Conversation.ScheduleMessage.Title" = "Schedule Message"; -"Conversation.ScheduleMessage.SendToday" = "Send today at %@"; -"Conversation.ScheduleMessage.SendTomorrow" = "Send tomorrow at %@"; -"Conversation.ScheduleMessage.SendOn" = "Send on %@ at %@"; - -"Conversation.SetReminder.Title" = "Set a Reminder"; -"Conversation.SetReminder.RemindToday" = "Remind today at %@"; -"Conversation.SetReminder.RemindTomorrow" = "Remind tomorrow at %@"; -"Conversation.SetReminder.RemindOn" = "Remind on %@ at %@"; - -"ScheduledMessages.Title" = "Scheduled Messages"; -"ScheduledMessages.RemindersTitle" = "Reminders"; -"ScheduledMessages.ScheduledDate" = "Scheduled for %@"; -"ScheduledMessages.ScheduledToday" = "Scheduled for today"; -"ScheduledMessages.SendNow" = "Send Now"; -"ScheduledMessages.EditTime" = "Reschedule"; -"ScheduledMessages.ClearAll" = "Clear All"; -"ScheduledMessages.ClearAllConfirmation" = "Clear Scheduled Messages"; -"ScheduledMessages.Delete" = "Delete Scheduled Message"; -"ScheduledMessages.DeleteMany" = "Delete Scheduled Messages"; -"ScheduledMessages.EmptyPlaceholder" = "No scheduled messages here yet..."; -"ScheduledMessages.BotActionUnavailable" = "This action will become available after the message is published."; -"ScheduledMessages.PollUnavailable" = "Voting will become available after the message is published."; -"ScheduledMessages.ReminderNotification" = "📅 Reminder"; - -"Conversation.SendMessage.SetReminder" = "Set a Reminder"; - -"Conversation.SelectedMessages_1" = "%@ Selected"; -"Conversation.SelectedMessages_2" = "%@ Selected"; -"Conversation.SelectedMessages_3_10" = "%@ Selected"; -"Conversation.SelectedMessages_any" = "%@ Selected"; -"Conversation.SelectedMessages_many" = "%@ Selected"; -"Conversation.SelectedMessages_0" = "%@ Selected"; - -"AccentColor.Title" = "Accent Color"; - -"Appearance.ThemePreview.ChatList.1.Name" = "Alicia Torreaux"; -"Appearance.ThemePreview.ChatList.1.Text" = "Bob says hi. 😊 ❤️ 😱"; -"Appearance.ThemePreview.ChatList.2.Name" = "Roberto"; -"Appearance.ThemePreview.ChatList.2.Text" = "Say hello to Alice 👋"; -"Appearance.ThemePreview.ChatList.3.Name" = "Campus Public Chat"; -"Appearance.ThemePreview.ChatList.3.AuthorName" = "Jennie Alpha"; -"Appearance.ThemePreview.ChatList.3.Text" = "We just reached 2,500 members! WOO!"; -"Appearance.ThemePreview.ChatList.4.Name" = "Veronica"; -"Appearance.ThemePreview.ChatList.4.Text" = "Table for four, 2PM. Be there."; -"Appearance.ThemePreview.ChatList.5.Name" = "Animal Videos"; -"Appearance.ThemePreview.ChatList.5.Text" = "Vote now! Moar cat videos in this channel?"; -"Appearance.ThemePreview.ChatList.6.Name" = "Little Sister"; -"Appearance.ThemePreview.ChatList.6.Text" = "Don't tell mom yet, but I got the job! I'm going to ROME!"; -"Appearance.ThemePreview.ChatList.7.Name" = "Jennie Alpha"; -"Appearance.ThemePreview.ChatList.7.Text" = "🖼 Check these out"; - -"Appearance.ThemePreview.Chat.1.Text" = "Does he want me to, to turn from the right or turn from the left? 🤔"; -"Appearance.ThemePreview.Chat.2.ReplyName" = "Bob Harris"; -"Appearance.ThemePreview.Chat.2.Text" = "Right side. And, uh, with intensity."; -"Appearance.ThemePreview.Chat.3.Text" = "Is that everything? It seemed like he said quite a bit more than that. 😯"; -"Appearance.ThemePreview.Chat.3.TextWithLink" = "Is that everything? It seemed like he said [quite a bit more] than that. 😯"; - -"Appearance.ThemePreview.Chat.4.Text" = "For relaxing times, make it Suntory time. 😎"; -"Appearance.ThemePreview.Chat.5.Text" = "He wants you to turn, look in camera. O.K.?"; -"Appearance.ThemePreview.Chat.6.Text" = "That’s all he said?"; -"Appearance.ThemePreview.Chat.7.Text" = "Yes, turn to camera."; - -"GroupInfo.Permissions.SlowmodeValue.Off" = "Off"; - -"Undo.ScheduledMessagesCleared" = "Scheduled messages cleared"; - -"Appearance.CreateTheme" = "Create New Theme"; -"Appearance.EditTheme" = "Edit Theme"; -"Appearance.ShareTheme" = "Share"; -"Appearance.RemoveTheme" = "Remove"; -"Appearance.RemoveThemeConfirmation" = "Remove Theme"; - -"Conversation.Theme" = "Color Theme"; -"Conversation.ViewTheme" = "VIEW THEME"; - -"Message.Theme" = "Color Theme"; - -"EditTheme.CreateTitle" = "Create Theme"; -"EditTheme.EditTitle" = "Edit Theme"; -"EditTheme.Title" = "Theme Name"; -"EditTheme.ShortLink" = "link"; -"EditTheme.Preview" = "CHAT PREVIEW"; -"EditTheme.UploadNewTheme" = "Create from File..."; -"EditTheme.UploadEditedTheme" = "Update from File..."; -"EditTheme.ThemeTemplateAlertTitle" = "New Theme Added"; -"EditTheme.ThemeTemplateAlertText" = "Press and hold on your theme to edit it or get a sharing link. Users who install your theme will get automatic updates each time you change it.\n\nFor advanced editing purposes, you can find a file with your theme in Saved Messages."; -"EditTheme.FileReadError" = "Invalid theme file"; - -"EditTheme.Create.TopInfo" = "The theme will be based on your currently selected colors and wallpaper."; -"EditTheme.Create.BottomInfo" = "You can also use a manually edited custom theme file."; - -"EditTheme.Expand.TopInfo" = "The theme will be based on your currently selected colors and wallpaper."; -"EditTheme.Expand.BottomInfo" = "You can also use a manually edited custom theme file."; - -"EditTheme.Edit.TopInfo" = "Your theme will be updated for all users each time you change it. Anyone can install it using this link.\n\nTheme links must be at least **5** characters long and can use **a-z**, **0-9** and underscores."; -"EditTheme.Edit.BottomInfo" = "You can select a new file to update the theme. It will be updated for all users."; - -"EditTheme.Create.Preview.IncomingReplyName" = "Bob"; -"EditTheme.Create.Preview.IncomingReplyText" = "How does it work?"; -"EditTheme.Create.Preview.IncomingText" = "Use your current colors"; -"EditTheme.Create.Preview.OutgoingText" = "Or upload a theme file"; - -"EditTheme.Expand.Preview.IncomingReplyName" = "Bob"; -"EditTheme.Expand.Preview.IncomingReplyText" = "How does it work?"; -"EditTheme.Expand.Preview.IncomingText" = "Use your current colors"; -"EditTheme.Expand.Preview.OutgoingText" = "Or upload a theme file"; - -"EditTheme.Edit.Preview.IncomingReplyName" = "Bob"; -"EditTheme.Edit.Preview.IncomingReplyText" = "How does it work?"; -"EditTheme.Edit.Preview.IncomingText" = "Use your current colors"; -"EditTheme.Edit.Preview.OutgoingText" = "Or upload a theme file"; - -"EditTheme.ErrorLinkTaken" = "Sorry, this link is already taken"; -"EditTheme.ErrorInvalidCharacters" = "Sorry, this link is invalid."; - -"Wallpaper.ErrorNotFound" = "Sorry, this chat background doesn't seem to exist."; -"Theme.ErrorNotFound" = "Sorry, this color theme doesn't seem to exist."; -"Theme.Unsupported" = "Sorry, this color theme doesn't support your device yet."; - -"Theme.UsersCount_1" = "%@ person is using this theme"; -"Theme.UsersCount_2" = "%@ people are using this theme"; -"Theme.UsersCount_3_10" = "%@ people are using this theme"; -"Theme.UsersCount_any" = "%@ people are using this theme"; -"Theme.UsersCount_many" = "%@ people are using this theme"; -"Theme.UsersCount_0" = "%@ people are using this theme"; - -"Conversation.SendMessageErrorTooMuchScheduled" = "Sorry, you can not schedule more than 100 messages."; - -"ChatList.Context.MarkAllAsRead" = "Mark All as Read"; -"ChatList.Context.HideArchive" = "Hide Above the List"; -"ChatList.Context.UnhideArchive" = "Pin in the list"; -"ChatList.Context.RemoveFromRecents" = "Clear from Recents"; -"ChatList.Context.AddToContacts" = "Add to Contacts"; -"ChatList.Context.MarkAsRead" = "Mark as Read"; -"ChatList.Context.MarkAsUnread" = "Mark as Unread"; -"ChatList.Context.Archive" = "Archive"; -"ChatList.Context.Unarchive" = "Unarchive"; -"ChatList.Context.Pin" = "Pin"; -"ChatList.Context.Unpin" = "Unpin"; -"ChatList.Context.Mute" = "Mute"; -"ChatList.Context.Unmute" = "Unmute"; -"ChatList.Context.JoinChannel" = "Join Channel"; -"ChatList.Context.Delete" = "Delete"; - -"ContactList.Context.SendMessage" = "Send Message"; -"ContactList.Context.StartSecretChat" = "Start Secret Chat"; -"ContactList.Context.Call" = "Call"; -"ContactList.Context.VideoCall" = "Video Call"; - -"Theme.Context.Apply" = "Apply"; - -"Settings.Context.Logout" = "Logout"; - -"Channel.EditAdmin.PermissionDeleteMessagesOfOthers" = "Delete Messages of Others"; -"Channel.AdminLog.CanDeleteMessagesOfOthers" = "Delete Messages of Others"; - -"ChatSearch.ResultsTooltip" = "Tap to view as a list."; - -"Wallet.Updated.JustNow" = "updated just now"; -"Wallet.Updated.MinutesAgo_0" = "%@ minutes ago"; //three to ten -"Wallet.Updated.MinutesAgo_1" = "1 minute ago"; //one -"Wallet.Updated.MinutesAgo_2" = "2 minutes ago"; //two -"Wallet.Updated.MinutesAgo_3_10" = "%@ minutes ago"; //three to ten -"Wallet.Updated.MinutesAgo_many" = "%@ minutes ago"; // more than ten -"Wallet.Updated.MinutesAgo_any" = "%@ minutes ago"; // more than ten -"Wallet.Updated.HoursAgo_0" = "%@ hours ago"; -"Wallet.Updated.HoursAgo_1" = "1 hour ago"; -"Wallet.Updated.HoursAgo_2" = "2 hours ago"; -"Wallet.Updated.HoursAgo_3_10" = "%@ hours ago"; -"Wallet.Updated.HoursAgo_any" = "%@ hours ago"; -"Wallet.Updated.HoursAgo_many" = "%@ hours ago"; -"Wallet.Updated.HoursAgo_0" = "%@ hours ago"; -"Wallet.Updated.YesterdayAt" = "yesterday at %@"; -"Wallet.Updated.AtDate" = "%@"; -"Wallet.Updated.TodayAt" = "today at %@"; - -"Wallet.Info.WalletCreated" = "Wallet Created"; -"Wallet.Info.Address" = "Your wallet address"; -"Wallet.Info.YourBalance" = "your balance"; -"Wallet.Info.Receive" = "Receive"; -"Wallet.Info.ReceiveGrams" = "Receive Grams"; -"Wallet.Info.Send" = "Send"; -"Wallet.Info.RefreshErrorTitle" = "No network"; -"Wallet.Info.RefreshErrorText" = "Couldn't refresh balance. Please make sure your internet connection is working and try again."; -"Wallet.Info.RefreshErrorNetworkText" = "Wallet state can not be retrieved at this time. Please try again later."; -"Wallet.Info.UnknownTransaction" = "Empty Transaction"; -"Wallet.Info.TransactionTo" = "to"; -"Wallet.Info.TransactionFrom" = "from"; -"Wallet.Info.Updating" = "updating"; -"Wallet.Info.TransactionBlockchainFee" = "%@ blockchain fees"; -"Wallet.Info.TransactionPendingHeader" = "Pending"; -"Wallet.Qr.ScanCode" = "Scan QR Code"; -"Wallet.Qr.Title" = "QR Code"; -"Wallet.Receive.Title" = "Receive Grams"; -"Wallet.Receive.AddressHeader" = "YOUR WALLET ADDRESS"; -"Wallet.Receive.InvoiceUrlHeader" = "INVOICE URL"; -"Wallet.Receive.CopyAddress" = "Copy Wallet Address"; -"Wallet.Receive.CopyInvoiceUrl" = "Copy Invoice URL"; -"Wallet.Receive.ShareAddress" = "Share Wallet Address"; -"Wallet.Receive.ShareInvoiceUrl" = "Share Invoice URL"; -"Wallet.Receive.ShareUrlInfo" = "Share this link with other Gram wallet owners to receive Grams from them. Note: this link won't work for real Grams."; -"Wallet.Receive.AmountHeader" = "AMOUNT"; -"Wallet.Receive.AmountText" = "Grams to receive"; -"Wallet.Receive.AmountInfo" = "You can specify the amount and purpose of the payment to save the sender some time."; -"Wallet.Receive.CommentHeader" = "COMMENT (OPTIONAL)"; -"Wallet.Receive.CommentInfo" = "Description of the payment"; -"Wallet.Receive.AddressCopied" = "Address copied to clipboard."; -"Wallet.Receive.InvoiceUrlCopied" = "Invoice URL copied to clipboard."; -"Wallet.Send.Title" = "Send Grams"; -"Wallet.Send.AddressHeader" = "RECIPIENT WALLET ADDRESS"; -"Wallet.Send.AddressText" = "Enter wallet address..."; -"Wallet.Send.AddressInfo" = "Paste the 48-letter address of the recipient here or ask them to send you a ton:// link."; -"Wallet.Send.Balance" = "Balance: %@"; -"Wallet.Send.AmountText" = "Grams to send"; -"Wallet.Send.Confirmation" = "Confirmation"; -"Wallet.Send.ConfirmationText" = "Do you want to send **%1$@** Grams to\n\n%2$@?\n\nBlockchain fees: ~%3$@ grams"; -"Wallet.Send.ConfirmationConfirm" = "Confirm"; -"Wallet.Send.Send" = "Send"; -"Wallet.Send.OwnAddressAlertTitle" = "Warning"; -"Wallet.Send.OwnAddressAlertText" = "Sending Grams from a wallet to the same wallet doesn't make sense, you will simply waste a portion of the value on blockchain fees."; -"Wallet.Send.OwnAddressAlertProceed" = "Proceed"; -"Wallet.Send.TransactionInProgress" = "Please wait until the current transaction is completed."; -"Wallet.Send.SyncInProgress" = "Please wait while the wallet finishes syncing with the TON Blockchain."; -"Wallet.Send.EncryptComment" = "Encrypt Text"; -"Wallet.Settings.Title" = "Settings"; -"Wallet.Settings.Configuration" = "Server Settings"; -"Wallet.Settings.ConfigurationInfo" = "Advanced Settings"; -"Wallet.Settings.BackupWallet" = "Backup Wallet"; -"Wallet.Settings.DeleteWallet" = "Delete Wallet"; -"Wallet.Settings.DeleteWalletInfo" = "This will disconnect the wallet from this app. You will be able to restore your wallet using 24 secret words – or import another wallet.\n\nGram Wallets are located in the decentralized TON Blockchain. If you want a wallet to be deleted, simply transfer all the grams from it and leave it empty."; -"Wallet.Intro.NotNow" = "Not Now"; -"Wallet.Intro.ImportExisting" = "Import existing wallet"; -"Wallet.Intro.CreateErrorTitle" = "An Error Occurred"; -"Wallet.Intro.CreateErrorText" = "Sorry. Please try again."; -"Wallet.Intro.Title" = "Gram Wallet"; -"AppWallet.Intro.Text" = "The gram wallet allows you to make fast and secure blockchain-based payments without intermediaries."; -"Wallet.Intro.Text" = "Gram wallet allows you to make fast and secure blockchain-based payments without intermediaries."; -"Wallet.Intro.CreateWallet" = "Create My Wallet"; -"Wallet.Intro.Terms" = "By creating a wallet you accept the\n[Terms of Conditions]()."; -"TelegramWallet.Intro.TermsUrl" = "https://telegram.org/tos/wallet"; -"Wallet.Created.Title" = "Congratulations"; -"Wallet.Created.Text" = "Your Gram wallet has just been created. Only you control it.\n\nTo be able to always have access to it, please write down your secret words and\nset up a secure passcode."; -"Wallet.Created.Proceed" = "Proceed"; -"Wallet.Created.ExportErrorTitle" = "Error"; -"Wallet.Created.ExportErrorText" = "Encryption error. Please make sure you have enabled a device passcode in iOS settings and try again."; -"Wallet.Completed.Title" = "Ready to go!"; -"Wallet.Completed.Text" = "You’re all set. Now you have a wallet that only you control - directly, without middlemen or bankers."; -"Wallet.Completed.ViewWallet" = "View My Wallet"; -"Wallet.RestoreFailed.Title" = "Too Bad"; -"Wallet.RestoreFailed.Text" = "Without the secret words, you can't\nrestore access to your wallet."; -"Wallet.RestoreFailed.CreateWallet" = "Create a New Wallet"; -"Wallet.RestoreFailed.EnterWords" = "Enter 24 words"; -"Wallet.Sending.Title" = "Sending Grams"; -"Wallet.Sending.Text" = "Please wait a few seconds for your transaction to be processed..."; -"Wallet.Sending.Title" = "Sending Grams"; -"Wallet.Sent.Title" = "Done!"; -"Wallet.Sent.Text" = "**%@ Grams** have been sent."; -"Wallet.Sent.ViewWallet" = "View My Wallet"; -"Wallet.SecureStorageNotAvailable.Title" = "Set a Passcode"; -"Wallet.SecureStorageNotAvailable.Text" = "Please set up a Passcode on your device to enable secure payments with your Gram wallet."; -"Wallet.SecureStorageReset.Title" = "Security Settings Have Changed"; -"Wallet.SecureStorageReset.BiometryTouchId" = "Touch ID"; -"Wallet.SecureStorageReset.BiometryFaceId" = "Face ID"; -"Wallet.SecureStorageReset.BiometryText" = "Unfortunately, your wallet is no longer available because your system Passcode or %@ has been turned off. Please enable them before proceeding."; -"Wallet.SecureStorageReset.PasscodeText" = "Unfortunately, your wallet is no longer available because your system Passcode has been turned off. Please enable it before proceeding."; -"Wallet.SecureStorageChanged.BiometryText" = "Unfortunately, your wallet is no longer available due to the change in your system security settings (Passcode/%@). To restore your wallet, tap \"Import existing wallet\"."; -"Wallet.SecureStorageChanged.PasscodeText" = "Unfortunately, your wallet is no longer available due to the change in your system security settings (Passcode). To restore your wallet, tap \"Import existing wallet\"."; -"Wallet.SecureStorageChanged.ImportWallet" = "Import Existing Wallet"; -"Wallet.SecureStorageChanged.CreateWallet" = "Create New Wallet"; -"Wallet.TransactionInfo.Title" = "Transaction"; -"Wallet.TransactionInfo.NoAddress" = "No Address"; -"Wallet.TransactionInfo.RecipientHeader" = "RECIPIENT"; -"Wallet.TransactionInfo.SenderHeader" = "SENDER"; -"Wallet.TransactionInfo.CopyAddress" = "Copy Wallet Address"; -"Wallet.TransactionInfo.AddressCopied" = "Address copied to clipboard."; -"Wallet.TransactionInfo.SendGrams" = "Send Grams to This Address"; -"Wallet.TransactionInfo.CommentHeader" = "COMMENT"; -"Wallet.TransactionInfo.StorageFeeHeader" = "STORAGE FEE"; -"Wallet.TransactionInfo.OtherFeeHeader" = "TRANSACTION FEE"; -"Wallet.TransactionInfo.StorageFeeInfo" = "Blockchain validators collect a tiny fee for storing information about your decentralized wallet and processing your transactions."; -"Wallet.TransactionInfo.StorageFeeInfoUrl" = "Blockchain validators collect a tiny fee for storing information about your decentralized wallet and processing your transactions. [More info]()"; -"Wallet.TransactionInfo.OtherFeeInfo" = "Blockchain validators collect a tiny fee for processing your decentralized transactions."; -"Wallet.TransactionInfo.OtherFeeInfoUrl" = "Blockchain validators collect a tiny fee for processing your decentralized transactions. [More info]()"; -"AppWallet.TransactionInfo.FeeInfoURL" = "https://telegram.org/wallet/fee"; -"Wallet.WordCheck.Title" = "Test Time!"; -"Wallet.WordCheck.Text" = "Let’s check that you wrote them down correctly. Please enter the words\n**%1$@**, **%2$@** and **%3$@**"; -"Wallet.WordCheck.Continue" = "Continue"; -"Wallet.WordCheck.IncorrectHeader" = "Incorrect words!"; -"Wallet.WordCheck.IncorrectText" = "The secret words you have entered do not match the ones in the list."; -"Wallet.WordCheck.TryAgain" = "Try Again"; -"Wallet.WordCheck.ViewWords" = "View Words"; -"Wallet.WordImport.Title" = "24 Secret Words"; -"Wallet.WordImport.Text" = "Please restore access to your wallet by\nentering the 24 secret words you wrote down when creating the wallet."; -"Wallet.WordImport.Continue" = "Continue"; -"Wallet.WordImport.CanNotRemember" = "I don't have them"; -"Wallet.WordImport.IncorrectTitle" = "Incorrect words"; -"Wallet.WordImport.IncorrectText" = "Sorry, you have entered incorrect secret words. Please double check and try again."; -"Wallet.Words.Title" = "24 Secret Words"; -"Wallet.Words.Text" = "Write down these 24 words in the correct order and store them in a secret place.\n\nUse these secret words to restore access to your wallet if you lose your passcode or device."; -"Wallet.Words.Done" = "Done"; -"Wallet.Words.NotDoneTitle" = "Sure Done?"; -"Wallet.Words.NotDoneText" = "You didn't have enough time to write those words down."; -"Wallet.Words.NotDoneOk" = "OK, Sorry"; -"Wallet.Words.NotDoneResponse" = "Apologies Accepted"; -"Wallet.Send.NetworkErrorTitle" = "No network"; -"Wallet.Send.NetworkErrorText" = "Couldn't send grams. Please make sure your internet connection is working and try again."; -"Wallet.Send.ErrorNotEnoughFundsTitle" = "Insufficient Grams"; -"Wallet.Send.ErrorNotEnoughFundsText" = "Unfortunately, your transfer couldn't be completed. You don't have enough grams."; -"Wallet.Send.ErrorInvalidAddress" = "Invalid wallet address. Please correct and try again."; -"Wallet.Send.ErrorDecryptionFailed" = "Please make sure that your device has a passcode set in iOS Settings and try again."; -"Wallet.Send.UninitializedTitle" = "Warning"; -"Wallet.Send.UninitializedText" = "This address belongs to an empty wallet. Are you sure you want to transfer grams to it?"; -"Wallet.Send.SendAnyway" = "Send Anyway"; -"Wallet.Receive.CreateInvoice" = "Create Invoice"; -"Wallet.Receive.CreateInvoiceInfo" = "You can specify the amount and purpose of the payment to save the sender some time."; - -"Conversation.WalletRequiredTitle" = "Gram Wallet Required"; -"Conversation.WalletRequiredText" = "This link can be used to send money on the TON Blockchain. To do this, you need to set up a Gram wallet first."; -"Conversation.WalletRequiredNotNow" = "Not Now"; -"Conversation.WalletRequiredSetup" = "Set Up"; - -"Wallet.Configuration.Title" = "Server Settings"; -"Wallet.Configuration.Apply" = "Save"; -"Wallet.Configuration.SourceHeader" = "SOURCE"; -"Wallet.Configuration.SourceURL" = "URL"; -"Wallet.Configuration.SourceJSON" = "JSON"; -"Wallet.Configuration.SourceInfo" = "Using a different configuration allows you to change Lite Server addresses."; -"Wallet.Configuration.BlockchainIdHeader" = "BLOCKCHAIN ID"; -"Wallet.Configuration.BlockchainIdPlaceholder" = "Blockchain ID"; -"Wallet.Configuration.BlockchainIdInfo" = "This setting is for developers. Change it only if you are working on creating your own TON network."; -"Wallet.Configuration.ApplyErrorTitle" = "Error"; -"Wallet.Configuration.ApplyErrorTextURLInvalid" = "The URL you have entered is invalid. Please try again."; -"Wallet.Configuration.ApplyErrorTextURLUnreachable" = "There was an error while downloading configuration from %@\nPlease try again."; -"Wallet.Configuration.ApplyErrorTextURLInvalidData" = "This blockchain configuration is invalid. Please try again."; -"Wallet.Configuration.ApplyErrorTextJSONInvalidData" = "This blockchain configuration is invalid. Please try again."; -"Wallet.Configuration.BlockchainNameChangedTitle" = "Warning"; -"Wallet.Configuration.BlockchainNameChangedText" = "Are you sure you want to change the blockchain ID? You don't need this unless you're testing your own TON network.\n\nIf you proceed, you will need to reconnect your wallet using 24 secret words."; -"Wallet.Configuration.BlockchainNameChangedProceed" = "Proceed"; - -"Wallet.CreateInvoice.Title" = "Create Invoice"; - -"Wallet.Navigation.Close" = "Close"; -"Wallet.Navigation.Back" = "Back"; -"Wallet.Navigation.Done" = "Done"; -"Wallet.Navigation.Cancel" = "Cancel"; -"Wallet.Alert.OK" = "OK"; -"Wallet.Alert.Cancel" = "Cancel"; - -"Wallet.Month.GenJanuary" = "January"; -"Wallet.Month.GenFebruary" = "February"; -"Wallet.Month.GenMarch" = "March"; -"Wallet.Month.GenApril" = "April"; -"Wallet.Month.GenMay" = "May"; -"Wallet.Month.GenJune" = "June"; -"Wallet.Month.GenJuly" = "July"; -"Wallet.Month.GenAugust" = "August"; -"Wallet.Month.GenSeptember" = "September"; -"Wallet.Month.GenOctober" = "October"; -"Wallet.Month.GenNovember" = "November"; -"Wallet.Month.GenDecember" = "December"; -"Wallet.Month.ShortJanuary" = "Jan"; -"Wallet.Month.ShortFebruary" = "Feb"; -"Wallet.Month.ShortMarch" = "Mar"; -"Wallet.Month.ShortApril" = "Apr"; -"Wallet.Month.ShortMay" = "May"; -"Wallet.Month.ShortJune" = "Jun"; -"Wallet.Month.ShortJuly" = "Jul"; -"Wallet.Month.ShortAugust" = "Aug"; -"Wallet.Month.ShortSeptember" = "Sep"; -"Wallet.Month.ShortOctober" = "Oct"; -"Wallet.Month.ShortNovember" = "Nov"; -"Wallet.Month.ShortDecember" = "Dec"; - -"Wallet.Weekday.Today" = "Today"; -"Wallet.Weekday.Yesterday" = "Yesterday"; - -"Wallet.Info.TransactionDateHeader" = "%1$@ %2$@"; -"Wallet.Info.TransactionDateHeaderYear" = "%1$@ %2$@, %3$@"; - -"Wallet.UnknownError" = "An error occurred. Please try again later."; - -"Wallet.ContextMenuCopy" = "Copy"; - -"Wallet.Time.PreciseDate_m1" = "Jan %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m2" = "Feb %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m3" = "Mar %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m4" = "Apr %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m5" = "May %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m6" = "Jun %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m7" = "Jul %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m8" = "Aug %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m9" = "Sep %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m10" = "Oct %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m11" = "Nov %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m12" = "Dec %1$@, %2$@ at %3$@"; - -"Wallet.VoiceOver.Editing.ClearText" = "Clear text"; - -"Wallet.Receive.ShareInvoiceUrlInfo" = "Share this link with other Gram wallet owners to receive %@ Grams from them."; - -"Settings.Wallet" = "Gram Wallet"; -"SettingsSearch.Synonyms.Wallet" = "TON Telegram Open Network Crypto"; - -"Conversation.ClearCache" = "Clear Cache"; -"ClearCache.Description" = "Media files will be deleted from your phone, but available for re-downloading when necessary."; -"ClearCache.FreeSpaceDescription" = "If you want to save space on your device, you don't need to delete anything.\n\nYou can use cache settings to remove unnecessary media — and re-download files if you need them again."; -"ClearCache.FreeSpace" = "Free Space"; -"ClearCache.Success" = "**%@** freed on your %@!"; -"ClearCache.StorageUsage" = "Storage Usage"; - -"Conversation.ScheduleMessage.SendWhenOnline" = "Send When Online"; -"ScheduledMessages.ScheduledOnline" = "Scheduled until online"; - -"Conversation.SwipeToReplyHintTitle" = "Swipe To Reply"; -"Conversation.SwipeToReplyHintText" = "Swipe left on any message to reply to it."; - -"TwoFactorSetup.Intro.Title" = "Additional Password"; -"TwoFactorSetup.Intro.Text" = "You can set a password that will be\nrequired when you log in on a new device in addition to the code you get via SMS."; -"TwoFactorSetup.Intro.Action" = "Set Additional Password"; - -"TwoFactorSetup.Password.Title" = "Create Password"; -"TwoFactorSetup.Password.PlaceholderPassword" = "Password"; -"TwoFactorSetup.Password.PlaceholderConfirmPassword" = "Re-enter Password"; -"TwoFactorSetup.Password.Action" = "Create Password"; - -"TwoFactorSetup.Email.Title" = "Recovery Email"; -"TwoFactorSetup.Email.Text" = "You can set a recovery email to be able to reset you password and restore access to your Telegram account."; -"TwoFactorSetup.Email.Placeholder" = "Your email address"; -"TwoFactorSetup.Email.Action" = "Continue"; -"TwoFactorSetup.Email.SkipAction" = "Skip setting email"; -"TwoFactorSetup.Email.SkipConfirmationTitle" = "No, seriously."; -"TwoFactorSetup.Email.SkipConfirmationText" = "If you forget your password, you will lose access to your Telegram account. There will be no way to restore it."; -"TwoFactorSetup.Email.SkipConfirmationSkip" = "Skip"; - -"TwoFactorSetup.EmailVerification.Title" = "Recovery Email"; -"TwoFactorSetup.EmailVerification.Text" = "Please enter code we've just emailed at %@"; -"TwoFactorSetup.EmailVerification.Placeholder" = "Code"; -"TwoFactorSetup.EmailVerification.Action" = "Continue"; -"TwoFactorSetup.EmailVerification.ChangeAction" = "Change Email"; -"TwoFactorSetup.EmailVerification.ResendAction" = "Re-send Code"; - -"TwoFactorSetup.Hint.Title" = "Hint"; -"TwoFactorSetup.Hint.Text" = "You can create an optional hint for\nyour password."; -"TwoFactorSetup.Hint.Placeholder" = "Hint (optional)"; -"TwoFactorSetup.Hint.Action" = "Continue"; -"TwoFactorSetup.Hint.SkipAction" = "Skip setting hint"; - -"TwoFactorSetup.Done.Title" = "Password Set!"; -"TwoFactorSetup.Done.Text" = "This password will be required when you log in on a new device in addition to the code you get via SMS."; -"TwoFactorSetup.Done.Action" = "Return to Settings"; - -"AutoNightTheme.System" = "System"; - -"ChatSettings.OpenLinksIn" = "Open Links in"; -"SettingsSearch.Synonyms.ChatSettings.OpenLinksIn" = "Browser"; - -"WebBrowser.Title" = "Web Browser"; -"WebBrowser.DefaultBrowser" = "DEFAULT WEB BROWSER"; -"WebBrowser.InAppSafari" = "In-App Safari"; - -"Widget.ApplicationLocked" = "Unlock the app to use the widget"; - -"Group.ErrorSupergroupConversionNotPossible" = "Sorry, you are a member of too many groups and channels. Please leave some before creating a new one."; - -"ClearCache.StorageTitle" = "%@ STORAGE"; -"ClearCache.StorageCache" = "Telegram Cache"; -"ClearCache.StorageServiceFiles" = "Telegram Service Files"; -"ClearCache.StorageOtherApps" = "Other Apps"; -"ClearCache.StorageFree" = "Free"; -"ClearCache.ClearCache" = "Clear Telegram Cache"; -"ClearCache.Clear" = "Clear"; -"ClearCache.Forever" = "Forever"; - -"ChatList.DeletedChats_1" = "Deleted 1 chat"; -"ChatList.DeletedChats_any" = "Deleted %@ chats"; - -"Appearance.ColorThemeNight" = "COLOR THEME — AUTO-NIGHT MODE"; - -"UserInfo.StartSecretChatConfirmation" = "Are you sure you want to start a secret chat with\n%@?"; -"UserInfo.StartSecretChatStart" = "Start"; - -"Wallet.AccessDenied.Title" = "Please Allow Access"; -"Wallet.AccessDenied.Camera" = "TON Wallet needs access to your camera to take photos and videos.\n\nPlease go to Settings > Privacy > Camera and set TON Wallet to ON."; -"Wallet.AccessDenied.Settings" = "Settings"; - -"GroupInfo.ShowMoreMembers_0" = "%@ more"; -"GroupInfo.ShowMoreMembers_1" = "%@ more"; -"GroupInfo.ShowMoreMembers_2" = "%@ more"; -"GroupInfo.ShowMoreMembers_3_10" = "%@ more"; -"GroupInfo.ShowMoreMembers_many" = "%@ more"; -"GroupInfo.ShowMoreMembers_any" = "%@ more"; - -"ContactInfo.Note" = "note"; - -"Group.Location.CreateInThisPlace" = "Create a group in this place"; - -"Theme.Colors.Accent" = "Accent"; -"Theme.Colors.Background" = "Background"; -"Theme.Colors.Messages" = "Messages"; -"Theme.Colors.ColorWallpaperWarning" = "Are you sure you want to change your chat wallpaper to a color?"; -"Theme.Colors.ColorWallpaperWarningProceed" = "Proceed"; - -"ChatSettings.IntentsSettings" = "Share Sheet"; -"IntentsSettings.Title" = "Share Sheet"; -"IntentsSettings.MainAccount" = "Main Account"; -"IntentsSettings.MainAccountInfo" = "Choose an account for Siri and share suggestions."; -"IntentsSettings.SuggestedChats" = "Suggested Chats"; -"IntentsSettings.SuggestedChatsContacts" = "Contacts"; -"IntentsSettings.SuggestedChatsSavedMessages" = "Saved Messages"; -"IntentsSettings.SuggestedChatsPrivateChats" = "Private Chats"; -"IntentsSettings.SuggestedChatsGroups" = "Groups"; -"IntentsSettings.SuggestedChatsInfo" = "Archived chats will not be suggested."; -"IntentsSettings.SuggestedAndSpotlightChatsInfo" = "Suggestions will appear in the Share Sheet and Spotlight search results. Archived chats will not be suggested."; -"IntentsSettings.SuggestBy" = "Suggest By"; -"IntentsSettings.SuggestByAll" = "All Sent Messages"; -"IntentsSettings.SuggestByShare" = "Only Shared Messages"; -"IntentsSettings.ResetAll" = "Reset All Share Suggestions"; -"IntentsSettings.Reset" = "Reset"; - -"Conversation.SendingOptionsTooltip" = "Hold this button to schedule your message\nor send it without sound."; - -"Appearance.TextSizeSetting" = "Text Size"; -"Appearance.TextSize.Automatic" = "System"; -"Appearance.TextSize.Title" = "Text Size"; -"Appearance.TextSize.UseSystem" = "User System Text Size"; -"Appearance.TextSize.Apply" = "Set"; - -"Shortcut.SwitchAccount" = "Switch Account"; - -"Settings.Devices" = "Devices"; -"Settings.AddDevice" = "Scan QR"; -"AuthSessions.DevicesTitle" = "Devices"; -"AuthSessions.AddDevice" = "Scan QR"; -"AuthSessions.AddDevice.ScanInfo" = "Scan a QR code to log into\nthis account on another device."; -"AuthSessions.AddDevice.ScanTitle" = "Scan QR Code"; -"AuthSessions.AddDevice.InvalidQRCode" = "Invalid QR Code"; -"AuthSessions.AddDeviceIntro.Title" = "Log in by QR Code"; -"AuthSessions.AddDeviceIntro.Text1" = "Download Telegram on your computer from [desktop.telegram.org]()"; -"AuthSessions.AddDeviceIntro.Text2" = "Run Telegram on your computer to get the QR code"; -"AuthSessions.AddDeviceIntro.Text3" = "Scan the QR code to connect your account"; -"AuthSessions.AddDeviceIntro.Action" = "Scan QR Code"; -"AuthSessions.AddedDeviceTitle" = "Login Successful"; -"AuthSessions.AddedDeviceTerminate" = "Terminate"; - -"Map.SendThisPlace" = "Send This Place"; -"Map.SetThisPlace" = "Set This Place"; -"Map.AddressOnMap" = "Address On Map"; -"Map.PlacesNearby" = "Places Nearby"; -"Map.Home" = "Home"; -"Map.Work" = "Work"; -"Map.HomeAndWorkTitle" = "Home & Work Addresses"; -"Map.HomeAndWorkInfo" = "Telegram uses the Home and Work addresses from your Contact Card.\n\nKeep your Contact Card up to date for quick access to sending Home and Work addresses."; -"Map.SearchNoResultsDescription" = "There were no results for \"%@\".\nTry a new search."; - -"ChatList.Search.ShowMore" = "Show more"; -"ChatList.Search.ShowLess" = "Show less"; - -"AuthSessions.OtherDevices" = "The official Telegram App is available for iPhone, iPad, Android, macOS, Windows and Linux. [Learn More]()"; - -"MediaPlayer.UnknownArtist" = "Unknown Artist"; -"MediaPlayer.UnknownTrack" = "Unknown Track"; - -"Contacts.InviteContacts_1" = "Invite %@ Contact"; -"Contacts.InviteContacts_2" = "Invite %@ Contacts"; -"Contacts.InviteContacts_3_10" = "Invite %@ Contacts"; -"Contacts.InviteContacts_any" = "Invite %@ Contacts"; -"Contacts.InviteContacts_many" = "Invite %@ Contacts"; -"Contacts.InviteContacts_0" = "Invite %@ Contacts"; - -"Theme.Context.ChangeColors" = "Change Colors"; - -"EditTheme.ChangeColors" = "Change Colors"; - -"Theme.Colors.Proceed" = "Proceed"; - -"AuthSessions.AddDevice.UrlLoginHint" = "This code can be used to allow someone to log in to your Telegram account.\n\nTo confirm Telegram login, please go to Settings > Devices > Scan QR and scan the code."; - -"Appearance.RemoveThemeColor" = "Remove"; -"Appearance.RemoveThemeColorConfirmation" = "Remove Color"; - -"WallpaperPreview.PatternTitle" = "Choose Pattern"; -"WallpaperPreview.PatternPaternDiscard" = "Discard"; -"WallpaperPreview.PatternPaternApply" = "Apply"; - -"ChatContextMenu.TextSelectionTip" = "Hold a word, then move cursor to select more| text to copy."; - -"OldChannels.Title" = "Limit Reached"; -"OldChannels.NoticeTitle" = "Too Many Groups and Channels"; -"OldChannels.NoticeText" = "Sorry, you are member of too many groups and channels.\nPlease leave some before joining new one."; -"OldChannels.NoticeCreateText" = "Sorry, you are member of too many groups and channels.\nPlease leave some before creating a new one."; -"OldChannels.NoticeUpgradeText" = "Sorry, you are a member of too many groups and channels.\nFor technical reasons, you need to leave some first before changing this setting in your groups."; -"OldChannels.ChannelsHeader" = "MOST INACTIVE"; -"OldChannels.Leave_1" = "Leave %@ Chat"; -"OldChannels.Leave_any" = "Leave %@ Chats"; - -"OldChannels.ChannelFormat" = "channel, "; -"OldChannels.GroupEmptyFormat" = "group, "; -"OldChannels.GroupFormat_1" = "%@ member "; -"OldChannels.GroupFormat_any" = "%@ members "; - -"OldChannels.InactiveWeek_1" = "inactive %@ week"; -"OldChannels.InactiveWeek_any" = "inactive %@ weeks"; - -"OldChannels.InactiveMonth_1" = "inactive %@ month"; -"OldChannels.InactiveMonth_any" = "inactive %@ months"; - -"OldChannels.InactiveYear_1" = "inactive %@ year"; -"OldChannels.InactiveYear_any" = "inactive %@ years"; - -"PrivacySettings.WebSessions" = "Active Websites"; - -"Appearance.ShareThemeColor" = "Share"; - -"Theme.ThemeChanged" = "Color Theme Changed"; -"Theme.ThemeChangedText" = "You can change it back in\n[Settings > Appearance]()."; - -"StickerPackActionInfo.AddedTitle" = "Stickers Added"; -"StickerPackActionInfo.AddedText" = "%@ has been added to your stickers."; -"StickerPackActionInfo.RemovedTitle" = "Stickers Removed"; -"StickerPackActionInfo.ArchivedTitle" = "Stickers Archived"; -"StickerPackActionInfo.RemovedText" = "%@ is no longer in your stickers."; - -"Conversation.ContextMenuCancelEditing" = "Cancel Editing"; - -"Map.NoPlacesNearby" = "There are no known places nearby.\nTry a different location."; - -"CreatePoll.QuizTitle" = "New Quiz"; -"CreatePoll.QuizOptionsHeader" = "QUIZ ANSWERS"; -"CreatePoll.Anonymous" = "Anonymous Voting"; -"CreatePoll.MultipleChoice" = "Multiple Choice"; -"CreatePoll.MultipleChoiceQuizAlert" = "A quiz has one correct answer."; -"CreatePoll.Quiz" = "Quiz Mode"; -"CreatePoll.QuizInfo" = "Polls in Quiz Mode have one correct answer. Users can't revoke their answers."; -"CreatePoll.QuizTip" = "Tap to choose the correct answer"; - -"MessagePoll.LabelPoll" = "Public Poll"; -"MessagePoll.LabelAnonymousQuiz" = "Anonymous Quiz"; -"MessagePoll.LabelQuiz" = "Quiz"; -"MessagePoll.SubmitVote" = "Vote"; -"MessagePoll.ViewResults" = "View Results"; -"MessagePoll.QuizNoUsers" = "Nobody answered yet"; -"MessagePoll.QuizCount_0" = "%@ answered"; -"MessagePoll.QuizCount_1" = "1 answered"; -"MessagePoll.QuizCount_2" = "2 answered"; -"MessagePoll.QuizCount_3_10" = "%@ answered"; -"MessagePoll.QuizCount_many" = "%@ answered"; -"MessagePoll.QuizCount_any" = "%@ answered"; - -"PollResults.Title" = "Poll Results"; -"PollResults.Collapse" = "COLLAPSE"; -"PollResults.ShowMore_1" = "Show More (%@)"; -"PollResults.ShowMore_any" = "Show More (%@)"; - -"Conversation.StopQuiz" = "Stop Quiz"; -"Conversation.StopQuizConfirmationTitle" = "If you stop this quiz now, nobody will be able to submit answers. This action cannot be undone."; -"Conversation.StopQuizConfirmation" = "Stop Quiz"; - -"Forward.ErrorDisabledForChat" = "Sorry, you can't forward messages to this chat."; -"Forward.ErrorPublicPollDisabledInChannels" = "Sorry, public polls can’t be forwarded to channels."; -"Forward.ErrorPublicQuizDisabledInChannels" = "Sorry, public polls can’t be forwarded to channels."; - -"Map.PlacesInThisArea" = "Places In This Area"; - -"Appearance.BubbleCornersSetting" = "Message Corners"; -"Appearance.BubbleCorners.Title" = "Message Corners"; -"Appearance.BubbleCorners.AdjustAdjacent" = "Adjust Adjacent Corners"; -"Appearance.BubbleCorners.Apply" = "Set"; - -"Conversation.LiveLocationYouAndOther" = "**You** and %@"; - -"PeopleNearby.MakeVisible" = "Make Myself Visible"; -"PeopleNearby.MakeInvisible" = "Stop Showing Me"; - -"PeopleNearby.ShowMorePeople_0" = "Show %@ More People"; -"PeopleNearby.ShowMorePeople_1" = "Show %@ More People"; -"PeopleNearby.ShowMorePeople_2" = "Show %@ More People"; -"PeopleNearby.ShowMorePeople_3_10" = "Show %@ More People"; -"PeopleNearby.ShowMorePeople_many" = "Show %@ More People"; -"PeopleNearby.ShowMorePeople_any" = "Show %@ More People"; - -"PeopleNearby.VisibleUntil" = "visible until %@"; - -"PeopleNearby.MakeVisibleTitle" = "Make Myself Visible"; -"PeopleNearby.MakeVisibleDescription" = "Users nearby will be able to view your profile and send you messages. This may help you find new friends, but could also attract excessive attention. You can stop sharing your profile at any time.\n\nYour phone number will remain hidden."; - -"PeopleNearby.DiscoverDescription" = "Exchange contact info with people nearby\nand find new friends."; - -"Time.TomorrowAt" = "tomorrow at %@"; - -"PeerInfo.ButtonMessage" = "Message"; -"PeerInfo.ButtonDiscuss" = "Discuss"; -"PeerInfo.ButtonCall" = "Call"; -"PeerInfo.ButtonVideoCall" = "Video"; -"PeerInfo.ButtonMute" = "Mute"; -"PeerInfo.ButtonUnmute" = "Unmute"; -"PeerInfo.ButtonMore" = "More"; -"PeerInfo.ButtonAddMember" = "Add Members"; -"PeerInfo.ButtonSearch" = "Search"; -"PeerInfo.ButtonLeave" = "Leave"; - -"PeerInfo.PaneMedia" = "Media"; -"PeerInfo.PaneFiles" = "Files"; -"PeerInfo.PaneLinks" = "Links"; -"PeerInfo.PaneVoiceAndVideo" = "Voice"; -"PeerInfo.PaneAudio" = "Audio"; -"PeerInfo.PaneGroups" = "Groups"; -"PeerInfo.PaneMembers" = "Members"; -"PeerInfo.PaneGifs" = "GIFs"; - -"PeerInfo.AddToContacts" = "Add to Contacts"; - -"PeerInfo.BioExpand" = "more"; - -"External.OpenIn" = "Open in %@"; - -"ChatList.EmptyChatList" = "You have no\nconversations yet."; -"ChatList.EmptyChatListFilterTitle" = "Folder is empty."; -"ChatList.EmptyChatListFilterText" = "No chats currently match this folder."; - -"ChatList.EmptyChatListNewMessage" = "New Message"; -"ChatList.EmptyChatListEditFilter" = "Edit Folder"; - -"ChatListFilter.AddChatsTitle" = "Add Chats..."; - -"Stats.Overview" = "OVERVIEW"; -"Stats.Followers" = "Followers"; -"Stats.EnabledNotifications" = "Enabled Notifications"; -"Stats.ViewsPerPost" = "Views Per Post"; -"Stats.SharesPerPost" = "Shares Per Post"; - -"Stats.GrowthTitle" = "GROWTH"; -"Stats.FollowersTitle" = "FOLLOWERS"; -"Stats.NotificationsTitle" = "NOTIFICATIONS"; -"Stats.InteractionsTitle" = "INTERACTIONS"; -"Stats.InstantViewInteractionsTitle" = "INSTANT VIEW INTERACTIONS"; -"Stats.ViewsBySourceTitle" = "VIEWS BY SOURCE"; -"Stats.ViewsByHoursTitle" = "VIEWS BY HOURS"; -"Stats.FollowersBySourceTitle" = "FOLLOWERS BY SOURCE"; -"Stats.LanguagesTitle" = "LANGUAGES"; -"Stats.PostsTitle" = "RECENT POSTS"; - -"Stats.MessageViews_0" = "%@ views"; -"Stats.MessageViews_1" = "%@ view"; -"Stats.MessageViews_2" = "%@ views"; -"Stats.MessageViews_3_10" = "%@ views"; -"Stats.MessageViews_many" = "%@ views"; -"Stats.MessageViews_any" = "%@ views"; - -"Stats.MessageForwards_0" = "%@ forwards"; -"Stats.MessageForwards_1" = "%@ forward"; -"Stats.MessageForwards_2" = "%@ forwards"; -"Stats.MessageForwards_3_10" = "%@ forwards"; -"Stats.MessageForwards_many" = "%@ forwards"; -"Stats.MessageForwards_any" = "%@ forwards"; - -"Stats.LoadingTitle" = "Preparing stats"; -"Stats.LoadingText" = "Please wait a few moments while\nwe generate your stats"; - -"Stats.ZoomOut" = "Zoom Out"; -"Stats.Total" = "Total"; - -"InstantPage.Views_0" = "%@ views"; -"InstantPage.Views_1" = "%@ view"; -"InstantPage.Views_2" = "%@ views"; -"InstantPage.Views_3_10" = "%@ views"; -"InstantPage.Views_many" = "%@ views"; -"InstantPage.Views_any" = "%@ views"; -"InstantPage.FeedbackButtonShort" = "Wrong layout?"; - -"ChatList.EditFolder" = "Edit Folder"; -"ChatList.AddChatsToFolder" = "Add Chats"; -"ChatList.RemoveFolderConfirmation" = "This will remove the folder, your chats will not be deleted."; -"ChatList.RemoveFolderAction" = "Remove"; -"ChatList.RemoveFolder" = "Remove"; -"ChatList.ReorderTabs" = "Reorder Tabs"; -"ChatList.TabIconFoldersTooltipNonEmptyFolders" = "Hold on 'Chats' to edit folders and switch between views."; -"ChatList.TabIconFoldersTooltipEmptyFolders" = "Hold to organize your chats with folders."; -"ChatList.AddFolder" = "Add Folder"; -"ChatList.EditFolders" = "Edit Folders"; -"ChatList.FolderAllChats" = "All Chats"; -"ChatList.Tabs.AllChats" = "All Chats"; -"ChatList.Tabs.All" = "All"; -"Settings.ChatFolders" = "Chat Folders"; -"ChatList.ChatTypesSection" = "CHAT TYPES"; -"ChatList.PeerTypeNonContact" = "user"; -"ChatList.PeerTypeContact" = "contact"; -"ChatList.PeerTypeBot" = "bot"; -"ChatList.PeerTypeGroup" = "group"; -"ChatList.PeerTypeChannel" = "channel"; -"ChatListFolderSettings.Info" = "Create folders for different groups of chats and\nquickly switch between them."; - -"ChatListFolderSettings.Title" = "Folders"; -"ChatListFolderSettings.FoldersSection" = "FOLDERS"; -"ChatListFolderSettings.NewFolder" = "Create New Folder"; -"ChatListFolderSettings.EditFoldersInfo" = "Tap \"Edit\" to change the order or delete folders."; -"ChatListFolderSettings.RecommendedFoldersSection" = "RECOMMENDED FOLDERS"; -"ChatListFolderSettings.RecommendedNewFolder" = "Add Custom Folder"; - -"ChatListFolder.TitleCreate" = "New Folder"; -"ChatListFolder.TitleEdit" = "Edit Folder"; -"ChatListFolder.CategoryContacts" = "Contacts"; -"ChatListFolder.CategoryNonContacts" = "Non-Contacts"; -"ChatListFolder.CategoryBots" = "Bots"; -"ChatListFolder.CategoryGroups" = "Groups"; -"ChatListFolder.CategoryChannels" = "Channels"; -"ChatListFolder.CategoryMuted" = "Muted"; -"ChatListFolder.CategoryRead" = "Read"; -"ChatListFolder.CategoryArchived" = "Archived"; -"ChatListFolder.NameSectionHeader" = "FOLDER NAME"; -"ChatListFolder.NamePlaceholder" = "Folder Name"; -"ChatListFolder.IncludedSectionHeader" = "INCLUDED CHATS"; -"ChatListFolder.AddChats" = "Add Chats"; -"ChatListFolder.IncludeSectionInfo" = "Choose chats and types of chats that will appear in this folder."; -"ChatListFolder.ExcludedSectionHeader" = "EXCLUDED CHATS"; -"ChatListFolder.ExcludeSectionInfo" = "Choose chats and types of chats that will never appear in this folder."; -"ChatListFolder.NameNonMuted" = "Not Muted"; -"ChatListFolder.NameUnread" = "Unread"; -"ChatListFolder.NameChannels" = "Channels"; -"ChatListFolder.NameContacts" = "Contacts"; -"ChatListFolder.NameNonContacts" = "Non-Contacts"; -"ChatListFolder.NameBots" = "Bots"; -"ChatListFolder.NameGroups" = "Groups"; -"ChatListFolder.DiscardConfirmation" = "You have changed the filter. Discard changes?"; -"ChatListFolder.DiscardDiscard" = "Discard"; -"ChatListFolder.DiscardCancel" = "No"; -"ChatListFolder.IncludeChatsTitle" = "Include Chats"; -"ChatListFolder.ExcludeChatsTitle" = "Exclude Chats"; - -"ChatListFolderSettings.AddRecommended" = "ADD"; - -"ChatListFilter.ShowMoreChats_0" = "Show %@ More Chats"; -"ChatListFilter.ShowMoreChats_1" = "Show %@ More Chat"; -"ChatListFilter.ShowMoreChats_2" = "Show %@ More Chats"; -"ChatListFilter.ShowMoreChats_3_10" = "Show %@ More Chats"; -"ChatListFilter.ShowMoreChats_many" = "Show %@ More Chats"; -"ChatListFilter.ShowMoreChats_any" = "Show %@ More Chats"; - -"MuteFor.Forever" = "Mute Forever"; - -"Conversation.Dice.u1F3B2" = "Send a dice emoji to any chat to roll a die."; -"Conversation.Dice.u1F3AF" = "Send a dart emoji to try your luck."; -"Conversation.SendDice" = "Send"; - -"Conversation.ContextMenuDiscuss" = "Discuss"; - -"CreatePoll.ExplanationHeader" = "EXPLANATION"; -"CreatePoll.Explanation" = "Add a Comment (Optional)"; -"CreatePoll.ExplanationInfo" = "Users will see this comment after choosing a wrong answer, good for educational purposes."; - -"FeaturedStickers.OtherSection" = "OTHER STICKERS"; - -"ChatList.GenericPsaAlert" = "This provides public service announcements in your chat list."; -"ChatList.PsaAlert.covid" = "This message provides you with a public service announcement in relation to the undergoing pandemics. Learn more about this initiative at https://telegram.org/blog/coronavirus"; -"ChatList.GenericPsaLabel" = "PSA"; -"ChatList.PsaLabel.covid" = "Covid-19"; -"Chat.GenericPsaTooltip" = "This is a public service announcement"; -"Chat.PsaTooltip.covid" = "This message provides you with a public service announcement in relation to the undergoing pandemics. Learn more about this initiative at https://telegram.org/blog/coronavirus"; - -"Message.GenericForwardedPsa" = "Public Service Announcement\nFrom: %@"; -"Message.ForwardedPsa.covid" = "Covid-19 Notification\nFrom: %@"; - -"Channel.AboutItem" = "about"; -"PeerInfo.GroupAboutItem" = "about"; - -"Widget.ApplicationStartRequired" = "Open the app to use the widget"; - -"ChatList.Context.AddToFolder" = "Add to Folder"; -"ChatList.Context.RemoveFromFolder" = "Remove from Folder"; -"ChatList.Context.Back" = "Back"; -"ChatList.AddedToFolderTooltip" = "%1$@ has been added to folder %2$@"; -"ChatList.RemovedFromFolderTooltip" = "%1$@ has been removed from folder %2$@"; - -"OwnershipTransfer.Transfer" = "Transfer"; - -"TwoStepAuth.Disable" = "Disable"; - -"Chat.Gifs.TrendingSectionHeader" = "TRENDING GIFS"; -"Chat.Gifs.SavedSectionHeader" = "MY GIFS"; - -"Paint.Framed" = "Framed"; - -"Media.SendingOptionsTooltip" = "Hold this button to send your message with a self-destruct timer."; -"Media.SendWithTimer" = "Send With Timer"; - -"Conversation.Timer.Title" = "Send With Timer"; -"Conversation.Timer.Send" = "Send With Timer"; - -"Paint.Pen" = "Pen"; -"Paint.Marker" = "Marker"; -"Paint.Neon" = "Neon"; -"Paint.Arrow" = "Arrow"; - -"Conversation.NoticeInvitedByInChannel" = "%@ invited you to this channel"; -"Conversation.NoticeInvitedByInGroup" = "%@ invited you to this group"; - -"ChatList.MessagePhotos_1" = "1 Photo"; -"ChatList.MessagePhotos_any" = "%@ Photos"; -"ChatList.MessageVideos_1" = "1 Videos"; -"ChatList.MessageVideos_any" = "%@ Videos"; - -"Conversation.PrivateChannelTimeLimitedAlertTitle" = "Join Channel"; -"Conversation.PrivateChannelTimeLimitedAlertText" = "This channel is private. Please join it to continue viewing its content."; -"Conversation.PrivateChannelTimeLimitedAlertJoin" = "Join"; - -"KeyCommand.SearchInChat" = "Search in Chat"; - -"PhotoEditor.SkinTool" = "Soften Skin"; -"PhotoEditor.BlurToolPortrait" = "Portrait"; -"PhotoEditor.SelectCoverFrame" = "Choose a cover for your profile video"; - -"Conversation.PeerNearbyTitle" = "%1$@ is %2$@ away"; -"Conversation.PeerNearbyText" = "Send a message or tap on the greeting below to show that you are ready to chat."; -"Conversation.PeerNearbyDistance" = "%1$@ is %2$@ away"; - -"ProfilePhoto.MainPhoto" = "Main Photo"; -"ProfilePhoto.SetMainPhoto" = "Set as Main Photo"; - -"ProfilePhoto.MainVideo" = "Main Video"; -"ProfilePhoto.SetMainVideo" = "Set as Main Video"; - -"Stats.GroupOverview" = "OVERVIEW"; -"Stats.GroupMembers" = "Members"; -"Stats.GroupMessages" = "Messages"; -"Stats.GroupViewers" = "Viewing Members"; -"Stats.GroupPosters" = "Posting Members"; - -"Stats.GroupGrowthTitle" = "GROWTH"; -"Stats.GroupMembersTitle" = "GROUP MEMBERS"; -"Stats.GroupNewMembersBySourceTitle" = "NEW MEMBERS BY SOURCE"; -"Stats.GroupLanguagesTitle" = "MEMBERS' PRIMARY LANGUAGE"; -"Stats.GroupMessagesTitle" = "MESSAGES"; -"Stats.GroupActionsTitle" = "ACTIONS"; -"Stats.GroupTopHoursTitle" = "TOP HOURS"; -"Stats.GroupTopWeekdaysTitle" = "TOP DAYS OF WEEK"; -"Stats.GroupTopPostersTitle" = "TOP MEMBERS"; -"Stats.GroupTopAdminsTitle" = "TOP ADMINS"; -"Stats.GroupTopInvitersTitle" = "TOP INVITERS"; - -"Stats.GroupTopPosterMessages_0" = "%@ messages"; -"Stats.GroupTopPosterMessages_1" = "%@ message"; -"Stats.GroupTopPosterMessages_2" = "%@ messages"; -"Stats.GroupTopPosterMessages_3_10" = "%@ messages"; -"Stats.GroupTopPosterMessages_many" = "%@ messages"; -"Stats.GroupTopPosterMessages_any" = "%@ messages"; - -"Stats.GroupTopPosterChars_0" = "%@ symbols per message"; -"Stats.GroupTopPosterChars_1" = "%@ symbol per message"; -"Stats.GroupTopPosterChars_2" = "%@ symbols per message"; -"Stats.GroupTopPosterChars_3_10" = "%@ symbols per message"; -"Stats.GroupTopPosterChars_many" = "%@ symbols per message"; -"Stats.GroupTopPosterChars_any" = "%@ symbols per message"; - -"Stats.GroupTopPoster.History" = "History"; -"Stats.GroupTopPoster.Promote" = "Promote"; - -"Stats.GroupTopAdminDeletions_0" = "%@ deletions"; -"Stats.GroupTopAdminDeletions_1" = "%@ deletion"; -"Stats.GroupTopAdminDeletions_2" = "%@ deletions"; -"Stats.GroupTopAdminDeletions_3_10" = "%@ deletions"; -"Stats.GroupTopAdminDeletions_many" = "%@ deletions"; -"Stats.GroupTopAdminDeletions_any" = "%@ deletions"; - -"Stats.GroupTopAdminKicks_0" = "%@ kicks"; -"Stats.GroupTopAdminKicks_1" = "%@ kick"; -"Stats.GroupTopAdminKicks_2" = "%@ kicks"; -"Stats.GroupTopAdminKicks_3_10" = "%@ kicks"; -"Stats.GroupTopAdminKicks_many" = "%@ kicks"; -"Stats.GroupTopAdminKicks_any" = "%@ kicks"; - -"Stats.GroupTopAdminBans_0" = "%@ bans"; -"Stats.GroupTopAdminBans_1" = "%@ ban"; -"Stats.GroupTopAdminBans_2" = "%@ bans"; -"Stats.GroupTopAdminBans_3_10" = "%@ bans"; -"Stats.GroupTopAdminBans_many" = "%@ bans"; -"Stats.GroupTopAdminBans_any" = "%@ bans"; - -"Stats.GroupTopAdmin.Actions" = "Actions"; -"Stats.GroupTopAdmin.Promote" = "Promote"; - -"Stats.GroupTopInviterInvites_0" = "%@ invitations"; -"Stats.GroupTopInviterInvites_1" = "%@ invitation"; -"Stats.GroupTopInviterInvites_2" = "%@ invitations"; -"Stats.GroupTopInviterInvites_3_10" = "%@ invitations"; -"Stats.GroupTopInviterInvites_many" = "%@ invitations"; -"Stats.GroupTopInviterInvites_any" = "%@ invitations"; - -"Stats.GroupTopInviter.History" = "History"; -"Stats.GroupTopInviter.Promote" = "Promote"; - -"PrivacySettings.AutoArchiveTitle" = "NEW CHATS FROM UNKNOWN USERS"; -"PrivacySettings.AutoArchive" = "Archive and Mute"; -"PrivacySettings.AutoArchiveInfo" = "Automatically archive and mute new chats, groups and channels from non-contacts."; - -"Call.RemoteVideoPaused" = "%@'s video is paused"; - -"Settings.SetProfilePhotoOrVideo" = "Set Photo or Video"; -"Settings.SetNewProfilePhotoOrVideo" = "Set New Photo or Video"; -"Settings.ViewVideo" = "View Video"; -"Settings.RemoveVideo" = "Remove Video"; - -"Conversation.Unarchive" = "Unarchive"; -"Conversation.UnarchiveDone" = "The chat was moved to your main list."; - -"ChatList.AutoarchiveSuggestion.Title" = "Hide new chats?"; -"ChatList.AutoarchiveSuggestion.Text" = "You are receiving lots of new chats from users who are not in your Contact List. Do you want to have such chats **automatically muted** and **archived**?"; -"ChatList.AutoarchiveSuggestion.OpenSettings" = "Go to Settings"; - -"SettingsSearch.Synonyms.ChatSettings.IntentsSettings" = "Siri Suggestions"; - -"Stats.GroupShowMoreTopPosters_0" = "Show %@ More"; -"Stats.GroupShowMoreTopPosters_1" = "Show %@ More"; -"Stats.GroupShowMoreTopPosters_2" = "Show %@ More"; -"Stats.GroupShowMoreTopPosters_3_10" = "Show %@ More"; -"Stats.GroupShowMoreTopPosters_many" = "Show %@ More"; -"Stats.GroupShowMoreTopPosters_any" = "Show %@ More"; - -"Stats.GroupShowMoreTopAdmins_0" = "Show %@ More"; -"Stats.GroupShowMoreTopAdmins_1" = "Show %@ More"; -"Stats.GroupShowMoreTopAdmins_2" = "Show %@ More"; -"Stats.GroupShowMoreTopAdmins_3_10" = "Show %@ More"; -"Stats.GroupShowMoreTopAdmins_many" = "Show %@ More"; -"Stats.GroupShowMoreTopAdmins_any" = "Show %@ More"; - -"Stats.GroupShowMoreTopInviters_0" = "Show %@ More"; -"Stats.GroupShowMoreTopInviters_1" = "Show %@ More"; -"Stats.GroupShowMoreTopInviters_2" = "Show %@ More"; -"Stats.GroupShowMoreTopInviters_3_10" = "Show %@ More"; -"Stats.GroupShowMoreTopInviters_many" = "Show %@ More"; -"Stats.GroupShowMoreTopInviters_any" = "Show %@ More"; - -"Settings.AddAnotherAccount" = "Add Another Account"; -"Settings.AddAnotherAccount.Help" = "You can add up to three accounts with different phone numbers."; - -"ProfilePhoto.OpenGallery" = "Open Gallery"; -"ProfilePhoto.SearchWeb" = "Search Web"; -"ProfilePhoto.OpenInEditor" = "Open in Editor"; - -"Settings.EditAccount" = "Edit Account"; -"Settings.EditPhoto" = "Edit"; -"Settings.EditVideo" = "Edit"; -"Settings.CancelUpload" = "Cancel Upload"; - -"Settings.FrequentlyAskedQuestions" = "Frequently Asked Questions"; - -"Notification.ChangedGroupVideo" = "%@ changed group video"; -"Group.MessageVideoUpdated" = "Group video updated"; -"Channel.MessageVideoUpdated" = "Channel video updated"; - -"Conversation.Dice.u1F3C0" = "Send a basketball emoji to try your luck."; -"Conversation.Dice.u26BD" = "Send a football emoji to try your luck."; - -"SettingsSearch_Synonyms_ChatFolders" = ""; - -"EditProfile.NameAndPhotoOrVideoHelp" = "Enter your name and add an optional profile photo or video."; - -"Settings.RemoveConfirmation" = "Remove"; - -"Conversation.ContextMenuOpenProfile" = "Open Profile"; -"Conversation.ContextMenuSendMessage" = "Send Message"; -"Conversation.ContextMenuMention" = "Mention"; - -"Conversation.ContextMenuOpenChannelProfile" = "Open Profile"; -"Conversation.ContextMenuOpenChannel" = "Open Channel"; - -"Cache.KeepMediaHelp" = "Photos, videos and other files from cloud chats that you have **not accessed** during this period will be removed from this device to save disk space."; - - -"Cache.MaximumCacheSize" = "Maximum Cache Size"; -"Cache.NoLimit" = "No Limit"; -"Cache.MaximumCacheSizeHelp" = "If your cache size exceeds this limit, the oldest media will be deleted.\n\nAll media will stay in the Telegram cloud and can be re-downloaded if you need it again."; - -"Stats.MessageTitle" = "Message Statistics"; -"Stats.MessageOverview" = "Overview"; -"Stats.MessageInteractionsTitle" = "Interactions"; -"Stats.MessagePublicForwardsTitle" = "Public Shares"; - -"Call.CameraTooltip" = "Tap here to turn on your camera"; -"Call.CameraConfirmationText" = "Switch to video call?"; -"Call.CameraConfirmationConfirm" = "Switch"; - -"Call.YourMicrophoneOff" = "Your microphone is off"; -"Call.MicrophoneOff" = "%@'s microphone is off"; -"Call.CameraOff" = "%@'s camera is off"; -"Call.BatteryLow" = "%@'s battery level is low"; - -"Call.Audio" = "audio"; -"Call.AudioRouteMute" = "Mute Yourself"; - -"AccessDenied.VideoCallCamera" = "Telegram needs access to your camera to make video calls.\n\nPlease go to Settings > Privacy > Camera and set Telegram to ON."; - -"Call.AccountIsLoggedOnCurrentDevice" = "Sorry, you can't call %@ because that account is logged in to Telegram on the device you're using for the call."; - -"ChatList.Search.FilterMedia" = "Media"; -"ChatList.Search.FilterPhotos" = "Photos"; -"ChatList.Search.FilterVideos" = "Video"; -"ChatList.Search.FilterLinks" = "Links"; -"ChatList.Search.FilterFiles" = "Files"; -"ChatList.Search.FilterMusic" = "Audio"; - -"ChatList.Search.NoResults" = "No Results"; -"ChatList.Search.NoResultsDescription" = "There were no results for \"%@\".\nTry a new search."; - -<<<<<<< HEAD -"ChatList.Search.Messages_0" = "%@ messages"; -"ChatList.Search.Messages_1" = "%@ message"; -"ChatList.Search.Messages_2" = "%@ messages"; -"ChatList.Search.Messages_3_10" = "%@ messages"; -"ChatList.Search.Messages_many" = "%@ messages"; -"ChatList.Search.Messages_any" = "%@ messages"; - -"ChatList.Search.Photos_0" = "%@ photos"; -"ChatList.Search.Photos_1" = "%@ photo"; -"ChatList.Search.Photos_2" = "%@ photos"; -"ChatList.Search.Photos_3_10" = "%@ photos"; -"ChatList.Search.Photos_many" = "%@ photos"; -"ChatList.Search.Photos_any" = "%@ photos"; - -"ChatList.Search.Links_0" = "%@ links"; -"ChatList.Search.Links_1" = "%@ link"; -"ChatList.Search.Links_2" = "%@ links"; -"ChatList.Search.Links_3_10" = "%@ links"; -"ChatList.Search.Links_many" = "%@ links"; -"ChatList.Search.Links_any" = "%@ links"; - -"ChatList.Search.Files_0" = "%@ files"; -"ChatList.Search.Files_1" = "%@ file"; -"ChatList.Search.Files_2" = "%@ files"; -"ChatList.Search.Files_3_10" = "%@ files"; -"ChatList.Search.Files_many" = "%@ files"; -"ChatList.Search.Files_any" = "%@ files"; - -"ChatList.Search.Music_0" = "%@ audio files"; -"ChatList.Search.Music_1" = "%@ audio file"; -"ChatList.Search.Music_2" = "%@ audio files"; -"ChatList.Search.Music_3_10" = "%@ audio files"; -"ChatList.Search.Music_many" = "%@ audio files"; -"ChatList.Search.Music_any" = "%@ audio files"; -======= -"Conversation.InputTextAnonymousPlaceholder" = "Send anonymously"; ->>>>>>> features/comments diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 07d3a40d74..4bc3fe02d3 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -170,7 +170,6 @@ public enum ResolvedUrl { case botStart(peerId: PeerId, payload: String) case groupBotStart(peerId: PeerId, payload: String) case channelMessage(peerId: PeerId, messageId: MessageId) - case replyThreadMessage(replyThreadMessageId: MessageId, isChannelPost: Bool, maxReadMessageId: MessageId?, messageId: MessageId) case stickerPack(name: String) case instantView(TelegramMediaWebpage, String?) case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?) @@ -250,12 +249,6 @@ public enum ChatSearchDomain: Equatable { } } } - -public enum ChatLocation: Equatable { - case peer(PeerId) - case replyThread(threadMessageId: MessageId, isChannelPost: Bool, maxReadMessageId: MessageId?) -} - public final class NavigateToChatControllerParams { public let navigationController: NavigationController public let chatController: ChatController? @@ -529,8 +522,8 @@ public protocol SharedAccountContext: class { func handleTextLinkAction(context: AccountContext, peerId: PeerId?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?) func openChatMessage(_ params: OpenChatMessageParams) -> Bool - func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError> - func makeOverlayAudioPlayerController(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, isGlobalSearch: Bool, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController + func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError> + func makeOverlayAudioPlayerController(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController func makePeerInfoController(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool) -> ViewController? func makeChannelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant) -> ViewController? func makeDeviceContactInfoController(context: AccountContext, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController @@ -639,9 +632,6 @@ public final class TonContext { #endif -public protocol ChatLocationContextHolder: class { -} - public protocol AccountContext: class { var sharedContext: SharedAccountContext { get } var account: Account { get } @@ -669,7 +659,4 @@ public protocol AccountContext: class { func storeSecureIdPassword(password: String) func getStoredSecureIdPassword() -> String? - - func chatLocationInput(for location: ChatLocation, contextHolder: Atomic) -> ChatLocationInput - func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic, messageIndex: MessageIndex) } diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 03bd0b2a26..e0fe69e795 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -10,93 +10,6 @@ import SwiftSignalKit import TelegramPresentationData import TelegramUIPreferences -public final class ChatMessageItemAssociatedData: Equatable { - public enum ChannelDiscussionGroupStatus: Equatable { - case unknown - case known(PeerId?) - } - - public let automaticDownloadPeerType: MediaAutoDownloadPeerType - public let automaticDownloadNetworkType: MediaAutoDownloadNetworkType - public let isRecentActions: Bool - public let isScheduledMessages: Bool - public let contactsPeerIds: Set - public let channelDiscussionGroup: ChannelDiscussionGroupStatus - public let animatedEmojiStickers: [String: [StickerPackItem]] - public let forcedResourceStatus: FileMediaResourceStatus? - - public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, isScheduledMessages: Bool = false, contactsPeerIds: Set = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil) { - self.automaticDownloadPeerType = automaticDownloadPeerType - self.automaticDownloadNetworkType = automaticDownloadNetworkType - self.isRecentActions = isRecentActions - self.isScheduledMessages = isScheduledMessages - self.contactsPeerIds = contactsPeerIds - self.channelDiscussionGroup = channelDiscussionGroup - self.animatedEmojiStickers = animatedEmojiStickers - self.forcedResourceStatus = forcedResourceStatus - } - - public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool { - if lhs.automaticDownloadPeerType != rhs.automaticDownloadPeerType { - return false - } - if lhs.automaticDownloadNetworkType != rhs.automaticDownloadNetworkType { - return false - } - if lhs.isRecentActions != rhs.isRecentActions { - return false - } - if lhs.isScheduledMessages != rhs.isScheduledMessages { - return false - } - if lhs.contactsPeerIds != rhs.contactsPeerIds { - return false - } - if lhs.channelDiscussionGroup != rhs.channelDiscussionGroup { - return false - } - if lhs.animatedEmojiStickers != rhs.animatedEmojiStickers { - return false - } - if lhs.forcedResourceStatus != rhs.forcedResourceStatus { - return false - } - return true - } -} - -public enum ChatControllerInteractionLongTapAction { - case url(String) - case mention(String) - case peerMention(PeerId, String) - case command(String) - case hashtag(String) - case timecode(Double, String) - case bankCard(String) -} - -public enum ChatHistoryMessageSelection: Equatable { - case none - case selectable(selected: Bool) - - public static func ==(lhs: ChatHistoryMessageSelection, rhs: ChatHistoryMessageSelection) -> Bool { - switch lhs { - case .none: - if case .none = rhs { - return true - } else { - return false - } - case let .selectable(selected): - if case .selectable(selected) = rhs { - return true - } else { - return false - } - } - } -} - public enum ChatControllerInitialBotStartBehavior { case interactive case automatic(returnToPeerId: PeerId, scheduled: Bool) diff --git a/submodules/AccountContext/Sources/ChatHistoryLocation.swift b/submodules/AccountContext/Sources/ChatHistoryLocation.swift index e4e59fd224..5541df6c25 100644 --- a/submodules/AccountContext/Sources/ChatHistoryLocation.swift +++ b/submodules/AccountContext/Sources/ChatHistoryLocation.swift @@ -15,8 +15,8 @@ public enum ChatHistoryLocation: Equatable { } public struct ChatHistoryLocationInput: Equatable { - public var content: ChatHistoryLocation - public var id: Int32 + public let content: ChatHistoryLocation + public let id: Int32 public init(content: ChatHistoryLocation, id: Int32) { self.content = content diff --git a/submodules/AccountContext/Sources/GalleryController.swift b/submodules/AccountContext/Sources/GalleryController.swift index 24d7acda1f..c88a5fdafb 100644 --- a/submodules/AccountContext/Sources/GalleryController.swift +++ b/submodules/AccountContext/Sources/GalleryController.swift @@ -1,13 +1,5 @@ import Foundation import Postbox -import SwiftSignalKit -import TelegramCore - -public enum GalleryControllerItemSource { - case peerMessagesAtId(messageId: MessageId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic) - case standaloneMessage(Message) - case custom(messages: Signal<([Message], Int32, Bool), NoError>, messageId: MessageId, loadMore: (() -> Void)?) -} public final class GalleryControllerActionInteraction { public let openUrl: (String, Bool) -> Void diff --git a/submodules/AccountContext/Sources/MediaManager.swift b/submodules/AccountContext/Sources/MediaManager.swift index 8169ca55dc..ba033b358d 100644 --- a/submodules/AccountContext/Sources/MediaManager.swift +++ b/submodules/AccountContext/Sources/MediaManager.swift @@ -8,120 +8,6 @@ import AsyncDisplayKit import TelegramAudio import UniversalMediaPlayer -public enum PeerMessagesMediaPlaylistId: Equatable, SharedMediaPlaylistId { - case peer(PeerId) - case recentActions(PeerId) - case custom - - public func isEqual(to: SharedMediaPlaylistId) -> Bool { - if let to = to as? PeerMessagesMediaPlaylistId { - return self == to - } - return false - } -} - -public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation { - case messages(peerId: PeerId, tagMask: MessageTags, at: MessageId) - case singleMessage(MessageId) - case recentActions(Message) - case custom(messages: Signal<([Message], Int32, Bool), NoError>, at: MessageId, loadMore: (() -> Void)?) - - public var playlistId: PeerMessagesMediaPlaylistId { - switch self { - case let .messages(peerId, _, _): - return .peer(peerId) - case let .singleMessage(id): - return .peer(id.peerId) - case let .recentActions(message): - return .recentActions(message.id.peerId) - case .custom: - return .custom - } - } - - public var messageId: MessageId? { - switch self { - case let .messages(_, _, messageId), let .singleMessage(messageId), let .custom(_, messageId, _): - return messageId - default: - return nil - } - } - - public func isEqual(to: SharedMediaPlaylistLocation) -> Bool { - if let to = to as? PeerMessagesPlaylistLocation { - return self == to - } else { - return false - } - } - - public static func ==(lhs: PeerMessagesPlaylistLocation, rhs: PeerMessagesPlaylistLocation) -> Bool { - switch lhs { - case let .messages(peerId, tagMask, at): - if case .messages(peerId, tagMask, at) = rhs { - return true - } else { - return false - } - case let .singleMessage(messageId): - if case .singleMessage(messageId) = rhs { - return true - } else { - return false - } - case let .recentActions(lhsMessage): - if case let .recentActions(rhsMessage) = rhs, lhsMessage.id == rhsMessage.id { - return true - } else { - return false - } - case let .custom(_, lhsAt, _): - if case let .custom(_, rhsAt, _) = rhs, lhsAt == rhsAt { - return true - } else { - return false - } - } - } -} - -public func peerMessageMediaPlayerType(_ message: Message) -> MediaManagerPlayerType? { - func extractFileMedia(_ message: Message) -> TelegramMediaFile? { - var file: TelegramMediaFile? - for media in message.media { - if let media = media as? TelegramMediaFile { - file = media - break - } else if let media = media as? TelegramMediaWebpage, case let .Loaded(content) = media.content, let f = content.file { - file = f - break - } - } - return file - } - - if let file = extractFileMedia(message) { - if file.isVoice || file.isInstantVideo { - return .voice - } else if file.isMusic { - return .music - } - } - return nil -} - -public func peerMessagesMediaPlaylistAndItemId(_ message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? { - if isGlobalSearch { - return (PeerMessagesMediaPlaylistId.custom, PeerMessagesMediaPlaylistItemId(messageId: message.id)) - } else if isRecentActions { - return (PeerMessagesMediaPlaylistId.recentActions(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id)) - } else { - return (PeerMessagesMediaPlaylistId.peer(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id)) - } -} - public enum MediaManagerPlayerType { case voice case music diff --git a/submodules/AccountContext/Sources/OpenChatMessage.swift b/submodules/AccountContext/Sources/OpenChatMessage.swift index be3fcfd330..00f353051c 100644 --- a/submodules/AccountContext/Sources/OpenChatMessage.swift +++ b/submodules/AccountContext/Sources/OpenChatMessage.swift @@ -6,7 +6,6 @@ import SyncCore import SwiftSignalKit import Display import AsyncDisplayKit -import UniversalMediaPlayer public enum ChatControllerInteractionOpenMessageMode { case `default` @@ -19,8 +18,6 @@ public enum ChatControllerInteractionOpenMessageMode { public final class OpenChatMessageParams { public let context: AccountContext - public let chatLocation: ChatLocation? - public let chatLocationContextHolder: Atomic? public let message: Message public let standalone: Bool public let reverseMessageGalleryOrder: Bool @@ -39,13 +36,9 @@ public final class OpenChatMessageParams { public let setupTemporaryHiddenMedia: (Signal, Int, Media) -> Void public let chatAvatarHiddenMedia: (Signal, Media) -> Void public let actionInteraction: GalleryControllerActionInteraction? - public let playlistLocation: PeerMessagesPlaylistLocation? - public let gallerySource: GalleryControllerItemSource? public init( context: AccountContext, - chatLocation: ChatLocation?, - chatLocationContextHolder: Atomic?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, @@ -63,13 +56,9 @@ public final class OpenChatMessageParams { sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, setupTemporaryHiddenMedia: @escaping (Signal, Int, Media) -> Void, chatAvatarHiddenMedia: @escaping (Signal, Media) -> Void, - actionInteraction: GalleryControllerActionInteraction? = nil, - playlistLocation: PeerMessagesPlaylistLocation? = nil, - gallerySource: GalleryControllerItemSource? = nil + actionInteraction: GalleryControllerActionInteraction? = nil ) { self.context = context - self.chatLocation = chatLocation - self.chatLocationContextHolder = chatLocationContextHolder self.message = message self.standalone = standalone self.reverseMessageGalleryOrder = reverseMessageGalleryOrder @@ -88,7 +77,5 @@ public final class OpenChatMessageParams { self.setupTemporaryHiddenMedia = setupTemporaryHiddenMedia self.chatAvatarHiddenMedia = chatAvatarHiddenMedia self.actionInteraction = actionInteraction - self.playlistLocation = playlistLocation - self.gallerySource = gallerySource } } diff --git a/submodules/AccountContext/Sources/StoredMessageFromSearchPeer.swift b/submodules/AccountContext/Sources/StoredMessageFromSearchPeer.swift index 845e350199..a085442e95 100644 --- a/submodules/AccountContext/Sources/StoredMessageFromSearchPeer.swift +++ b/submodules/AccountContext/Sources/StoredMessageFromSearchPeer.swift @@ -29,7 +29,7 @@ public func storedMessageFromSearch(account: Account, message: Message) -> Signa } } - let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media) + let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media) let _ = transaction.addMessages([storeMessage], location: .Random) } @@ -46,7 +46,7 @@ public func storeMessageFromSearch(transaction: Transaction, message: Message) { } } - let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media) + let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media) let _ = transaction.addMessages([storeMessage], location: .Random) } diff --git a/submodules/AvatarNode/Sources/AvatarNode.swift b/submodules/AvatarNode/Sources/AvatarNode.swift index 9ce01b26a9..4a84a6bc84 100644 --- a/submodules/AvatarNode/Sources/AvatarNode.swift +++ b/submodules/AvatarNode/Sources/AvatarNode.swift @@ -15,7 +15,6 @@ import Emoji private let deletedIcon = UIImage(bundleImageName: "Avatar/DeletedIcon")?.precomposed() private let savedMessagesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/SavedMessagesIcon"), color: .white) private let archivedChatsIcon = UIImage(bundleImageName: "Avatar/ArchiveAvatarIcon")?.precomposed() -private let repliesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/RepliesMessagesIcon"), color: .white) public func avatarPlaceholderFont(size: CGFloat) -> UIFont { return Font.with(size: size, design: .round, traits: [.bold]) @@ -101,7 +100,6 @@ private func ==(lhs: AvatarNodeState, rhs: AvatarNodeState) -> Bool { private enum AvatarNodeIcon: Equatable { case none case savedMessagesIcon - case repliesIcon case archivedChatsIcon(hiddenByDefault: Bool) case editAvatarIcon case deletedIcon @@ -111,7 +109,6 @@ public enum AvatarNodeImageOverride: Equatable { case none case image(TelegramMediaImageRepresentation) case savedMessagesIcon - case repliesIcon case archivedChatsIcon(hiddenByDefault: Bool) case editAvatarIcon case deletedIcon @@ -311,9 +308,6 @@ public final class AvatarNode: ASDisplayNode { case .savedMessagesIcon: representation = nil icon = .savedMessagesIcon - case .repliesIcon: - representation = nil - icon = .repliesIcon case let .archivedChatsIcon(hiddenByDefault): representation = nil icon = .archivedChatsIcon(hiddenByDefault: hiddenByDefault) @@ -458,8 +452,6 @@ public final class AvatarNode: ASDisplayNode { colorsArray = grayscaleColors } else if case .savedMessagesIcon = parameters.icon { colorsArray = savedMessagesColors - } else if case .repliesIcon = parameters.icon { - colorsArray = savedMessagesColors } else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme { colorsArray = [theme.list.itemAccentColor.withAlphaComponent(0.1).cgColor, theme.list.itemAccentColor.withAlphaComponent(0.1).cgColor] } else if case let .archivedChatsIcon(hiddenByDefault) = parameters.icon, let theme = parameters.theme { @@ -514,15 +506,6 @@ public final class AvatarNode: ASDisplayNode { if let savedMessagesIcon = savedMessagesIcon { context.draw(savedMessagesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - savedMessagesIcon.size.width) / 2.0), y: floor((bounds.size.height - savedMessagesIcon.size.height) / 2.0)), size: savedMessagesIcon.size)) } - } else if case .repliesIcon = parameters.icon { - let factor = bounds.size.width / 60.0 - context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0) - context.scaleBy(x: factor, y: -factor) - context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0) - - if let repliesIcon = repliesIcon { - context.draw(repliesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - repliesIcon.size.width) / 2.0), y: floor((bounds.size.height - repliesIcon.size.height) / 2.0)), size: repliesIcon.size)) - } } else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme, !parameters.hasImage { context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0) context.scaleBy(x: 1.0, y: -1.0) diff --git a/submodules/ChatInterfaceState/BUCK b/submodules/ChatInterfaceState/BUCK deleted file mode 100644 index 13b14d8c7b..0000000000 --- a/submodules/ChatInterfaceState/BUCK +++ /dev/null @@ -1,22 +0,0 @@ -load("//Config:buck_rule_macros.bzl", "static_library") - -static_library( - name = "ChatInterfaceState", - srcs = glob([ - "Sources/**/*.swift", - ]), - deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", - "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", - "//submodules/Display:Display#shared", - "//submodules/Postbox:Postbox#shared", - "//submodules/TelegramCore:TelegramCore#shared", - "//submodules/SyncCore:SyncCore#shared", - "//submodules/TextFormat:TextFormat", - "//submodules/AccountContext:AccountContext", - ], - frameworks = [ - "$SDKROOT/System/Library/Frameworks/Foundation.framework", - "$SDKROOT/System/Library/Frameworks/UIKit.framework", - ], -) diff --git a/submodules/ChatInterfaceState/BUILD b/submodules/ChatInterfaceState/BUILD deleted file mode 100644 index 08daa544b5..0000000000 --- a/submodules/ChatInterfaceState/BUILD +++ /dev/null @@ -1,22 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") - -swift_library( - name = "ChatInterfaceState", - module_name = "ChatInterfaceState", - srcs = glob([ - "Sources/**/*.swift", - ]), - deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/Postbox:Postbox", - "//submodules/TelegramCore:TelegramCore", - "//submodules/SyncCore:SyncCore", - "//submodules/TextFormat:TextFormat", - "//submodules/AccountContext:AccountContext", - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift b/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift index 4fcfeebe60..1cb70fd606 100644 --- a/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift +++ b/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift @@ -4,7 +4,7 @@ import Display import TelegramPresentationData import ListSectionHeaderNode -public enum ChatListSearchItemHeaderType { +public enum ChatListSearchItemHeaderType: Int32 { case localPeers case members case contacts @@ -13,117 +13,15 @@ public enum ChatListSearchItemHeaderType { case globalPeers case deviceContacts case recentPeers - case phoneNumber - case exceptions - case addToExceptions - case mapAddress - case nearbyVenues - case chats - case chatTypes - case faq - case messages(Int32) - - fileprivate func title(strings: PresentationStrings) -> String { - switch self { - case .localPeers: - return strings.DialogList_SearchSectionDialogs - case .members: - return strings.Channel_Info_Members - case .contacts: - return strings.Contacts_TopSection - case .bots: - return strings.MemberSearch_BotSection - case .admins: - return strings.Channel_Management_Title - case .globalPeers: - return strings.DialogList_SearchSectionGlobal - case .deviceContacts: - return strings.Contacts_NotRegisteredSection - case .recentPeers: - return strings.DialogList_SearchSectionRecent - case .phoneNumber: - return strings.Contacts_PhoneNumber - case .exceptions: - return strings.GroupInfo_Permissions_Exceptions - case .addToExceptions: - return strings.Exceptions_AddToExceptions - case .mapAddress: - return strings.Map_AddressOnMap - case .nearbyVenues: - return strings.Map_PlacesNearby - case .chats: - return strings.Cache_ByPeerHeader - case .chatTypes: - return strings.ChatList_ChatTypesSection - case .faq: - return strings.Settings_FrequentlyAskedQuestions - case let .messages(count): - return strings.ChatList_Search_Messages(count) - } - } - - fileprivate var id: ChatListSearchItemHeaderId { - switch self { - case .localPeers: - return .localPeers - case .members: - return .members - case .contacts: - return .contacts - case .bots: - return .bots - case .admins: - return .admins - case .globalPeers: - return .globalPeers - case .deviceContacts: - return .deviceContacts - case .recentPeers: - return .recentPeers - case .phoneNumber: - return .phoneNumber - case .exceptions: - return .exceptions - case .addToExceptions: - return .addToExceptions - case .mapAddress: - return .mapAddress - case .nearbyVenues: - return .nearbyVenues - case .chats: - return .chats - case .chatTypes: - return .chatTypes - case .faq: - return .faq - case .messages: - return .messages - } - } -} - -private enum ChatListSearchItemHeaderId: Int32 { - case localPeers - case members - case contacts - case bots - case admins - case globalPeers - case deviceContacts - case recentPeers - case phoneNumber - case exceptions - case addToExceptions - case mapAddress - case nearbyVenues - case chats - case chatTypes - case faq case messages - case photos - case links - case files - case music + case phoneNumber + case exceptions + case addToExceptions + case mapAddress + case nearbyVenues + case chats + case chatTypes + case faq } public final class ChatListSearchItemHeader: ListViewItemHeader { @@ -139,7 +37,7 @@ public final class ChatListSearchItemHeader: ListViewItemHeader { public init(type: ChatListSearchItemHeaderType, theme: PresentationTheme, strings: PresentationStrings, actionTitle: String? = nil, action: (() -> Void)? = nil) { self.type = type - self.id = Int64(self.type.id.rawValue) + self.id = Int64(self.type.rawValue) self.theme = theme self.strings = strings self.actionTitle = actionTitle @@ -177,7 +75,43 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode { super.init() - self.sectionHeaderNode.title = type.title(strings: strings).uppercased() + switch type { + case .localPeers: + self.sectionHeaderNode.title = strings.DialogList_SearchSectionDialogs.uppercased() + case .members: + self.sectionHeaderNode.title = strings.Channel_Info_Members.uppercased() + case .contacts: + self.sectionHeaderNode.title = strings.Contacts_TopSection.uppercased() + case .bots: + self.sectionHeaderNode.title = strings.MemberSearch_BotSection.uppercased() + case .admins: + self.sectionHeaderNode.title = strings.Channel_Management_Title.uppercased() + case .globalPeers: + self.sectionHeaderNode.title = strings.DialogList_SearchSectionGlobal.uppercased() + case .deviceContacts: + self.sectionHeaderNode.title = strings.Contacts_NotRegisteredSection.uppercased() + case .messages: + 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() + case .exceptions: + self.sectionHeaderNode.title = strings.GroupInfo_Permissions_Exceptions.uppercased() + case .addToExceptions: + self.sectionHeaderNode.title = strings.Exceptions_AddToExceptions.uppercased() + case .mapAddress: + self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased() + case .nearbyVenues: + self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased() + case .chats: + self.sectionHeaderNode.title = strings.Cache_ByPeerHeader.uppercased() + case .chatTypes: + self.sectionHeaderNode.title = strings.ChatList_ChatTypesSection.uppercased() + case .faq: + self.sectionHeaderNode.title = strings.Settings_FrequentlyAskedQuestions.uppercased() + } + self.sectionHeaderNode.action = actionTitle self.sectionHeaderNode.activateAction = action @@ -193,7 +127,43 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode { self.actionTitle = actionTitle self.action = action - self.sectionHeaderNode.title = type.title(strings: strings).uppercased() + switch type { + case .localPeers: + self.sectionHeaderNode.title = strings.DialogList_SearchSectionDialogs.uppercased() + case .members: + self.sectionHeaderNode.title = strings.Channel_Info_Members.uppercased() + case .contacts: + self.sectionHeaderNode.title = strings.Contacts_TopSection.uppercased() + case .bots: + self.sectionHeaderNode.title = strings.MemberSearch_BotSection.uppercased() + case .admins: + self.sectionHeaderNode.title = strings.Channel_Management_Title.uppercased() + case .globalPeers: + self.sectionHeaderNode.title = strings.DialogList_SearchSectionGlobal.uppercased() + case .deviceContacts: + self.sectionHeaderNode.title = strings.Contacts_NotRegisteredSection.uppercased() + case .messages: + 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() + case .exceptions: + self.sectionHeaderNode.title = strings.GroupInfo_Permissions_Exceptions.uppercased() + case .addToExceptions: + self.sectionHeaderNode.title = strings.Exceptions_AddToExceptions.uppercased() + case .mapAddress: + self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased() + case .nearbyVenues: + self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased() + case .chats: + self.sectionHeaderNode.title = strings.Cache_ByPeerHeader.uppercased() + case .chatTypes: + self.sectionHeaderNode.title = strings.ChatList_ChatTypesSection.uppercased() + case .faq: + self.sectionHeaderNode.title = strings.Settings_FrequentlyAskedQuestions.uppercased() + } + self.sectionHeaderNode.action = actionTitle self.sectionHeaderNode.activateAction = action diff --git a/submodules/ChatListUI/BUCK b/submodules/ChatListUI/BUCK index 3a99a42a00..2694f6a128 100644 --- a/submodules/ChatListUI/BUCK +++ b/submodules/ChatListUI/BUCK @@ -46,15 +46,6 @@ static_library( "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/TooltipUI:TooltipUI", - "//submodules/ListMessageItem:ListMessageItem", - "//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge", - "//submodules/MediaPlayer:UniversalMediaPlayer", - "//submodules/TelegramUIPreferences:TelegramUIPreferences", - "//submodules/GalleryData:GalleryData", - "//submodules/InstantPageUI:InstantPageUI", - "//submodules/ListSectionHeaderNode:ListSectionHeaderNode", - "//submodules/ChatInterfaceState:ChatInterfaceState", - "//submodules/GridMessageSelectionNode:GridMessageSelectionNode", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index e0756763e3..0eb8212ebd 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -46,15 +46,6 @@ swift_library( "//submodules/ItemListPeerActionItem:ItemListPeerActionItem", "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/TooltipUI:TooltipUI", - "//submodules/ListMessageItem:ListMessageItem", - "//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge", - "//submodules/MediaPlayer:UniversalMediaPlayer", - "//submodules/GalleryData:GalleryData", - "//submodules/InstantPageUI:InstantPageUI", - "//submodules/ListSectionHeaderNode:ListSectionHeaderNode", - "//submodules/ChatInterfaceState:ChatInterfaceState", - "//submodules/ShareController:ShareController", - "//submodules/GridMessageSelectionNode:GridMessageSelectionNode", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 3c596ca0b3..44e06dca1e 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1548,7 +1548,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, let isEmpty = resolvedItems.count <= 1 || displayTabsAtBottom - if wasEmpty != isEmpty, strongSelf.displayNavigationBar { + if wasEmpty != isEmpty { strongSelf.navigationBar?.setSecondaryContentNode(isEmpty ? nil : strongSelf.tabContainerNode) if let parentController = strongSelf.parent as? TabBarController { parentController.navigationBar?.setSecondaryContentNode(isEmpty ? nil : strongSelf.tabContainerNode) @@ -1681,79 +1681,20 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, if let scrollToTop = strongSelf.scrollToTop { scrollToTop() } - if let searchContentNode = strongSelf.searchContentNode { - var updatedSearchOptionsImpl: ((ChatListSearchOptions?, Bool) -> Void)? - - if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, navigationController: strongSelf.navigationController as? NavigationController, updatedSearchOptions: { options, hasDate in - updatedSearchOptionsImpl?(options, hasDate) - }) { - let (filterContainerNode, activate) = filterContainerNodeAndActivate - strongSelf.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: false) - if let parentController = strongSelf.parent as? TabBarController { - parentController.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: true) - } - activate() - - var currentHasSuggestions = true - updatedSearchOptionsImpl = { [weak self, weak filterContainerNode] options, hasSuggestions in - guard let strongSelf = self, let strongFilterContainerNode = filterContainerNode else { - return - } - if currentHasSuggestions != hasSuggestions { - currentHasSuggestions = hasSuggestions - - var node: ASDisplayNode? - if let options = options, options.messageTags != nil && !hasSuggestions { - } else { - node = strongFilterContainerNode - } - - strongSelf.navigationBar?.setSecondaryContentNode(node, animated: false) - if let parentController = strongSelf.parent as? TabBarController { - parentController.navigationBar?.setSecondaryContentNode(node, animated: true) - } - - let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) - if let layout = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, transition: transition) - (strongSelf.parent as? TabBarController)?.updateLayout(transition: transition) - } - } - } - } + strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode) } - - let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) - strongSelf.setDisplayNavigationBar(false, transition: transition) - - (strongSelf.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.4, curve: .spring)) + strongSelf.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring)) }) } } public func deactivateSearch(animated: Bool) { if !self.displayNavigationBar { + self.setDisplayNavigationBar(true, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate) if let searchContentNode = self.searchContentNode { self.chatListDisplayNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated) } - - let filtersIsEmpty: Bool - if let (resolvedItems, displayTabsAtBottom) = tabContainerData { - filtersIsEmpty = resolvedItems.count <= 1 || displayTabsAtBottom - } else { - filtersIsEmpty = true - } - - self.navigationBar?.setSecondaryContentNode(filtersIsEmpty ? nil : self.tabContainerNode, animated: false) - if let parentController = self.parent as? TabBarController { - parentController.navigationBar?.setSecondaryContentNode(filtersIsEmpty ? nil : self.tabContainerNode, animated: animated) - } - - let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate - self.setDisplayNavigationBar(true, transition: transition) - - (self.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.4, curve: .spring)) } } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index f66e5f0318..d336726443 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -200,7 +200,7 @@ private final class ChatListShimmerNode: ASDisplayNode { }, present: { _ in }) let items = (0 ..< 2).map { _ -> ChatListItem in - return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } var itemNodes: [ChatListItemNode] = [] @@ -1147,12 +1147,12 @@ final class ChatListControllerNode: ASDisplayNode { } } - func activateSearch(placeholderNode: SearchBarPlaceholderNode, navigationController: NavigationController?, updatedSearchOptions: ((ChatListSearchOptions?, Bool) -> Void)?) -> (ASDisplayNode, () -> Void)? { + func activateSearch(placeholderNode: SearchBarPlaceholderNode) { guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else { - return nil + return } - let contentNode = ChatListSearchContainerNode(context: self.context, filter: [], groupId: self.groupId, openPeer: { [weak self] peer, dismissSearch in + self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ChatListSearchContainerNode(context: self.context, filter: [], groupId: self.groupId, openPeer: { [weak self] peer, dismissSearch in self?.requestOpenPeerFromSearch?(peer, dismissSearch) }, openDisabledPeer: { _ in }, openRecentPeerOptions: { [weak self] peer in @@ -1165,36 +1165,25 @@ final class ChatListControllerNode: ASDisplayNode { if let requestAddContact = self?.requestAddContact { requestAddContact(phoneNumber) } - }, peerContextAction: self.peerContextAction, present: { [weak self] c, a in - self?.controller?.present(c, in: .window(.root), with: a) - }, presentInGlobalOverlay: { [weak self] c, a in - self?.controller?.presentInGlobalOverlay(c, with: a) - }, navigationController: navigationController, updatedSearchOptions: { options, hasDate in - updatedSearchOptions?(options, hasDate) - }) - - self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: contentNode, cancel: { [weak self] in + }, peerContextAction: self.peerContextAction, present: { [weak self] c in + self?.controller?.present(c, in: .window(.root)) + }), cancel: { [weak self] in if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() } }) self.containerNode.accessibilityElementsHidden = true - - return (contentNode.filterContainerNode, { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: cleanNavigationBarHeight, transition: .immediate) - strongSelf.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in - if let strongSelf = self, let strongPlaceholderNode = placeholderNode { - if isSearchBar { - strongPlaceholderNode.supernode?.insertSubnode(subnode, aboveSubnode: strongPlaceholderNode) - } else { - strongSelf.insertSubnode(subnode, belowSubnode: navigationBar) - } + + self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: cleanNavigationBarHeight, transition: .immediate) + self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in + if let strongSelf = self, let strongPlaceholderNode = placeholderNode { + if isSearchBar { + strongPlaceholderNode.supernode?.insertSubnode(subnode, aboveSubnode: strongPlaceholderNode) + } else { + strongSelf.insertSubnode(subnode, belowSubnode: navigationBar) } - }, placeholder: placeholderNode) - }) + } + }, placeholder: placeholderNode) } func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) { diff --git a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift index 5424516ee6..d7c12d1c90 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift @@ -625,7 +625,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { let previousContentWidth = self.scrollNode.view.contentSize.width if self.currentParams?.presentationData.theme !== presentationData.theme { - self.selectedLineNode.image = generateImage(CGSize(width: 8.0, height: 4.0), rotatedContext: { size, context in + self.selectedLineNode.image = generateImage(CGSize(width: 7.0, height: 4.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(presentationData.theme.list.itemAccentColor.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.width))) diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 0a5714d4d3..7e9b5bf567 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -17,43 +17,12 @@ import ContactListUI import ContextUI import PhoneNumberFormat import ItemListUI -import SearchBarNode -import ListMessageItem -import TelegramBaseController -import OverlayStatusController -import UniversalMediaPlayer -import PresentationDataUtils -import AnimatedStickerNode -import AppBundle -import GalleryData -import InstantPageUI -import ChatInterfaceState -import ShareController - -private final class PassthroughContainerNode: ASDisplayNode { - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if let subnodes = self.subnodes { - for subnode in subnodes { - if let result = subnode.view.hitTest(self.view.convert(point, to: subnode.view), with: event) { - return result - } - } - } - return nil - } -} private enum ChatListRecentEntryStableId: Hashable { case topPeers case peerId(PeerId) } -private enum ChatListTokenId: Int32 { - case filter - case peer - case date -} - private enum ChatListRecentEntry: Comparable, Identifiable { case topPeers([Peer], PresentationTheme, PresentationStrings) case peer(index: Int, peer: RecentlySearchedPeer, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool) @@ -281,7 +250,7 @@ public enum ChatListSearchSectionExpandType { public enum ChatListSearchEntry: Comparable, Identifiable { case localPeer(Peer, Peer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType) case globalPeer(FoundPeer, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType) - case message(Message, RenderedPeer, CombinedPeerReadState?, ChatListPresentationData, Int32, Bool?, Bool) + case message(Message, RenderedPeer, CombinedPeerReadState?, ChatListPresentationData) case addContact(String, PresentationTheme, PresentationStrings) public var stableId: ChatListSearchEntryStableId { @@ -290,7 +259,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { return .localPeerId(peer.id) case let .globalPeer(peer, _, _, _, _, _, _, _): return .globalPeerId(peer.peer.id) - case let .message(message, _, _, _, _, _, _): + case let .message(message, _, _, _): return .messageId(message.id) case .addContact: return .addContact @@ -311,8 +280,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable { } else { return false } - case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData, lhsTotalCount, lhsSelected, lhsDisplayCustomHeader): - if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData, rhsTotalCount, rhsSelected, rhsDisplayCustomHeader) = rhs { + case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData): + if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData) = rhs { if lhsMessage.id != rhsMessage.id { return false } @@ -328,15 +297,6 @@ public enum ChatListSearchEntry: Comparable, Identifiable { if lhsCombinedPeerReadState != rhsCombinedPeerReadState { return false } - if lhsTotalCount != rhsTotalCount { - return false - } - if lhsSelected != rhsSelected { - return false - } - if lhsDisplayCustomHeader != rhsDisplayCustomHeader { - return false - } return true } else { return false @@ -376,8 +336,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable { case .message, .addContact: return true } - case let .message(lhsMessage, _, _, _, _, _, _): - if case let .message(rhsMessage, _, _, _, _, _, _) = rhs { + case let .message(lhsMessage, _, _, _): + if case let .message(rhsMessage, _, _, _) = rhs { return lhsMessage.index < rhsMessage.index } else if case .addContact = rhs { return true @@ -389,7 +349,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { } } - public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (Peer) -> Void, searchResults: [Message], searchOptions: ChatListSearchOptions?, messageContextAction: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?) -> ListViewItem { + public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void) -> ListViewItem { switch self { case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType): let primaryPeer: Peer @@ -464,7 +424,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { gesture?.cancel() } } - }, arrowAction: nil) + }) case let .globalPeer(peer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType): var enabled = true if filter.contains(.onlyWriteable) { @@ -524,14 +484,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable { peerContextAction(peer.peer, .search, node, gesture) } }) - case let .message(message, peer, readState, presentationData, totalCount, selected, displayCustomHeader): - let header = ChatListSearchItemHeader(type: .messages(totalCount), theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) - let selection: ChatHistoryMessageSelection = selected.flatMap { .selectable(selected: $0) } ?? .none - if let tags = searchOptions?.messageTags, tags != .photoOrVideo { - return ListMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .builtin(WallpaperSettings())), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(peer.peerId), interaction: listInteraction, message: message, selection: selection, displayHeader: enableHeaders && !displayCustomHeader, customHeader: nil, hintIsLink: tags == .webPage, isGlobalSearchResult: true) - } else { - return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(messages: [message], peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: header, enableContextActions: false, hiddenOffset: false, interaction: interaction) - } + case let .message(message, peer, readState, presentationData): + return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(messages: [message], peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, hiddenOffset: 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) @@ -551,20 +505,12 @@ public struct ChatListSearchContainerTransition { public let insertions: [ListViewInsertItem] public let updates: [ListViewUpdateItem] public let displayingResults: Bool - public let isEmpty: Bool - public let isLoading: Bool - public let query: String - public let animated: Bool - public init(deletions: [ListViewDeleteItem], insertions: [ListViewInsertItem], updates: [ListViewUpdateItem], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, query: String, animated: Bool) { + public init(deletions: [ListViewDeleteItem], insertions: [ListViewInsertItem], updates: [ListViewUpdateItem], displayingResults: Bool) { self.deletions = deletions self.insertions = insertions self.updates = updates self.displayingResults = displayingResults - self.isEmpty = isEmpty - self.isLoading = isLoading - self.query = query - self.animated = animated } } @@ -578,20 +524,19 @@ private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates) } -public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, searchQuery: String, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (Peer) -> Void, searchResults: [Message], searchOptions: ChatListSearchOptions?, messageContextAction: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?) -> ChatListSearchContainerTransition { +public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void) -> ChatListSearchContainerTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchResults: searchResults, searchOptions: searchOptions, messageContextAction: messageContextAction), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchResults: searchResults, searchOptions: searchOptions, messageContextAction: messageContextAction), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, interaction: interaction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, interaction: interaction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults), directionHint: nil) } - return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, isLoading: isLoading, query: searchQuery, animated: animated) + return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults) } private struct ChatListSearchContainerNodeState: Equatable { let peerIdWithRevealedOptions: PeerId? - init(peerIdWithRevealedOptions: PeerId? = nil) { self.peerIdWithRevealedOptions = peerIdWithRevealedOptions } @@ -611,11 +556,6 @@ private struct ChatListSearchContainerNodeState: Equatable { private struct ChatListSearchContainerNodeSearchState: Equatable { var expandLocalSearch: Bool = false var expandGlobalSearch: Bool = false - var selectedMessageIds: Set? - - func withUpdatedSelectedMessageIds(_ selectedMessageIds: Set?) -> ChatListSearchContainerNodeSearchState { - return ChatListSearchContainerNodeSearchState(expandLocalSearch: self.expandLocalSearch, expandGlobalSearch: self.expandGlobalSearch, selectedMessageIds: selectedMessageIds) - } } private func doesPeerMatchFilter(peer: Peer, filter: ChatListNodePeersFilter) -> Bool { @@ -641,7 +581,6 @@ private struct ChatListSearchMessagesResult { let messages: [Message] let readStates: [PeerId: CombinedPeerReadState] let hasMore: Bool - let totalCount: Int32 let state: SearchMessagesState } @@ -656,59 +595,21 @@ public enum ChatListSearchContextActionSource { case search } -public struct ChatListSearchOptions { - let peer: (PeerId, String)? - let minDate: (Int32, String)? - let maxDate: (Int32, String)? - let messageTags: MessageTags? - - func withUpdatedPeer(_ peerIdAndName: (PeerId, String)?) -> ChatListSearchOptions { - return ChatListSearchOptions(peer: peerIdAndName, minDate: self.minDate, maxDate: self.maxDate, messageTags: self.messageTags) - } - - func withUpdatedMinDate(_ minDateAndTitle: (Int32, String)?) -> ChatListSearchOptions { - return ChatListSearchOptions(peer: self.peer, minDate: minDateAndTitle, maxDate: self.maxDate, messageTags: self.messageTags) - } - - func withUpdatedMaxDate(_ maxDateAndTitle: (Int32, String)?) -> ChatListSearchOptions { - return ChatListSearchOptions(peer: self.peer, minDate: self.minDate, maxDate: maxDateAndTitle, messageTags: self.messageTags) - } - - func withUpdatedMessageTags(_ messageTags: MessageTags?) -> ChatListSearchOptions { - return ChatListSearchOptions(peer: self.peer, minDate: self.minDate, maxDate: self.maxDate, messageTags: messageTags) - } -} - public final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { private let context: AccountContext - private let peersFilter: ChatListNodePeersFilter private var interaction: ChatListNodeInteraction? - private let openMessage: (Peer, MessageId) -> Void - private let navigationController: NavigationController? - let filterContainerNode: ChatListSearchFiltersContainerNode - private var selectionPanelNode: ChatListSearchMessageSelectionPanelNode? private let recentListNode: ListView - private let loadingNode: ASImageNode private let listNode: ListView - private let mediaNode: ChatListSearchMediaNode private let dimNode: ASDisplayNode private var enqueuedRecentTransitions: [(ChatListSearchContainerRecentTransition, Bool)] = [] private var enqueuedTransitions: [(ChatListSearchContainerTransition, Bool)] = [] - private var validLayout: (ContainerViewLayout, CGFloat)? - - private var present: ((ViewController, Any?) -> Void)? - private var presentInGlobalOverlay: ((ViewController, Any?) -> Void)? - - private let activeActionDisposable = MetaDisposable() + private var validLayout: ContainerViewLayout? private let recentDisposable = MetaDisposable() private let updatedRecentPeersDisposable = MetaDisposable() - private var searchQueryValue: String? - private let searchQuery = Promise(nil) - private var searchOptionsValue: ChatListSearchOptions? - private let searchOptions = Promise(nil) + private let searchQuery = Promise() private let searchDisposable = MetaDisposable() private var presentationData: PresentationData @@ -719,95 +620,48 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo private let statePromise: ValuePromise private var searchStateValue = ChatListSearchContainerNodeSearchState() private let searchStatePromise: ValuePromise - private let searchContextValue = Atomic(value: nil) private let _isSearching = ValuePromise(false, ignoreRepeated: true) override public var isSearching: Signal { return self._isSearching.get() } - private var mediaStatusDisposable: Disposable? - private var playlistPreloadDisposable: Disposable? + private let filter: ChatListNodePeersFilter - private var playlistStateAndType: (SharedMediaPlaylistItem, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, MusicPlaybackSettingsOrder, MediaManagerPlayerType, Account)? - private var mediaAccessoryPanelContainer: PassthroughContainerNode - private var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)? - private var dismissingPanel: ASDisplayNode? - - private let updatedSearchOptions: ((ChatListSearchOptions?, Bool) -> Void)? - - private let emptyResultsTitleNode: ImmediateTextNode - private let emptyResultsTextNode: ImmediateTextNode - private let emptyResultsAnimationNode: AnimatedStickerNode - private var animationSize: CGSize = CGSize() - - public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?, updatedSearchOptions: ((ChatListSearchOptions?, Bool) -> Void)? = nil) { + public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController) -> Void) { self.context = context - self.peersFilter = filter + self.filter = filter self.dimNode = ASDisplayNode() - self.navigationController = navigationController - self.updatedSearchOptions = updatedSearchOptions - self.present = present - self.presentInGlobalOverlay = presentInGlobalOverlay + let openPeer: (Peer, Bool) -> Void = { peer, value in + originalOpenPeer(peer, value) + + if peer.id.namespace != Namespaces.Peer.SecretChat { + addAppLogEvent(postbox: context.account.postbox, time: Date().timeIntervalSince1970, type: "search_global_open_peer", peerId: peer.id, data: .dictionary([:])) + } + } + + let openMessage: (Peer, MessageId) -> Void = { peer, messageId in + originalOpenMessage(peer, messageId) + + if peer.id.namespace != Namespaces.Peer.SecretChat { + addAppLogEvent(postbox: context.account.postbox, time: Date().timeIntervalSince1970, type: "search_global_open_message", peerId: peer.id, data: .dictionary(["msg_id": .number(Double(messageId.id))])) + } + } - self.openMessage = originalOpenMessage - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationDataPromise = Promise(ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations)) - self.filterContainerNode = ChatListSearchFiltersContainerNode() - self.recentListNode = ListView() self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor - - var openMediaMessageImpl: ((Message, ChatControllerInteractionOpenMessageMode) -> Void)? - var messageContextActionImpl: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)? - var toggleMessageSelectionImpl: ((MessageId, Bool) -> Void)? - var transitionNodeImpl: ((MessageId, Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?)? - var addToTransitionSurfaceImpl: ((UIView) -> Void)? - - self.mediaNode = ChatListSearchMediaNode(context: self.context, contentType: .photoOrVideo, openMessage: { message, mode in - openMediaMessageImpl?(message, mode) - }, messageContextAction: { message, sourceNode, sourceRect, gesture in - messageContextActionImpl?(message, sourceNode, sourceRect, gesture) - }, toggleMessageSelection: { messageId, selected in - toggleMessageSelectionImpl?(messageId, selected) - }) - - self.loadingNode = ASImageNode() - self.listNode = ListView() self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor self.statePromise = ValuePromise(self.stateValue, ignoreRepeated: true) self.searchStatePromise = ValuePromise(self.searchStateValue, ignoreRepeated: true) - self.mediaAccessoryPanelContainer = PassthroughContainerNode() - self.mediaAccessoryPanelContainer.clipsToBounds = true - - self.emptyResultsTitleNode = ImmediateTextNode() - self.emptyResultsTitleNode.displaysAsynchronously = false - self.emptyResultsTitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.ChatList_Search_NoResults, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.freeTextColor) - self.emptyResultsTitleNode.textAlignment = .center - self.emptyResultsTitleNode.isHidden = true - - self.emptyResultsTextNode = ImmediateTextNode() - self.emptyResultsTextNode.displaysAsynchronously = false - self.emptyResultsTextNode.maximumNumberOfLines = 0 - self.emptyResultsTextNode.textAlignment = .center - self.emptyResultsTextNode.isHidden = true - - self.emptyResultsAnimationNode = AnimatedStickerNode() - self.emptyResultsAnimationNode.isHidden = true - super.init() - if let path = getAppBundle().path(forResource: "ChatListNoResults", ofType: "tgs") { - self.emptyResultsAnimationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) - self.animationSize = CGSize(width: 124.0, height: 124.0) - } - self.dimNode.backgroundColor = filter.contains(.excludeRecent) ? UIColor.black.withAlphaComponent(0.5) : self.presentationData.theme.chatList.backgroundColor self.backgroundColor = filter.contains(.excludeRecent) ? nil : self.presentationData.theme.chatList.backgroundColor @@ -815,17 +669,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.addSubnode(self.dimNode) self.addSubnode(self.recentListNode) self.addSubnode(self.listNode) - self.addSubnode(self.loadingNode) - self.addSubnode(self.mediaNode) - - self.addSubnode(self.mediaAccessoryPanelContainer) - - self.addSubnode(self.emptyResultsAnimationNode) - self.addSubnode(self.emptyResultsTitleNode) - self.addSubnode(self.emptyResultsTextNode) let searchContext = Promise(nil) - let searchContextValue = self.searchContextValue + let searchContextValue = Atomic(value: nil) let updateSearchContext: ((ChatListSearchMessagesContext?) -> (ChatListSearchMessagesContext?, Bool)) -> Void = { f in var shouldUpdate = false let updated = searchContextValue.modify { current in @@ -843,15 +689,32 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } self.listNode.isHidden = true - self.mediaNode.isHidden = true + self.listNode.visibleBottomContentOffsetChanged = { offset in + guard case let .known(value) = offset, value < 100.0 else { + return + } + updateSearchContext { previous in + guard let previous = previous else { + return (nil, false) + } + if previous.loadMoreIndex != nil { + return (previous, false) + } + guard let last = previous.result.messages.last else { + return (previous, false) + } + return (ChatListSearchMessagesContext(result: previous.result, loadMoreIndex: last.index), true) + } + } self.recentListNode.isHidden = filter.contains(.excludeRecent) - + let currentRemotePeers = Atomic<([FoundPeer], [FoundPeer])?>(value: nil) + let presentationDataPromise = self.presentationDataPromise let searchStatePromise = self.searchStatePromise - let foundItems = combineLatest(self.searchQuery.get(), self.searchOptions.get()) - |> mapToSignal { query, options -> Signal<([ChatListSearchEntry], Bool)?, NoError> in - if query == nil && options == nil { + let foundItems = self.searchQuery.get() + |> mapToSignal { query -> Signal<([ChatListSearchEntry], Bool)?, NoError> in + guard let query = query, !query.isEmpty else { let _ = currentRemotePeers.swap(nil) return .single(nil) } @@ -859,68 +722,71 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> take(1) - let foundLocalPeers: Signal<(peers: [RenderedPeer], unread: [PeerId: (Int32, Bool)]), NoError> - - if let query = query { - foundLocalPeers = context.account.postbox.searchPeers(query: query.lowercased()) - |> mapToSignal { local -> Signal<([PeerView], [RenderedPeer]), NoError> in - return combineLatest(local.map { context.account.postbox.peerView(id: $0.peerId) }) |> map { views in - return (views, local) - } + let foundLocalPeers = context.account.postbox.searchPeers(query: query.lowercased()) + |> mapToSignal { local -> Signal<([PeerView], [RenderedPeer]), NoError> in + return combineLatest(local.map { context.account.postbox.peerView(id: $0.peerId) }) |> map { views in + return (views, local) } - |> mapToSignal { viewsAndPeers -> Signal<(peers: [RenderedPeer], unread: [PeerId: (Int32, Bool)]), NoError> in - return context.account.postbox.unreadMessageCountsView(items: viewsAndPeers.0.map {.peer($0.peerId)}) |> map { values in - var unread: [PeerId: (Int32, Bool)] = [:] - for peerView in viewsAndPeers.0 { - var isMuted: Bool = false - if let nofiticationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - switch nofiticationSettings.muteState { - case .muted: - isMuted = true - default: - break - } - } - - let unreadCount = values.count(for: .peer(peerView.peerId)) - if let unreadCount = unreadCount, unreadCount > 0 { - unread[peerView.peerId] = (unreadCount, isMuted) + } + |> mapToSignal { viewsAndPeers -> Signal<(peers: [RenderedPeer], unread: [PeerId: (Int32, Bool)]), NoError> in + return context.account.postbox.unreadMessageCountsView(items: viewsAndPeers.0.map {.peer($0.peerId)}) |> map { values in + var unread: [PeerId: (Int32, Bool)] = [:] + for peerView in viewsAndPeers.0 { + var isMuted: Bool = false + if let nofiticationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { + switch nofiticationSettings.muteState { + case .muted: + isMuted = true + default: + break } } - return (peers: viewsAndPeers.1, unread: unread) + + let unreadCount = values.count(for: .peer(peerView.peerId)) + if let unreadCount = unreadCount, unreadCount > 0 { + unread[peerView.peerId] = (unreadCount, isMuted) + } } + return (peers: viewsAndPeers.1, unread: unread) } - } else { - foundLocalPeers = .single((peers: [], unread: [:])) } let foundRemotePeers: Signal<([FoundPeer], [FoundPeer], Bool), NoError> let currentRemotePeersValue = currentRemotePeers.with { $0 } ?? ([], []) - if let query = query { - foundRemotePeers = ( - .single((currentRemotePeersValue.0, currentRemotePeersValue.1, true)) - |> then( - searchPeers(account: context.account, query: query) - |> map { ($0.0, $0.1, false) } - |> delay(0.2, queue: Queue.concurrentDefaultQueue()) - ) + foundRemotePeers = ( + .single((currentRemotePeersValue.0, currentRemotePeersValue.1, true)) + |> then( + searchPeers(account: context.account, query: query) + |> map { ($0.0, $0.1, false) } + |> delay(0.2, queue: Queue.concurrentDefaultQueue()) ) - } else { - foundRemotePeers = .single(([], [], false)) - } + ) let location: SearchMessagesLocation - if let options = options { - if let (peerId, _) = options.peer { - location = .peer(peerId: peerId, fromId: nil, tags: options.messageTags, topMsgId: nil, minDate: options.minDate?.0, maxDate: options.maxDate?.0) - } else { - - location = .general(tags: options.messageTags, minDate: options.minDate?.0, maxDate: options.maxDate?.0) - } + let messageTags: MessageTags? + if query.hasPrefix("%media ") { + messageTags = .photoOrVideo + } else if query.hasPrefix("%photo ") { + messageTags = .photo + } else if query.hasPrefix("%video ") { + messageTags = .video + } else if query.hasPrefix("%file ") { + messageTags = .file + } else if query.hasPrefix("%music ") { + messageTags = .music + } else if query.hasPrefix("%link ") { + messageTags = .webPage + } else if query.hasPrefix("%gif ") { + messageTags = .gif } else { - location = .general(tags: nil, minDate: nil, maxDate: nil) + messageTags = nil + } + location = .general(tags: messageTags) + + var finalQuery = query + if let _ = messageTags, let index = finalQuery.firstIndex(of: " ") { + finalQuery = String(finalQuery.suffix(from: finalQuery.index(after: index))) } - let finalQuery = query ?? "" updateSearchContext { _ in return (nil, true) } @@ -929,21 +795,21 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo foundRemoteMessages = .single((([], [:], 0), false)) } else { if !finalQuery.isEmpty { - addAppLogEvent(postbox: context.account.postbox, type: "search_global_query") + addAppLogEvent(postbox: context.account.postbox, time: Date().timeIntervalSince1970, type: "search_global_query", peerId: nil, data: .dictionary([:])) } let searchSignal = searchMessages(account: context.account, location: location, query: finalQuery, state: nil, limit: 50) |> map { result, updatedState -> ChatListSearchMessagesResult in - return ChatListSearchMessagesResult(query: finalQuery, messages: result.messages.sorted(by: { $0.index > $1.index }), readStates: result.readStates, hasMore: !result.completed, totalCount: result.totalCount, state: updatedState) + return ChatListSearchMessagesResult(query: finalQuery, messages: result.messages.sorted(by: { $0.index > $1.index }), readStates: result.readStates, hasMore: !result.completed, state: updatedState) } let loadMore = searchContext.get() |> mapToSignal { searchContext -> Signal<(([Message], [PeerId: CombinedPeerReadState], Int32), Bool), NoError> in - if let searchContext = searchContext, searchContext.result.hasMore { + if let searchContext = searchContext { if let _ = searchContext.loadMoreIndex { return searchMessages(account: context.account, location: location, query: finalQuery, state: searchContext.result.state, limit: 80) |> map { result, updatedState -> ChatListSearchMessagesResult in - return ChatListSearchMessagesResult(query: finalQuery, messages: result.messages.sorted(by: { $0.index > $1.index }), readStates: result.readStates, hasMore: !result.completed, totalCount: result.totalCount, state: updatedState) + return ChatListSearchMessagesResult(query: finalQuery, messages: result.messages.sorted(by: { $0.index > $1.index }), readStates: result.readStates, hasMore: !result.completed, state: updatedState) } |> mapToSignal { foundMessages -> Signal<(([Message], [PeerId: CombinedPeerReadState], Int32), Bool), NoError> in updateSearchContext { previous in @@ -953,7 +819,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo return .complete() } } else { - return .single(((searchContext.result.messages, searchContext.result.readStates, searchContext.result.totalCount), false)) + return .single(((searchContext.result.messages, searchContext.result.readStates, 0), false)) } } else { return .complete() @@ -967,7 +833,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo updateSearchContext { _ in return (ChatListSearchMessagesContext(result: foundMessages, loadMoreIndex: nil), true) } - return ((foundMessages.messages, foundMessages.readStates, foundMessages.totalCount), false) + return ((foundMessages.messages, foundMessages.readStates, 0), false) } |> delay(0.2, queue: Queue.concurrentDefaultQueue()) |> then(loadMore) @@ -977,7 +843,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo let resolvedMessage = .single(nil) |> then(context.sharedContext.resolveUrl(account: context.account, url: finalQuery) |> mapToSignal { resolvedUrl -> Signal in - if case let .channelMessage(_, messageId) = resolvedUrl { + if case let .channelMessage(peerId, messageId) = resolvedUrl { return downloadMessage(postbox: context.account.postbox, network: context.account.network, messageId: messageId) } else { return .single(nil) @@ -986,8 +852,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo return combineLatest(accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, presentationDataPromise.get(), searchStatePromise.get(), resolvedMessage) |> map { accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, presentationData, searchState, resolvedMessage -> ([ChatListSearchEntry], Bool)? in - let isSearching = foundRemotePeers.2 || foundRemoteMessages.1 var entries: [ChatListSearchEntry] = [] + let isSearching = foundRemotePeers.2 || foundRemoteMessages.1 var index = 0 let _ = currentRemotePeers.swap((foundRemotePeers.0, foundRemotePeers.1)) @@ -1053,16 +919,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo existingPeerIds.removeAll() - let localExpandType: ChatListSearchSectionExpandType - if let _ = options?.messageTags { - if totalNumberOfLocalPeers > 3 { - localExpandType = searchState.expandLocalSearch ? .collapse : .expand - } else { - localExpandType = .none - } - } else { - localExpandType = .none - } + let localExpandType: ChatListSearchSectionExpandType = .none let globalExpandType: ChatListSearchSectionExpandType if totalNumberOfGlobalPeers > 3 { globalExpandType = searchState.expandGlobalSearch ? .collapse : .expand @@ -1070,52 +927,52 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo globalExpandType = .none } - let lowercasedQuery = finalQuery.lowercased() - if lowercasedQuery.count > 1 && (presentationData.strings.DialogList_SavedMessages.lowercased().hasPrefix(lowercasedQuery) || "saved messages".hasPrefix(lowercasedQuery)) { - if !existingPeerIds.contains(accountPeer.id), filteredPeer(accountPeer, accountPeer) { - existingPeerIds.insert(accountPeer.id) - entries.append(.localPeer(accountPeer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType)) - index += 1 - } - } - - var numberOfLocalPeers = 0 - for renderedPeer in foundLocalPeers.peers { - if case .expand = localExpandType, numberOfLocalPeers >= 3 { - break + if let _ = messageTags { + } else { + let lowercasedQuery = finalQuery.lowercased() + if presentationData.strings.DialogList_SavedMessages.lowercased().hasPrefix(lowercasedQuery) || "saved messages".hasPrefix(lowercasedQuery) { + if !existingPeerIds.contains(accountPeer.id), filteredPeer(accountPeer, accountPeer) { + existingPeerIds.insert(accountPeer.id) + entries.append(.localPeer(accountPeer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType)) + index += 1 + } } - if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != context.account.peerId, filteredPeer(peer, accountPeer) { - if !existingPeerIds.contains(peer.id) { - existingPeerIds.insert(peer.id) - var associatedPeer: Peer? - if let associatedPeerId = peer.associatedPeerId { - associatedPeer = renderedPeer.peers[associatedPeerId] + var numberOfLocalPeers = 0 + for renderedPeer in foundLocalPeers.peers { + if case .expand = localExpandType, numberOfLocalPeers >= 5 { + break + } + + if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != context.account.peerId, filteredPeer(peer, accountPeer) { + if !existingPeerIds.contains(peer.id) { + existingPeerIds.insert(peer.id) + var associatedPeer: Peer? + if let associatedPeerId = peer.associatedPeerId { + associatedPeer = renderedPeer.peers[associatedPeerId] + } + entries.append(.localPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType)) + index += 1 + numberOfLocalPeers += 1 } - entries.append(.localPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType)) + } + } + + for peer in foundRemotePeers.0 { + if case .expand = localExpandType, numberOfLocalPeers >= 5 { + break + } + + if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, accountPeer) { + existingPeerIds.insert(peer.peer.id) + entries.append(.localPeer(peer.peer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType)) index += 1 numberOfLocalPeers += 1 } } - } - - for peer in foundRemotePeers.0 { - if case .expand = localExpandType, numberOfLocalPeers >= 3 { - break - } - - if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, accountPeer) { - existingPeerIds.insert(peer.peer.id) - entries.append(.localPeer(peer.peer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType)) - index += 1 - numberOfLocalPeers += 1 - } - } - var numberOfGlobalPeers = 0 - index = 0 - if let _ = options?.messageTags { - } else { + var numberOfGlobalPeers = 0 + index = 0 for peer in foundRemotePeers.1 { if case .expand = globalExpandType, numberOfGlobalPeers >= 3 { break @@ -1137,30 +994,25 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo peer = RenderedPeer(peer: channelPeer) } } - entries.append(.message(message, peer, nil, presentationData, 1, nil, true)) + entries.append(.message(message, peer, nil, presentationData)) index += 1 } - var firstHeaderId: Int64? if !foundRemotePeers.2 { index = 0 for message in foundRemoteMessages.0.0 { - let headerId = listMessageDateHeaderId(timestamp: message.timestamp) - if firstHeaderId == nil { - firstHeaderId = headerId - } var peer = RenderedPeer(message: message) if let group = message.peers[message.id.peerId] as? TelegramGroup, let migrationReference = group.migrationReference { if let channelPeer = message.peers[migrationReference.peerId] { peer = RenderedPeer(peer: channelPeer) } } - entries.append(.message(message, peer, foundRemoteMessages.0.1[message.id.peerId], presentationData, foundRemoteMessages.0.2, searchState.selectedMessageIds?.contains(message.id), headerId == firstHeaderId)) + entries.append(.message(message, peer, foundRemoteMessages.0.1[message.id.peerId], presentationData)) index += 1 } } - if let _ = addContact, isViablePhoneNumber(finalQuery) { + if addContact != nil && isViablePhoneNumber(finalQuery) { entries.append(.addContact(finalQuery, presentationData.theme, presentationData.strings)) } @@ -1168,120 +1020,11 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } } - let foundMessages = searchContext.get() |> map { searchContext -> ([Message], Int32, Bool) in - if let result = searchContext?.result { - return (result.messages, result.totalCount, result.hasMore) - } else { - return ([], 0, false) - } - } - - let loadMore = { - updateSearchContext { previous in - guard let previous = previous else { - return (nil, false) - } - if previous.loadMoreIndex != nil { - return (previous, false) - } - guard let last = previous.result.messages.last else { - return (previous, false) - } - return (ChatListSearchMessagesContext(result: previous.result, loadMoreIndex: last.index), true) - } - } - - let openUrlImpl: (String) -> Void = { url in - openUserGeneratedUrl(context: context, url: url, concealed: false, present: { c in - present(c, nil) - }, openResolved: { [weak self] resolved in - context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, openPeer: { peerId, navigation in - // self?.openPeer(peerId: peerId, navigation: navigation) - }, sendFile: nil, - sendSticker: nil, - present: { c, a in - present(c, a) - }, dismissInput: { - self?.dismissInput() - }, contentContext: nil) - }) - } - - openMediaMessageImpl = { [weak self] message, mode in - let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, mode: mode, navigationController: navigationController, dismissInput: { - self?.dismissInput() - }, present: { c, a in - present(c, a) - }, transitionNode: { messageId, media in - return transitionNodeImpl?(messageId, media) - }, addToTransitionSurface: { view in - addToTransitionSurfaceImpl?(view) - }, openUrl: { url in - openUrlImpl(url) - }, openPeer: { peer, navigation in - //self?.openPeer(peerId: peer.id, navigation: navigation) - }, callPeer: { _, _ in - }, enqueueMessage: { _ in - }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, gallerySource: .custom(messages: foundMessages, messageId: message.id, loadMore: { - loadMore() - }))) - } - - messageContextActionImpl = { [weak self] message, sourceNode, sourceRect, gesture in - if let strongSelf = self { - strongSelf.messageContextActions(message, node: sourceNode, rect: sourceRect, gesture: gesture) - } - } - - toggleMessageSelectionImpl = { [weak self] messageId, selected in - if let strongSelf = self { - strongSelf.updateSearchState { state in - var selectedMessageIds = state.selectedMessageIds ?? Set() - if selected { - selectedMessageIds.insert(messageId) - } else { - selectedMessageIds.remove(messageId) - } - return state.withUpdatedSelectedMessageIds(selectedMessageIds) - } - } - } - - transitionNodeImpl = { [weak self] messageId, media in - if let strongSelf = self { - return strongSelf.mediaNode.transitionNodeForGallery(messageId: messageId, media: media) - } else { - return nil - } - } - - addToTransitionSurfaceImpl = { [weak self] view in - if let strongSelf = self { - strongSelf.mediaNode.addToTransitionSurface(view: view) - } - } - let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil) - let openPeer: (Peer, Bool) -> Void = { peer, value in - originalOpenPeer(peer, value) - - if peer.id.namespace != Namespaces.Peer.SecretChat { - addAppLogEvent(postbox: context.account.postbox, type: "search_global_open_peer", peerId: peer.id) - } - } - - let openMessage: (Peer, MessageId) -> Void = { peer, messageId in - originalOpenMessage(peer, messageId) - - if peer.id.namespace != Namespaces.Peer.SecretChat { - addAppLogEvent(postbox: context.account.postbox, type: "search_global_open_message", peerId: peer.id, data: .dictionary(["msg_id": .number(Double(messageId.id))])) - } - } - let interaction = ChatListNodeInteraction(activateSearch: { }, peerSelected: { [weak self] peer, _ in - self?.dismissInput() + self?.view.endEditing(true) openPeer(peer, false) let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start() self?.listNode.clearHighlightAnimated(true) @@ -1289,14 +1032,14 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo }, togglePeerSelected: { _ in }, additionalCategorySelected: { _ in }, messageSelected: { [weak self] peer, message, _ in - self?.dismissInput() + self?.view.endEditing(true) if let peer = message.peers[message.id.peerId] { openMessage(peer, message.id) } self?.listNode.clearHighlightAnimated(true) }, groupSelected: { _ in }, addContact: { [weak self] phoneNumber in - self?.dismissInput() + self?.view.endEditing(true) addContact?(phoneNumber) self?.listNode.clearHighlightAnimated(true) }, setPeerIdWithRevealedOptions: { [weak self] peerId, fromPeerId in @@ -1330,7 +1073,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo gesture?.cancel() } }, present: { c in - present(c, nil) + present(c) }) self.interaction = interaction @@ -1435,105 +1178,16 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } })) - let listInteraction = ListMessageItemInteraction(openMessage: { [weak self] message, mode -> Bool in - self?.dismissInput() - return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, mode: mode, navigationController: navigationController, dismissInput: { [weak self] in - self?.dismissInput() - }, present: { c, a in - present(c, a) - }, transitionNode: { [weak self] messageId, media in - var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? - if let strongSelf = self { - strongSelf.listNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ListMessageNode { - if let result = itemNode.transitionNode(id: messageId, media: media) { - transitionNode = result - } - } - } - } - return transitionNode - }, addToTransitionSurface: { view in - self?.view.addSubview(view) - }, openUrl: { url in - openUrlImpl(url) - }, openPeer: { peer, navigation in - // self?.openPeer(peerId: peer.id, navigation: navigation) - }, callPeer: { _, _ in - }, enqueueMessage: { _ in - }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: .custom(messages: foundMessages, at: message.id, loadMore: { - loadMore() - }), gallerySource: .custom(messages: foundMessages, messageId: message.id, loadMore: { - loadMore() - }))) - }, openMessageContextMenu: { [weak self] message, bool, node, rect, gesture in - self?.messageContextAction(message, node: node, rect: rect, gesture: gesture) - }, toggleMessagesSelection: { messageId, selected in - if let messageId = messageId.first { - toggleMessageSelectionImpl?(messageId, selected) - } - }, openUrl: { url, _, _, message in - openUrlImpl(url) - }, openInstantPage: { message, data in - if let (webpage, anchor) = instantPageAndAnchor(message: message) { - let pageController = InstantPageController(context: context, webPage: webpage, sourcePeerType: .channel, anchor: anchor) - navigationController?.pushViewController(pageController) - } - }, longTap: { action, message in - }, getHiddenMedia: { - return [:] - }) - - let previousSearchState = Atomic(value: nil) self.searchDisposable.set((foundItems |> deliverOnMainQueue).start(next: { [weak self] entriesAndFlags in if let strongSelf = self { - let previousState = previousSearchState.swap(strongSelf.searchStateValue) - - let isSearching = entriesAndFlags?.1 ?? false - strongSelf._isSearching.set(isSearching) - - if strongSelf.searchOptionsValue?.messageTags == .photoOrVideo { - var totalCount: Int32 = 0 - if let entries = entriesAndFlags?.0 { - for entry in entries { - if case let .message(_, _, _, _, count, _, _) = entry { - totalCount = count - break - } - } - } - var entries: [ChatListSearchEntry]? = entriesAndFlags?.0 ?? [] - if isSearching && (entries?.isEmpty ?? true) { - entries = nil - } - strongSelf.mediaNode.updateHistory(entries: entries, totalCount: totalCount, updateType: .Initial) - } - - var entriesAndFlags = entriesAndFlags - - var peers: [Peer] = [] - if let entries = entriesAndFlags?.0 { - var filteredEntries: [ChatListSearchEntry] = [] - for entry in entries { - if case let .localPeer(peer, _, _, _, _, _, _, _, _) = entry { - peers.append(peer) - } else { - filteredEntries.append(entry) - } - } - - if strongSelf.searchOptionsValue?.messageTags != nil || strongSelf.searchOptionsValue?.maxDate != nil || strongSelf.searchOptionsValue?.peer != nil { - entriesAndFlags?.0 = filteredEntries - } - } + strongSelf._isSearching.set(entriesAndFlags?.1 ?? false) let previousEntries = previousSearchItems.swap(entriesAndFlags?.0) - let newEntries = entriesAndFlags?.0 ?? [] - let animated = (previousState?.selectedMessageIds == nil) != (strongSelf.searchStateValue.selectedMessageIds == nil) let firstTime = previousEntries == nil - let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags?.0 != nil, isEmpty: !isSearching && (entriesAndFlags?.0.isEmpty ?? false), isLoading: isSearching, animated: animated, searchQuery: strongSelf.searchQueryValue ?? "", context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: filter, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: { + let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entriesAndFlags?.0 ?? [], displayingResults: entriesAndFlags?.0 != nil, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: filter, interaction: interaction, peerContextAction: peerContextAction, + toggleExpandLocalResults: { guard let strongSelf = self else { return } @@ -1551,33 +1205,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo state.expandGlobalSearch = !state.expandGlobalSearch return state } - }, searchPeer: { peer in - guard let strongSelf = self else { - return - } - strongSelf.updateSearchOptions(strongSelf.currentSearchOptions.withUpdatedPeer((peer.id, peer.compactDisplayTitle)), clearQuery: true) - strongSelf.dismissInput?() - }, searchResults: newEntries.compactMap { entry -> Message? in - if case let .message(message, _, _, _, _, _, _) = entry { - return message - } else { - return nil - } - }, searchOptions: strongSelf.searchOptionsValue, messageContextAction: { message, node, rect, gesture in - guard let strongSelf = self else { - return - } - strongSelf.messageContextAction(message, node: node, rect: rect, gesture: gesture) }) strongSelf.enqueueTransition(transition, firstTime: firstTime) - - let previousPossiblePeers = strongSelf.possiblePeers - strongSelf.possiblePeers = Array(peers.prefix(10)) - - strongSelf.updatedSearchOptions?(strongSelf.searchOptionsValue, strongSelf.hasSuggestions) - if let (layout, navigationBarHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } } })) @@ -1601,104 +1230,13 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.listNode.beganInteractiveDragging = { [weak self] in self?.dismissInput?() } - - self.mediaNode.beganInteractiveDragging = { [weak self] in - self?.dismissInput?() - } - - self.listNode.visibleBottomContentOffsetChanged = { offset in - guard case let .known(value) = offset, value < 160.0 else { - return - } - loadMore() - } - - self.mediaNode.loadMore = { - loadMore() - } - - self.filterContainerNode.filterPressed = { [weak self] filter in - guard let strongSelf = self else { - return - } - var messageTags = strongSelf.currentSearchOptions.messageTags - var maxDate = strongSelf.currentSearchOptions.maxDate - var peer = strongSelf.currentSearchOptions.peer - var clearQuery: Bool = false - switch filter { - case .media: - messageTags = .photoOrVideo - case .links: - messageTags = .webPage - case .files: - messageTags = .file - case .music: - messageTags = .music - case .voice: - messageTags = .voiceOrInstantVideo - case let .date(date, title): - maxDate = (date, title) - clearQuery = true - case let .peer(id, name): - peer = (id, name) - clearQuery = true - } - strongSelf.updateSearchOptions(strongSelf.currentSearchOptions.withUpdatedMessageTags(messageTags).withUpdatedMaxDate(maxDate).withUpdatedPeer(peer), clearQuery: clearQuery) - } - - self.mediaStatusDisposable = (combineLatest(context.sharedContext.mediaManager.globalMediaPlayerState, self.searchOptions.get()) - |> mapToSignal { playlistStateAndType, searchOptions -> Signal<(Account, SharedMediaPlayerItemPlaybackState, MediaManagerPlayerType)?, NoError> in - if let (account, state, type) = playlistStateAndType { - switch state { - case let .state(state): - if let playlistId = state.playlistId as? PeerMessagesMediaPlaylistId, case .custom = playlistId { - if case .music = type, searchOptions?.messageTags == .music { - return .single((account, state, type)) - } else if case .voice = type, searchOptions?.messageTags == .voiceOrInstantVideo { - return .single((account, state, type)) - } else { - return .single(nil) |> delay(0.1, queue: .mainQueue()) - } - } else { - return .single(nil) |> delay(0.1, queue: .mainQueue()) - } - case .loading: - return .single(nil) |> delay(0.1, queue: .mainQueue()) - } - } else { - return .single(nil) - } - } - |> deliverOnMainQueue).start(next: { [weak self] playlistStateAndType in - guard let strongSelf = self else { - return - } - if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.1.item) || - !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.1, playlistStateAndType?.1.previousItem) || - !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.2, playlistStateAndType?.1.nextItem) || - strongSelf.playlistStateAndType?.3 != playlistStateAndType?.1.order || strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 { - - if let playlistStateAndType = playlistStateAndType { - strongSelf.playlistStateAndType = (playlistStateAndType.1.item, playlistStateAndType.1.previousItem, playlistStateAndType.1.nextItem, playlistStateAndType.1.order, playlistStateAndType.2, playlistStateAndType.0) - } else { - strongSelf.playlistStateAndType = nil - } - - if let (layout, navigationBarHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.4, curve: .spring)) - } - } - }) } deinit { - self.activeActionDisposable.dispose() self.updatedRecentPeersDisposable.dispose() self.recentDisposable.dispose() self.searchDisposable.dispose() self.presentationDataDisposable?.dispose() - self.mediaStatusDisposable?.dispose() - self.playlistPreloadDisposable?.dispose() } override public func didLoad() { @@ -1712,80 +1250,10 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } } - private var currentSearchOptions: ChatListSearchOptions { - return self.searchOptionsValue ?? ChatListSearchOptions(peer: nil, minDate: nil, maxDate: nil, messageTags: nil) - } - - public override func searchTokensUpdated(tokens: [SearchBarToken]) { - var updatedOptions = self.searchOptionsValue - var tokensIdSet = Set() - for token in tokens { - tokensIdSet.insert(token.id) - } - if !tokensIdSet.contains(ChatListTokenId.filter.rawValue) && updatedOptions?.messageTags != nil { - updatedOptions = updatedOptions?.withUpdatedMessageTags(nil) - } - if !tokensIdSet.contains(ChatListTokenId.date.rawValue) && updatedOptions?.maxDate != nil { - updatedOptions = updatedOptions?.withUpdatedMaxDate(nil) - } - if !tokensIdSet.contains(ChatListTokenId.peer.rawValue) && updatedOptions?.peer != nil { - updatedOptions = updatedOptions?.withUpdatedPeer(nil) - } - self.updateSearchOptions(updatedOptions) - } - - private func updateSearchOptions(_ options: ChatListSearchOptions?, clearQuery: Bool = false) { - self.searchOptionsValue = options - self.searchOptions.set(.single(options)) - - var tokens: [SearchBarToken] = [] - if let messageTags = options?.messageTags { - var title: String? - var icon: UIImage? - if messageTags == .photoOrVideo { - title = self.presentationData.strings.ChatList_Search_FilterMedia - icon = UIImage(bundleImageName: "Chat List/Search/Media") - } else if messageTags == .webPage { - title = self.presentationData.strings.ChatList_Search_FilterLinks - icon = UIImage(bundleImageName: "Chat List/Search/Links") - } else if messageTags == .file { - title = self.presentationData.strings.ChatList_Search_FilterFiles - icon = UIImage(bundleImageName: "Chat List/Search/Files") - } else if messageTags == .music { - title = self.presentationData.strings.ChatList_Search_FilterMusic - icon = UIImage(bundleImageName: "Chat List/Search/Music") - } else if messageTags == .voiceOrInstantVideo { - title = self.presentationData.strings.ChatList_Search_FilterVoice - icon = UIImage(bundleImageName: "Chat List/Search/Voice") - } - - if let title = title { - tokens.append(SearchBarToken(id: ChatListTokenId.filter.rawValue, icon: icon, title: title)) - } - } - - if let (_, peerName) = options?.peer { - tokens.append(SearchBarToken(id: ChatListTokenId.peer.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/User"), title: peerName)) - } - - if let (_, dateTitle) = options?.maxDate { - tokens.append(SearchBarToken(id: ChatListTokenId.date.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Calendar"), title: dateTitle)) - - self.possibleDates = [] - } - - if clearQuery { - self.setQuery?(nil, tokens, "") - } else { - self.setQuery?(nil, tokens, self.searchQueryValue ?? "") - } - - self.updatedSearchOptions?(options, self.hasSuggestions) - } private func updateTheme(theme: PresentationTheme) { - self.backgroundColor = self.peersFilter.contains(.excludeRecent) ? nil : theme.chatList.backgroundColor - self.dimNode.backgroundColor = self.peersFilter.contains(.excludeRecent) ? UIColor.black.withAlphaComponent(0.5) : theme.chatList.backgroundColor + self.backgroundColor = self.filter.contains(.excludeRecent) ? nil : theme.chatList.backgroundColor + self.dimNode.backgroundColor = self.filter.contains(.excludeRecent) ? UIColor.black.withAlphaComponent(0.5) : theme.chatList.backgroundColor self.recentListNode.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor self.listNode.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor @@ -1815,38 +1283,12 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.searchStateValue = state self.searchStatePromise.set(state) } - self.mediaNode.selectedMessageIds = self.searchStateValue.selectedMessageIds - self.mediaNode.updateSelectedMessages(animated: true) - self.selectionPanelNode?.selectedMessages = self.searchStateValue.selectedMessageIds ?? [] } - var possibleDates: [(Date, String?)] = [] - var possiblePeers: [Peer] = [] override public func searchTextUpdated(text: String) { let searchQuery: String? = !text.isEmpty ? text : nil self.interaction?.searchTextHighightState = searchQuery self.searchQuery.set(.single(searchQuery)) - self.searchQueryValue = searchQuery - - let previousPossibleDate = self.possibleDates - self.possibleDates = suggestDates(for: text, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat) - - if previousPossibleDate.isEmpty != self.possibleDates.isEmpty { - self.updatedSearchOptions?(self.searchOptionsValue, self.hasSuggestions) - if let (layout, navigationBarHeight) = self.validLayout { - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - } - } - - private var hasSuggestions: Bool { - if !self.possibleDates.isEmpty && self.searchOptionsValue?.maxDate == nil { - return true - } else if !self.possiblePeers.isEmpty && self.searchOptionsValue?.peer == nil { - return true - } else { - return false - } } private func enqueueRecentTransition(_ transition: ChatListSearchContainerRecentTransition, firstTime: Bool) { @@ -1886,87 +1328,21 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } private func dequeueTransition() { - if let (transition, firstTime) = self.enqueuedTransitions.first { + if let (transition, _) = self.enqueuedTransitions.first { self.enqueuedTransitions.remove(at: 0) var options = ListViewDeleteAndInsertOptions() options.insert(.PreferSynchronousDrawing) options.insert(.PreferSynchronousResourceLoading) - if transition.animated { - options.insert(.AnimateInsertion) - } - + let displayingResults = transition.displayingResults self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in if let strongSelf = self { - let searchOptions = strongSelf.searchOptionsValue - strongSelf.listNode.isHidden = searchOptions?.messageTags == .photoOrVideo && (strongSelf.searchQueryValue ?? "").isEmpty - strongSelf.mediaNode.isHidden = !strongSelf.listNode.isHidden - - let displayingResults = transition.displayingResults - if !displayingResults { - strongSelf.listNode.isHidden = true - strongSelf.mediaNode.isHidden = true - } - - let emptyResults = displayingResults && transition.isEmpty - if emptyResults { - let emptyResultsTitle: String - let emptyResultsText: String - if !transition.query.isEmpty { - emptyResultsTitle = strongSelf.presentationData.strings.ChatList_Search_NoResults - emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsQueryDescription(transition.query).0 - } else { - if let searchOptions = searchOptions, searchOptions.messageTags != nil && searchOptions.minDate == nil && searchOptions.maxDate == nil && searchOptions.peer == nil { - emptyResultsTitle = strongSelf.presentationData.strings.ChatList_Search_NoResultsFilter - if searchOptions.messageTags == .photoOrVideo { - emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsFitlerMedia - } else if searchOptions.messageTags == .webPage { - emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsFitlerLinks - } else if searchOptions.messageTags == .file { - emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsFitlerFiles - } else if searchOptions.messageTags == .music { - emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsFitlerMusic - } else if searchOptions.messageTags == .voiceOrInstantVideo { - emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsFitlerVoice - } else { - emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsDescription - } - } else { - emptyResultsTitle = strongSelf.presentationData.strings.ChatList_Search_NoResults - emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsDescription - } - } - - strongSelf.emptyResultsTitleNode.attributedText = NSAttributedString(string: emptyResultsTitle, font: Font.semibold(17.0), textColor: strongSelf.presentationData.theme.list.freeTextColor) - strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: emptyResultsText, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.freeTextColor) - } - - if let (layout, navigationBarHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - - strongSelf.emptyResultsAnimationNode.isHidden = !emptyResults - strongSelf.emptyResultsTitleNode.isHidden = !emptyResults - strongSelf.emptyResultsTextNode.isHidden = !emptyResults - strongSelf.emptyResultsAnimationNode.visibility = emptyResults - - if let searchOptions = searchOptions, searchOptions.messageTags != nil && searchOptions.messageTags != .photoOrVideo, transition.query.isEmpty { - if searchOptions.messageTags == .webPage { - strongSelf.loadingNode.image = UIImage(bundleImageName: "Chat List/Search/M_Links") - } else if searchOptions.messageTags == .file { - strongSelf.loadingNode.image = UIImage(bundleImageName: "Chat List/Search/M_Files") - } else if searchOptions.messageTags == .music || searchOptions.messageTags == .voiceOrInstantVideo { - strongSelf.loadingNode.image = UIImage(bundleImageName: "Chat List/Search/M_Music") - } - - strongSelf.loadingNode.isHidden = !transition.isLoading - } else { - strongSelf.loadingNode.isHidden = true - } - strongSelf.recentListNode.isHidden = displayingResults || strongSelf.peersFilter.contains(.excludeRecent) + strongSelf.listNode.isHidden = !displayingResults + strongSelf.recentListNode.isHidden = displayingResults || strongSelf.filter.contains(.excludeRecent) strongSelf.dimNode.isHidden = displayingResults - strongSelf.backgroundColor = !displayingResults && strongSelf.peersFilter.contains(.excludeRecent) ? nil : strongSelf.presentationData.theme.chatList.backgroundColor + strongSelf.backgroundColor = !displayingResults && strongSelf.filter.contains(.excludeRecent) ? nil : strongSelf.presentationData.theme.chatList.backgroundColor + } }) } @@ -1976,335 +1352,18 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) let hadValidLayout = self.validLayout != nil - self.validLayout = (layout, navigationBarHeight) - - var topInset = navigationBarHeight - - var topPanelHeight: CGFloat = 0.0 - if let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType { - let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight - topPanelHeight = panelHeight - let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight)) - if let (mediaAccessoryPanel, mediaType) = self.mediaAccessoryPanel, mediaType == type { - transition.updateFrame(layer: mediaAccessoryPanel.layer, frame: panelFrame) - mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition) - switch order { - case .regular: - mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, previousItem, nextItem) - case .reversed: - mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nextItem, previousItem) - case .random: - mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nil, nil) - } - let delayedStatus = self.context.sharedContext.mediaManager.globalMediaPlayerState - |> mapToSignal { value -> Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError> in - guard let value = value else { - return .single(nil) - } - switch value.1 { - case .state: - return .single(value) - case .loading: - return .single(value) |> delay(0.1, queue: .mainQueue()) - } - } - - mediaAccessoryPanel.containerNode.headerNode.playbackStatus = delayedStatus - |> map { state -> MediaPlayerStatus in - if let stateOrLoading = state?.1, case let .state(state) = stateOrLoading { - return state.status - } else { - return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) - } - } - } else { - if let (mediaAccessoryPanel, _) = self.mediaAccessoryPanel { - self.mediaAccessoryPanel = nil - self.dismissingPanel = mediaAccessoryPanel - mediaAccessoryPanel.animateOut(transition: transition, completion: { [weak self, weak mediaAccessoryPanel] in - mediaAccessoryPanel?.removeFromSupernode() - if let strongSelf = self, strongSelf.dismissingPanel === mediaAccessoryPanel { - strongSelf.dismissingPanel = nil - } - }) - } - - let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context) - mediaAccessoryPanel.containerNode.headerNode.displayScrubber = item.playbackData?.type != .instantVideo - mediaAccessoryPanel.close = { [weak self] in - if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { - strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause)) - } - } - mediaAccessoryPanel.toggleRate = { - [weak self] in - guard let strongSelf = self else { - return - } - let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> AudioPlaybackRate in - let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings) as? MusicPlaybackSettings ?? MusicPlaybackSettings.defaultSettings - - let nextRate: AudioPlaybackRate - switch settings.voicePlaybackRate { - case .x1: - nextRate = .x2 - case .x2: - nextRate = .x1 - } - transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in - return settings.withUpdatedVoicePlaybackRate(nextRate) - }) - return nextRate - } - |> deliverOnMainQueue).start(next: { baseRate in - guard let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType else { - return - } - strongSelf.context.sharedContext.mediaManager.playlistControl(.setBaseRate(baseRate), type: type) - }) - } - mediaAccessoryPanel.togglePlayPause = { [weak self] in - if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { - strongSelf.context.sharedContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: type) - } - } - mediaAccessoryPanel.playPrevious = { [weak self] in - if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { - strongSelf.context.sharedContext.mediaManager.playlistControl(.next, type: type) - } - } - mediaAccessoryPanel.playNext = { [weak self] in - if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { - strongSelf.context.sharedContext.mediaManager.playlistControl(.previous, type: type) - } - } - mediaAccessoryPanel.tapAction = { [weak self] in - guard let strongSelf = self, let (state, _, _, order, type, account) = strongSelf.playlistStateAndType else { - return - } - if let id = state.id as? PeerMessagesMediaPlaylistItemId { - if type == .music { - let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), context: strongSelf.context, chatLocation: .peer(id.messageId.peerId), chatLocationContextHolder: Atomic(value: nil), tagMask: MessageTags.music) - - var cancelImpl: (() -> Void)? - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let progressSignal = Signal { subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { - cancelImpl?() - })) - self?.interaction?.present(controller) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = MetaDisposable() - var progressStarted = false - strongSelf.playlistPreloadDisposable?.dispose() - - - strongSelf.playlistPreloadDisposable = (signal - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - |> deliverOnMainQueue).start(next: { index in - guard let strongSelf = self else { - return - } - if let _ = index.0 { - let controllerContext: AccountContext - if account.id == strongSelf.context.account.id { - controllerContext = strongSelf.context - } else { - controllerContext = strongSelf.context.sharedContext.makeTempAccountContext(account: account) - } - let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, peerId: id.messageId.peerId, type: type, initialMessageId: id.messageId, initialOrder: order, isGlobalSearch: true, parentNavigationController: strongSelf.navigationController) - strongSelf.dismissInput() - strongSelf.interaction?.present(controller) - } else if index.1 { - if !progressStarted { - progressStarted = true - progressDisposable.set(progressSignal.start()) - } - } - }, completed: { - }) - cancelImpl = { - self?.playlistPreloadDisposable?.dispose() - } - } else { - strongSelf.context.sharedContext.navigateToChat(accountId: strongSelf.context.account.id, peerId: id.messageId.peerId, messageId: id.messageId) - } - } - } - mediaAccessoryPanel.frame = panelFrame - if let dismissingPanel = self.dismissingPanel { - self.mediaAccessoryPanelContainer.insertSubnode(mediaAccessoryPanel, aboveSubnode: dismissingPanel) - } else { - self.mediaAccessoryPanelContainer.addSubnode(mediaAccessoryPanel) - } - self.mediaAccessoryPanel = (mediaAccessoryPanel, type) - mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: .immediate) - switch order { - case .regular: - mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, previousItem, nextItem) - case .reversed: - mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nextItem, previousItem) - case .random: - mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nil, nil) - } - mediaAccessoryPanel.containerNode.headerNode.playbackStatus = self.context.sharedContext.mediaManager.globalMediaPlayerState - |> map { state -> MediaPlayerStatus in - if let stateOrLoading = state?.1, case let .state(state) = stateOrLoading { - return state.status - } else { - return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) - } - } - mediaAccessoryPanel.animateIn(transition: transition) - } - } else if let (mediaAccessoryPanel, _) = self.mediaAccessoryPanel { - self.mediaAccessoryPanel = nil - self.dismissingPanel = mediaAccessoryPanel - mediaAccessoryPanel.animateOut(transition: transition, completion: { [weak self, weak mediaAccessoryPanel] in - mediaAccessoryPanel?.removeFromSupernode() - if let strongSelf = self, strongSelf.dismissingPanel === mediaAccessoryPanel { - strongSelf.dismissingPanel = nil - } - }) - } - - transition.updateFrame(node: self.mediaAccessoryPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: MediaNavigationAccessoryHeaderNode.minimizedHeight))) - topInset += topPanelHeight + self.validLayout = layout + let topInset = navigationBarHeight transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset))) - transition.updateFrame(node: self.filterContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight + 6.0), size: CGSize(width: layout.size.width, height: 37.0))) - - self.loadingNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: 422.0)) - - let filters: [ChatListSearchFilter] - var customFilters: [ChatListSearchFilter] = [] - if !self.possibleDates.isEmpty && self.searchOptionsValue?.maxDate == nil { - let formatter = DateFormatter() - formatter.timeStyle = .none - formatter.dateStyle = .medium - - for (date, string) in self.possibleDates { - let title = string ?? formatter.string(from: date) - customFilters.append(.date(Int32(date.timeIntervalSince1970), title)) - } - } - if !self.possiblePeers.isEmpty && self.searchOptionsValue?.peer == nil { - for peer in self.possiblePeers { - customFilters.append(.peer(peer.id, peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder))) - } - } - - if !customFilters.isEmpty { - filters = customFilters - } else { - filters = [.media, .links, .files, .music, .voice] - } - - self.filterContainerNode.update(size: CGSize(width: layout.size.width, height: 37.0), sideInset: layout.safeInsets.left, filters: filters.map { .filter($0) }, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) - - - if let selectedMessageIds = self.searchStateValue.selectedMessageIds { - var wasAdded = false - let selectionPanelNode: ChatListSearchMessageSelectionPanelNode - if let current = self.selectionPanelNode { - selectionPanelNode = current - } else { - wasAdded = true - selectionPanelNode = ChatListSearchMessageSelectionPanelNode(context: self.context, deleteMessages: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.deleteMessages(messageIds: nil) - }, shareMessages: { [weak self] in - guard let strongSelf = self, let messageIds = strongSelf.searchStateValue.selectedMessageIds, !messageIds.isEmpty else { - return - } - let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Message] in - var messages: [Message] = [] - for id in messageIds { - if let message = transaction.getMessage(id) { - messages.append(message) - } - } - return messages - } - |> deliverOnMainQueue).start(next: { messages in - if let strongSelf = self, !messages.isEmpty { - let shareController = ShareController(context: strongSelf.context, subject: .messages(messages.sorted(by: { lhs, rhs in - return lhs.index < rhs.index - })), externalShare: true, immediateExternalShare: true) - strongSelf.dismissInput() - strongSelf.present?(shareController, nil) - } - }) - }, forwardMessages: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.forwardMessages(messageIds: nil) - }) - self.selectionPanelNode = selectionPanelNode - self.addSubnode(selectionPanelNode) - } - selectionPanelNode.selectedMessages = selectedMessageIds - let panelHeight = selectionPanelNode.update(layout: layout, presentationData: self.presentationData, transition: wasAdded ? .immediate : transition) - let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight)) - if wasAdded { - selectionPanelNode.frame = panelFrame - transition.animatePositionAdditive(node: selectionPanelNode, offset: CGPoint(x: 0.0, y: panelHeight)) - } else { - transition.updateFrame(node: selectionPanelNode, frame: panelFrame) - } - } else if let selectionPanelNode = self.selectionPanelNode { - self.selectionPanelNode = nil - transition.updateFrame(node: selectionPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: selectionPanelNode.bounds.size), completion: { [weak selectionPanelNode] _ in - selectionPanelNode?.removeFromSupernode() - }) - } - let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) self.recentListNode.frame = CGRect(origin: CGPoint(), size: layout.size) - self.recentListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: layout.insets(options: [.input]).bottom, right: layout.safeInsets.right), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.recentListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: navigationBarHeight, left: layout.safeInsets.left, bottom: layout.insets(options: [.input]).bottom, right: layout.safeInsets.right), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size) - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: layout.insets(options: [.input]).bottom, right: layout.safeInsets.right), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - - self.mediaNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset)) - self.mediaNode.update(size: layout.size, sideInset: layout.safeInsets.left, bottomInset: layout.insets(options: [.input]).bottom, visibleHeight: layout.size.height - navigationBarHeight, isScrollingLockedAtTop: false, expandProgress: 1.0, presentationData: self.presentationData, synchronous: true, transition: transition) - - let padding: CGFloat = 16.0 - let emptyTitleSize = self.emptyResultsTitleNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude)) - let emptyTextSize = self.emptyResultsTextNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude)) - - let insets = layout.insets(options: [.input]) - var emptyAnimationHeight = self.animationSize.height - var emptyAnimationSpacing: CGFloat = 8.0 - if case .landscape = layout.orientation, case .compact = layout.metrics.widthClass { - emptyAnimationHeight = 0.0 - emptyAnimationSpacing = 0.0 - } - let emptyTextSpacing: CGFloat = 8.0 - let emptyTotalHeight = emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSize.height + emptyTextSpacing - let emptyAnimationY = navigationBarHeight + floorToScreenPixels((layout.size.height - navigationBarHeight - max(insets.bottom, layout.intrinsicInsets.bottom) - emptyTotalHeight) / 2.0) - - let textTransition = ContainedViewLayoutTransition.immediate - textTransition.updateFrame(node: self.emptyResultsAnimationNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - self.animationSize.width) / 2.0, y: emptyAnimationY), size: self.animationSize)) - textTransition.updateFrame(node: self.emptyResultsTitleNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - emptyTitleSize.width) / 2.0, y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing), size: emptyTitleSize)) - textTransition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize)) - self.emptyResultsAnimationNode.updateLayout(size: self.animationSize) + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: navigationBarHeight, left: layout.safeInsets.left, bottom: layout.insets(options: [.input]).bottom, right: layout.safeInsets.right), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) if !hadValidLayout { while !self.enqueuedRecentTransitions.isEmpty { @@ -2379,7 +1438,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo actionSheet?.dismissAnimated() }) ])]) - self.dismissInput() + self.view.window?.endEditing(true) self.interaction?.present(actionSheet) } @@ -2390,267 +1449,4 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.recentListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } } - - func messageContextActions(_ message: Message, node: ASDisplayNode?, rect: CGRect?, gesture anyRecognizer: UIGestureRecognizer?) { - let gesture: ContextGesture? = anyRecognizer as? ContextGesture - let _ = (chatMediaListPreviewControllerData(context: self.context, chatLocation: .peer(message.id.peerId), chatLocationContextHolder: Atomic(value: nil), message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: self.navigationController) - |> deliverOnMainQueue).start(next: { [weak self] previewData in - guard let strongSelf = self else { - gesture?.cancel() - return - } - if let previewData = previewData { - let context = strongSelf.context - let strings = strongSelf.presentationData.strings -// let items = chatAvailableMessageActionsImpl(postbox: strongSelf.context.account.postbox, accountPeerId: strongSelf.context.account.peerId, messageIds: [message.id]) -// |> map { actions -> [ContextMenuItem] in - var items: [ContextMenuItem] = [] - - items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { - self?.openMessage(message.peers[message.id.peerId]!, message.id) - }) - }))) - - items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { - if let strongSelf = self { - strongSelf.forwardMessages(messageIds: [message.id]) - } - }) - }))) - - items.append(.separator) - items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuMore, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - if let strongSelf = self { - strongSelf.dismissInput() - - strongSelf.updateSearchState { state in - return state.withUpdatedSelectedMessageIds([message.id]) - } - - if let (layout, navigationBarHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - } - - f(.default) - }))) - - switch previewData { - case let .gallery(gallery): - gallery.setHintWillBePresentedInPreviewingContext(true) - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture) - strongSelf.presentInGlobalOverlay?(contextController, nil) - case .instantPage: - break - } - } - }) - } - - func messageContextAction(_ message: Message, node: ASDisplayNode?, rect: CGRect?, gesture anyRecognizer: UIGestureRecognizer?) { - guard let node = node as? ContextExtractedContentContainingNode else { - return - } - let _ = storedMessageFromSearch(account: self.context.account, message: message).start() - - var linkForCopying: String? - var currentSupernode: ASDisplayNode? = node - while true { - if currentSupernode == nil { - break - } else if let currentSupernode = currentSupernode as? ListMessageSnippetItemNode { - linkForCopying = currentSupernode.currentPrimaryUrl - break - } else { - currentSupernode = currentSupernode?.supernode - } - } - - let gesture: ContextGesture? = anyRecognizer as? ContextGesture - var items: [ContextMenuItem] = [] - - if let linkForCopying = linkForCopying { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuCopyLink, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in - c.dismiss(completion: {}) - UIPasteboard.general.string = linkForCopying - }))) - } - - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { [weak self] in - if let strongSelf = self { - strongSelf.forwardMessages(messageIds: Set([message.id])) - } - }) - }))) - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { [weak self] in - self?.openMessage(message.peers[message.id.peerId]!, message.id) - }) - }))) - - items.append(.separator) - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuMore, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { - if let strongSelf = self { - strongSelf.dismissInput() - - strongSelf.updateSearchState { state in - return state.withUpdatedSelectedMessageIds([message.id]) - } - - if let (layout, navigationBarHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - } - }) - }))) - - let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture) - self.presentInGlobalOverlay?(controller, nil) - } - - public override func searchTextClearTokens() { - self.updateSearchOptions(nil) - self.setQuery?(nil, [], self.searchQueryValue ?? "") - } - func deleteMessages(messageIds: Set?) { - let messageIds = messageIds ?? self.searchStateValue.selectedMessageIds - } - - func forwardMessages(messageIds: Set?) { - let messageIds = messageIds ?? self.searchStateValue.selectedMessageIds - if let messageIds = messageIds, !messageIds.isEmpty { - let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled])) - peerSelectionController.peerSelected = { [weak self, weak peerSelectionController] peerId in - if let strongSelf = self, let _ = peerSelectionController { - if peerId == strongSelf.context.account.peerId { - let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in - return .forward(source: id, grouping: .auto, attributes: []) - }) - |> deliverOnMainQueue).start(next: { [weak self] messageIds in - if let strongSelf = self { - let signals: [Signal] = messageIds.compactMap({ id -> Signal? in - guard let id = id else { - return nil - } - return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id) - |> mapToSignal { status, _ -> Signal in - if status != nil { - return .never() - } else { - return .single(true) - } - } - |> take(1) - }) - strongSelf.activeActionDisposable.set((combineLatest(signals) - |> deliverOnMainQueue).start(completed: { - guard let strongSelf = self else { - return - } - strongSelf.present?(OverlayStatusController(theme: strongSelf.presentationData.theme, type: .success), nil) - })) - } - }) - if let peerSelectionController = peerSelectionController { - peerSelectionController.dismiss() - } - - strongSelf.updateSearchState { state in - return state.withUpdatedSelectedMessageIds(nil) - } - if let (layout, navigationBarHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - } else { - let _ = (strongSelf.context.account.postbox.transaction({ transaction -> Void in - transaction.updatePeerChatInterfaceState(peerId, update: { currentState in - if let currentState = currentState as? ChatInterfaceState { - return currentState.withUpdatedForwardMessageIds(Array(messageIds)) - } else { - return ChatInterfaceState().withUpdatedForwardMessageIds(Array(messageIds)) - } - }) - }) |> deliverOnMainQueue).start(completed: { - if let strongSelf = self { -// strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone) - - let controller = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peerId), subject: nil, botStart: nil, mode: .standard(previewing: false)) - strongSelf.navigationController?.pushViewController(controller, animated: false, completion: { - if let peerSelectionController = peerSelectionController { - peerSelectionController.dismiss() - } - }) - - strongSelf.updateSearchState { state in - return state.withUpdatedSelectedMessageIds(nil) - } - if let (layout, navigationBarHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - } - }) - } - } - } - self.navigationController?.pushViewController(peerSelectionController) - } - } - - private func dismissInput() { - self.view.window?.endEditing(true) - } -} - -private final class MessageContextExtractedContentSource: ContextExtractedContentSource { - let keepInPlace: Bool = false - let ignoreContentTouches: Bool = true - - private let sourceNode: ContextExtractedContentContainingNode - - init(sourceNode: ContextExtractedContentContainingNode) { - self.sourceNode = sourceNode - } - - func takeView() -> ContextControllerTakeViewInfo? { - return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) - } - - func putBack() -> ContextControllerPutBackViewInfo? { - return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds) - } -} - -private final class ContextControllerContentSourceImpl: ContextControllerContentSource { - let controller: ViewController - weak var sourceNode: ASDisplayNode? - - let navigationController: NavigationController? = nil - - let passthroughTouches: Bool = false - - init(controller: ViewController, sourceNode: ASDisplayNode?) { - self.controller = controller - self.sourceNode = sourceNode - } - - func transitionInfo() -> ContextControllerTakeControllerInfo? { - let sourceNode = self.sourceNode - return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in - if let sourceNode = sourceNode { - return (sourceNode, sourceNode.bounds) - } else { - return nil - } - }) - } - - func animatedIn() { - self.controller.didAppearInContextPreview() - } } diff --git a/submodules/ChatListUI/Sources/ChatListSearchFiltersContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchFiltersContainerNode.swift deleted file mode 100644 index 0023b47b7b..0000000000 --- a/submodules/ChatListUI/Sources/ChatListSearchFiltersContainerNode.swift +++ /dev/null @@ -1,306 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import SyncCore -import Postbox -import TelegramCore -import TelegramPresentationData - -enum ChatListSearchFilter: Equatable { - case media - case links - case files - case music - case voice - case peer(PeerId, String) - case date(Int32, String) - - var id: Int32 { - switch self { - case .media: - return 0 - case .links: - return 1 - case .files: - return 2 - case .music: - return 3 - case .voice: - return 4 - case let .peer(peerId, _): - return peerId.id - case let .date(date, _): - return date - } - } -} - -private final class ItemNode: ASDisplayNode { - private let pressed: () -> Void - - private let iconNode: ASImageNode - private let titleNode: ImmediateTextNode - private let buttonNode: HighlightTrackingButtonNode - - private var selectionFraction: CGFloat = 0.0 - private(set) var unreadCount: Int = 0 - - private var isReordering: Bool = false - - private var theme: PresentationTheme? - - init(pressed: @escaping () -> Void) { - self.pressed = pressed - - let titleInset: CGFloat = 4.0 - - self.iconNode = ASImageNode() - self.iconNode.displaysAsynchronously = false - self.iconNode.displayWithoutProcessing = true - - self.titleNode = ImmediateTextNode() - self.titleNode.displaysAsynchronously = false - self.titleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0) - - self.buttonNode = HighlightTrackingButtonNode() - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.iconNode) - self.addSubnode(self.buttonNode) - - self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) - self.buttonNode.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.iconNode.layer.removeAnimation(forKey: "opacity") - strongSelf.iconNode.alpha = 0.4 - - strongSelf.titleNode.layer.removeAnimation(forKey: "opacity") - strongSelf.titleNode.alpha = 0.4 - } else { - strongSelf.iconNode.alpha = 1.0 - strongSelf.iconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - - strongSelf.titleNode.alpha = 1.0 - strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - } - - @objc private func buttonPressed() { - self.pressed() - } - - func update(type: ChatListSearchFilter, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { - let title: String - let icon: UIImage? - - let color = presentationData.theme.list.itemSecondaryTextColor - switch type { - case .media: - title = presentationData.strings.ChatList_Search_FilterMedia - icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Media"), color: color) - case .links: - title = presentationData.strings.ChatList_Search_FilterLinks - icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Links"), color: color) - case .files: - title = presentationData.strings.ChatList_Search_FilterFiles - icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Files"), color: color) - case .music: - title = presentationData.strings.ChatList_Search_FilterMusic - icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Music"), color: color) - case .voice: - title = presentationData.strings.ChatList_Search_FilterVoice - icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Voice"), color: color) - case let .peer(_, displayTitle): - title = displayTitle - icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/User"), color: color) - case let .date(_, displayTitle): - title = displayTitle - icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Calendar"), color: color) - } - - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: color) - - if self.theme !== presentationData.theme { - self.theme = presentationData.theme - self.iconNode.image = icon - } - } - - func updateLayout(height: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { - let iconInset: CGFloat = 22.0 - if let image = self.iconNode.image { - self.iconNode.frame = CGRect(x: 0.0, y: floorToScreenPixels((height - image.size.height) / 2.0), width: image.size.width, height: image.size.height) - } - - let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude)) - let titleFrame = CGRect(origin: CGPoint(x: -self.titleNode.insets.left + iconInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize) - self.titleNode.frame = titleFrame - - return titleSize.width - self.titleNode.insets.left - self.titleNode.insets.right + iconInset - } - - func updateArea(size: CGSize, sideInset: CGFloat, transition: ContainedViewLayoutTransition) { - self.buttonNode.frame = CGRect(origin: CGPoint(x: -sideInset, y: 0.0), size: CGSize(width: size.width + sideInset * 2.0, height: size.height)) - - self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -sideInset, bottom: 0.0, right: -sideInset) - } -} - -enum ChatListSearchFilterEntryId: Hashable { - case filter(Int32) -} - -enum ChatListSearchFilterEntry: Equatable { - case filter(ChatListSearchFilter) - - var id: ChatListSearchFilterEntryId { - switch self { - case let .filter(filter): - return .filter(filter.id) - } - } -} - -final class ChatListSearchFiltersContainerNode: ASDisplayNode { - private let scrollNode: ASScrollNode - private var itemNodes: [ChatListSearchFilterEntryId: ItemNode] = [:] - - var filterPressed: ((ChatListSearchFilter) -> Void)? - - private var currentParams: (size: CGSize, sideInset: CGFloat, filters: [ChatListSearchFilterEntry], presentationData: PresentationData)? - - override init() { - self.scrollNode = ASScrollNode() - - super.init() - - self.scrollNode.view.showsHorizontalScrollIndicator = false - self.scrollNode.view.scrollsToTop = false - self.scrollNode.view.delaysContentTouches = false - self.scrollNode.view.canCancelContentTouches = true - if #available(iOS 11.0, *) { - self.scrollNode.view.contentInsetAdjustmentBehavior = .never - } - - self.addSubnode(self.scrollNode) - } - - func cancelAnimations() { - self.scrollNode.layer.removeAllAnimations() - } - - func update(size: CGSize, sideInset: CGFloat, filters: [ChatListSearchFilterEntry], presentationData: PresentationData, transition proposedTransition: ContainedViewLayoutTransition) { - let isFirstTime = self.currentParams == nil - let transition: ContainedViewLayoutTransition = isFirstTime ? .immediate : proposedTransition - - if self.currentParams?.presentationData.theme !== presentationData.theme { - self.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor - } - - self.currentParams = (size: size, sideInset: sideInset, filters: filters, presentationData: presentationData) - - transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size)) - - for i in 0 ..< filters.count { - let filter = filters[i] - if case let .filter(type) = filter { - let itemNode: ItemNode - var itemNodeTransition = transition - if let current = self.itemNodes[filter.id] { - itemNode = current - } else { - itemNodeTransition = .immediate - itemNode = ItemNode(pressed: { [weak self] in - self?.filterPressed?(type) - }) - self.itemNodes[filter.id] = itemNode - } - itemNode.update(type: type, presentationData: presentationData, transition: itemNodeTransition) - } - } - var removeKeys: [ChatListSearchFilterEntryId] = [] - for (id, _) in self.itemNodes { - if !filters.contains(where: { $0.id == id }) { - removeKeys.append(id) - } - } - for id in removeKeys { - if let itemNode = self.itemNodes.removeValue(forKey: id) { - transition.updateAlpha(node: itemNode, alpha: 0.0, completion: { [weak itemNode] _ in - itemNode?.removeFromSupernode() - }) - transition.updateTransformScale(node: itemNode, scale: 0.1) - } - } - - var tabSizes: [(ChatListSearchFilterEntryId, CGSize, ItemNode, Bool)] = [] - var totalRawTabSize: CGFloat = 0.0 - - for filter in filters { - guard let itemNode = self.itemNodes[filter.id] else { - continue - } - let wasAdded = itemNode.supernode == nil - var itemNodeTransition = transition - if wasAdded { - itemNodeTransition = .immediate - self.scrollNode.addSubnode(itemNode) - } - let paneNodeWidth = itemNode.updateLayout(height: size.height, transition: itemNodeTransition) - let paneNodeSize = CGSize(width: paneNodeWidth, height: size.height) - tabSizes.append((filter.id, paneNodeSize, itemNode, wasAdded)) - totalRawTabSize += paneNodeSize.width - } - - let minSpacing: CGFloat = 24.0 - var spacing = minSpacing - - let resolvedSideInset: CGFloat = 16.0 + sideInset - var leftOffset: CGFloat = resolvedSideInset - - var longTitlesWidth: CGFloat = resolvedSideInset - var titlesWidth: CGFloat = 0.0 - for i in 0 ..< tabSizes.count { - let (_, paneNodeSize, _, _) = tabSizes[i] - longTitlesWidth += paneNodeSize.width - titlesWidth += paneNodeSize.width - if i != tabSizes.count - 1 { - longTitlesWidth += minSpacing - } - } - longTitlesWidth += resolvedSideInset - - let verticalOffset: CGFloat = -3.0 - for i in 0 ..< tabSizes.count { - let (_, paneNodeSize, paneNode, wasAdded) = tabSizes[i] - let itemNodeTransition = transition - - let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0) + verticalOffset), size: paneNodeSize) - - if wasAdded { - paneNode.frame = paneFrame - paneNode.alpha = 0.0 - paneNode.subnodeTransform = CATransform3DMakeScale(0.1, 0.1, 1.0) - itemNodeTransition.updateSublayerTransformScale(node: paneNode, scale: 1.0) - itemNodeTransition.updateAlpha(node: paneNode, alpha: 1.0) - } else { - itemNodeTransition.updateFrameAdditive(node: paneNode, frame: paneFrame) - } - - paneNode.updateArea(size: paneFrame.size, sideInset: spacing / 2.0, transition: itemNodeTransition) - paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -spacing / 2.0, bottom: 0.0, right: -spacing / 2.0) - - leftOffset += paneNodeSize.width + spacing - } - leftOffset -= spacing - leftOffset += resolvedSideInset - - self.scrollNode.view.contentSize = CGSize(width: leftOffset, height: size.height) - } -} diff --git a/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift b/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift deleted file mode 100644 index 2de228d537..0000000000 --- a/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift +++ /dev/null @@ -1,1135 +0,0 @@ -import AsyncDisplayKit -import Display -import TelegramCore -import SyncCore -import SwiftSignalKit -import Postbox -import TelegramPresentationData -import AccountContext -import ContextUI -import PhotoResources -import RadialStatusNode -import TelegramStringFormatting -import UniversalMediaPlayer -import ListMessageItem -import ChatMessageInteractiveMediaBadge -import ShimmerEffect -import GridMessageSelectionNode - -private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6) -private let mediaBadgeTextColor = UIColor.white - -private final class VisualMediaItemInteraction { - let openMessage: (Message) -> Void - let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void - let toggleSelection: (MessageId, Bool) -> Void - - var hiddenMedia: [MessageId: [Media]] = [:] - var selectedMessageIds: Set? - - init( - openMessage: @escaping (Message) -> Void, - openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, - toggleSelection: @escaping (MessageId, Bool) -> Void - ) { - self.openMessage = openMessage - self.openMessageContextActions = openMessageContextActions - self.toggleSelection = toggleSelection - } -} - -private final class VisualMediaItemNode: ASDisplayNode { - private let context: AccountContext - private let interaction: VisualMediaItemInteraction - - private var displayLink: ConstantDisplayLinkAnimator? - private var displayLinkTimestamp: Double = 0.0 - - private let containerNode: ContextControllerSourceNode - private let imageNode: TransformImageNode - private var statusNode: RadialStatusNode - private let mediaBadgeNode: ChatMessageInteractiveMediaBadge - private var placeholderNode: ShimmerEffectNode? - private var selectionNode: GridMessageSelectionNode? - - private let fetchStatusDisposable = MetaDisposable() - private let fetchDisposable = MetaDisposable() - private var resourceStatus: MediaResourceStatus? - - private var item: (VisualMediaItem, Media?, CGSize, CGSize?)? - private var theme: PresentationTheme? - - private var hasVisibility: Bool = false - - init(context: AccountContext, interaction: VisualMediaItemInteraction) { - self.context = context - self.interaction = interaction - - self.containerNode = ContextControllerSourceNode() - self.imageNode = TransformImageNode() - self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6)) - let progressDiameter: CGFloat = 40.0 - self.statusNode.frame = CGRect(x: 0.0, y: 0.0, width: progressDiameter, height: progressDiameter) - self.statusNode.isUserInteractionEnabled = false - - self.mediaBadgeNode = ChatMessageInteractiveMediaBadge() - self.mediaBadgeNode.frame = CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 50.0, height: 50.0)) - - super.init() - - self.addSubnode(self.containerNode) - self.containerNode.addSubnode(self.imageNode) - self.containerNode.addSubnode(self.mediaBadgeNode) - - self.containerNode.activated = { [weak self] gesture, _ in - guard let strongSelf = self, let item = strongSelf.item else { - return - } - if let message = item.0.message { - strongSelf.interaction.openMessageContextActions(message, strongSelf.containerNode, strongSelf.containerNode.bounds, gesture) - } - } - } - - deinit { - self.fetchStatusDisposable.dispose() - self.fetchDisposable.dispose() - } - - override func didLoad() { - super.didLoad() - - let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) - recognizer.tapActionAtPoint = { _ in - return .waitForSingleTap - } - self.imageNode.view.addGestureRecognizer(recognizer) - - self.mediaBadgeNode.pressed = { [weak self] in - self?.progressPressed() - } - } - - @objc func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { - guard let message = self.item?.0.message else { - return - } - if case .ended = recognizer.state { - if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation { - if case .tap = gesture { - if let (item, _, _, _) = self.item { - var media: Media? - for value in message.media { - if let image = value as? TelegramMediaImage { - media = image - break - } else if let file = value as? TelegramMediaFile { - media = file - break - } - } - - if let media = media { - if let file = media as? TelegramMediaFile { - if isMediaStreamable(message: message, media: file) { - self.interaction.openMessage(message) - } else { - self.progressPressed() - } - } else { - self.interaction.openMessage(message) - } - } - } - } - } - } - } - - private func progressPressed() { - guard let message = self.item?.0.message else { - return - } - - var media: Media? - for value in message.media { - if let image = value as? TelegramMediaImage { - media = image - break - } else if let file = value as? TelegramMediaFile { - media = file - break - } - } - - if let resourceStatus = self.resourceStatus, let file = media as? TelegramMediaFile { - switch resourceStatus { - case .Fetching: - messageMediaFileCancelInteractiveFetch(context: self.context, messageId: message.id, file: file) - case .Local: - self.interaction.openMessage(message) - case .Remote: - self.fetchDisposable.set(messageMediaFileInteractiveFetched(context: self.context, message: message, file: file, userInitiated: true).start()) - } - } - } - - func cancelPreviewGesture() { - self.containerNode.cancelGesture() - } - - func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) { - self.placeholderNode?.updateAbsoluteRect(absoluteRect, within: containerSize) - } - - func update(size: CGSize, item: VisualMediaItem, theme: PresentationTheme, synchronousLoad: Bool) { - if item === self.item?.0 && size == self.item?.2 { - return - } - self.theme = theme - var media: Media? - if let message = item.message { - for value in message.media { - if let image = value as? TelegramMediaImage { - media = image - break - } else if let file = value as? TelegramMediaFile { - media = file - break - } - } - } - - if let media = media, (self.item?.1 == nil || !media.isEqual(to: self.item!.1!)), let message = item.message { - var mediaDimensions: CGSize? - if let image = media as? TelegramMediaImage, let largestSize = largestImageRepresentation(image.representations)?.dimensions { - mediaDimensions = largestSize.cgSize - - self.imageNode.setSignal(mediaGridMessagePhoto(account: context.account, photoReference: .message(message: MessageReference(message), media: image), fullRepresentationSize: CGSize(width: 300.0, height: 300.0), synchronousLoad: synchronousLoad), attemptSynchronously: synchronousLoad, dispatchOnDisplayLink: true) - - self.fetchStatusDisposable.set(nil) - self.statusNode.transitionToState(.none, completion: { [weak self] in - self?.statusNode.isHidden = true - }) - self.mediaBadgeNode.isHidden = true - self.resourceStatus = nil - } else if let file = media as? TelegramMediaFile, file.isVideo { - mediaDimensions = file.dimensions?.cgSize - self.imageNode.setSignal(mediaGridMessageVideo(postbox: context.account.postbox, videoReference: .message(message: MessageReference(message), media: file), synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: true), attemptSynchronously: synchronousLoad) - - self.mediaBadgeNode.isHidden = file.isAnimated - - self.resourceStatus = nil - - self.item = (item, media, size, mediaDimensions) - - self.fetchStatusDisposable.set((messageMediaFileStatus(context: context, messageId: message.id, file: file) - |> deliverOnMainQueue).start(next: { [weak self] status in - if let strongSelf = self, let (item, _, _, _) = strongSelf.item { - strongSelf.resourceStatus = status - - let isStreamable = isMediaStreamable(message: message, media: file) - - var statusState: RadialStatusNodeState = .none - if isStreamable || file.isAnimated { - statusState = .none - } else { - switch status { - case let .Fetching(_, progress): - let adjustedProgress = max(progress, 0.027) - statusState = .progress(color: .white, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: true) - case .Local: - statusState = .none - case .Remote: - statusState = .download(.white) - } - } - - switch statusState { - case .none: - break - default: - strongSelf.statusNode.isHidden = false - } - - strongSelf.statusNode.transitionToState(statusState, animated: true, completion: { - if let strongSelf = self { - if case .none = statusState { - strongSelf.statusNode.isHidden = true - } - } - }) - - if let duration = file.duration { - let durationString = stringForDuration(duration) - - var badgeContent: ChatMessageInteractiveMediaBadgeContent? - var mediaDownloadState: ChatMessageInteractiveMediaDownloadState? - - if isStreamable { - switch status { - case let .Fetching(_, progress): - let progressString = String(format: "%d%%", Int(progress * 100.0)) - badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: progressString)) - mediaDownloadState = .compactFetching(progress: 0.0) - case .Local: - badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) - case .Remote: - badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) - mediaDownloadState = .compactRemote - } - } else { - badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) - } - - strongSelf.mediaBadgeNode.update(theme: nil, content: badgeContent, mediaDownloadState: mediaDownloadState, alignment: .right, animated: false, badgeAnimated: false) - } - } - })) - if self.statusNode.supernode == nil { - self.imageNode.addSubnode(self.statusNode) - } - } else { - self.mediaBadgeNode.isHidden = true - } - self.item = (item, media, size, mediaDimensions) - - let progressDiameter: CGFloat = 40.0 - self.statusNode.frame = CGRect(origin: CGPoint(x: floor((size.width - progressDiameter) / 2.0), y: floor((size.height - progressDiameter) / 2.0)), size: CGSize(width: progressDiameter, height: progressDiameter)) - - self.mediaBadgeNode.frame = CGRect(origin: CGPoint(x: size.width - 3.0, y: size.height - 18.0 - 3.0), size: CGSize(width: 50.0, height: 50.0)) - - self.selectionNode?.frame = CGRect(origin: CGPoint(), size: size) - - self.updateHiddenMedia() - - if let placeholderNode = self.placeholderNode { - self.placeholderNode = nil - placeholderNode.removeFromSupernode() - } - } else if item.isEmpty, self.placeholderNode == nil { - let placeholderNode = ShimmerEffectNode() - placeholderNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: [.rect(rect: CGRect(origin: CGPoint(), size: size))], size: size) - self.addSubnode(placeholderNode) - self.placeholderNode = placeholderNode - } - - let imageFrame = CGRect(origin: CGPoint(), size: size) - self.placeholderNode?.frame = imageFrame - - if let (item, media, _, mediaDimensions) = self.item { - self.item = (item, media, size, mediaDimensions) - - self.containerNode.frame = imageFrame - self.imageNode.frame = imageFrame - - if let mediaDimensions = mediaDimensions { - let imageSize = mediaDimensions.aspectFilled(imageFrame.size) - self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageFrame.size, intrinsicInsets: UIEdgeInsets(), emptyColor: theme.list.mediaPlaceholderColor))() - } - - self.updateSelectionState(animated: false) - } - } - - func updateIsVisible(_ isVisible: Bool) { - self.hasVisibility = isVisible -// if let _ = self.videoLayerFrameManager { -// let displayLink: ConstantDisplayLinkAnimator -// if let current = self.displayLink { -// displayLink = current -// } else { -// displayLink = ConstantDisplayLinkAnimator { [weak self] in -// guard let strongSelf = self else { -// return -// } -// strongSelf.displayLinkTimestamp += 1.0 / 30.0 -// } -// displayLink.frameInterval = 2 -// self.displayLink = displayLink -// } -// } - self.displayLink?.isPaused = !self.hasVisibility || self.isHidden - } - - func updateSelectionState(animated: Bool) { - if let (item, _, _, _) = self.item, let theme = self.theme, let message = item.message { - self.containerNode.isGestureEnabled = self.interaction.selectedMessageIds == nil - - if let selectedIds = self.interaction.selectedMessageIds { - let selected = selectedIds.contains(message.id) - - if let selectionNode = self.selectionNode { - selectionNode.updateSelected(selected, animated: animated) - selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size) - } else { - let selectionNode = GridMessageSelectionNode(theme: theme, toggle: { [weak self] value in - if let strongSelf = self, let messageId = strongSelf.item?.0.message?.id { - var toggledValue = true - if let selectedMessageIds = strongSelf.interaction.selectedMessageIds, selectedMessageIds.contains(messageId) { - toggledValue = false - } - strongSelf.interaction.toggleSelection(messageId, toggledValue) - } - }) - - selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size) - self.containerNode.addSubnode(selectionNode) - self.selectionNode = selectionNode - selectionNode.updateSelected(selected, animated: false) - if animated { - selectionNode.animateIn() - } - } - } else { - if let selectionNode = self.selectionNode { - self.selectionNode = nil - if animated { - selectionNode.animateOut { [weak selectionNode] in - selectionNode?.removeFromSupernode() - } - } else { - selectionNode.removeFromSupernode() - } - } - } - } - } - - func transitionNode() -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { - let imageNode = self.imageNode - return (self.imageNode, self.imageNode.bounds, { [weak self, weak imageNode] in - var statusNodeHidden = false - var accessoryHidden = false - if let strongSelf = self { - statusNodeHidden = strongSelf.statusNode.isHidden - accessoryHidden = strongSelf.mediaBadgeNode.isHidden - strongSelf.statusNode.isHidden = true - strongSelf.mediaBadgeNode.isHidden = true - } - let view = imageNode?.view.snapshotView(afterScreenUpdates: false) - if let strongSelf = self { - strongSelf.statusNode.isHidden = statusNodeHidden - strongSelf.mediaBadgeNode.isHidden = accessoryHidden - } - return (view, nil) - }) - } - - func updateHiddenMedia() { - if let (item, _, _, _) = self.item, let message = item.message { - if let _ = self.interaction.hiddenMedia[message.id] { - self.isHidden = true - } else { - self.isHidden = false - } - } else { - self.isHidden = false - } - self.displayLink?.isPaused = !self.hasVisibility || self.isHidden - } -} - -private final class VisualMediaItem { - let index: UInt32? - let message: Message? - let dimensions: CGSize - let aspectRatio: CGFloat - let isEmpty: Bool - - init(index: UInt32) { - self.index = index - self.message = nil - self.dimensions = CGSize(width: 100.0, height: 100.0) - self.aspectRatio = 1.0 - self.isEmpty = true - } - - init(message: Message) { - self.index = nil - self.message = message - - var aspectRatio: CGFloat = 1.0 - var dimensions = CGSize(width: 100.0, height: 100.0) - for media in message.media { - if let file = media as? TelegramMediaFile { - if let dimensionsValue = file.dimensions, dimensions.height > 1 { - dimensions = dimensionsValue.cgSize - aspectRatio = CGFloat(dimensionsValue.width) / CGFloat(dimensionsValue.height) - } - } - } - self.aspectRatio = aspectRatio - self.dimensions = dimensions - self.isEmpty = false - } - - var stableId: UInt32 { - if let message = self.message { - return message.stableId - } else if let index = self.index { - return index - } else { - return 0 - } - } -} - -private final class FloatingHeaderNode: ASDisplayNode { - private let backgroundNode: ASImageNode - private let labelNode: ImmediateTextNode - - private var currentParams: (constrainedWidth: CGFloat, year: Int32, month: Int32, theme: PresentationTheme)? - private var currentSize: CGSize? - - override init() { - self.backgroundNode = ASImageNode() - self.backgroundNode.displaysAsynchronously = false - self.backgroundNode.displayWithoutProcessing = true - - self.labelNode = ImmediateTextNode() - self.labelNode.displaysAsynchronously = false - - super.init() - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.labelNode) - } - - func update(constrainedWidth: CGFloat, year: Int32, month: Int32, theme: PresentationTheme, strings: PresentationStrings) -> CGSize { - if let currentParams = self.currentParams, let currentSize = self.currentSize { - if currentParams.constrainedWidth == constrainedWidth && - currentParams.year == year && - currentParams.month == month && - currentParams.theme === theme { - return currentSize - } - } - - if self.currentParams?.theme !== theme { - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 27.0, color: mediaBadgeBackgroundColor) - } - - self.currentParams = (constrainedWidth, year, month, theme) - - self.labelNode.attributedText = NSAttributedString(string: stringForMonth(strings: strings, month: month, ofYear: year), font: Font.regular(14.0), textColor: .white) - let labelSize = self.labelNode.updateLayout(CGSize(width: constrainedWidth, height: .greatestFiniteMagnitude)) - - let sideInset: CGFloat = 10.0 - self.labelNode.frame = CGRect(origin: CGPoint(x: sideInset, y: floor((27.0 - labelSize.height) / 2.0)), size: labelSize) - self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: labelSize.width + sideInset * 2.0, height: 27.0)) - - let size = CGSize(width: labelSize.width + sideInset * 2.0, height: 27.0) - return size - } -} - -private enum ItemsLayout { - final class Grid { - let containerWidth: CGFloat - let itemCount: Int - let itemSpacing: CGFloat - let itemsInRow: Int - let itemSize: CGFloat - let rowCount: Int - let contentHeight: CGFloat - - init(containerWidth: CGFloat, itemCount: Int, bottomInset: CGFloat) { - self.containerWidth = containerWidth - self.itemCount = itemCount - self.itemSpacing = 1.0 - self.itemsInRow = max(3, min(6, Int(containerWidth / 140.0))) - self.itemSize = floor(containerWidth / CGFloat(itemsInRow)) - - self.rowCount = itemCount / self.itemsInRow + (itemCount % self.itemsInRow == 0 ? 0 : 1) - - self.contentHeight = CGFloat(self.rowCount + 1) * self.itemSpacing + CGFloat(rowCount) * itemSize + bottomInset - } - - func visibleRange(rect: CGRect) -> (Int, Int) { - var minVisibleRow = Int(floor((rect.minY - self.itemSpacing) / (self.itemSize + self.itemSpacing))) - minVisibleRow = max(0, minVisibleRow) - var maxVisibleRow = Int(ceil((rect.maxY - self.itemSpacing) / (self.itemSize + itemSpacing))) - maxVisibleRow = min(self.rowCount - 1, maxVisibleRow) - - let minVisibleIndex = minVisibleRow * itemsInRow - let maxVisibleIndex = min(self.itemCount - 1, (maxVisibleRow + 1) * itemsInRow - 1) - - return (minVisibleIndex, maxVisibleIndex) - } - - func frame(forItemAt index: Int, sideInset: CGFloat) -> CGRect { - let rowIndex = index / Int(self.itemsInRow) - let columnIndex = index % Int(self.itemsInRow) - let itemOrigin = CGPoint(x: sideInset + CGFloat(columnIndex) * (self.itemSize + self.itemSpacing), y: self.itemSpacing + CGFloat(rowIndex) * (self.itemSize + self.itemSpacing)) - return CGRect(origin: itemOrigin, size: CGSize(width: columnIndex == self.itemsInRow ? (self.containerWidth - itemOrigin.x) : self.itemSize, height: self.itemSize)) - } - } - - final class Balanced { - let frames: [CGRect] - let contentHeight: CGFloat - - init(containerWidth: CGFloat, items: [VisualMediaItem], bottomInset: CGFloat) { - self.frames = calculateItemFrames(items: items, containerWidth: containerWidth) - if let last = self.frames.last { - self.contentHeight = last.maxY + bottomInset - } else { - self.contentHeight = bottomInset - } - } - - func visibleRange(rect: CGRect) -> (Int, Int) { - for i in 0 ..< self.frames.count { - if self.frames[i].maxY >= rect.minY { - for j in i ..< self.frames.count { - if self.frames[j].minY >= rect.maxY { - return (i, j - 1) - } - } - return (i, self.frames.count - 1) - } - } - return (0, -1) - } - - func frame(forItemAt index: Int, sideInset: CGFloat) -> CGRect { - if index >= 0 && index < self.frames.count { - return self.frames[index] - } else { - assertionFailure() - return CGRect(origin: CGPoint(), size: CGSize(width: 100.0, height: 100.0)) - } - } - } - - case grid(Grid) - case balanced(Balanced) - - var contentHeight: CGFloat { - switch self { - case let .grid(grid): - return grid.contentHeight - case let .balanced(balanced): - return balanced.contentHeight - } - } - - func visibleRange(rect: CGRect) -> (Int, Int) { - switch self { - case let .grid(grid): - return grid.visibleRange(rect: rect) - case let .balanced(balanced): - return balanced.visibleRange(rect: rect) - } - } - - func frame(forItemAt index: Int, sideInset: CGFloat) -> CGRect { - switch self { - case let .grid(grid): - return grid.frame(forItemAt: index, sideInset: sideInset) - case let .balanced(balanced): - return balanced.frame(forItemAt: index, sideInset: sideInset) - } - } -} - -final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate { - enum ContentType { - case photoOrVideo - case gifs - } - - private let context: AccountContext - private let contentType: ContentType - - private let scrollNode: ASScrollNode - private let floatingHeaderNode: FloatingHeaderNode - private var flashHeaderDelayTimer: Foundation.Timer? - private var isDeceleratingAfterTracking = false - - private var _itemInteraction: VisualMediaItemInteraction? - private var itemInteraction: VisualMediaItemInteraction { - return self._itemInteraction! - } - - private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData)? - - private let ready = Promise() - private var didSetReady: Bool = false - var isReady: Signal { - return self.ready.get() - } - - let shouldReceiveExpandProgressUpdates: Bool = false - - private let listDisposable = MetaDisposable() - private var hiddenMediaDisposable: Disposable? - private var mediaItems: [VisualMediaItem] = [] - private var itemsLayout: ItemsLayout? - private var visibleMediaItems: [UInt32: VisualMediaItemNode] = [:] - private var initialized = false - - private var decelerationAnimator: ConstantDisplayLinkAnimator? - - private var animationTimer: SwiftSignalKit.Timer? - - public var beganInteractiveDragging: (() -> Void)? - public var loadMore: (() -> Void)? - - init(context: AccountContext, contentType: ContentType, openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Void, messageContextAction: @escaping (Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void, toggleMessageSelection: @escaping (MessageId, Bool) -> Void) { - self.context = context - self.contentType = contentType - - self.scrollNode = ASScrollNode() - self.floatingHeaderNode = FloatingHeaderNode() - self.floatingHeaderNode.alpha = 0.0 - - super.init() - - self._itemInteraction = VisualMediaItemInteraction( - openMessage: { message in - let _ = openMessage(message, .default) - }, - openMessageContextActions: { message, sourceNode, sourceRect, gesture in - messageContextAction(message, sourceNode, sourceRect, gesture) - }, - toggleSelection: { id, value in - toggleMessageSelection(id, value) - } - ) - - self.scrollNode.view.delaysContentTouches = false - self.scrollNode.view.canCancelContentTouches = true - self.scrollNode.view.showsVerticalScrollIndicator = true - if #available(iOS 11.0, *) { - self.scrollNode.view.contentInsetAdjustmentBehavior = .never - } - self.scrollNode.view.scrollsToTop = false - self.scrollNode.view.delegate = self - - self.addSubnode(self.scrollNode) - self.addSubnode(self.floatingHeaderNode) - - self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().start(next: { [weak self] ids in - guard let strongSelf = self else { - return - } - var hiddenMedia: [MessageId: [Media]] = [:] - for id in ids { - if case let .chat(accountId, messageId, media) = id, accountId == strongSelf.context.account.id { - hiddenMedia[messageId] = [media] - } - } - strongSelf.itemInteraction.hiddenMedia = hiddenMedia - for (_, itemNode) in strongSelf.visibleMediaItems { - itemNode.updateHiddenMedia() - } - }) - } - - deinit { - self.listDisposable.dispose() - self.hiddenMediaDisposable?.dispose() - self.animationTimer?.invalidate() - } - - - func updateHistory(entries: [ChatListSearchEntry]?, totalCount: Int32, updateType: ViewUpdateType) { - switch updateType { - case .FillHole: - break - default: - self.mediaItems.removeAll() - let loading: Bool - if let entries = entries { - loading = false - for entry in entries { - if case let .message(message, _, _, _, _, _, _) = entry { - self.mediaItems.append(VisualMediaItem(message: message)) - } - } - } else { - loading = true - for i in 0 ..< 21 { - self.mediaItems.append(VisualMediaItem(index: UInt32(i))) - } - } - self.itemsLayout = nil - - let wasInitialized = self.initialized - self.initialized = true - - if let (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams { - self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: true, transition: .immediate) - if !self.didSetReady { - self.didSetReady = true - self.ready.set(.single(true)) - } - } - } - } - - func scrollToTop() -> Bool { - if self.scrollNode.view.contentOffset.y > 0.0 { - self.scrollNode.view.setContentOffset(CGPoint(), animated: true) - return true - } else { - return false - } - } - - func findLoadedMessage(id: MessageId) -> Message? { - for item in self.mediaItems { - if item.message?.id == id { - return item.message - } - } - return nil - } - - func updateHiddenMedia() { - for (_, itemNode) in self.visibleMediaItems { - itemNode.updateHiddenMedia() - } - } - - func cancelPreviewGestures() { - for (_, itemNode) in self.visibleMediaItems { - itemNode.cancelPreviewGesture() - } - } - - func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { - for item in self.mediaItems { - if let message = item.message, message.id == messageId { - if let itemNode = self.visibleMediaItems[message.stableId] { - return itemNode.transitionNode() - } - break - } - } - return nil - } - - func addToTransitionSurface(view: UIView) { - self.scrollNode.view.addSubview(view) - } - - var selectedMessageIds: Set? { - didSet { - self.itemInteraction.selectedMessageIds = self.selectedMessageIds - } - } - - func updateSelectedMessages(animated: Bool) { - for (_, itemNode) in self.visibleMediaItems { - itemNode.updateSelectionState(animated: animated) - } - } - - func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { - self.currentParams = (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) - - transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) - - let availableWidth = size.width - sideInset * 2.0 - - let itemsLayout: ItemsLayout - if let current = self.itemsLayout { - itemsLayout = current - } else { - switch self.contentType { - case .photoOrVideo, .gifs: - itemsLayout = .grid(ItemsLayout.Grid(containerWidth: availableWidth, itemCount: self.mediaItems.count, bottomInset: bottomInset)) - /*case .gifs: - itemsLayout = .balanced(ItemsLayout.Balanced(containerWidth: availableWidth, items: self.mediaItems, bottomInset: bottomInset))*/ - } - self.itemsLayout = itemsLayout - } - - self.scrollNode.view.contentSize = CGSize(width: size.width, height: itemsLayout.contentHeight) - self.updateVisibleItems(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, theme: presentationData.theme, strings: presentationData.strings, synchronousLoad: synchronous) - - if isScrollingLockedAtTop { - if self.scrollNode.view.contentOffset.y > .ulpOfOne { - transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size)) - } - } - self.scrollNode.view.isScrollEnabled = !isScrollingLockedAtTop - } - - func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { - self.decelerationAnimator?.isPaused = true - self.decelerationAnimator = nil - - for (_, itemNode) in self.visibleMediaItems { - itemNode.cancelPreviewGesture() - } - - self.updateHeaderFlashing(animated: true) - - self.beganInteractiveDragging?() - } - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - if let (size, sideInset, bottomInset, visibleHeight, _, _, presentationData) = self.currentParams { - self.updateVisibleItems(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, theme: presentationData.theme, strings: presentationData.strings, synchronousLoad: false) - - if scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.bounds.height * 2.0 { - self.loadMore?() - } - } - } - - func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - if decelerate { - self.isDeceleratingAfterTracking = true - self.updateHeaderFlashing(animated: true) - } else { - self.isDeceleratingAfterTracking = false - self.resetHeaderFlashTimer(start: true) - self.updateHeaderFlashing(animated: true) - } - } - - func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { - self.isDeceleratingAfterTracking = false - self.resetHeaderFlashTimer(start: true) - self.updateHeaderFlashing(animated: true) - } - - private func updateVisibleItems(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, theme: PresentationTheme, strings: PresentationStrings, synchronousLoad: Bool) { - guard let itemsLayout = self.itemsLayout else { - return - } - - let headerItemMinY = self.scrollNode.view.bounds.minY + 20.0 - let activeRect = self.scrollNode.view.bounds - let visibleRect = activeRect.insetBy(dx: 0.0, dy: -400.0) - - let (minVisibleIndex, maxVisibleIndex) = itemsLayout.visibleRange(rect: visibleRect) - - var headerItem: Message? - - var validIds = Set() - if minVisibleIndex <= maxVisibleIndex { - for i in minVisibleIndex ... maxVisibleIndex { - let stableId = self.mediaItems[i].stableId - validIds.insert(stableId) - - let itemFrame = itemsLayout.frame(forItemAt: i, sideInset: sideInset) - - let itemNode: VisualMediaItemNode - if let current = self.visibleMediaItems[stableId] { - itemNode = current - } else { - itemNode = VisualMediaItemNode(context: self.context, interaction: self.itemInteraction) - self.visibleMediaItems[stableId] = itemNode - self.scrollNode.addSubnode(itemNode) - } - itemNode.frame = itemFrame - itemNode.updateAbsoluteRect(itemFrame, within: self.scrollNode.view.bounds.size) - if headerItem == nil && itemFrame.maxY > headerItemMinY { - headerItem = self.mediaItems[i].message - } - var itemSynchronousLoad = false - if itemFrame.maxY <= visibleHeight { - itemSynchronousLoad = synchronousLoad - } - itemNode.update(size: itemFrame.size, item: self.mediaItems[i], theme: theme, synchronousLoad: itemSynchronousLoad) - itemNode.updateIsVisible(itemFrame.intersects(activeRect)) - } - } - var removeKeys: [UInt32] = [] - for (id, _) in self.visibleMediaItems { - if !validIds.contains(id) { - removeKeys.append(id) - } - } - for id in removeKeys { - if let itemNode = self.visibleMediaItems.removeValue(forKey: id) { - itemNode.removeFromSupernode() - } - } - - if let headerItem = headerItem { - let (year, month) = listMessageDateHeaderInfo(timestamp: headerItem.timestamp) - let headerSize = self.floatingHeaderNode.update(constrainedWidth: size.width, year: year, month: month, theme: theme, strings: strings) - self.floatingHeaderNode.frame = CGRect(origin: CGPoint(x: floor((size.width - headerSize.width) / 2.0), y: 7.0), size: headerSize) - self.floatingHeaderNode.isHidden = false - } else { - self.floatingHeaderNode.isHidden = true - } - } - - private func resetHeaderFlashTimer(start: Bool, duration: Double = 0.3) { - if let flashHeaderDelayTimer = self.flashHeaderDelayTimer { - flashHeaderDelayTimer.invalidate() - self.flashHeaderDelayTimer = nil - } - - if start { - final class TimerProxy: NSObject { - private let action: () -> () - - init(_ action: @escaping () -> ()) { - self.action = action - super.init() - } - - @objc func timerEvent() { - self.action() - } - } - - let timer = Timer(timeInterval: duration, target: TimerProxy { [weak self] in - if let strongSelf = self { - if let flashHeaderDelayTimer = strongSelf.flashHeaderDelayTimer { - flashHeaderDelayTimer.invalidate() - strongSelf.flashHeaderDelayTimer = nil - strongSelf.updateHeaderFlashing(animated: true) - } - } - }, selector: #selector(TimerProxy.timerEvent), userInfo: nil, repeats: false) - self.flashHeaderDelayTimer = timer - RunLoop.main.add(timer, forMode: RunLoop.Mode.common) - self.updateHeaderFlashing(animated: true) - } - } - - private func headerIsFlashing() -> Bool { - return self.scrollNode.view.isDragging || self.isDeceleratingAfterTracking || self.flashHeaderDelayTimer != nil - } - - private func updateHeaderFlashing(animated: Bool) { - let flashing = self.headerIsFlashing() - let alpha: CGFloat = flashing ? 1.0 : 0.0 - let previousAlpha = self.floatingHeaderNode.alpha - - if !previousAlpha.isEqual(to: alpha) { - self.floatingHeaderNode.alpha = alpha - if animated { - let duration: Double = flashing ? 0.3 : 0.4 - self.floatingHeaderNode.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration) - } - } - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - guard let result = super.hitTest(point, with: event) else { - return nil - } - if self.decelerationAnimator != nil { - self.decelerationAnimator?.isPaused = true - self.decelerationAnimator = nil - - return self.scrollNode.view - } - return result - } -} - -private func calculateItemFrames(items: [VisualMediaItem], containerWidth: CGFloat) -> [CGRect] { - var frames: [CGRect] = [] - - var rowsCount = 0 - var firstRowMax = 0; - - let viewPortAvailableSize = containerWidth - - let preferredRowSize: CGFloat = 100.0 - let itemsCount = items.count - let spanCount: CGFloat = 100.0 - var spanLeft = spanCount - var currentItemsInRow = 0 - var currentItemsSpanAmount: CGFloat = 0.0 - - var itemSpans: [Int: CGFloat] = [:] - var itemsToRow: [Int: Int] = [:] - - for a in 0 ..< itemsCount { - var size: CGSize = items[a].dimensions - if size.width <= 0.0 { - size.width = 100.0 - } - if size.height <= 0.0 { - size.height = 100.0 - } - let aspect: CGFloat = size.width / size.height - if aspect > 4.0 || aspect < 0.2 { - size.width = max(size.width, size.height) - size.height = size.width - } - - var requiredSpan = min(spanCount, floor(spanCount * (size.width / size.height * preferredRowSize / viewPortAvailableSize))) - let moveToNewRow = spanLeft < requiredSpan || requiredSpan > 33.0 && spanLeft < requiredSpan - 15.0 - if moveToNewRow { - if spanLeft > 0 { - let spanPerItem = floor(spanLeft / CGFloat(currentItemsInRow)) - - let start = a - currentItemsInRow - var b = start - while b < start + currentItemsInRow { - if (b == start + currentItemsInRow - 1) { - itemSpans[b] = itemSpans[b]! + spanLeft - } else { - itemSpans[b] = itemSpans[b]! + spanPerItem - } - spanLeft -= spanPerItem; - - b += 1 - } - - itemsToRow[a - 1] = rowsCount - } - rowsCount += 1 - currentItemsSpanAmount = 0 - currentItemsInRow = 0 - spanLeft = spanCount - } else { - if spanLeft < requiredSpan { - requiredSpan = spanLeft - } - } - if rowsCount == 0 { - firstRowMax = max(firstRowMax, a) - } - if a == itemsCount - 1 { - itemsToRow[a] = rowsCount - } - currentItemsSpanAmount += requiredSpan - currentItemsInRow += 1 - spanLeft -= requiredSpan - spanLeft = max(0, spanLeft) - - itemSpans[a] = requiredSpan - } - if itemsCount != 0 { - rowsCount += 1 - } - - var verticalOffset: CGFloat = 1.0 - - var currentRowHorizontalOffset: CGFloat = 0.0 - for index in 0 ..< items.count { - guard let width = itemSpans[index] else { - continue - } - let itemWidth = floor(width * containerWidth / 100.0) - 1 - - var itemSize = CGSize(width: itemWidth, height: preferredRowSize) - if itemsToRow[index] != nil && currentRowHorizontalOffset + itemSize.width >= containerWidth - 10.0 { - itemSize.width = max(itemSize.width, containerWidth - currentRowHorizontalOffset) - } - frames.append(CGRect(origin: CGPoint(x: currentRowHorizontalOffset, y: verticalOffset), size: itemSize)) - currentRowHorizontalOffset += itemSize.width + 1.0 - - if itemsToRow[index] != nil { - verticalOffset += preferredRowSize + 1.0 - currentRowHorizontalOffset = 0.0 - } - } - - return frames -} diff --git a/submodules/ChatListUI/Sources/ChatListSearchMessageSelectionPanelNode.swift b/submodules/ChatListUI/Sources/ChatListSearchMessageSelectionPanelNode.swift deleted file mode 100644 index 09ae186c2a..0000000000 --- a/submodules/ChatListUI/Sources/ChatListSearchMessageSelectionPanelNode.swift +++ /dev/null @@ -1,182 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import Display -import Postbox -import TelegramCore -import SyncCore -import SwiftSignalKit -import TelegramPresentationData -import AccountContext -import AppBundle - -final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode { - private let context: AccountContext - private var theme: PresentationTheme - - private let deleteMessages: () -> Void - private let shareMessages: () -> Void - private let forwardMessages: () -> Void - - private let separatorNode: ASDisplayNode - private let backgroundNode: ASDisplayNode - private let deleteButton: HighlightableButtonNode - private let forwardButton: HighlightableButtonNode - private let shareButton: HighlightableButtonNode - - private var actions: ChatAvailableMessageActions? - - private let canDeleteMessagesDisposable = MetaDisposable() - - private var validLayout: ContainerViewLayout? - - var selectedMessages = Set() { - didSet { - if oldValue != self.selectedMessages { - self.forwardButton.isEnabled = self.selectedMessages.count != 0 - - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - if self.selectedMessages.isEmpty { - self.actions = nil - if let layout = self.validLayout { - let _ = self.update(layout: layout, presentationData: presentationData, transition: .immediate) - } - self.canDeleteMessagesDisposable.set(nil) - } else { - self.canDeleteMessagesDisposable.set((self.context.sharedContext.chatAvailableMessageActions(postbox: context.account.postbox, accountPeerId: context.account.peerId, messageIds: self.selectedMessages) - |> deliverOnMainQueue).start(next: { [weak self] actions in - if let strongSelf = self { - strongSelf.actions = actions - if let layout = strongSelf.validLayout { - let _ = strongSelf.update(layout: layout, presentationData: presentationData, transition: .immediate) - } - } - })) - } - } - } - } - - init(context: AccountContext, deleteMessages: @escaping () -> Void, shareMessages: @escaping () -> Void, forwardMessages: @escaping () -> Void) { - self.context = context - self.deleteMessages = deleteMessages - self.shareMessages = shareMessages - self.forwardMessages = forwardMessages - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - self.theme = presentationData.theme - - self.separatorNode = ASDisplayNode() - self.separatorNode.backgroundColor = presentationData.theme.chat.inputPanel.panelSeparatorColor - - self.backgroundNode = ASDisplayNode() - self.backgroundNode.backgroundColor = presentationData.theme.chat.inputPanel.panelBackgroundColor - - self.deleteButton = HighlightableButtonNode(pointerStyle: .default) - self.deleteButton.isEnabled = false - self.deleteButton.isAccessibilityElement = true - self.deleteButton.accessibilityLabel = presentationData.strings.VoiceOver_MessageContextDelete - - self.forwardButton = HighlightableButtonNode(pointerStyle: .default) - self.forwardButton.isAccessibilityElement = true - self.forwardButton.accessibilityLabel = presentationData.strings.VoiceOver_MessageContextForward - - self.shareButton = HighlightableButtonNode(pointerStyle: .default) - self.shareButton.isEnabled = false - self.shareButton.isAccessibilityElement = true - self.shareButton.accessibilityLabel = presentationData.strings.VoiceOver_MessageContextShare - - self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) - self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) - self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) - self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) - self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) - self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) - - super.init() - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.deleteButton) - self.addSubnode(self.forwardButton) - self.addSubnode(self.shareButton) - self.addSubnode(self.separatorNode) - - self.forwardButton.isEnabled = false - - self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), forControlEvents: .touchUpInside) - self.forwardButton.addTarget(self, action: #selector(self.forwardButtonPressed), forControlEvents: .touchUpInside) - self.shareButton.addTarget(self, action: #selector(self.shareButtonPressed), forControlEvents: .touchUpInside) - } - - deinit { - self.canDeleteMessagesDisposable.dispose() - } - - func update(layout: ContainerViewLayout, presentationData: PresentationData, transition: ContainedViewLayoutTransition) -> CGFloat { - if presentationData.theme !== self.theme { - self.theme = presentationData.theme - - self.backgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor - self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor - - self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) - self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) - self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) - self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) - self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) - self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) - } - - let width = layout.size.width - let insets = layout.insets(options: []) - let leftInset = insets.left - let rightInset = insets.left - - let panelHeight: CGFloat - if case .regular = layout.metrics.widthClass, case .regular = layout.metrics.heightClass { - panelHeight = 49.0 - } else { - panelHeight = 45.0 - } - - if let actions = self.actions { - self.deleteButton.isEnabled = false - self.forwardButton.isEnabled = actions.options.contains(.forward) - self.shareButton.isEnabled = false - - self.deleteButton.isEnabled = !actions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty - self.shareButton.isEnabled = !actions.options.intersection([.forward]).isEmpty - - self.deleteButton.isHidden = !self.deleteButton.isEnabled - } else { - self.deleteButton.isEnabled = false - self.deleteButton.isHidden = true - self.forwardButton.isEnabled = false - self.shareButton.isEnabled = false - } - - self.deleteButton.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 57.0, height: panelHeight)) - self.forwardButton.frame = CGRect(origin: CGPoint(x: width - rightInset - 57.0, y: 0.0), size: CGSize(width: 57.0, height: panelHeight)) - self.shareButton.frame = CGRect(origin: CGPoint(x: floor((width - rightInset - 57.0) / 2.0), y: 0.0), size: CGSize(width: 57.0, height: panelHeight)) - - let panelHeightWithInset = panelHeight + layout.intrinsicInsets.bottom - 49.0 - - transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: panelHeightWithInset))) - transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - - return panelHeightWithInset - } - - @objc func deleteButtonPressed() { - self.deleteMessages() - } - - @objc func forwardButtonPressed() { - self.forwardMessages() - } - - @objc func shareButtonPressed() { - self.shareMessages() - } -} diff --git a/submodules/ChatListUI/Sources/DateSuggestion.swift b/submodules/ChatListUI/Sources/DateSuggestion.swift deleted file mode 100644 index 79b709a0ef..0000000000 --- a/submodules/ChatListUI/Sources/DateSuggestion.swift +++ /dev/null @@ -1,144 +0,0 @@ -import Foundation -import TelegramPresentationData - -private let telegramReleaseDate = Date(timeIntervalSince1970: 1376438400.0) - -func suggestDates(for string: String, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) -> [(Date, String?)] { - let string = string.folding(options: .diacriticInsensitive, locale: .current).trimmingCharacters(in: .whitespacesAndNewlines).lowercased() - if string.count < 3 { - return [] - } - - let months: [Int: (String, String)] = [ - 1: (strings.Month_GenJanuary, strings.Month_ShortJanuary), - 2: (strings.Month_GenFebruary, strings.Month_ShortFebruary), - 3: (strings.Month_GenMarch, strings.Month_ShortMarch), - 4: (strings.Month_GenApril, strings.Month_ShortApril), - 5: (strings.Month_GenMay, strings.Month_ShortMay), - 6: (strings.Month_GenJune, strings.Month_ShortJune), - 7: (strings.Month_GenJuly, strings.Month_ShortJuly), - 8: (strings.Month_GenAugust, strings.Month_ShortAugust), - 9: (strings.Month_GenSeptember, strings.Month_ShortSeptember), - 10: (strings.Month_GenOctober, strings.Month_ShortOctober), - 11: (strings.Month_GenNovember, strings.Month_ShortNovember), - 12: (strings.Month_GenDecember, strings.Month_ShortDecember) - ] - - let weekDays: [Int: (String, String)] = [ - 1: (strings.Weekday_Monday, strings.Weekday_ShortMonday), - 2: (strings.Weekday_Tuesday, strings.Weekday_ShortTuesday), - 3: (strings.Weekday_Wednesday, strings.Weekday_ShortWednesday), - 4: (strings.Weekday_Thursday, strings.Weekday_ShortThursday), - 5: (strings.Weekday_Friday, strings.Weekday_ShortFriday), - 6: (strings.Weekday_Saturday, strings.Weekday_ShortSaturday), - 7: (strings.Weekday_Sunday, strings.Weekday_ShortSunday.lowercased()), - ] - - let today = strings.Weekday_Today - let yesterday = strings.Weekday_Yesterday - let dateSeparator = dateTimeFormat.dateSeparator - - var result: [(Date, String?)] = [] - - let calendar = Calendar.current - func getUpperDate(for date: Date) -> Date { - let components = calendar.dateComponents(in: .current, from: date) - let upperComponents = DateComponents(year: components.year, month: components.month, day: components.day, hour: 23, minute: 59, second: 59) - return calendar.date(from: upperComponents)! - } - - let now = Date() - let nowComponents = calendar.dateComponents(in: .current, from: now) - guard let year = nowComponents.year else { - return [] - } - - let midnight = calendar.startOfDay(for: now) - if today.lowercased().hasPrefix(string) { - let todayDate = getUpperDate(for: midnight) - result.append((todayDate, today)) - } - if yesterday.lowercased().hasPrefix(string) { - let yesterdayMidnight = calendar.date(byAdding: .day, value: -1, to: midnight)! - let yesterdayDate = getUpperDate(for: yesterdayMidnight) - result.append((yesterdayDate, yesterday)) - } - - func getUpperMonthDate(month: Int, year: Int) -> Date { - let monthComponents = DateComponents(year: year, month: month) - let date = calendar.date(from: monthComponents)! - let range = calendar.range(of: .day, in: .month, for: date)! - let numDays = range.count - let upperComponents = DateComponents(year: year, month: month, day: numDays, hour: 23, minute: 59, second: 59) - return calendar.date(from: upperComponents)! - } - - let decimalRange = string.rangeOfCharacter(from: .decimalDigits) - if decimalRange != nil { - if string.count == 4, let value = Int(string), value <= year { - let date = getUpperMonthDate(month: 12, year: value) - if date > telegramReleaseDate { - result.append((date, "\(value)")) - } - } else if !dateSeparator.isEmpty && string.contains(dateSeparator) { - let stringComponents = string.components(separatedBy: dateSeparator) - if stringComponents.count > 1 { - let locale = Locale(identifier: strings.baseLanguageCode) - do { - let dd = try NSDataDetector(types: NSTextCheckingResult.CheckingType.date.rawValue) - if let match = dd.firstMatch(in: string, options: [], range: NSMakeRange(0, string.utf16.count)), let date = match.date, date > telegramReleaseDate { - var resultDate = date - if resultDate > now { - if let date = calendar.date(byAdding: .year, value: -1, to: resultDate) { - resultDate = date - } - } - - for i in 0..<5 { - if let date = calendar.date(byAdding: .year, value: -i, to: resultDate) { - result.append((date, nil)) - } - } - } - } catch { - - } - } - } - } else { - for (day, value) in weekDays { - let dayName = value.0.lowercased() - let shortDayName = value.1.lowercased() - if string == shortDayName || (string.count >= shortDayName.count && dayName.hasPrefix(string)) { - var nextDateComponent = calendar.dateComponents([.hour, .minute, .second], from: now) - nextDateComponent.weekday = day + calendar.firstWeekday - if let date = calendar.nextDate(after: now, matching: nextDateComponent, matchingPolicy: .nextTime, direction: .backward) { - let upperDate = getUpperDate(for: date) - for i in 0..<5 { - if let date = calendar.date(byAdding: .hour, value: -24 * 7 * i, to: upperDate) { - if calendar.isDate(date, equalTo: now, toGranularity: .weekOfYear) { - result.append((date, value.0)) - } else { - result.append((date, nil)) - } - } - } - } - } - } - for (month, value) in months { - let monthName = value.0.lowercased() - let shortMonthName = value.1.lowercased() - if string == shortMonthName || (string.count >= shortMonthName.count && monthName.hasPrefix(string)) { - for i in (year - 7 ... year).reversed() { - let date = getUpperMonthDate(month: month, year: i) - if date <= now && date > telegramReleaseDate { - result.append((date, nil)) - } - } - } - } - } - - return result -} diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 399a096eb7..f8f9c90175 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -637,9 +637,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let peer = peer { var overrideImage: AvatarNodeImageOverride? - if peer.id.isReplies { - overrideImage = .repliesIcon - } else if peer.id == item.context.account.peerId && !displayAsMessage { + if peer.id == item.context.account.peerId && !displayAsMessage { overrideImage = .savedMessagesIcon } else if peer.isDeleted { overrideImage = .deletedIcon @@ -1101,9 +1099,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { titleAttributedString = NSAttributedString(string: item.presentationData.strings.ChatList_ArchivedChatsTitle, font: titleFont, textColor: theme.titleColor) } else if itemPeer.chatMainPeer?.id == item.context.account.peerId { titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_SavedMessages, font: titleFont, textColor: theme.titleColor) - } else if let id = itemPeer.chatMainPeer?.id, id.isReplies { - titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Replies, font: titleFont, textColor: theme.titleColor) - } else if let displayTitle = itemPeer.chatMainPeer?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) { + } else if let displayTitle = itemPeer.chatMainPeer?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) { titleAttributedString = NSAttributedString(string: displayTitle, font: titleFont, textColor: item.index.messageIndex.id.peerId.namespace == Namespaces.Peer.SecretChat ? theme.secretTitleColor : theme.titleColor) } case .group: diff --git a/submodules/ChatMessageInteractiveMediaBadge/BUCK b/submodules/ChatMessageInteractiveMediaBadge/BUCK deleted file mode 100644 index 37339b5f96..0000000000 --- a/submodules/ChatMessageInteractiveMediaBadge/BUCK +++ /dev/null @@ -1,20 +0,0 @@ -load("//Config:buck_rule_macros.bzl", "static_library") - -static_library( - name = "ChatMessageInteractiveMediaBadge", - srcs = glob([ - "Sources/**/*.swift", - ]), - deps = [ - "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", - "//submodules/Display:Display#shared", - "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/TextFormat:TextFormat", - "//submodules/RadialStatusNode:RadialStatusNode", - "//submodules/AppBundle:AppBundle", - ], - frameworks = [ - "$SDKROOT/System/Library/Frameworks/Foundation.framework", - "$SDKROOT/System/Library/Frameworks/UIKit.framework", - ], -) diff --git a/submodules/ChatMessageInteractiveMediaBadge/BUILD b/submodules/ChatMessageInteractiveMediaBadge/BUILD deleted file mode 100644 index bb8f11a6d3..0000000000 --- a/submodules/ChatMessageInteractiveMediaBadge/BUILD +++ /dev/null @@ -1,20 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") - -swift_library( - name = "ChatMessageInteractiveMediaBadge", - module_name = "ChatMessageInteractiveMediaBadge", - srcs = glob([ - "Sources/**/*.swift", - ]), - deps = [ - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/TextFormat:TextFormat", - "//submodules/RadialStatusNode:RadialStatusNode", - "//submodules/AppBundle:AppBundle", - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index 8f9dc6203b..8623c54638 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -140,7 +140,6 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { let deletePeer: ((PeerId) -> Void)? let itemHighlighting: ContactItemHighlighting? let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? - let arrowAction: (() -> Void)? public let selectable: Bool @@ -148,7 +147,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { public let header: ListViewItemHeader? - public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], additionalActions: [ContactsPeerItemAction] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, arrowAction: (() -> Void)? = nil) { + public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], additionalActions: [ContactsPeerItemAction] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) { self.presentationData = presentationData self.style = style self.sectionId = sectionId @@ -173,7 +172,6 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { self.itemHighlighting = itemHighlighting self.selectable = enabled || disabledAction != nil self.contextAction = contextAction - self.arrowAction = arrowAction if let index = index { var letter: String = "#" @@ -320,7 +318,6 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { private var badgeTextNode: TextNode? private var selectionNode: CheckNode? private var actionButtonNodes: [HighlightableButtonNode]? - private var arrowButtonNode: HighlightableButtonNode? private var isHighlighted: Bool = false @@ -506,11 +503,6 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { break } - var arrowButtonImage: UIImage? - if let _ = item.arrowAction { - arrowButtonImage = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Arrow"), color: item.presentationData.theme.list.disclosureArrowColor) - } - var actionButtons: [ActionButton]? struct ActionButton { let image: UIImage? @@ -556,8 +548,6 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { if let user = peer as? TelegramUser { if peer.id == item.context.account.peerId, case .generalSearch = item.peerMode { titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_SavedMessages, font: titleBoldFont, textColor: textColor) - } else if peer.id.isReplies { - titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Replies, font: titleBoldFont, textColor: textColor) } else if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty { let string = NSMutableAttributedString() switch item.displayOrder { @@ -675,10 +665,6 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { additionalTitleInset += badgeSize - if let arrowButtonImage = arrowButtonImage { - additionalTitleInset += arrowButtonImage.size.width + 4.0 - } - let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset - additionalTitleInset), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: multilineStatus ? 3 : 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset - badgeSize), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -749,8 +735,6 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { var overrideImage: AvatarNodeImageOverride? if peer.id == item.context.account.peerId, case .generalSearch = item.peerMode { overrideImage = .savedMessagesIcon - } else if peer.id.isReplies, case .generalSearch = item.peerMode { - overrideImage = .repliesIcon } else if peer.isDeleted { overrideImage = .deletedIcon } @@ -872,23 +856,6 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { actionButtonNodes.forEach { $0.removeFromSupernode() } } - if let arrowButtonImage = arrowButtonImage { - if strongSelf.arrowButtonNode == nil { - let arrowButtonNode = HighlightableButtonNode() - arrowButtonNode.addTarget(self, action: #selector(strongSelf.arrowButtonPressed), forControlEvents: .touchUpInside) - strongSelf.arrowButtonNode = arrowButtonNode - strongSelf.containerNode.addSubnode(arrowButtonNode) - } - if let arrowButtonNode = strongSelf.arrowButtonNode { - arrowButtonNode.setImage(arrowButtonImage, for: .normal) - - transition.updateFrame(node: arrowButtonNode, frame: CGRect(origin: CGPoint(x: params.width - params.rightInset - 12.0 - arrowButtonImage.size.width, y: floor((nodeLayout.contentSize.height - arrowButtonImage.size.height) / 2.0)), size: arrowButtonImage.size)) - } - } else if let arrowButtonNode = strongSelf.arrowButtonNode { - strongSelf.arrowButtonNode = nil - arrowButtonNode.removeFromSupernode() - } - let badgeBackgroundWidth: CGFloat if let currentBadgeBackgroundImage = currentBadgeBackgroundImage, let (badgeTextLayout, badgeTextApply) = badgeTextLayoutAndApply { let badgeBackgroundNode: ASImageNode @@ -909,12 +876,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { badgeBackgroundNode.image = currentBadgeBackgroundImage badgeBackgroundWidth = max(badgeTextLayout.size.width + 10.0, currentBadgeBackgroundImage.size.width) - var badgeBackgroundFrame = CGRect(x: revealOffset + params.width - params.rightInset - badgeBackgroundWidth - 6.0, y: floor((nodeLayout.contentSize.height - currentBadgeBackgroundImage.size.height) / 2.0), width: badgeBackgroundWidth, height: currentBadgeBackgroundImage.size.height) - - if let arrowButtonImage = arrowButtonImage { - badgeBackgroundFrame.origin.x -= arrowButtonImage.size.width + 6.0 - } - + let badgeBackgroundFrame = CGRect(x: revealOffset + params.width - params.rightInset - badgeBackgroundWidth - 6.0, y: floor((nodeLayout.contentSize.height - currentBadgeBackgroundImage.size.height) / 2.0), width: badgeBackgroundWidth, height: currentBadgeBackgroundImage.size.height) let badgeTextFrame = CGRect(origin: CGPoint(x: badgeBackgroundFrame.midX - badgeTextLayout.size.width / 2.0, y: badgeBackgroundFrame.minY + 2.0), size: badgeTextLayout.size) let badgeTextNode = badgeTextApply() @@ -1102,10 +1064,4 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { return nil } } - - @objc func arrowButtonPressed() { - if let (item, _, _, _, _, _) = self.layoutParams { - item.arrowAction?() - } - } } diff --git a/submodules/DeleteChatPeerActionSheetItem/Sources/DeleteChatPeerActionSheetItem.swift b/submodules/DeleteChatPeerActionSheetItem/Sources/DeleteChatPeerActionSheetItem.swift index afa68e40d7..1e1d100d6f 100644 --- a/submodules/DeleteChatPeerActionSheetItem/Sources/DeleteChatPeerActionSheetItem.swift +++ b/submodules/DeleteChatPeerActionSheetItem/Sources/DeleteChatPeerActionSheetItem.swift @@ -77,8 +77,6 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode { if chatPeer.id == context.account.peerId { self.avatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .savedMessagesIcon) - } else if chatPeer.id.isReplies { - self.avatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .repliesIcon) } else { var overrideImage: AvatarNodeImageOverride? if chatPeer.isDeleted { diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 58eb59e1c9..0398a1f050 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -3,17 +3,6 @@ import AsyncDisplayKit private var backArrowImageCache: [Int32: UIImage] = [:] -class SparseNode: ASDisplayNode { - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - let result = super.hitTest(point, with: event) - if result != self.view { - return result - } else { - return nil - } - } -} - public final class NavigationBarTheme { public static func generateBackArrowImage(color: UIColor) -> UIImage? { return generateImage(CGSize(width: 13.0, height: 22.0), rotatedContext: { size, context in @@ -137,8 +126,7 @@ open class NavigationBar: ASDisplayNode { } private let stripeNode: ASDisplayNode - private let clippingNode: SparseNode - private let buttonsContainerNode: ASDisplayNode + private let clippingNode: ASDisplayNode public private(set) var contentNode: NavigationBarContentNode? public private(set) var secondaryContentNode: ASDisplayNode? @@ -272,7 +260,7 @@ open class NavigationBar: ASDisplayNode { self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: self.presentationData.theme.primaryTextColor) self.titleNode.accessibilityLabel = title if self.titleNode.supernode == nil { - self.buttonsContainerNode.addSubnode(self.titleNode) + self.clippingNode.addSubnode(self.titleNode) } } else { self.titleNode.removeFromSupernode() @@ -291,7 +279,7 @@ open class NavigationBar: ASDisplayNode { } if let titleView = self.titleView { - self.buttonsContainerNode.view.addSubview(titleView) + self.clippingNode.view.addSubview(titleView) } self.invalidateCalculatedLayout() @@ -511,7 +499,7 @@ open class NavigationBar: ASDisplayNode { } if self.leftButtonNode.supernode == nil { - self.buttonsContainerNode.addSubnode(self.leftButtonNode) + self.clippingNode.addSubnode(self.leftButtonNode) } if animated { @@ -552,9 +540,9 @@ open class NavigationBar: ASDisplayNode { if let backTitle = backTitle { self.backButtonNode.updateManualText(backTitle) if self.backButtonNode.supernode == nil { - self.buttonsContainerNode.addSubnode(self.backButtonNode) - self.buttonsContainerNode.addSubnode(self.backButtonArrow) - self.buttonsContainerNode.addSubnode(self.badgeNode) + self.clippingNode.addSubnode(self.backButtonNode) + self.clippingNode.addSubnode(self.backButtonArrow) + self.clippingNode.addSubnode(self.badgeNode) } if animated { @@ -600,7 +588,7 @@ open class NavigationBar: ASDisplayNode { } self.rightButtonNode.updateItems(items) if self.rightButtonNode.supernode == nil { - self.buttonsContainerNode.addSubnode(self.rightButtonNode) + self.clippingNode.addSubnode(self.rightButtonNode) } if animated { self.rightButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) @@ -660,25 +648,25 @@ open class NavigationBar: ASDisplayNode { if let transitionTitleNode = value.navigationBar?.makeTransitionTitleNode(foregroundColor: self.presentationData.theme.primaryTextColor) { self.transitionTitleNode = transitionTitleNode if self.leftButtonNode.supernode != nil { - self.buttonsContainerNode.insertSubnode(transitionTitleNode, belowSubnode: self.leftButtonNode) + self.clippingNode.insertSubnode(transitionTitleNode, belowSubnode: self.leftButtonNode) } else if self.backButtonNode.supernode != nil { - self.buttonsContainerNode.insertSubnode(transitionTitleNode, belowSubnode: self.backButtonNode) + self.clippingNode.insertSubnode(transitionTitleNode, belowSubnode: self.backButtonNode) } else { - self.buttonsContainerNode.addSubnode(transitionTitleNode) + self.clippingNode.addSubnode(transitionTitleNode) } } case .bottom: if let transitionBackButtonNode = value.navigationBar?.makeTransitionBackButtonNode(accentColor: self.presentationData.theme.buttonColor) { self.transitionBackButtonNode = transitionBackButtonNode - self.buttonsContainerNode.addSubnode(transitionBackButtonNode) + self.clippingNode.addSubnode(transitionBackButtonNode) } if let transitionBackArrowNode = value.navigationBar?.makeTransitionBackArrowNode(accentColor: self.presentationData.theme.buttonColor) { self.transitionBackArrowNode = transitionBackArrowNode - self.buttonsContainerNode.addSubnode(transitionBackArrowNode) + self.clippingNode.addSubnode(transitionBackArrowNode) } if let transitionBadgeNode = value.navigationBar?.makeTransitionBadgeNode() { self.transitionBadgeNode = transitionBadgeNode - self.buttonsContainerNode.addSubnode(transitionBadgeNode) + self.clippingNode.addSubnode(transitionBadgeNode) } } } @@ -713,12 +701,9 @@ open class NavigationBar: ASDisplayNode { self.rightButtonNode = NavigationButtonNode() self.rightButtonNode.hitTestSlop = UIEdgeInsets(top: -4.0, left: -4.0, bottom: -4.0, right: -10.0) - self.clippingNode = SparseNode() + self.clippingNode = ASDisplayNode() self.clippingNode.clipsToBounds = true - self.buttonsContainerNode = ASDisplayNode() - self.buttonsContainerNode.clipsToBounds = true - self.backButtonNode.color = self.presentationData.theme.buttonColor self.backButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor self.leftButtonNode.color = self.presentationData.theme.buttonColor @@ -735,7 +720,6 @@ open class NavigationBar: ASDisplayNode { super.init() - self.addSubnode(self.buttonsContainerNode) self.addSubnode(self.clippingNode) self.backgroundColor = self.presentationData.theme.backgroundColor @@ -837,7 +821,7 @@ open class NavigationBar: ASDisplayNode { self.validLayout = (size, defaultHeight, additionalHeight, leftInset, rightInset, appearsHidden) if let secondaryContentNode = self.secondaryContentNode { -// transition.updateAlpha(node: secondaryContentNode, alpha: appearsHidden ? 0.0 : 1.0) + transition.updateAlpha(node: secondaryContentNode, alpha: appearsHidden ? 0.0 : 1.0) } let apparentAdditionalHeight: CGFloat = self.secondaryContentNode != nil ? NavigationBar.defaultSecondaryContentHeight : 0.0 @@ -846,7 +830,6 @@ open class NavigationBar: ASDisplayNode { let backButtonInset: CGFloat = leftInset + 27.0 transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: size)) - transition.updateFrame(node: self.buttonsContainerNode, frame: CGRect(origin: CGPoint(), size: size)) var expansionHeight: CGFloat = 0.0 if let contentNode = self.contentNode { var contentNodeFrame: CGRect @@ -856,9 +839,7 @@ open class NavigationBar: ASDisplayNode { contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)) case .expansion: expansionHeight = contentNode.height - - let additionalExpansionHeight: CGFloat = self.secondaryContentNode != nil && appearsHidden ? NavigationBar.defaultSecondaryContentHeight : 0.0 - contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - expansionHeight - apparentAdditionalHeight - additionalExpansionHeight), size: CGSize(width: size.width, height: expansionHeight)) + contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - expansionHeight - apparentAdditionalHeight), size: CGSize(width: size.width, height: expansionHeight)) if appearsHidden { if self.secondaryContentNode != nil { contentNodeFrame.origin.y += NavigationBar.defaultSecondaryContentHeight @@ -1193,10 +1174,10 @@ open class NavigationBar: ASDisplayNode { contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - if case .replacement = contentNode.mode, !self.buttonsContainerNode.alpha.isZero { - self.buttonsContainerNode.alpha = 0.0 + if case .replacement = contentNode.mode, !self.clippingNode.alpha.isZero { + self.clippingNode.alpha = 0.0 if animated { - self.buttonsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + self.clippingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) } } @@ -1206,36 +1187,23 @@ open class NavigationBar: ASDisplayNode { } else { self.requestLayout() } - } else if self.buttonsContainerNode.alpha.isZero { - self.buttonsContainerNode.alpha = 1.0 + } else if self.clippingNode.alpha.isZero { + self.clippingNode.alpha = 1.0 if animated { - self.buttonsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.clippingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } } } } - public func setSecondaryContentNode(_ secondaryContentNode: ASDisplayNode?, animated: Bool = false) { - if self.secondaryContentNode !== secondaryContentNode { + public func setSecondaryContentNode(_ secondatryContentNode: ASDisplayNode?) { + if self.secondaryContentNode !== secondatryContentNode { if let previous = self.secondaryContentNode { - if animated && previous.supernode === self.clippingNode { - previous.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak previous] finished in - if finished { - previous?.removeFromSupernode() - previous?.layer.removeAllAnimations() - } - }) - } else { - previous.removeFromSupernode() - } + previous.removeFromSupernode() } - self.secondaryContentNode = secondaryContentNode - if let secondaryContentNode = secondaryContentNode { + self.secondaryContentNode = secondatryContentNode + if let secondaryContentNode = secondatryContentNode { self.clippingNode.addSubnode(secondaryContentNode) - - if animated { - secondaryContentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - } } } } @@ -1255,11 +1223,11 @@ open class NavigationBar: ASDisplayNode { if let contentNode = self.contentNode, case .replacement = contentNode.mode { } else { let targetAlpha: CGFloat = hidden ? 0.0 : 1.0 - let previousAlpha = self.buttonsContainerNode.alpha + let previousAlpha = self.clippingNode.alpha if previousAlpha != targetAlpha { - self.buttonsContainerNode.alpha = targetAlpha + self.clippingNode.alpha = targetAlpha if animated { - self.buttonsContainerNode.layer.animateAlpha(from: previousAlpha, to: targetAlpha, duration: 0.2) + self.clippingNode.layer.animateAlpha(from: previousAlpha, to: targetAlpha, duration: 0.2) } } } @@ -1279,7 +1247,7 @@ open class NavigationBar: ASDisplayNode { return nil } - if result == self.view || result == self.buttonsContainerNode.view { + if result == self.view || result == self.clippingNode.view { return nil } diff --git a/submodules/Display/Source/TabBarController.swift b/submodules/Display/Source/TabBarController.swift index 6adad68208..313b2382e1 100644 --- a/submodules/Display/Source/TabBarController.swift +++ b/submodules/Display/Source/TabBarController.swift @@ -340,9 +340,9 @@ open class TabBarController: ViewController { } } - public func updateLayout(transition: ContainedViewLayoutTransition = .immediate) { + public func updateLayout() { if let layout = self.validLayout { - self.containerLayoutUpdated(layout, transition: transition) + self.containerLayoutUpdated(layout, transition: .immediate) } } diff --git a/submodules/Display/Source/ViewController.swift b/submodules/Display/Source/ViewController.swift index 37c1a7a2a1..c3d7b1d01d 100644 --- a/submodules/Display/Source/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -400,9 +400,6 @@ public enum TabBarItemContextActionType { if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode, !self.displayNavigationBar { navigationBarFrame.origin.y += contentNode.height + statusBarHeight } - if let _ = navigationBar.contentNode, let _ = navigationBar.secondaryContentNode, !self.displayNavigationBar { - navigationBarFrame.origin.y += NavigationBar.defaultSecondaryContentHeight - } navigationBar.updateLayout(size: navigationBarFrame.size, defaultHeight: defaultNavigationBarHeight, additionalHeight: 0.0, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, appearsHidden: !self.displayNavigationBar, transition: transition) if !transition.isAnimated { navigationBar.layer.cancelAnimationsRecursive(key: "bounds") diff --git a/submodules/FileMediaResourceStatus/BUCK b/submodules/FileMediaResourceStatus/BUCK deleted file mode 100644 index 1fa2f38081..0000000000 --- a/submodules/FileMediaResourceStatus/BUCK +++ /dev/null @@ -1,23 +0,0 @@ -load("//Config:buck_rule_macros.bzl", "static_library") - -static_library( - name = "FileMediaResourceStatus", - srcs = glob([ - "Sources/**/*.swift", - ]), - deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", - "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", - "//submodules/Display:Display#shared", - "//submodules/Postbox:Postbox#shared", - "//submodules/TelegramCore:TelegramCore#shared", - "//submodules/SyncCore:SyncCore#shared", - "//submodules/AccountContext:AccountContext", - "//submodules/MediaPlayer:UniversalMediaPlayer", - ], - frameworks = [ - "$SDKROOT/System/Library/Frameworks/Foundation.framework", - "$SDKROOT/System/Library/Frameworks/UIKit.framework", - "$SDKROOT/System/Library/Frameworks/WebKit.framework", - ], -) diff --git a/submodules/FileMediaResourceStatus/BUILD b/submodules/FileMediaResourceStatus/BUILD deleted file mode 100644 index b58e90917c..0000000000 --- a/submodules/FileMediaResourceStatus/BUILD +++ /dev/null @@ -1,22 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") - -swift_library( - name = "FileMediaResourceStatus", - module_name = "FileMediaResourceStatus", - srcs = glob([ - "Sources/**/*.swift", - ]), - deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/Postbox:Postbox", - "//submodules/TelegramCore:TelegramCore", - "//submodules/SyncCore:SyncCore", - "//submodules/AccountContext:AccountContext", - "//submodules/MediaPlayer:UniversalMediaPlayer", - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/GalleryData/BUCK b/submodules/GalleryData/BUCK deleted file mode 100644 index 34c899e95f..0000000000 --- a/submodules/GalleryData/BUCK +++ /dev/null @@ -1,31 +0,0 @@ -load("//Config:buck_rule_macros.bzl", "static_library") - -static_library( - name = "GalleryData", - srcs = glob([ - "Sources/**/*.swift", - ]), - deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", - "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", - "//submodules/Display:Display#shared", - "//submodules/Postbox:Postbox#shared", - "//submodules/TelegramCore:TelegramCore#shared", - "//submodules/SyncCore:SyncCore#shared", - "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/AccountContext:AccountContext", - "//submodules/AppBundle:AppBundle", - "//submodules/PresentationDataUtils:PresentationDataUtils", - "//submodules/InstantPageUI:InstantPageUI", - "//submodules/GalleryUI:GalleryUI", - "//submodules/PeerAvatarGalleryUI:PeerAvatarGalleryUI", - "//submodules/MediaResources:MediaResources", - "//submodules/WebsiteType:WebsiteType", - ], - frameworks = [ - "$SDKROOT/System/Library/Frameworks/Foundation.framework", - "$SDKROOT/System/Library/Frameworks/UIKit.framework", - "$SDKROOT/System/Library/Frameworks/QuickLook.framework", - "$SDKROOT/System/Library/Frameworks/SafariServices.framework", - ], -) diff --git a/submodules/GalleryData/BUILD b/submodules/GalleryData/BUILD deleted file mode 100644 index 100f76b985..0000000000 --- a/submodules/GalleryData/BUILD +++ /dev/null @@ -1,29 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") - -swift_library( - name = "GalleryData", - module_name = "GalleryData", - srcs = glob([ - "Sources/**/*.swift", - ]), - deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/Postbox:Postbox", - "//submodules/TelegramCore:TelegramCore", - "//submodules/SyncCore:SyncCore", - "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/AccountContext:AccountContext", - "//submodules/AppBundle:AppBundle", - "//submodules/PresentationDataUtils:PresentationDataUtils", - "//submodules/InstantPageUI:InstantPageUI", - "//submodules/GalleryUI:GalleryUI", - "//submodules/PeerAvatarGalleryUI:PeerAvatarGalleryUI", - "//submodules/MediaResources:MediaResources", - "//submodules/WebsiteType:WebsiteType", - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/GalleryData/Sources/GalleryData.swift b/submodules/GalleryData/Sources/GalleryData.swift deleted file mode 100644 index a04e70fa18..0000000000 --- a/submodules/GalleryData/Sources/GalleryData.swift +++ /dev/null @@ -1,296 +0,0 @@ -import Foundation -import Display -import AsyncDisplayKit -import Postbox -import TelegramCore -import SyncCore -import SwiftSignalKit -import PassKit -import Lottie -import TelegramUIPreferences -import TelegramPresentationData -import AccountContext -import InstantPageUI -import PeerAvatarGalleryUI -import GalleryUI -import MediaResources -import WebsiteType - -public enum ChatMessageGalleryControllerData { - case url(String) - case pass(TelegramMediaFile) - case instantPage(InstantPageGalleryController, Int, Media) - case map(TelegramMediaMap) - case stickerPack(StickerPackReference) - case audio(TelegramMediaFile) - case document(TelegramMediaFile, Bool) - case gallery(Signal) - case secretGallery(SecretMediaPreviewController) - case chatAvatars(AvatarGalleryController, Media) - case theme(TelegramMediaFile) - case other(Media) -} - -private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, media: [MediaId: Media], counter: inout Int) -> [InstantPageGalleryEntry] { - 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, 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, _, _): - if let m = media[id] { - 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 .collage(items, _): - var result: [InstantPageGalleryEntry] = [] - for item in items { - result.append(contentsOf: instantPageBlockMedia(pageId: pageId, block: item, media: media, counter: &counter)) - } - return result - case let .slideshow(items, _): - var result: [InstantPageGalleryEntry] = [] - for item in items { - result.append(contentsOf: instantPageBlockMedia(pageId: pageId, block: item, media: media, counter: &counter)) - } - return result - default: - break - } - return [] -} - -public func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage, galleryMedia: Media) -> [InstantPageGalleryEntry] { - var result: [InstantPageGalleryEntry] = [] - var counter: Int = 0 - - for block in page.blocks { - result.append(contentsOf: instantPageBlockMedia(pageId: webpageId, block: block, media: page.media, counter: &counter)) - } - - var found = false - for item in result { - if item.media.media.id == galleryMedia.id { - found = true - break - } - } - - if !found { - 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, credit: item.credit, location: InstantPageGalleryEntryLocation(position: Int32(i), totalCount: Int32(result.count))) - } - return result -} - -public func chatMessageGalleryControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic?, message: Message, navigationController: NavigationController?, standalone: Bool, reverseMessageGalleryOrder: Bool, mode: ChatControllerInteractionOpenMessageMode, source: GalleryControllerItemSource?, synchronousLoad: Bool, actionInteraction: GalleryControllerActionInteraction?) -> ChatMessageGalleryControllerData? { - var galleryMedia: Media? - var otherMedia: Media? - var instantPageMedia: (TelegramMediaWebpage, [InstantPageGalleryEntry])? - for media in message.media { - if let action = media as? TelegramMediaAction { - switch action.action { - case let .photoUpdated(image): - if let peer = messageMainPeer(message), let image = image { - let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer, message.timestamp, nil, message.id, image.immediateThumbnailData, "action")]) - let galleryController = AvatarGalleryController(context: context, peer: peer, sourceCorners: .roundRect(15.5), remoteEntries: promise, skipInitial: true, replaceRootController: { controller, ready in - - }) - return .chatAvatars(galleryController, image) - } - default: - break - } - } else if let file = media as? TelegramMediaFile { - galleryMedia = file - } else if let image = media as? TelegramMediaImage { - galleryMedia = image - } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { - if let file = content.file { - galleryMedia = file - } else if let image = content.image { - if case .link = mode { - } else if ["photo", "document", "video", "gif", "telegram_album"].contains(content.type) { - galleryMedia = image - } - } - - if let instantPage = content.instantPage, let galleryMedia = galleryMedia { - switch instantPageType(of: content) { - case .album: - let medias = instantPageGalleryMedia(webpageId: webpage.webpageId, page: instantPage, galleryMedia: galleryMedia) - if medias.count > 1 { - instantPageMedia = (webpage, medias) - } - default: - break - } - } - } else if let mapMedia = media as? TelegramMediaMap { - galleryMedia = mapMedia - } else if let contactMedia = media as? TelegramMediaContact { - otherMedia = contactMedia - } - } - - var stream = false - var autoplayingVideo = false - var landscape = false - var timecode: Double? = nil - - switch mode { - case .stream: - stream = true - case .automaticPlayback: - autoplayingVideo = true - case .landscape: - autoplayingVideo = true - landscape = true - case let .timecode(time): - timecode = time - default: - break - } - - if let (webPage, instantPageMedia) = instantPageMedia, let galleryMedia = galleryMedia { - var centralIndex: Int = 0 - for i in 0 ..< instantPageMedia.count { - if instantPageMedia[i].media.media.id == galleryMedia.id { - centralIndex = i - break - } - } - - let gallery = InstantPageGalleryController(context: context, webPage: webPage, message: message, entries: instantPageMedia, centralIndex: centralIndex, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, replaceRootController: { [weak navigationController] controller, ready in - if let navigationController = navigationController { - navigationController.replaceTopController(controller, animated: false, ready: ready) - } - }, baseNavigationController: navigationController) - return .instantPage(gallery, centralIndex, galleryMedia) - } else if let galleryMedia = galleryMedia { - if let mapMedia = galleryMedia as? TelegramMediaMap { - return .map(mapMedia) - } else if let file = galleryMedia as? TelegramMediaFile, (file.isSticker || file.isAnimatedSticker) { - for attribute in file.attributes { - if case let .Sticker(_, reference, _) = attribute { - if let reference = reference { - return .stickerPack(reference) - } - break - } - } - } else if let file = galleryMedia as? TelegramMediaFile, file.isAnimatedSticker { - return nil - } else if let file = galleryMedia as? TelegramMediaFile, file.isMusic || file.isVoice || file.isInstantVideo { - return .audio(file) - } else if let file = galleryMedia as? TelegramMediaFile, file.mimeType == "application/vnd.apple.pkpass" || (file.fileName != nil && file.fileName!.lowercased().hasSuffix(".pkpass")) { - return .pass(file) - } else { - if let file = galleryMedia as? TelegramMediaFile { - if let fileName = file.fileName { - let ext = (fileName as NSString).pathExtension.lowercased() - if ext == "tgios-theme" { - return .theme(file) - } else if ext == "wav" || ext == "opus" { - return .audio(file) - } else if ext == "json", let fileSize = file.size, fileSize < 1024 * 1024 { - if let path = context.account.postbox.mediaBox.completedResourcePath(file.resource), let composition = LOTComposition(filePath: path), composition.timeDuration > 0.0 { - let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? ChatLocation.peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic(value: nil)), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in - navigationController?.replaceTopController(controller, animated: false, ready: ready) - }, baseNavigationController: navigationController, actionInteraction: actionInteraction) - return .gallery(.single(gallery)) - } - } - - if ext == "mkv" { - return .document(file, true) - } - } - - if internalDocumentItemSupportsMimeType(file.mimeType, fileName: file.fileName ?? "file") { - let gallery = GalleryController(context: context, source: source ?? .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? .peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic(value: nil)), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in - navigationController?.replaceTopController(controller, animated: false, ready: ready) - }, baseNavigationController: navigationController, actionInteraction: actionInteraction) - return .gallery(.single(gallery)) - } - - if !file.isVideo { - return .document(file, false) - } - } - - if message.containsSecretMedia { - let gallery = SecretMediaPreviewController(context: context, messageId: message.id) - return .secretGallery(gallery) - } else { - let startTimecode: Signal - if let timecode = timecode { - startTimecode = .single(timecode) - } else { - startTimecode = mediaPlaybackStoredState(postbox: context.account.postbox, messageId: message.id) - |> map { state in - return state?.timestamp - } - } - - return .gallery(startTimecode - |> deliverOnMainQueue - |> map { timecode in - let gallery = GalleryController(context: context, source: source ?? (standalone ? .standaloneMessage(message) : .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? .peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic(value: nil))), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in - navigationController?.replaceTopController(controller, animated: false, ready: ready) - }, baseNavigationController: navigationController, actionInteraction: actionInteraction) - gallery.temporaryDoNotWaitForReady = autoplayingVideo - return gallery - }) - } - } - } - if let otherMedia = otherMedia { - return .other(otherMedia) - } else { - return nil - } -} - -public enum ChatMessagePreviewControllerData { - case instantPage(InstantPageGalleryController, Int, Media) - case gallery(GalleryController) -} - -public func chatMessagePreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> ChatMessagePreviewControllerData? { - if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) { - switch mediaData { - case let .gallery(gallery): - break - case let .instantPage(gallery, centralIndex, galleryMedia): - return .instantPage(gallery, centralIndex, galleryMedia) - default: - break - } - } - return nil -} - -public func chatMediaListPreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> Signal { - if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) { - switch mediaData { - case let .gallery(gallery): - return gallery - |> map { gallery in - return .gallery(gallery) - } - case let .instantPage(gallery, centralIndex, galleryMedia): - return .single(.instantPage(gallery, centralIndex, galleryMedia)) - default: - break - } - } - return .single(nil) -} diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index c785e51ba3..16f78eee92 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -246,20 +246,20 @@ public final class GalleryControllerPresentationArguments { private enum GalleryMessageHistoryView { case view(MessageHistoryView) - case entries([MessageHistoryEntry], Bool, Bool) + case single(MessageHistoryEntry) var entries: [MessageHistoryEntry] { switch self { case let .view(view): return view.entries - case let .entries(entries, _, _): - return entries + case let .single(entry): + return [entry] } } var tagMask: MessageTags? { switch self { - case .entries: + case .single: return nil case let .view(view): return view.tagMask @@ -268,8 +268,8 @@ private enum GalleryMessageHistoryView { var hasEarlier: Bool { switch self { - case let .entries(_, hasEarlier, _): - return hasEarlier + case .single: + return false case let .view(view): return view.earlierId != nil } @@ -277,14 +277,19 @@ private enum GalleryMessageHistoryView { var hasLater: Bool { switch self { - case let .entries(_ , _, hasLater): - return hasLater + case .single: + return false case let .view(view): return view.laterId != nil } } } +public enum GalleryControllerItemSource { + case peerMessagesAtId(MessageId) + case standaloneMessage(Message) +} + public enum GalleryControllerInteractionTapAction { case url(url: String, concealed: Bool) case textMention(String) @@ -352,7 +357,6 @@ public class GalleryController: ViewController, StandalonePresentableController private var entries: [MessageHistoryEntry] = [] private var hasLeftEntries: Bool = false private var hasRightEntries: Bool = false - private var loadingMore: Bool = false private var tagMask: MessageTags? private var centralEntryStableId: UInt32? private var configuration: GalleryConfiguration? @@ -413,23 +417,17 @@ public class GalleryController: ViewController, StandalonePresentableController let message: Signal switch source { - case let .peerMessagesAtId(messageId, _, _): + case let .peerMessagesAtId(messageId): message = context.account.postbox.messageAtId(messageId) case let .standaloneMessage(m): message = .single(m) - case let .custom(messages, messageId, _): - message = messages - |> take(1) - |> map { messages, _, _ in - return messages.first(where: { $0.id == messageId }) - } } let messageView = message |> filter({ $0 != nil }) |> mapToSignal { message -> Signal in switch source { - case let .peerMessagesAtId(_, chatLocation, chatLocationContextHolder): + case .peerMessagesAtId: if let tags = tagsForMessage(message!) { let namespaces: MessageIdNamespaces if Namespaces.Message.allScheduled.contains(message!.id.namespace) { @@ -437,27 +435,16 @@ public class GalleryController: ViewController, StandalonePresentableController } else { namespaces = .not(Namespaces.Message.allScheduled) } - return context.account.postbox.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(message!.index), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, namespaces: namespaces, orderStatistics: [.combinedLocation]) - |> mapToSignal { (view, _, _) -> Signal in - let mapped = GalleryMessageHistoryView.view(view) - return .single(mapped) + return context.account.postbox.aroundMessageHistoryViewForLocation(.peer(message!.id.peerId), anchor: .index(message!.index), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, namespaces: namespaces, orderStatistics: [.combinedLocation]) + |> mapToSignal { (view, _, _) -> Signal in + let mapped = GalleryMessageHistoryView.view(view) + return .single(mapped) } } else { - return .single(GalleryMessageHistoryView.entries([MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))], false, false)) - } - case .standaloneMessage: - return .single(GalleryMessageHistoryView.entries([MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))], false ,false)) - case let .custom(messages, _, _): - return messages - |> map { messages, totalCount, hasMore in - var entries: [MessageHistoryEntry] = [] - var index = messages.count - for message in messages.reversed() { - entries.append(MessageHistoryEntry(message: message, isRead: false, location: MessageHistoryEntryLocation(index: Int(totalCount) - index, count: Int(totalCount)), monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))) - index -= 1 - } - return GalleryMessageHistoryView.entries(entries, hasMore, false) + return .single(GalleryMessageHistoryView.single(MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)))) } + case .standaloneMessage: + return .single(GalleryMessageHistoryView.single(MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)))) } } |> take(1) @@ -483,7 +470,7 @@ public class GalleryController: ViewController, StandalonePresentableController loop: for i in 0 ..< entries.count { let message = entries[i].message switch source { - case let .peerMessagesAtId(messageId, _, _): + case let .peerMessagesAtId(messageId): if message.id == messageId { centralEntryStableId = message.stableId break loop @@ -493,11 +480,6 @@ public class GalleryController: ViewController, StandalonePresentableController centralEntryStableId = message.stableId break loop } - case let .custom(_, messageId, _): - if message.id == messageId { - centralEntryStableId = message.stableId - break loop - } } } @@ -831,7 +813,7 @@ public class GalleryController: ViewController, StandalonePresentableController self.isOpaqueWhenInOverlay = true switch source { - case let .peerMessagesAtId(id, _, _): + case let .peerMessagesAtId(id): if id.peerId.namespace == Namespaces.Peer.SecretChat { self.screenCaptureEventsDisposable = (screenCaptureEvents() |> deliverOnMainQueue).start(next: { [weak self] _ in @@ -1002,125 +984,70 @@ public class GalleryController: ViewController, StandalonePresentableController } switch strongSelf.source { - case let .peerMessagesAtId(_, chatLocation, chatLocationContextHolder): - var reloadAroundIndex: MessageIndex? - if index <= 2 && strongSelf.hasLeftEntries { - reloadAroundIndex = strongSelf.entries.first?.index - } else if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries { - reloadAroundIndex = strongSelf.entries.last?.index - } - if let reloadAroundIndex = reloadAroundIndex, let tagMask = strongSelf.tagMask { - let namespaces: MessageIdNamespaces - if Namespaces.Message.allScheduled.contains(message.id.namespace) { - namespaces = .just(Namespaces.Message.allScheduled) - } else { - namespaces = .not(Namespaces.Message.allScheduled) - } - let signal = strongSelf.context.account.postbox.aroundMessageHistoryViewForLocation(strongSelf.context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(reloadAroundIndex), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [.combinedLocation]) - |> mapToSignal { (view, _, _) -> Signal in - let mapped = GalleryMessageHistoryView.view(view) - return .single(mapped) - } - |> take(1) - - strongSelf.updateVisibleDisposable.set((signal - |> deliverOnMainQueue).start(next: { view in - guard let strongSelf = self, let view = view else { - return - } - - let entries = view.entries - - if strongSelf.invertItemOrder { - strongSelf.entries = entries.reversed() - strongSelf.hasLeftEntries = view.hasLater - strongSelf.hasRightEntries = view.hasEarlier - } else { - strongSelf.entries = entries - strongSelf.hasLeftEntries = view.hasEarlier - strongSelf.hasRightEntries = view.hasLater - } - if strongSelf.isViewLoaded { - var items: [GalleryItem] = [] - var centralItemIndex: Int? - for entry in strongSelf.entries { - var isCentral = false - if entry.message.stableId == strongSelf.centralEntryStableId { - isCentral = true - } - if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in - if let strongSelf = self { - strongSelf.presentInGlobalOverlay(c, with: a) - } - }) { - if isCentral { - centralItemIndex = items.count - } - items.append(item) - } - } - - strongSelf.galleryNode.pager.replaceItems(items, centralItemIndex: centralItemIndex) - } - })) + case let .peerMessagesAtId(initialMessageId): + var reloadAroundIndex: MessageIndex? + if index <= 2 && strongSelf.hasLeftEntries { + reloadAroundIndex = strongSelf.entries.first?.index + } else if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries { + reloadAroundIndex = strongSelf.entries.last?.index } - case let .custom(messages, _, loadMore): - if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries && !strongSelf.loadingMore { - strongSelf.loadingMore = true - loadMore?() - - strongSelf.updateVisibleDisposable.set((messages - |> deliverOnMainQueue).start(next: { messages, totalCount, hasMore in - guard let strongSelf = self else { - return - } - - var entries: [MessageHistoryEntry] = [] - var index = messages.count - for message in messages.reversed() { - entries.append(MessageHistoryEntry(message: message, isRead: false, location: MessageHistoryEntryLocation(index: Int(totalCount) - index, count: Int(totalCount)), monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))) - index -= 1 - } - - if entries.count > strongSelf.entries.count { - if strongSelf.invertItemOrder { - strongSelf.entries = entries.reversed() - strongSelf.hasLeftEntries = false - strongSelf.hasRightEntries = hasMore - } else { - strongSelf.entries = entries - strongSelf.hasLeftEntries = hasMore - strongSelf.hasRightEntries = false - } - if strongSelf.isViewLoaded { - var items: [GalleryItem] = [] - var centralItemIndex: Int? - for entry in strongSelf.entries { - var isCentral = false - if entry.message.stableId == strongSelf.centralEntryStableId { - isCentral = true - } - if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in - if let strongSelf = self { - strongSelf.presentInGlobalOverlay(c, with: a) - } - }) { - if isCentral { - centralItemIndex = items.count - } - items.append(item) - } - } - - strongSelf.galleryNode.pager.replaceItems(items, centralItemIndex: centralItemIndex) - } - } - - strongSelf.loadingMore = false - })) + if let reloadAroundIndex = reloadAroundIndex, let tagMask = strongSelf.tagMask { + let namespaces: MessageIdNamespaces + if Namespaces.Message.allScheduled.contains(message.id.namespace) { + namespaces = .just(Namespaces.Message.allScheduled) + } else { + namespaces = .not(Namespaces.Message.allScheduled) } - default: - break + let signal = strongSelf.context.account.postbox.aroundMessageHistoryViewForLocation(.peer(initialMessageId.peerId), anchor: .index(reloadAroundIndex), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [.combinedLocation]) + |> mapToSignal { (view, _, _) -> Signal in + let mapped = GalleryMessageHistoryView.view(view) + return .single(mapped) + } + |> take(1) + + strongSelf.updateVisibleDisposable.set((signal + |> deliverOnMainQueue).start(next: { view in + guard let strongSelf = self, let view = view else { + return + } + + let entries = view.entries + + if strongSelf.invertItemOrder { + strongSelf.entries = entries.reversed() + strongSelf.hasLeftEntries = view.hasLater + strongSelf.hasRightEntries = view.hasEarlier + } else { + strongSelf.entries = entries + strongSelf.hasLeftEntries = view.hasEarlier + strongSelf.hasRightEntries = view.hasLater + } + if strongSelf.isViewLoaded { + var items: [GalleryItem] = [] + var centralItemIndex: Int? + for entry in strongSelf.entries { + var isCentral = false + if entry.message.stableId == strongSelf.centralEntryStableId { + isCentral = true + } + if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in + if let strongSelf = self { + strongSelf.presentInGlobalOverlay(c, with: a) + } + }) { + if isCentral { + centralItemIndex = items.count + } + items.append(item) + } + } + + strongSelf.galleryNode.pager.replaceItems(items, centralItemIndex: centralItemIndex) + } + })) + } + default: + break } } if strongSelf.didSetReady { @@ -1225,7 +1152,6 @@ public class GalleryController: ViewController, StandalonePresentableController self.adjustedForInitialPreviewingLayout = true self.galleryNode.setControlsHidden(true, animated: false) if let centralItemNode = self.galleryNode.pager.centralItemNode(), let itemSize = centralItemNode.contentSize() { - centralItemNode.adjustForPreviewing() self.preferredContentSize = itemSize.aspectFitted(layout.size) self.containerLayoutUpdated(ContainerViewLayout(size: self.preferredContentSize, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate) } diff --git a/submodules/GalleryUI/Sources/GalleryItemNode.swift b/submodules/GalleryUI/Sources/GalleryItemNode.swift index 7f90361440..ebd39a13fe 100644 --- a/submodules/GalleryUI/Sources/GalleryItemNode.swift +++ b/submodules/GalleryUI/Sources/GalleryItemNode.swift @@ -85,9 +85,6 @@ open class GalleryItemNode: ASDisplayNode { open func controlsVisibilityUpdated(isVisible: Bool) { } - open func adjustForPreviewing() { - } - open func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) { } diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index f5e21124f7..e406599fa7 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -1352,7 +1352,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { switch contentInfo { case let .message(message): - let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: .peer(message.id.peerId), chatLocationContextHolder: Atomic(value: nil)), replaceRootController: { controller, ready in + let gallery = GalleryController(context: context, source: .peerMessagesAtId(message.id), replaceRootController: { controller, ready in if let baseNavigationController = baseNavigationController { baseNavigationController.replaceTopController(controller, animated: false, ready: ready) } @@ -1434,7 +1434,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { switch contentInfo { case let .message(message): - let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: .peer(message.id.peerId), chatLocationContextHolder: Atomic(value: nil)), replaceRootController: { controller, ready in + let gallery = GalleryController(context: context, source: .peerMessagesAtId(message.id), replaceRootController: { controller, ready in if let baseNavigationController = baseNavigationController { baseNavigationController.replaceTopController(controller, animated: false, ready: ready) } @@ -1537,12 +1537,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } - override func adjustForPreviewing() { - super.adjustForPreviewing() - - self.scrubberView.isHidden = true - } - override func footerContent() -> Signal<(GalleryFooterContentNode?, GalleryOverlayContentNode?), NoError> { return .single((self.footerContentNode, nil)) } diff --git a/submodules/HashtagSearchUI/BUCK b/submodules/HashtagSearchUI/BUCK index 430c1f9319..dd0dc6ebee 100644 --- a/submodules/HashtagSearchUI/BUCK +++ b/submodules/HashtagSearchUI/BUCK @@ -17,7 +17,6 @@ static_library( "//submodules/TelegramBaseController:TelegramBaseController", "//submodules/ChatListUI:ChatListUI", "//submodules/SegmentedControlNode:SegmentedControlNode", - "//submodules/ListMessageItem:ListMessageItem", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/HashtagSearchUI/BUILD b/submodules/HashtagSearchUI/BUILD index 40d2b21af0..0384121b97 100644 --- a/submodules/HashtagSearchUI/BUILD +++ b/submodules/HashtagSearchUI/BUILD @@ -18,7 +18,6 @@ swift_library( "//submodules/TelegramBaseController:TelegramBaseController", "//submodules/ChatListUI:ChatListUI", "//submodules/SegmentedControlNode:SegmentedControlNode", - "//submodules/ListMessageItem:ListMessageItem", ], visibility = [ "//visibility:public", diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index 45c43d96d7..538fa413c0 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -9,7 +9,6 @@ import TelegramPresentationData import TelegramBaseController import AccountContext import ChatListUI -import ListMessageItem public final class HashtagSearchController: TelegramBaseController { private let queue = Queue() @@ -42,11 +41,11 @@ public final class HashtagSearchController: TelegramBaseController { let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations) - let location: SearchMessagesLocation = .general(tags: nil, minDate: nil, maxDate: nil) + let location: SearchMessagesLocation = .general(tags: nil) let search = searchMessages(account: context.account, location: location, query: query, state: nil) let foundMessages: Signal<[ChatListSearchEntry], NoError> = search |> map { result, _ in - return result.messages.map({ .message($0, RenderedPeer(message: $0), result.readStates[$0.id.peerId], chatListPresentationData, result.totalCount, nil, false) }) + return result.messages.map({ .message($0, RenderedPeer(message: $0), result.readStates[$0.id.peerId], chatListPresentationData) }) } let interaction = ChatListNodeInteraction(activateSearch: { }, peerSelected: { _, _ in @@ -83,27 +82,10 @@ public final class HashtagSearchController: TelegramBaseController { if let strongSelf = self { let previousEntries = previousSearchItems.swap(entries) - let listInteraction = ListMessageItemInteraction(openMessage: { message, mode -> Bool in - return true - }, openMessageContextMenu: { message, bool, node, rect, gesture in - - }, toggleMessagesSelection: { messageId, selected in - - }, openUrl: { url, _, _, message in - }, openInstantPage: { message, data in - - }, longTap: { action, message in - - }, getHiddenMedia: { - return [:] - }) - let firstTime = previousEntries == nil - let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, searchQuery: "", context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: { + let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], interaction: interaction, peerContextAction: nil, toggleExpandLocalResults: { }, toggleExpandGlobalResults: { - }, searchPeer: { _ in - - }, searchResults: [], searchOptions: nil, messageContextAction: nil) + }) strongSelf.controllerNode.enqueueTransition(transition, firstTime: firstTime) } }) diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift index e722b64105..ef47b6d8ea 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift @@ -42,8 +42,6 @@ final class HashtagSearchControllerNode: ASDisplayNode { var items: [String] = [] if peer?.id == context.account.peerId { items.append(presentationData.strings.Conversation_SavedMessages) - } else if let id = peer?.id, id.isReplies { - items.append(presentationData.strings.DialogList_Replies) } else { items.append(peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) ?? "") } diff --git a/submodules/InstantPageUI/Sources/InstantPageController.swift b/submodules/InstantPageUI/Sources/InstantPageController.swift index 5bbb5f9c17..b8d3fb4cfe 100644 --- a/submodules/InstantPageUI/Sources/InstantPageController.swift +++ b/submodules/InstantPageUI/Sources/InstantPageController.swift @@ -9,56 +9,6 @@ import TelegramPresentationData import TelegramUIPreferences import AccountContext -public func instantPageAndAnchor(message: Message) -> (TelegramMediaWebpage, String?)? { - for media in message.media { - if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { - if let _ = content.instantPage { - var textUrl: String? - if let pageUrl = URL(string: content.url) { - inner: for attribute in message.attributes { - if let attribute = attribute as? TextEntitiesMessageAttribute { - for entity in attribute.entities { - switch entity.type { - case let .TextUrl(url): - if let parsedUrl = URL(string: url) { - if pageUrl.scheme == parsedUrl.scheme && pageUrl.host == parsedUrl.host && pageUrl.path == parsedUrl.path { - textUrl = url - } - } - case .Url: - let nsText = message.text as NSString - var entityRange = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) - if entityRange.location + entityRange.length > nsText.length { - entityRange.location = max(0, nsText.length - entityRange.length) - entityRange.length = nsText.length - entityRange.location - } - let url = nsText.substring(with: entityRange) - if let parsedUrl = URL(string: url) { - if pageUrl.scheme == parsedUrl.scheme && pageUrl.host == parsedUrl.host && pageUrl.path == parsedUrl.path { - textUrl = url - } - } - default: - break - } - } - break inner - } - } - } - var anchor: String? - if let textUrl = textUrl, let anchorRange = textUrl.range(of: "#") { - anchor = String(textUrl[anchorRange.upperBound...]) - } - - return (webpage, anchor) - } - break - } - } - return nil -} - public final class InstantPageController: ViewController { private let context: AccountContext private var webPage: TelegramMediaWebpage diff --git a/submodules/InstantPageUI/Sources/InstantPageLayout.swift b/submodules/InstantPageUI/Sources/InstantPageLayout.swift index 4de40ca9b9..65fe599451 100644 --- a/submodules/InstantPageUI/Sources/InstantPageLayout.swift +++ b/submodules/InstantPageUI/Sources/InstantPageLayout.swift @@ -855,9 +855,6 @@ func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth: if let image = loadedContent.image, let id = image.id { media[id] = image } - if let video = loadedContent.file, let id = video.id { - media[id] = video - } var mediaIndexCounter: Int = 0 var embedIndexCounter: Int = 0 diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index 5df854a406..55e678f1cb 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -683,8 +683,6 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo if item.peer.id == item.context.account.peerId, case .threatSelfAsSaved = item.aliasHandling { titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_SavedMessages, font: currentBoldFont, textColor: titleColor) - } else if item.peer.id.isReplies { - titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Replies, font: currentBoldFont, textColor: titleColor) } else if let user = item.peer as? TelegramUser { if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty { let string = NSMutableAttributedString() @@ -1061,8 +1059,6 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo if item.peer.id == item.context.account.peerId, case .threatSelfAsSaved = item.aliasHandling { strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.peer, overrideImage: .savedMessagesIcon, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad) - } else if item.peer.id.isReplies { - strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.peer, overrideImage: .repliesIcon, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad) } else { var overrideImage: AvatarNodeImageOverride? if item.peer.isDeleted { diff --git a/submodules/LegacyDataImport/Sources/LegacyChatImport.swift b/submodules/LegacyDataImport/Sources/LegacyChatImport.swift index 8a14aebe91..99c05c5600 100644 --- a/submodules/LegacyDataImport/Sources/LegacyChatImport.swift +++ b/submodules/LegacyDataImport/Sources/LegacyChatImport.swift @@ -570,7 +570,7 @@ private func loadLegacyMessages(account: TemporaryAccount, basePath: String, acc } let (parsedTags, parsedGlobalTags) = tagsForStoreMessage(incoming: parsedFlags.contains(.Incoming), attributes: parsedAttributes, media: parsedMedia, textEntities: nil) - messages.append(StoreMessage(id: parsedId, globallyUniqueId: globallyUniqueId, groupingKey: parsedGroupingKey, threadId: nil, timestamp: timestamp, flags: parsedFlags, tags: parsedTags, globalTags: parsedGlobalTags, localTags: [], forwardInfo: nil, authorId: parsedAuthorId, text: text, attributes: parsedAttributes, media: parsedMedia)) + messages.append(StoreMessage(id: parsedId, globallyUniqueId: globallyUniqueId, groupingKey: parsedGroupingKey, timestamp: timestamp, flags: parsedFlags, tags: parsedTags, globalTags: parsedGlobalTags, localTags: [], forwardInfo: nil, authorId: parsedAuthorId, text: text, attributes: parsedAttributes, media: parsedMedia)) //Logger.shared.log("loadLegacyMessages", "message \(messageId) completed") diff --git a/submodules/ListMessageItem/BUCK b/submodules/ListMessageItem/BUCK deleted file mode 100644 index 64f9dd50e7..0000000000 --- a/submodules/ListMessageItem/BUCK +++ /dev/null @@ -1,36 +0,0 @@ -load("//Config:buck_rule_macros.bzl", "static_library") - -static_library( - name = "ListMessageItem", - srcs = glob([ - "Sources/**/*.swift", - ]), - deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", - "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", - "//submodules/Display:Display#shared", - "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/ItemListUI:ItemListUI", - "//submodules/AccountContext:AccountContext", - "//submodules/TextFormat:TextFormat", - "//submodules/AppBundle:AppBundle", - "//submodules/PresentationDataUtils:PresentationDataUtils", - "//submodules/TelegramUIPreferences:TelegramUIPreferences", - "//submodules/ListSectionHeaderNode:ListSectionHeaderNode", - "//submodules/TelegramStringFormatting:TelegramStringFormatting", - "//submodules/UrlHandling:UrlHandling", - "//submodules/UrlWhitelist:UrlWhitelist", - "//submodules/WebsiteType:WebsiteType", - "//submodules/PhotoResources:PhotoResources", - "//submodules/RadialStatusNode:RadialStatusNode", - "//submodules/SemanticStatusNode:SemanticStatusNode", - "//submodules/MusicAlbumArtResources:MusicAlbumArtResources", - "//submodules/MediaPlayer:UniversalMediaPlayer", - "//submodules/ContextUI:ContextUI", - "//submodules/FileMediaResourceStatus:FileMediaResourceStatus", - ], - frameworks = [ - "$SDKROOT/System/Library/Frameworks/Foundation.framework", - "$SDKROOT/System/Library/Frameworks/UIKit.framework", - ], -) diff --git a/submodules/ListMessageItem/BUILD b/submodules/ListMessageItem/BUILD deleted file mode 100644 index 55edc54d95..0000000000 --- a/submodules/ListMessageItem/BUILD +++ /dev/null @@ -1,38 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") - -swift_library( - name = "ListMessageItem", - module_name = "ListMessageItem", - srcs = glob([ - "Sources/**/*.swift", - ]), - deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/TelegramCore:TelegramCore", - "//submodules/SyncCore:SyncCore", - "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/ItemListUI:ItemListUI", - "//submodules/AccountContext:AccountContext", - "//submodules/TextFormat:TextFormat", - "//submodules/AppBundle:AppBundle", - "//submodules/PresentationDataUtils:PresentationDataUtils", - "//submodules/TelegramUIPreferences:TelegramUIPreferences", - "//submodules/ListSectionHeaderNode:ListSectionHeaderNode", - "//submodules/TelegramStringFormatting:TelegramStringFormatting", - "//submodules/UrlHandling:UrlHandling", - "//submodules/UrlWhitelist:UrlWhitelist", - "//submodules/WebsiteType:WebsiteType", - "//submodules/PhotoResources:PhotoResources", - "//submodules/RadialStatusNode:RadialStatusNode", - "//submodules/SemanticStatusNode:SemanticStatusNode", - "//submodules/MusicAlbumArtResources:MusicAlbumArtResources", - "//submodules/MediaPlayer:UniversalMediaPlayer", - "//submodules/ContextUI:ContextUI", - "//submodules/FileMediaResourceStatus:FileMediaResourceStatus", - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/LiveLocationManager/Sources/LiveLocationManager.swift b/submodules/LiveLocationManager/Sources/LiveLocationManager.swift index 4c31f5589d..acc55b9a41 100644 --- a/submodules/LiveLocationManager/Sources/LiveLocationManager.swift +++ b/submodules/LiveLocationManager/Sources/LiveLocationManager.swift @@ -250,7 +250,7 @@ public final class LiveLocationManagerImpl: LiveLocationManager { updatedMedia[i] = TelegramMediaMap(latitude: media.latitude, longitude: media.longitude, geoPlace: media.geoPlace, venue: media.venue, liveBroadcastingTimeout: max(0, timestamp - currentMessage.timestamp - 1)) } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: updatedMedia)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: updatedMedia)) }) } }).start() diff --git a/submodules/MediaResources/BUCK b/submodules/MediaResources/BUCK index 8635efccea..681d3aabaf 100644 --- a/submodules/MediaResources/BUCK +++ b/submodules/MediaResources/BUCK @@ -10,7 +10,6 @@ static_library( "//submodules/SyncCore:SyncCore#shared", "//submodules/Postbox:Postbox#shared", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", - "//submodules/TelegramUIPreferences:TelegramUIPreferences", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/MediaResources/BUILD b/submodules/MediaResources/BUILD index 7d8503018d..146fc750fd 100644 --- a/submodules/MediaResources/BUILD +++ b/submodules/MediaResources/BUILD @@ -11,7 +11,6 @@ swift_library( "//submodules/SyncCore:SyncCore", "//submodules/Postbox:Postbox", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/TelegramUIPreferences:TelegramUIPreferences", ], visibility = [ "//visibility:public", diff --git a/submodules/MtProtoKit/BUCK b/submodules/MtProtoKit/BUCK index e7c782ef4e..215e91f784 100644 --- a/submodules/MtProtoKit/BUCK +++ b/submodules/MtProtoKit/BUCK @@ -3,10 +3,10 @@ load("//Config:buck_rule_macros.bzl", "static_library", "framework", "glob_map", framework( name = "MtProtoKit", srcs = glob([ - "Sources/**/*.m", + "Sources/*.m", ]), headers = glob([ - "Sources/**/*.h", + "Sources/*.h", ]), exported_headers = glob([ "PublicHeaders/**/*.h", diff --git a/submodules/MtProtoKit/BUILD b/submodules/MtProtoKit/BUILD index c316d024a3..cdc4b42171 100644 --- a/submodules/MtProtoKit/BUILD +++ b/submodules/MtProtoKit/BUILD @@ -4,8 +4,8 @@ objc_library( enable_modules = True, module_name = "MtProtoKit", srcs = glob([ - "Sources/**/*.m", - "Sources/**/*.h", + "Sources/*.m", + "Sources/*.h", ]), hdrs = glob([ "PublicHeaders/**/*.h", diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTBindKeyMessageService.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTBindKeyMessageService.h deleted file mode 100644 index 9e5771a203..0000000000 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTBindKeyMessageService.h +++ /dev/null @@ -1,9 +0,0 @@ -#import -#import -#import - -@interface MTBindKeyMessageService : NSObject - -- (instancetype)initWithPersistentKey:(MTDatacenterAuthKey *)persistentKey ephemeralKey:(MTDatacenterAuthKey *)ephemeralKey completion:(void (^)(bool))completion; - -@end diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h index c4898b32db..c4cff82ef3 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h @@ -20,7 +20,7 @@ @optional - (void)contextDatacenterAddressSetUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId addressSet:(MTDatacenterAddressSet *)addressSet; -- (void)contextDatacenterAuthInfoUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo selector:(MTDatacenterAuthInfoSelector)selector; +- (void)contextDatacenterAuthInfoUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo; - (void)contextDatacenterAuthTokenUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authToken:(id)authToken; - (void)contextDatacenterTransportSchemesUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId shouldReset:(bool)shouldReset; - (void)contextIsPasswordRequiredUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId; @@ -49,7 +49,6 @@ @property (nonatomic, strong, readonly) MTApiEnvironment *apiEnvironment; @property (nonatomic, readonly) bool isTestingEnvironment; @property (nonatomic, readonly) bool useTempAuthKeys; -@property (nonatomic) int32_t tempKeyExpiration; + (int32_t)fixedTimeDifference; + (void)setFixedTimeDifference:(int32_t)fixedTimeDifference; @@ -74,7 +73,7 @@ - (void)updateAddressSetForDatacenterWithId:(NSInteger)datacenterId addressSet:(MTDatacenterAddressSet *)addressSet forceUpdateSchemes:(bool)forceUpdateSchemes; - (void)addAddressForDatacenterWithId:(NSInteger)datacenterId address:(MTDatacenterAddress *)address; - (void)updateTransportSchemeForDatacenterWithId:(NSInteger)datacenterId transportScheme:(MTTransportScheme *)transportScheme media:(bool)media isProxy:(bool)isProxy; -- (void)updateAuthInfoForDatacenterWithId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo selector:(MTDatacenterAuthInfoSelector)selector; +- (void)updateAuthInfoForDatacenterWithId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo; - (bool)isPasswordInputRequiredForDatacenterWithId:(NSInteger)datacenterId; - (bool)updatePasswordInputRequiredForDatacenterWithId:(NSInteger)datacenterId required:(bool)required; @@ -96,7 +95,7 @@ - (void)transportSchemeForDatacenterWithIdRequired:(NSInteger)datacenterId media:(bool)media; - (void)invalidateTransportSchemeForDatacenterId:(NSInteger)datacenterId transportScheme:(MTTransportScheme *)transportScheme isProbablyHttp:(bool)isProbablyHttp media:(bool)media; - (void)revalidateTransportSchemeForDatacenterId:(NSInteger)datacenterId transportScheme:(MTTransportScheme *)transportScheme media:(bool)media; -- (MTDatacenterAuthInfo *)authInfoForDatacenterWithId:(NSInteger)datacenterId selector:(MTDatacenterAuthInfoSelector)selector; +- (MTDatacenterAuthInfo *)authInfoForDatacenterWithId:(NSInteger)datacenterId; - (NSArray *)publicKeysForDatacenterWithId:(NSInteger)datacenterId; - (void)updatePublicKeysForDatacenterWithId:(NSInteger)datacenterId publicKeys:(NSArray *)publicKeys; @@ -108,7 +107,8 @@ - (void)updateAuthTokenForDatacenterWithId:(NSInteger)datacenterId authToken:(id)authToken; - (void)addressSetForDatacenterWithIdRequired:(NSInteger)datacenterId; -- (void)authInfoForDatacenterWithIdRequired:(NSInteger)datacenterId isCdn:(bool)isCdn selector:(MTDatacenterAuthInfoSelector)selector; +- (void)authInfoForDatacenterWithIdRequired:(NSInteger)datacenterId isCdn:(bool)isCdn; +- (void)tempAuthKeyForDatacenterWithIdRequired:(NSInteger)datacenterId keyType:(MTDatacenterAuthTempKeyType)keyType; - (void)authTokenForDatacenterWithIdRequired:(NSInteger)datacenterId authToken:(id)authToken masterDatacenterId:(NSInteger)masterDatacenterId; - (void)reportProblemsWithDatacenterAddressForId:(NSInteger)datacenterId address:(MTDatacenterAddress *)address; diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTDatacenterAuthAction.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTDatacenterAuthAction.h index df67730fc7..c9643cdaf3 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTDatacenterAuthAction.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTDatacenterAuthAction.h @@ -4,12 +4,23 @@ @class MTContext; +@class MTDatacenterAuthAction; + +@protocol MTDatacenterAuthActionDelegate + +- (void)datacenterAuthActionCompleted:(MTDatacenterAuthAction *)action; + +@end @interface MTDatacenterAuthAction : NSObject -- (instancetype)initWithAuthKeyInfoSelector:(MTDatacenterAuthInfoSelector)authKeyInfoSelector isCdn:(bool)isCdn completion:(void (^)(MTDatacenterAuthAction *, bool))completion; +@property (nonatomic, readonly) bool tempAuth; +@property (nonatomic, weak) id delegate; +@property (nonatomic, copy) void (^completedWithResult)(bool); -- (void)execute:(MTContext *)context datacenterId:(NSInteger)datacenterId; +- (instancetype)initWithTempAuth:(bool)tempAuth tempAuthKeyType:(MTDatacenterAuthTempKeyType)tempAuthKeyType bindKey:(MTDatacenterAuthKey *)bindKey; + +- (void)execute:(MTContext *)context datacenterId:(NSInteger)datacenterId isCdn:(bool)isCdn; - (void)cancel; @end diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTDatacenterAuthInfo.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTDatacenterAuthInfo.h index 26c7c7ea3f..ae73123a64 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTDatacenterAuthInfo.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTDatacenterAuthInfo.h @@ -1,5 +1,10 @@ #import +typedef NS_ENUM(NSUInteger, MTDatacenterAuthTempKeyType) { + MTDatacenterAuthTempKeyTypeMain, + MTDatacenterAuthTempKeyTypeMedia +}; + @interface MTDatacenterAuthKey: NSObject @property (nonatomic, strong, readonly) NSData *authKey; @@ -10,24 +15,24 @@ @end -typedef NS_ENUM(int64_t, MTDatacenterAuthInfoSelector) { - MTDatacenterAuthInfoSelectorPersistent = 0, - MTDatacenterAuthInfoSelectorEphemeralMain, - MTDatacenterAuthInfoSelectorEphemeralMedia -}; - @interface MTDatacenterAuthInfo : NSObject @property (nonatomic, strong, readonly) NSData *authKey; @property (nonatomic, readonly) int64_t authKeyId; @property (nonatomic, strong, readonly) NSArray *saltSet; @property (nonatomic, strong, readonly) NSDictionary *authKeyAttributes; +@property (nonatomic, strong, readonly) MTDatacenterAuthKey *mainTempAuthKey; +@property (nonatomic, strong, readonly) MTDatacenterAuthKey *mediaTempAuthKey; -- (instancetype)initWithAuthKey:(NSData *)authKey authKeyId:(int64_t)authKeyId saltSet:(NSArray *)saltSet authKeyAttributes:(NSDictionary *)authKeyAttributes; +@property (nonatomic, strong, readonly) MTDatacenterAuthKey *persistentAuthKey; + +- (instancetype)initWithAuthKey:(NSData *)authKey authKeyId:(int64_t)authKeyId saltSet:(NSArray *)saltSet authKeyAttributes:(NSDictionary *)authKeyAttributes mainTempAuthKey:(MTDatacenterAuthKey *)mainTempAuthKey mediaTempAuthKey:(MTDatacenterAuthKey *)mediaTempAuthKey; - (int64_t)authSaltForMessageId:(int64_t)messageId; - (MTDatacenterAuthInfo *)mergeSaltSet:(NSArray *)updatedSaltSet forTimestamp:(NSTimeInterval)timestamp; - (MTDatacenterAuthInfo *)withUpdatedAuthKeyAttributes:(NSDictionary *)authKeyAttributes; +- (MTDatacenterAuthKey *)tempAuthKeyWithType:(MTDatacenterAuthTempKeyType)type; +- (MTDatacenterAuthInfo *)withUpdatedTempAuthKeyWithType:(MTDatacenterAuthTempKeyType)type key:(MTDatacenterAuthKey *)key; @end diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTMessageService.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTMessageService.h index 761ed3029e..2b89f85ecf 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTMessageService.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTMessageService.h @@ -1,13 +1,11 @@ #import -#import @class MTProto; @class MTIncomingMessage; @class MTMessageTransaction; @class MTApiEnvironment; -@class MTSessionInfo; @protocol MTMessageService @@ -17,10 +15,10 @@ - (void)mtProtoDidAddService:(MTProto *)mtProto; - (void)mtProtoDidRemoveService:(MTProto *)mtProto; - (void)mtProtoPublicKeysUpdated:(MTProto *)mtProto datacenterId:(NSInteger)datacenterId publicKeys:(NSArray *)publicKeys; -- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo; +- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto; - (void)mtProtoDidChangeSession:(MTProto *)mtProto; - (void)mtProtoServerDidChangeSession:(MTProto *)mtProto firstValidMessageId:(int64_t)firstValidMessageId otherValidMessageIds:(NSArray *)otherValidMessageIds; -- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector; +- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message; - (void)mtProto:(MTProto *)mtProto receivedQuickAck:(int32_t)quickAckId; - (void)mtProto:(MTProto *)mtProto transactionsMayHaveFailed:(NSArray *)transactionIds; - (void)mtProtoAllTransactionsMayHaveFailed:(MTProto *)mtProto; diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProto.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProto.h index 3405a7060b..231d1b4a26 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProto.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProto.h @@ -71,6 +71,4 @@ - (void)_messageResendRequestFailed:(int64_t)messageId; -+ (NSData *)_manuallyEncryptedMessage:(NSData *)preparedData messageId:(int64_t)messageId authKey:(MTDatacenterAuthKey *)authKey; - @end diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoEngine.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoEngine.h deleted file mode 100644 index a5e507e8de..0000000000 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoEngine.h +++ /dev/null @@ -1,13 +0,0 @@ -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface MTProtoEngine : NSObject - -- (instancetype)initWithPersistenceInterface:(id)persistenceInterface; - -@end - -NS_ASSUME_NONNULL_END - diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoInstance.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoInstance.h deleted file mode 100644 index 91a58939e5..0000000000 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoInstance.h +++ /dev/null @@ -1,12 +0,0 @@ -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface MTProtoInstance : NSObject - -- (instancetype)initWithEngine:(MTProtoEngine *)engine; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoPersistenceInterface.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoPersistenceInterface.h deleted file mode 100644 index ecabfee72f..0000000000 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoPersistenceInterface.h +++ /dev/null @@ -1,14 +0,0 @@ -#import - -@class MTSignal; - -NS_ASSUME_NONNULL_BEGIN - -@protocol MTProtoPersistenceInterface - -- (MTSignal *)get:(NSData *)key; -- (void)set:(NSData *)key value:(NSData *)value; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/MtProtoKit/Sources/MTBindKeyMessageService.m b/submodules/MtProtoKit/Sources/MTBindKeyMessageService.m deleted file mode 100644 index 6a9b69723b..0000000000 --- a/submodules/MtProtoKit/Sources/MTBindKeyMessageService.m +++ /dev/null @@ -1,156 +0,0 @@ -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import "MTInternalMessageParser.h" -#import "MTRpcResultMessage.h" -#import "MTBuffer.h" - -@interface MTBindKeyMessageService () { - MTDatacenterAuthKey *_persistentKey; - MTDatacenterAuthKey *_ephemeralKey; - void (^_completion)(bool); - - int64_t _currentMessageId; - id _currentTransactionId; -} - -@end - -@implementation MTBindKeyMessageService - -- (instancetype)initWithPersistentKey:(MTDatacenterAuthKey *)persistentKey ephemeralKey:(MTDatacenterAuthKey *)ephemeralKey completion:(void (^)(bool))completion { - self = [super init]; - if (self != nil) { - _persistentKey = persistentKey; - _ephemeralKey = ephemeralKey; - _completion = [completion copy]; - } - return self; -} - -- (void)mtProtoDidAddService:(MTProto *)mtProto -{ - [mtProto requestTransportTransaction]; -} - -- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo -{ - if (_currentTransactionId != nil) { - return nil; - } - - int64_t bindingMessageId = [sessionInfo generateClientMessageId:NULL]; - int32_t bindingSeqNo = [sessionInfo takeSeqNo:true]; - - int32_t expiresAt = (int32_t)([mtProto.context globalTime] + mtProto.context.tempKeyExpiration); - - int64_t randomId = 0; - arc4random_buf(&randomId, 8); - - int64_t nonce = 0; - arc4random_buf(&nonce, 8); - - MTBuffer *decryptedMessage = [[MTBuffer alloc] init]; - //bind_auth_key_inner#75a3f765 nonce:long temp_auth_key_id:long perm_auth_key_id:long temp_session_id:long expires_at:int = BindAuthKeyInner; - [decryptedMessage appendInt32:(int32_t)0x75a3f765]; - [decryptedMessage appendInt64:nonce]; - [decryptedMessage appendInt64:_ephemeralKey.authKeyId]; - [decryptedMessage appendInt64:_persistentKey.authKeyId]; - [decryptedMessage appendInt64:sessionInfo.sessionId]; - [decryptedMessage appendInt32:expiresAt]; - - NSData *encryptedMessage = [MTProto _manuallyEncryptedMessage:[decryptedMessage data] messageId:bindingMessageId authKey:_persistentKey]; - - MTBuffer *bindRequestData = [[MTBuffer alloc] init]; - - //auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int encrypted_message:bytes = Bool; - - [bindRequestData appendInt32:(int32_t)0xcdd42a05]; - [bindRequestData appendInt64:_persistentKey.authKeyId]; - [bindRequestData appendInt64:nonce]; - [bindRequestData appendInt32:expiresAt]; - [bindRequestData appendTLBytes:encryptedMessage]; - - MTOutgoingMessage *outgoingMessage = [[MTOutgoingMessage alloc] initWithData:bindRequestData.data metadata:[NSString stringWithFormat:@"auth.bindTempAuthKey"] additionalDebugDescription:nil shortMetadata:@"auth.bindTempAuthKey" messageId:bindingMessageId messageSeqNo:bindingSeqNo]; - - return [[MTMessageTransaction alloc] initWithMessagePayload:@[outgoingMessage] prepared:nil failed:nil completion:^(NSDictionary *messageInternalIdToTransactionId, NSDictionary *messageInternalIdToPreparedMessage, __unused NSDictionary *messageInternalIdToQuickAckId) { - MTPreparedMessage *preparedMessage = messageInternalIdToPreparedMessage[outgoingMessage.internalId]; - if (preparedMessage != nil && messageInternalIdToTransactionId[outgoingMessage.internalId] != nil) { - _currentMessageId = preparedMessage.messageId; - _currentTransactionId = messageInternalIdToTransactionId[outgoingMessage.internalId]; - } - }]; - - return nil; -} - -- (void)mtProto:(MTProto *)__unused mtProto messageDeliveryFailed:(int64_t)messageId { - if (messageId == _currentMessageId) { - _currentMessageId = 0; - _currentTransactionId = nil; - - [mtProto requestTransportTransaction]; - } -} - -- (void)mtProto:(MTProto *)mtProto transactionsMayHaveFailed:(NSArray *)transactionIds { - if (_currentTransactionId != nil && [transactionIds containsObject:_currentTransactionId]) { - _currentTransactionId = nil; - - [mtProto requestTransportTransaction]; - } -} - -- (void)mtProtoAllTransactionsMayHaveFailed:(MTProto *)mtProto { - if (_currentTransactionId != nil) { - _currentTransactionId = nil; - - [mtProto requestTransportTransaction]; - } -} - -- (void)mtProtoDidChangeSession:(MTProto *)mtProto { - _currentMessageId = 0; - _currentTransactionId = nil; - - [mtProto requestTransportTransaction]; -} - -- (void)mtProtoServerDidChangeSession:(MTProto *)mtProto firstValidMessageId:(int64_t)firstValidMessageId messageIdsInFirstValidContainer:(NSArray *)messageIdsInFirstValidContainer { - if (_currentMessageId != 0 && _currentMessageId < firstValidMessageId && ![messageIdsInFirstValidContainer containsObject:@(_currentMessageId)]) { - _currentMessageId = 0; - _currentTransactionId = nil; - - [mtProto requestTransportTransaction]; - } -} - -- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector { - if ([message.body isKindOfClass:[MTRpcResultMessage class]]) { - MTRpcResultMessage *rpcResultMessage = message.body; - if (rpcResultMessage.requestMessageId == _currentMessageId) { - bool success = false; - if (rpcResultMessage.data.length >= 4) { - uint32_t signature = 0; - [rpcResultMessage.data getBytes:&signature range:NSMakeRange(0, 4)]; - - //boolTrue#997275b5 = Bool; - if (signature == 0x997275b5U) { - success = true; - } - } - _completion(success); - } - } -} - -@end diff --git a/submodules/MtProtoKit/Sources/MTContext.m b/submodules/MtProtoKit/Sources/MTContext.m index 059f974907..506a954d98 100644 --- a/submodules/MtProtoKit/Sources/MTContext.m +++ b/submodules/MtProtoKit/Sources/MTContext.m @@ -114,34 +114,7 @@ @end -typedef int64_t MTDatacenterAuthInfoMapKey; - -typedef struct { - int32_t datacenterId; - MTDatacenterAuthInfoSelector selector; -} MTDatacenterAuthInfoMapKeyStruct; - -static MTDatacenterAuthInfoMapKey authInfoMapKey(int32_t datacenterId, MTDatacenterAuthInfoSelector selector) { - int64_t result = (((int64_t)(selector)) << 32) | ((int64_t)(datacenterId)); - return result; -} - -static NSNumber *authInfoMapIntegerKey(int32_t datacenterId, MTDatacenterAuthInfoSelector selector) { - return [NSNumber numberWithLongLong:authInfoMapKey(datacenterId, selector)]; -} - -static MTDatacenterAuthInfoMapKeyStruct parseAuthInfoMapKey(int64_t key) { - MTDatacenterAuthInfoMapKeyStruct result; - result.datacenterId = (int32_t)(key & 0x7fffffff); - result.selector = (int32_t)(((key >> 32) & 0x7fffffff)); - return result; -} - -static MTDatacenterAuthInfoMapKeyStruct parseAuthInfoMapKeyInteger(NSNumber *key) { - return parseAuthInfoMapKey([key longLongValue]); -} - -@interface MTContext () +@interface MTContext () { int64_t _uniqueId; @@ -154,7 +127,7 @@ static MTDatacenterAuthInfoMapKeyStruct parseAuthInfoMapKeyInteger(NSNumber *key NSMutableDictionary *> *_transportSchemeStats; MTTimer *_schemeStatsSyncTimer; - NSMutableDictionary *_datacenterAuthInfoById; + NSMutableDictionary *_datacenterAuthInfoById; NSMutableDictionary *_datacenterPublicKeysById; @@ -165,7 +138,8 @@ static MTDatacenterAuthInfoMapKeyStruct parseAuthInfoMapKeyInteger(NSNumber *key MTSignal *_discoverBackupAddressListSignal; NSMutableDictionary *_discoverDatacenterAddressActions; - NSMutableDictionary *_datacenterAuthActions; + NSMutableDictionary *_datacenterAuthActions; + NSMutableDictionary *_datacenterTempAuthActions; NSMutableDictionary *_datacenterTransferAuthActions; NSMutableDictionary *_datacenterCheckKeyRemovedActionTimestamps; @@ -227,11 +201,6 @@ static int32_t fixedTimeDifferenceValue = 0; _apiEnvironment = apiEnvironment; _isTestingEnvironment = isTestingEnvironment; _useTempAuthKeys = useTempAuthKeys; -#if DEBUG - _tempKeyExpiration = 1 * 60 * 60; -#else - _tempKeyExpiration = 1 * 60 * 60; -#endif _datacenterSeedAddressSetById = [[NSMutableDictionary alloc] init]; @@ -249,6 +218,7 @@ static int32_t fixedTimeDifferenceValue = 0; _discoverDatacenterAddressActions = [[NSMutableDictionary alloc] init]; _datacenterAuthActions = [[NSMutableDictionary alloc] init]; + _datacenterTempAuthActions = [[NSMutableDictionary alloc] init]; _datacenterTransferAuthActions = [[NSMutableDictionary alloc] init]; _datacenterCheckKeyRemovedActionTimestamps = [[NSMutableDictionary alloc] init]; _datacenterCheckKeyRemovedActions = [[NSMutableDictionary alloc] init]; @@ -288,6 +258,9 @@ static int32_t fixedTimeDifferenceValue = 0; NSDictionary *datacenterAuthActions = _datacenterAuthActions; _datacenterAuthActions = nil; + NSDictionary *datacenterTempAuthActions = _datacenterTempAuthActions; + _datacenterTempAuthActions = nil; + NSDictionary *discoverDatacenterAddressActions = _discoverDatacenterAddressActions; _discoverDatacenterAddressActions = nil; @@ -311,9 +284,17 @@ static int32_t fixedTimeDifferenceValue = 0; [action cancel]; } - for (NSNumber *key in datacenterAuthActions) + for (NSNumber *nDatacenterId in datacenterAuthActions) { - MTDatacenterAuthAction *action = datacenterAuthActions[key]; + MTDatacenterAuthAction *action = datacenterAuthActions[nDatacenterId]; + action.delegate = nil; + [action cancel]; + } + + for (NSNumber *nDatacenterId in datacenterTempAuthActions) + { + MTDatacenterAuthAction *action = datacenterTempAuthActions[nDatacenterId]; + action.delegate = nil; [action cancel]; } @@ -379,14 +360,6 @@ static int32_t fixedTimeDifferenceValue = 0; NSDictionary *datacenterAuthInfoById = [keychain objectForKey:@"datacenterAuthInfoById" group:@"persistent"]; if (datacenterAuthInfoById != nil) { _datacenterAuthInfoById = [[NSMutableDictionary alloc] initWithDictionary:datacenterAuthInfoById]; -#if DEBUG - /*NSArray *keys = [_datacenterAuthInfoById allKeys]; - for (NSNumber *key in keys) { - if (parseAuthInfoMapKeyInteger(key).selector != MTDatacenterAuthInfoSelectorPersistent) { - [_datacenterAuthInfoById removeObjectForKey:key]; - } - }*/ -#endif } NSDictionary *datacenterPublicKeysById = [keychain objectForKey:@"datacenterPublicKeysById" group:@"ephemeral"]; @@ -609,46 +582,29 @@ static int32_t fixedTimeDifferenceValue = 0; }]; } -- (void)updateAuthInfoForDatacenterWithId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo selector:(MTDatacenterAuthInfoSelector)selector +- (void)updateAuthInfoForDatacenterWithId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo { [[MTContext contextQueue] dispatchOnQueue:^ { if (datacenterId != 0) { - NSNumber *infoKey = authInfoMapIntegerKey((int32_t)datacenterId, selector); - - bool wasNil = _datacenterAuthInfoById[infoKey] == nil; + if (MTLogEnabled()) { + MTLog(@"[MTContext#%x: auth info updated for %d to %@]", (int)self, datacenterId, authInfo); + } if (authInfo != nil) { - _datacenterAuthInfoById[infoKey] = authInfo; + _datacenterAuthInfoById[@(datacenterId)] = authInfo; } else { - if (_datacenterAuthInfoById[infoKey] == nil) { - return; - } - [_datacenterAuthInfoById removeObjectForKey:infoKey]; + [_datacenterAuthInfoById removeObjectForKey:@(datacenterId)]; } - - if (MTLogEnabled()) { - MTLog(@"[MTContext#%x: auth info updated for %d selector %d to %@]", (int)self, datacenterId, selector, authInfo); - } - [_keychain setObject:_datacenterAuthInfoById forKey:@"datacenterAuthInfoById" group:@"persistent"]; NSArray *currentListeners = [[NSArray alloc] initWithArray:_changeListeners]; for (id listener in currentListeners) { - if ([listener respondsToSelector:@selector(contextDatacenterAuthInfoUpdated:datacenterId:authInfo:selector:)]) - [listener contextDatacenterAuthInfoUpdated:self datacenterId:datacenterId authInfo:authInfo selector:selector]; - } - - if (wasNil && authInfo != nil && selector == MTDatacenterAuthInfoSelectorPersistent) { - for (NSNumber *key in _datacenterAuthActions) { - MTDatacenterAuthInfoMapKeyStruct parsedKey = parseAuthInfoMapKeyInteger(key); - if (parsedKey.datacenterId == datacenterId && parsedKey.selector != MTDatacenterAuthInfoSelectorPersistent) { - [_datacenterAuthActions[key] execute:self datacenterId:datacenterId]; - } - } + if ([listener respondsToSelector:@selector(contextDatacenterAuthInfoUpdated:datacenterId:authInfo:)]) + [listener contextDatacenterAuthInfoUpdated:self datacenterId:datacenterId authInfo:authInfo]; } } }]; @@ -908,11 +864,10 @@ static int32_t fixedTimeDifferenceValue = 0; return results; } -- (MTDatacenterAuthInfo *)authInfoForDatacenterWithId:(NSInteger)datacenterId selector:(MTDatacenterAuthInfoSelector)selector { +- (MTDatacenterAuthInfo *)authInfoForDatacenterWithId:(NSInteger)datacenterId { __block MTDatacenterAuthInfo *result = nil; [[MTContext contextQueue] dispatchOnQueue:^{ - NSNumber *infoKey = authInfoMapIntegerKey((int32_t)datacenterId, selector); - result = _datacenterAuthInfoById[infoKey]; + result = _datacenterAuthInfoById[@(datacenterId)]; } synchronous:true]; return result; @@ -1296,44 +1251,48 @@ static int32_t fixedTimeDifferenceValue = 0; }]; } -- (void)authInfoForDatacenterWithIdRequired:(NSInteger)datacenterId isCdn:(bool)isCdn selector:(MTDatacenterAuthInfoSelector)selector +- (void)authInfoForDatacenterWithIdRequired:(NSInteger)datacenterId isCdn:(bool)isCdn { [[MTContext contextQueue] dispatchOnQueue:^ { - NSNumber *infoKey = authInfoMapIntegerKey((int32_t)datacenterId, selector); - - if (_datacenterAuthActions[infoKey] == nil) + if (_datacenterAuthActions[@(datacenterId)] == nil) { - __weak MTContext *weakSelf = self; - MTDatacenterAuthAction *authAction = [[MTDatacenterAuthAction alloc] initWithAuthKeyInfoSelector:selector isCdn:isCdn completion:^(MTDatacenterAuthAction *action, __unused bool success) { - [[MTContext contextQueue] dispatchOnQueue:^{ - __strong MTContext *strongSelf = weakSelf; - if (strongSelf == nil) { - return; - } + MTDatacenterAuthAction *authAction = [[MTDatacenterAuthAction alloc] initWithTempAuth:false tempAuthKeyType:MTDatacenterAuthTempKeyTypeMain bindKey:nil]; + authAction.delegate = self; + _datacenterAuthActions[@(datacenterId)] = authAction; + [authAction execute:self datacenterId:datacenterId isCdn:isCdn]; + } + }]; +} + +- (void)tempAuthKeyForDatacenterWithIdRequired:(NSInteger)datacenterId keyType:(MTDatacenterAuthTempKeyType)keyType { + [[MTContext contextQueue] dispatchOnQueue:^{ + if (_datacenterTempAuthActions[@(datacenterId)] == nil) { + MTDatacenterAuthAction *authAction = [[MTDatacenterAuthAction alloc] initWithTempAuth:true tempAuthKeyType:keyType bindKey:nil]; + authAction.delegate = self; + _datacenterTempAuthActions[@(datacenterId)] = authAction; + [authAction execute:self datacenterId:datacenterId isCdn:false]; + } + }]; +} + +- (void)datacenterAuthActionCompleted:(MTDatacenterAuthAction *)action +{ + [[MTContext contextQueue] dispatchOnQueue:^ + { + if (action.tempAuth) { + for (NSNumber *nDatacenterId in _datacenterTempAuthActions) { + if (_datacenterTempAuthActions[nDatacenterId] == action) { + [_datacenterTempAuthActions removeObjectForKey:nDatacenterId]; - for (NSNumber *key in _datacenterAuthActions) { - if (_datacenterAuthActions[key] == action) { - [_datacenterAuthActions removeObjectForKey:key]; - break; - } - } - }]; - }]; - _datacenterAuthActions[infoKey] = authAction; - - switch (selector) { - case MTDatacenterAuthInfoSelectorEphemeralMain: - case MTDatacenterAuthInfoSelectorEphemeralMedia: { - if ([self authInfoForDatacenterWithId:datacenterId selector:MTDatacenterAuthInfoSelectorPersistent] == nil) { - [self authInfoForDatacenterWithIdRequired:datacenterId isCdn:false selector:MTDatacenterAuthInfoSelectorPersistent]; - } else { - [authAction execute:self datacenterId:datacenterId]; - } break; } - default: { - [authAction execute:self datacenterId:datacenterId]; + } + } else { + for (NSNumber *nDatacenterId in _datacenterAuthActions) { + if (_datacenterAuthActions[nDatacenterId] == action) { + [_datacenterAuthActions removeObjectForKey:nDatacenterId]; + break; } } @@ -1398,11 +1357,21 @@ static int32_t fixedTimeDifferenceValue = 0; - (void)updatePeriodicTasks { + [[MTContext contextQueue] dispatchOnQueue:^ + { + int64_t saltsRequiredAtLeastUntilMessageId = (int64_t)(([self globalTime] + 24 * 60.0 * 60.0) * 4294967296); + + [_datacenterAuthInfoById enumerateKeysAndObjectsUsingBlock:^(NSNumber *nDatacenterId, MTDatacenterAuthInfo *authInfo, __unused BOOL *stop) + { + if ([authInfo authSaltForMessageId:saltsRequiredAtLeastUntilMessageId == 0]) { + } + }]; + }]; } - (void)checkIfLoggedOut:(NSInteger)datacenterId { [[MTContext contextQueue] dispatchOnQueue:^{ - MTDatacenterAuthInfo *authInfo = [self authInfoForDatacenterWithId:datacenterId selector:MTDatacenterAuthInfoSelectorPersistent]; + MTDatacenterAuthInfo *authInfo = [self authInfoForDatacenterWithId:datacenterId]; if (authInfo == nil || authInfo.authKey == nil) { return; } @@ -1413,7 +1382,7 @@ static int32_t fixedTimeDifferenceValue = 0; _datacenterCheckKeyRemovedActionTimestamps[@(datacenterId)] = currentTimestamp; [_datacenterCheckKeyRemovedActions[@(datacenterId)] dispose]; __weak MTContext *weakSelf = self; - _datacenterCheckKeyRemovedActions[@(datacenterId)] = [[MTDiscoverConnectionSignals checkIfAuthKeyRemovedWithContext:self datacenterId:datacenterId authKey:[[MTDatacenterAuthKey alloc] initWithAuthKey:authInfo.authKey authKeyId:authInfo.authKeyId notBound:false]] startWithNext:^(NSNumber* isRemoved) { + _datacenterCheckKeyRemovedActions[@(datacenterId)] = [[MTDiscoverConnectionSignals checkIfAuthKeyRemovedWithContext:self datacenterId:datacenterId authKey:authInfo.authKey] startWithNext:^(NSNumber *isRemoved) { [[MTContext contextQueue] dispatchOnQueue:^{ __strong MTContext *strongSelf = weakSelf; if (strongSelf == nil) { diff --git a/submodules/MtProtoKit/Sources/MTDatacenterAuthAction.m b/submodules/MtProtoKit/Sources/MTDatacenterAuthAction.m index 736b69f546..3896afcd51 100644 --- a/submodules/MtProtoKit/Sources/MTDatacenterAuthAction.m +++ b/submodules/MtProtoKit/Sources/MTDatacenterAuthAction.m @@ -12,15 +12,13 @@ #import #import #import -#import #import "MTBuffer.h" @interface MTDatacenterAuthAction () { - void (^_completion)(MTDatacenterAuthAction *, bool); - bool _isCdn; - MTDatacenterAuthInfoSelector _authKeyInfoSelector; + MTDatacenterAuthTempKeyType _tempAuthKeyType; + MTDatacenterAuthKey *_bindKey; NSInteger _datacenterId; __weak MTContext *_context; @@ -28,61 +26,71 @@ bool _awaitingAddresSetUpdate; MTProto *_authMtProto; MTProto *_bindMtProto; + + MTMetaDisposable *_verifyDisposable; } @end @implementation MTDatacenterAuthAction -- (instancetype)initWithAuthKeyInfoSelector:(MTDatacenterAuthInfoSelector)authKeyInfoSelector isCdn:(bool)isCdn completion:(void (^)(MTDatacenterAuthAction *, bool))completion { +- (instancetype)initWithTempAuth:(bool)tempAuth tempAuthKeyType:(MTDatacenterAuthTempKeyType)tempAuthKeyType bindKey:(MTDatacenterAuthKey *)bindKey { self = [super init]; if (self != nil) { - _authKeyInfoSelector = authKeyInfoSelector; - _isCdn = isCdn; - _completion = [completion copy]; + _tempAuth = tempAuth; + _tempAuthKeyType = tempAuthKeyType; + _bindKey = bindKey; + _verifyDisposable = [[MTMetaDisposable alloc] init]; } return self; } -- (void)dealloc { +- (void)dealloc +{ [self cleanup]; } -- (void)execute:(MTContext *)context datacenterId:(NSInteger)datacenterId { +- (void)execute:(MTContext *)context datacenterId:(NSInteger)datacenterId isCdn:(bool)isCdn +{ _datacenterId = datacenterId; _context = context; + _isCdn = isCdn; if (_datacenterId != 0 && context != nil) { bool alreadyCompleted = false; - - MTDatacenterAuthInfo *currentAuthInfo = [context authInfoForDatacenterWithId:_datacenterId selector:_authKeyInfoSelector]; - if (currentAuthInfo != nil) { - alreadyCompleted = true; + MTDatacenterAuthInfo *currentAuthInfo = [context authInfoForDatacenterWithId:_datacenterId]; + if (currentAuthInfo != nil && _bindKey == nil) { + if (_tempAuth) { + if ([currentAuthInfo tempAuthKeyWithType:_tempAuthKeyType] != nil) { + alreadyCompleted = true; + } + } else { + alreadyCompleted = true; + } } if (alreadyCompleted) { [self complete]; } else { _authMtProto = [[MTProto alloc] initWithContext:context datacenterId:_datacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0]; - _authMtProto.cdn = _isCdn; + _authMtProto.cdn = isCdn; _authMtProto.useUnauthorizedMode = true; - bool tempAuth = false; - switch (_authKeyInfoSelector) { - case MTDatacenterAuthInfoSelectorEphemeralMain: - tempAuth = true; - _authMtProto.media = false; - break; - case MTDatacenterAuthInfoSelectorEphemeralMedia: - tempAuth = true; - _authMtProto.media = true; - _authMtProto.enforceMedia = true; - break; - default: - break; + if (_tempAuth) { + switch (_tempAuthKeyType) { + case MTDatacenterAuthTempKeyTypeMain: + _authMtProto.media = false; + break; + case MTDatacenterAuthTempKeyTypeMedia: + _authMtProto.media = true; + _authMtProto.enforceMedia = true; + break; + default: + break; + } } - MTDatacenterAuthMessageService *authService = [[MTDatacenterAuthMessageService alloc] initWithContext:context tempAuth:tempAuth]; + MTDatacenterAuthMessageService *authService = [[MTDatacenterAuthMessageService alloc] initWithContext:context tempAuth:_tempAuth]; authService.delegate = self; [_authMtProto addMessageService:authService]; } @@ -96,71 +104,45 @@ } - (void)completeWithAuthKey:(MTDatacenterAuthKey *)authKey timestamp:(int64_t)timestamp { - if (MTLogEnabled()) { - MTLog(@"[MTDatacenterAuthAction#%p@%p: completeWithAuthKey %lld selector %d]", self, _context, authKey.authKeyId, _authKeyInfoSelector); - } - - switch (_authKeyInfoSelector) { - case MTDatacenterAuthInfoSelectorPersistent: { - MTDatacenterAuthInfo *authInfo = [[MTDatacenterAuthInfo alloc] initWithAuthKey:authKey.authKey authKeyId:authKey.authKeyId saltSet:@[[[MTDatacenterSaltInfo alloc] initWithSalt:0 firstValidMessageId:timestamp lastValidMessageId:timestamp + (29.0 * 60.0) * 4294967296]] authKeyAttributes:nil]; - - MTContext *context = _context; - [context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:authInfo selector:_authKeyInfoSelector]; - [self complete]; - } - break; - - case MTDatacenterAuthInfoSelectorEphemeralMain: - case MTDatacenterAuthInfoSelectorEphemeralMedia: { - MTContext *mainContext = _context; - if (mainContext != nil) { - MTDatacenterAuthInfo *persistentAuthInfo = [mainContext authInfoForDatacenterWithId:_datacenterId selector:MTDatacenterAuthInfoSelectorPersistent]; - if (persistentAuthInfo != nil) { - _bindMtProto = [[MTProto alloc] initWithContext:mainContext datacenterId:_datacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0]; - _bindMtProto.cdn = false; - _bindMtProto.useUnauthorizedMode = false; - _bindMtProto.useTempAuthKeys = true; - _bindMtProto.useExplicitAuthKey = authKey; - - switch (_authKeyInfoSelector) { - case MTDatacenterAuthInfoSelectorEphemeralMain: - _bindMtProto.media = false; - break; - case MTDatacenterAuthInfoSelectorEphemeralMedia: - _bindMtProto.media = true; - _bindMtProto.enforceMedia = true; - break; - default: - break; + if (_tempAuth) { + MTContext *mainContext = _context; + if (mainContext != nil) { + if (_bindKey != nil) { + _bindMtProto = [[MTProto alloc] initWithContext:mainContext datacenterId:_datacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0]; + _bindMtProto.cdn = false; + _bindMtProto.useUnauthorizedMode = false; + _bindMtProto.useTempAuthKeys = true; + __weak MTDatacenterAuthAction *weakSelf = self; + _bindMtProto.tempAuthKeyBindingResultUpdated = ^(bool success) { + __strong MTDatacenterAuthAction *strongSelf = weakSelf; + if (strongSelf == nil) { + return; } - - __weak MTDatacenterAuthAction *weakSelf = self; - [_bindMtProto addMessageService:[[MTBindKeyMessageService alloc] initWithPersistentKey:[[MTDatacenterAuthKey alloc] initWithAuthKey:persistentAuthInfo.authKey authKeyId:persistentAuthInfo.authKeyId notBound:false] ephemeralKey:authKey completion:^(bool success) { - __strong MTDatacenterAuthAction *strongSelf = weakSelf; - if (strongSelf == nil) { - return; - } - [strongSelf->_bindMtProto stop]; - - if (success) { - MTDatacenterAuthInfo *authInfo = [[MTDatacenterAuthInfo alloc] initWithAuthKey:authKey.authKey authKeyId:authKey.authKeyId saltSet:@[[[MTDatacenterSaltInfo alloc] initWithSalt:0 firstValidMessageId:timestamp lastValidMessageId:timestamp + (29.0 * 60.0) * 4294967296]] authKeyAttributes:nil]; - - [strongSelf->_context updateAuthInfoForDatacenterWithId:strongSelf->_datacenterId authInfo:authInfo selector:strongSelf->_authKeyInfoSelector]; - - [strongSelf complete]; - } else { - [strongSelf fail]; - } - }]]; - [_bindMtProto resume]; - } + [strongSelf->_bindMtProto stop]; + if (strongSelf->_completedWithResult) { + strongSelf->_completedWithResult(success); + } + }; + _bindMtProto.useExplicitAuthKey = authKey; + [_bindMtProto resume]; + } else { + MTContext *context = _context; + [context performBatchUpdates:^{ + MTDatacenterAuthInfo *authInfo = [context authInfoForDatacenterWithId:_datacenterId]; + if (authInfo != nil) { + authInfo = [authInfo withUpdatedTempAuthKeyWithType:_tempAuthKeyType key:authKey]; + [context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:authInfo]; + } + }]; + [self complete]; } } - break; - - default: - assert(false); - break; + } else { + MTDatacenterAuthInfo *authInfo = [[MTDatacenterAuthInfo alloc] initWithAuthKey:authKey.authKey authKeyId:authKey.authKeyId saltSet:@[[[MTDatacenterSaltInfo alloc] initWithSalt:0 firstValidMessageId:timestamp lastValidMessageId:timestamp + (29.0 * 60.0) * 4294967296]] authKeyAttributes:nil mainTempAuthKey:nil mediaTempAuthKey:nil]; + + MTContext *context = _context; + [context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:authInfo]; + [self complete]; } } @@ -171,10 +153,7 @@ [authMtProto stop]; - MTProto *bindMtProto = _bindMtProto; - _bindMtProto = nil; - - [bindMtProto stop]; + [_verifyDisposable dispose]; } - (void)cancel @@ -183,17 +162,18 @@ [self fail]; } -- (void)complete { - if (_completion) { - _completion(self, true); - } +- (void)complete +{ + id delegate = _delegate; + if ([delegate respondsToSelector:@selector(datacenterAuthActionCompleted:)]) + [delegate datacenterAuthActionCompleted:self]; } - (void)fail { - if (_completion) { - _completion(self, false); - } + id delegate = _delegate; + if ([delegate respondsToSelector:@selector(datacenterAuthActionCompleted:)]) + [delegate datacenterAuthActionCompleted:self]; } @end diff --git a/submodules/MtProtoKit/Sources/MTDatacenterAuthInfo.m b/submodules/MtProtoKit/Sources/MTDatacenterAuthInfo.m index d6b783dfcd..f9959626a9 100644 --- a/submodules/MtProtoKit/Sources/MTDatacenterAuthInfo.m +++ b/submodules/MtProtoKit/Sources/MTDatacenterAuthInfo.m @@ -27,7 +27,7 @@ @implementation MTDatacenterAuthInfo -- (instancetype)initWithAuthKey:(NSData *)authKey authKeyId:(int64_t)authKeyId saltSet:(NSArray *)saltSet authKeyAttributes:(NSDictionary *)authKeyAttributes +- (instancetype)initWithAuthKey:(NSData *)authKey authKeyId:(int64_t)authKeyId saltSet:(NSArray *)saltSet authKeyAttributes:(NSDictionary *)authKeyAttributes mainTempAuthKey:(MTDatacenterAuthKey *)mainTempAuthKey mediaTempAuthKey:(MTDatacenterAuthKey *)mediaTempAuthKey { self = [super init]; if (self != nil) @@ -36,6 +36,8 @@ _authKeyId = authKeyId; _saltSet = saltSet; _authKeyAttributes = authKeyAttributes; + _mainTempAuthKey = mainTempAuthKey; + _mediaTempAuthKey = mediaTempAuthKey; } return self; } @@ -49,6 +51,8 @@ _authKeyId = [aDecoder decodeInt64ForKey:@"authKeyId"]; _saltSet = [aDecoder decodeObjectForKey:@"saltSet"]; _authKeyAttributes = [aDecoder decodeObjectForKey:@"authKeyAttributes"]; + _mainTempAuthKey = [aDecoder decodeObjectForKey:@"tempAuthKey"]; + _mediaTempAuthKey = [aDecoder decodeObjectForKey:@"mediaTempAuthKey"]; } return self; } @@ -59,6 +63,8 @@ [aCoder encodeInt64:_authKeyId forKey:@"authKeyId"]; [aCoder encodeObject:_saltSet forKey:@"saltSet"]; [aCoder encodeObject:_authKeyAttributes forKey:@"authKeyAttributes"]; + [aCoder encodeObject:_mainTempAuthKey forKey:@"tempAuthKey"]; + [aCoder encodeObject:_mediaTempAuthKey forKey:@"mediaTempAuthKey"]; } - (int64_t)authSaltForMessageId:(int64_t)messageId @@ -107,11 +113,35 @@ } } - return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:mergedSaltSet authKeyAttributes:_authKeyAttributes]; + return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:mergedSaltSet authKeyAttributes:_authKeyAttributes mainTempAuthKey:_mainTempAuthKey mediaTempAuthKey:_mediaTempAuthKey]; } - (MTDatacenterAuthInfo *)withUpdatedAuthKeyAttributes:(NSDictionary *)authKeyAttributes { - return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:_saltSet authKeyAttributes:authKeyAttributes]; + return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:_saltSet authKeyAttributes:authKeyAttributes mainTempAuthKey:_mainTempAuthKey mediaTempAuthKey:_mediaTempAuthKey]; +} + +- (MTDatacenterAuthKey *)tempAuthKeyWithType:(MTDatacenterAuthTempKeyType)type { + switch (type) { + case MTDatacenterAuthTempKeyTypeMain: + return _mainTempAuthKey; + case MTDatacenterAuthTempKeyTypeMedia: + return _mediaTempAuthKey; + default: + NSAssert(false, @"unknown MTDatacenterAuthTempKeyType"); + return nil; + } +} + +- (MTDatacenterAuthInfo *)withUpdatedTempAuthKeyWithType:(MTDatacenterAuthTempKeyType)type key:(MTDatacenterAuthKey *)key { + switch (type) { + case MTDatacenterAuthTempKeyTypeMain: + return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:_saltSet authKeyAttributes:_authKeyAttributes mainTempAuthKey:key mediaTempAuthKey:_mediaTempAuthKey]; + case MTDatacenterAuthTempKeyTypeMedia: + return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:_saltSet authKeyAttributes:_authKeyAttributes mainTempAuthKey:_mainTempAuthKey mediaTempAuthKey:key]; + default: + NSAssert(false, @"unknown MTDatacenterAuthTempKeyType"); + return self; + } } - (MTDatacenterAuthKey *)persistentAuthKey { diff --git a/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m b/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m index e9d94c678b..a72758f2a0 100644 --- a/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m +++ b/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m @@ -222,7 +222,7 @@ typedef enum { } } -- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo +- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto { if (_currentStageTransactionId == nil) { @@ -308,7 +308,7 @@ typedef enum { return nil; } -- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector +- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message { if (_stage == MTDatacenterAuthStagePQ && [message.body isKindOfClass:[MTResPqMessage class]]) { @@ -389,7 +389,7 @@ typedef enum { [innerDataBuffer appendBytes:_nonce.bytes length:_nonce.length]; [innerDataBuffer appendBytes:_serverNonce.bytes length:_serverNonce.length]; [innerDataBuffer appendBytes:_newNonce.bytes length:_newNonce.length]; - [innerDataBuffer appendInt32:mtProto.context.tempKeyExpiration]; + [innerDataBuffer appendInt32:60 * 60 * 32]; NSData *innerDataBytes = innerDataBuffer.data; diff --git a/submodules/MtProtoKit/Sources/MTDiscoverConnectionSignals.m b/submodules/MtProtoKit/Sources/MTDiscoverConnectionSignals.m index 429ff755af..6ad6cc0976 100644 --- a/submodules/MtProtoKit/Sources/MTDiscoverConnectionSignals.m +++ b/submodules/MtProtoKit/Sources/MTDiscoverConnectionSignals.m @@ -249,11 +249,12 @@ MTMetaDisposable *disposable = [[MTMetaDisposable alloc] init]; [[MTContext contextQueue] dispatchOnQueue:^{ - MTDatacenterAuthAction *action = [[MTDatacenterAuthAction alloc] initWithAuthKeyInfoSelector:MTDatacenterAuthInfoSelectorEphemeralMain isCdn:false completion:^(__unused MTDatacenterAuthAction *action, bool success) { + MTDatacenterAuthAction *action = [[MTDatacenterAuthAction alloc] initWithTempAuth:true tempAuthKeyType:MTDatacenterAuthTempKeyTypeMain bindKey:authKey]; + action.completedWithResult = ^(bool success) { [subscriber putNext:@(!success)]; [subscriber putCompletion]; - }]; - [action execute:context datacenterId:datacenterId]; + }; + [action execute:context datacenterId:datacenterId isCdn:false]; [disposable setDisposable:[[MTBlockDisposable alloc] initWithBlock:^{ [action cancel]; diff --git a/submodules/MtProtoKit/Sources/MTDiscoverDatacenterAddressAction.m b/submodules/MtProtoKit/Sources/MTDiscoverDatacenterAddressAction.m index bfd5b5292d..940d8eb942 100644 --- a/submodules/MtProtoKit/Sources/MTDiscoverDatacenterAddressAction.m +++ b/submodules/MtProtoKit/Sources/MTDiscoverDatacenterAddressAction.m @@ -88,7 +88,7 @@ [self fail]; else { - if ([context authInfoForDatacenterWithId:_targetDatacenterId selector:MTDatacenterAuthInfoSelectorPersistent] != nil) + if ([context authInfoForDatacenterWithId:_targetDatacenterId] != nil) { _mtProto = [[MTProto alloc] initWithContext:context datacenterId:_targetDatacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0]; _mtProto.useTempAuthKeys = useTempAuthKeys; @@ -118,11 +118,11 @@ [_requestService addRequest:request]; } else - [context authInfoForDatacenterWithIdRequired:_targetDatacenterId isCdn:false selector:MTDatacenterAuthInfoSelectorPersistent]; + [context authInfoForDatacenterWithIdRequired:_targetDatacenterId isCdn:false]; } } -- (void)contextDatacenterAuthInfoUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)__unused authInfo selector:(MTDatacenterAuthInfoSelector)selector +- (void)contextDatacenterAuthInfoUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)__unused authInfo { if (_context != context || !_awaitingAddresSetUpdate) return; diff --git a/submodules/MtProtoKit/Sources/MTProto.m b/submodules/MtProtoKit/Sources/MTProto.m index b872c91085..7c96b4930f 100644 --- a/submodules/MtProtoKit/Sources/MTProto.m +++ b/submodules/MtProtoKit/Sources/MTProto.m @@ -14,6 +14,7 @@ #import #import #import +#import "MTBindingTempAuthKeyContext.h" #import #import @@ -57,11 +58,13 @@ typedef enum { MTProtoStateAwaitingDatacenterScheme = 1, MTProtoStateAwaitingDatacenterAuthorization = 2, + MTProtoStateAwaitingDatacenterTempAuthKey = 4, MTProtoStateAwaitingDatacenterAuthToken = 8, MTProtoStateAwaitingTimeFixAndSalts = 16, MTProtoStateAwaitingLostMessages = 32, MTProtoStateStopped = 64, - MTProtoStatePaused = 128 + MTProtoStatePaused = 128, + MTProtoStateBindingTempAuthKey = 256 } MTProtoState; static const NSUInteger MTMaxContainerSize = 3 * 1024; @@ -82,36 +85,17 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; @end -@interface MTProtoValidAuthInfo : NSObject - -@property (nonatomic, strong, readonly) MTDatacenterAuthInfo *authInfo; -@property (nonatomic, readonly) MTDatacenterAuthInfoSelector selector; - -@end - -@implementation MTProtoValidAuthInfo - -- (instancetype)initWithAuthInfo:(MTDatacenterAuthInfo *)authInfo selector:(MTDatacenterAuthInfoSelector)selector { - self = [super init]; - if (self != nil) { - _authInfo = authInfo; - _selector = selector; - } - return self; -} - -@end - @interface MTProto () { NSMutableArray *_messageServices; - MTProtoValidAuthInfo *_validAuthInfo; - NSNumber *_awaitingAuthInfoForSelector; - + MTDatacenterAuthInfo *_authInfo; MTSessionInfo *_sessionInfo; MTTimeFixContext *_timeFixContext; + int64_t _bindingTempAuthKeyId; + MTBindingTempAuthKeyContext *_bindingTempAuthKeyContext; + MTTransport *_transport; int _mtState; @@ -165,8 +149,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; _messageServices = [[NSMutableArray alloc] init]; _sessionInfo = [[MTSessionInfo alloc] initWithRandomSessionIdAndContext:_context]; - - + _authInfo = [_context authInfoForDatacenterWithId:_datacenterId]; _shouldStayConnected = true; } @@ -187,6 +170,15 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; }]; } +- (void)setUseExplicitAuthKey:(MTDatacenterAuthKey *)useExplicitAuthKey { + _useExplicitAuthKey = useExplicitAuthKey; + if (_useExplicitAuthKey != nil) { + _authInfo = [_authInfo withUpdatedTempAuthKeyWithType:MTDatacenterAuthTempKeyTypeMain key:useExplicitAuthKey]; + [self setMtState:_mtState | MTProtoStateBindingTempAuthKey]; + [self requestTransportTransaction]; + } +} + - (void)setUsageCalculationInfo:(MTNetworkUsageCalculationInfo *)usageCalculationInfo { [[MTProto managerQueue] dispatchOnQueue:^{ _usageCalculationInfo = usageCalculationInfo; @@ -286,6 +278,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; }]; _timeFixContext = nil; + _bindingTempAuthKeyContext = nil; if (_transport != nil) [self removeMessageService:_transport]; @@ -293,6 +286,20 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; _transport = transport; [previousTransport stop]; + if (_transport != nil && _useTempAuthKeys) { + MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; + /*if (_transport.scheme.address.preferForMedia) { + tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; + }*/ + + MTDatacenterAuthKey *effectiveAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; + if (effectiveAuthKey == nil) { + if (MTLogEnabled()) { + MTLog(@"[MTProto#%p setTransport temp auth key missing]", self); + } + } + } + if (_transport != nil) [self addMessageService:_transport]; @@ -316,13 +323,38 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; NSArray *transportSchemes = [_context transportSchemesForDatacenterWithId:_datacenterId media:_media enforceMedia:_enforceMedia isProxy:_apiEnvironment.socksProxySettings != nil]; + /*MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; + if (_transportScheme.address.preferForMedia) { + tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; + }*/ + if (transportSchemes.count == 0) { if ((_mtState & MTProtoStateAwaitingDatacenterScheme) == 0) { [self setMtState:_mtState | MTProtoStateAwaitingDatacenterScheme]; [_context transportSchemeForDatacenterWithIdRequired:_datacenterId media:_media]; } - } + } else if (!_useUnauthorizedMode && [_context authInfoForDatacenterWithId:_datacenterId] == nil) { + if (MTLogEnabled()) { + MTLog(@"[MTProto#%p@%p authInfoForDatacenterWithId:%d is nil]", self, _context, _datacenterId); + } + MTShortLog(@"[MTProto#%p@%p authInfoForDatacenterWithId:%d is nil]", self, _context, _datacenterId); + if ((_mtState & MTProtoStateAwaitingDatacenterAuthorization) == 0) { + [self setMtState:_mtState | MTProtoStateAwaitingDatacenterAuthorization]; + + if (MTLogEnabled()) { + MTLog(@"[MTProto#%p@%p requesting authInfo for %d]", self, _context, _datacenterId); + } + MTShortLog(@"[MTProto#%p@%p requesting authInfo for %d]", self, _context, _datacenterId); + [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:_cdn]; + } + }/* else if (!_useUnauthorizedMode && _useTempAuthKeys && [[_context authInfoForDatacenterWithId:_datacenterId] tempAuthKeyWithType:tempAuthKeyType] == nil) { + if ((_mtState & MTProtoStateAwaitingDatacenterTempAuthKey) == 0) { + [self setMtState:_mtState | MTProtoStateAwaitingDatacenterTempAuthKey]; + + [_context tempAuthKeyForDatacenterWithIdRequired:_datacenterId keyType:tempAuthKeyType]; + } + }*/ else if (_requiredAuthToken != nil && !_useUnauthorizedMode && ![_requiredAuthToken isEqual:[_context authTokenForDatacenterWithId:_datacenterId]]) { if ((_mtState & MTProtoStateAwaitingDatacenterAuthToken) == 0) { [self setMtState:_mtState | MTProtoStateAwaitingDatacenterAuthToken]; @@ -334,6 +366,8 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; MTTransport *transport = [[MTTcpTransport alloc] initWithDelegate:self context:_context datacenterId:_datacenterId schemes:transportSchemes proxySettings:_context.apiEnvironment.socksProxySettings usageCalculationInfo:_usageCalculationInfo]; [self setTransport:transport]; + + //[self checkTempAuthKeyBinding:transport.scheme.address]; } }]; } @@ -347,8 +381,13 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; } MTShortLog(@"[MTProto#%p@%p resetting session]", self, _context); + if (_authInfo.authKeyId != 0 && !self.cdn) { + [_context scheduleSessionCleanupForAuthKeyId:_authInfo.authKeyId sessionInfo:_sessionInfo]; + } + _sessionInfo = [[MTSessionInfo alloc] initWithRandomSessionIdAndContext:_context]; _timeFixContext = nil; + _bindingTempAuthKeyContext = nil; for (NSInteger i = (NSInteger)_messageServices.count - 1; i >= 0; i--) { @@ -363,6 +402,11 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; } - (void)finalizeSession { + [[MTProto managerQueue] dispatchOnQueue:^{ + if (_authInfo.authKeyId != 0 && !self.cdn) { + [_context scheduleSessionCleanupForAuthKeyId:_authInfo.authKeyId sessionInfo:_sessionInfo]; + } + }]; } - (void)requestTimeResync @@ -651,12 +695,12 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; - (bool)canAskForTransactions { - return (_mtState & (MTProtoStateAwaitingDatacenterScheme | MTProtoStateAwaitingDatacenterAuthorization | MTProtoStateAwaitingDatacenterAuthToken | MTProtoStateAwaitingTimeFixAndSalts | MTProtoStateStopped)) == 0; + return (_mtState & (MTProtoStateAwaitingDatacenterScheme | MTProtoStateAwaitingDatacenterAuthorization | MTProtoStateAwaitingDatacenterTempAuthKey | MTProtoStateAwaitingDatacenterAuthToken | MTProtoStateAwaitingTimeFixAndSalts | MTProtoStateBindingTempAuthKey | MTProtoStateStopped)) == 0; } - (bool)canAskForServiceTransactions { - return (_mtState & (MTProtoStateAwaitingDatacenterScheme | MTProtoStateAwaitingDatacenterAuthorization | MTProtoStateAwaitingDatacenterAuthToken | MTProtoStateStopped)) == 0; + return (_mtState & (MTProtoStateAwaitingDatacenterScheme | MTProtoStateAwaitingDatacenterAuthorization | MTProtoStateAwaitingDatacenterTempAuthKey | MTProtoStateAwaitingDatacenterAuthToken | MTProtoStateStopped)) == 0; } - (bool)timeFixOrSaltsMissing @@ -664,6 +708,11 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; return _mtState & MTProtoStateAwaitingTimeFixAndSalts; } +- (bool)bindingTempAuthKey +{ + return _mtState & MTProtoStateBindingTempAuthKey; +} + - (bool)isStopped { return (_mtState & MTProtoStateStopped) != 0; @@ -827,65 +876,6 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; return [[NSString alloc] initWithFormat:@"%@ (%" PRId64", %" PRId64"/%" PRId64")", message.body, message.messageId, message.authKeyId, message.sessionId]; } -- (MTDatacenterAuthKey *)getAuthKeyForCurrentScheme:(MTTransportScheme *)scheme createIfNeeded:(bool)createIfNeeded authInfoSelector:(MTDatacenterAuthInfoSelector *)authInfoSelector { - if (_useExplicitAuthKey) { - MTDatacenterAuthInfoSelector selector = MTDatacenterAuthInfoSelectorEphemeralMain; - if (authInfoSelector != nil) { - *authInfoSelector = selector; - } - - if (_validAuthInfo != nil && _validAuthInfo.selector == selector) { - return [[MTDatacenterAuthKey alloc] initWithAuthKey:_validAuthInfo.authInfo.authKey authKeyId:_validAuthInfo.authInfo.authKeyId notBound:false]; - } - - MTDatacenterAuthInfo *authInfo = [[MTDatacenterAuthInfo alloc] initWithAuthKey:_useExplicitAuthKey.authKey authKeyId:_useExplicitAuthKey.authKeyId saltSet:@[[[MTDatacenterSaltInfo alloc] initWithSalt:0 firstValidMessageId:0 lastValidMessageId:0]] authKeyAttributes:nil]; - - _validAuthInfo = [[MTProtoValidAuthInfo alloc] initWithAuthInfo:authInfo selector:selector]; - return [[MTDatacenterAuthKey alloc] initWithAuthKey:_validAuthInfo.authInfo.authKey authKeyId:_validAuthInfo.authInfo.authKeyId notBound:false]; - } else { - MTDatacenterAuthInfoSelector selector = MTDatacenterAuthInfoSelectorPersistent; - - if (_cdn) { - selector = MTDatacenterAuthInfoSelectorPersistent; - } else { - if (_useTempAuthKeys) { - if (scheme.address.preferForMedia) { - selector = MTDatacenterAuthInfoSelectorEphemeralMedia; - } else { - selector = MTDatacenterAuthInfoSelectorEphemeralMain; - } - } else { - selector = MTDatacenterAuthInfoSelectorPersistent; - } - } - - if (authInfoSelector != nil) { - *authInfoSelector = selector; - } - - if (_validAuthInfo != nil && _validAuthInfo.selector == selector) { - return [[MTDatacenterAuthKey alloc] initWithAuthKey:_validAuthInfo.authInfo.authKey authKeyId:_validAuthInfo.authInfo.authKeyId notBound:false]; - } else { - MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:_datacenterId selector:selector]; - if (authInfo != nil) { - _validAuthInfo = [[MTProtoValidAuthInfo alloc] initWithAuthInfo:authInfo selector:selector]; - return [[MTDatacenterAuthKey alloc] initWithAuthKey:_validAuthInfo.authInfo.authKey authKeyId:_validAuthInfo.authInfo.authKeyId notBound:false]; - } else { - if (createIfNeeded) { - [_context performBatchUpdates:^{ - [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:nil selector:selector]; - [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:_cdn selector:selector]; - }]; - _mtState |= MTProtoStateAwaitingDatacenterAuthorization; - _awaitingAuthInfoForSelector = @(selector); - } - - return nil; - } - } - } -} - - (void)transportReadyForTransaction:(MTTransport *)transport scheme:(MTTransportScheme *)scheme transportSpecificTransaction:(MTMessageTransaction *)transportSpecificTransaction forceConfirmations:(bool)forceConfirmations transactionReady:(void (^)(NSArray *))transactionReady { [[MTProto managerQueue] dispatchOnQueue:^ @@ -897,19 +887,6 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; return; } - MTDatacenterAuthKey *authKey = nil; - MTDatacenterAuthInfoSelector authInfoSelector = MTDatacenterAuthInfoSelectorPersistent; - if (!_useUnauthorizedMode) { - authKey = [self getAuthKeyForCurrentScheme:scheme createIfNeeded:true authInfoSelector:&authInfoSelector]; - - if (authKey == nil) { - if (transactionReady) { - transactionReady(nil); - } - return; - } - } - bool extendedPadding = false; if (transport.proxySettings != nil && transport.proxySettings.secret != nil) { MTProxySecret *parsedSecret = [MTProxySecret parseData:transport.proxySettings.secret]; @@ -931,7 +908,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; if (transportSpecificTransaction != nil) { - if (!(transportSpecificTransaction.requiresEncryption && _useUnauthorizedMode) && (!transportSpecificTransaction.requiresEncryption || authKey != nil)) + if (!(transportSpecificTransaction.requiresEncryption && _useUnauthorizedMode) && (!transportSpecificTransaction.requiresEncryption || _authInfo != nil)) { [messageTransactions addObject:transportSpecificTransaction]; } @@ -942,9 +919,9 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; NSMutableArray *messageServiceTransactions = [[NSMutableArray alloc] init]; for (id messageService in _messageServices) { - if ([messageService respondsToSelector:@selector(mtProtoMessageTransaction:authInfoSelector:sessionInfo:)]) + if ([messageService respondsToSelector:@selector(mtProtoMessageTransaction:)]) { - MTMessageTransaction *messageTransaction = [messageService mtProtoMessageTransaction:self authInfoSelector:authInfoSelector sessionInfo:transactionSessionInfo]; + MTMessageTransaction *messageTransaction = [messageService mtProtoMessageTransaction:self]; if (messageTransaction != nil) { for (MTOutgoingMessage *message in messageTransaction.messagePayload) @@ -1000,7 +977,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; int64_t messageSalt = 0; if (!_useUnauthorizedMode) { - messageSalt = [_validAuthInfo.authInfo authSaltForMessageId:[transactionSessionInfo actualClientMessagId]]; + messageSalt = [_authInfo authSaltForMessageId:[transactionSessionInfo actualClientMessagId]]; if (messageSalt == 0) saltSetEmpty = true; } @@ -1179,7 +1156,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; if (currentContainerMessages.count == 1) { int32_t quickAckId = 0; - NSData *messageData = [self _dataForEncryptedMessage:currentContainerMessages[0] authKey:authKey sessionInfo:transactionSessionInfo quickAckId:&quickAckId address:scheme.address extendedPadding:extendedPadding]; + NSData *messageData = [self _dataForEncryptedMessage:currentContainerMessages[0] sessionInfo:transactionSessionInfo quickAckId:&quickAckId address:scheme.address extendedPadding:extendedPadding]; if (messageData != nil) { [transactionPayloadList addObject:messageData]; @@ -1190,7 +1167,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; else if (currentContainerMessages.count != 0) { int32_t quickAckId = 0; - NSData *containerData = [self _dataForEncryptedContainerWithMessages:currentContainerMessages authKey:authKey sessionInfo:transactionSessionInfo quickAckId:&quickAckId address:scheme.address extendedPadding:extendedPadding]; + NSData *containerData = [self _dataForEncryptedContainerWithMessages:currentContainerMessages sessionInfo:transactionSessionInfo quickAckId:&quickAckId address:scheme.address extendedPadding:extendedPadding]; if (containerData != nil) { [transactionPayloadList addObject:containerData]; @@ -1332,7 +1309,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; } MTShortLog(@"[MTProto#%p@%p sending time fix ping (%" PRId64 "/%" PRId32 ", %" PRId64 ")]", self, _context, timeFixMessageId, timeFixSeqNo, _sessionInfo.sessionId); - [decryptedOs writeInt64:[_validAuthInfo.authInfo authSaltForMessageId:timeFixMessageId]]; // salt + [decryptedOs writeInt64:[_authInfo authSaltForMessageId:timeFixMessageId]]; // salt [decryptedOs writeInt64:_sessionInfo.sessionId]; [decryptedOs writeInt64:timeFixMessageId]; [decryptedOs writeInt32:timeFixSeqNo]; @@ -1342,7 +1319,18 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; NSData *decryptedData = [self paddedData:[decryptedOs currentBytes] extendedPadding:extendedPadding]; - MTDatacenterAuthKey *effectiveAuthKey = authKey; + MTDatacenterAuthKey *effectiveAuthKey; + if (_useTempAuthKeys) { + MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; + if (scheme.address.preferForMedia) { + tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; + } + + effectiveAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; + NSAssert(effectiveAuthKey != nil, @"effectiveAuthKey == nil"); + } else { + effectiveAuthKey = [[MTDatacenterAuthKey alloc] initWithAuthKey:_authInfo.authKey authKeyId:_authInfo.authKeyId notBound:false]; + } int xValue = 0; NSMutableData *msgKeyLargeData = [[NSMutableData alloc] init]; @@ -1396,6 +1384,115 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; transactionReady(nil); } } + else if (![self timeFixOrSaltsMissing] && [self bindingTempAuthKey] && [self canAskForServiceTransactions] && (_bindingTempAuthKeyContext == nil || _bindingTempAuthKeyContext.transactionId == nil)) + { + MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; + if (scheme.address.preferForMedia) { + tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; + } + + MTDatacenterAuthKey *effectiveTempAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; + NSAssert(effectiveTempAuthKey != nil, @"effectiveAuthKey == nil"); + + int64_t bindingMessageId = [_sessionInfo generateClientMessageId:NULL]; + int32_t bindingSeqNo = [_sessionInfo takeSeqNo:true]; + + int32_t expiresAt = (int32_t)([_context globalTime] + 60 * 60 * 32); + + int64_t randomId = 0; + arc4random_buf(&randomId, 8); + + int64_t nonce = 0; + arc4random_buf(&nonce, 8); + + MTBuffer *decryptedMessage = [[MTBuffer alloc] init]; + //bind_auth_key_inner#75a3f765 nonce:long temp_auth_key_id:long perm_auth_key_id:long temp_session_id:long expires_at:int = BindAuthKeyInner; + [decryptedMessage appendInt32:(int32_t)0x75a3f765]; + [decryptedMessage appendInt64:nonce]; + [decryptedMessage appendInt64:effectiveTempAuthKey.authKeyId]; + [decryptedMessage appendInt64:_authInfo.authKeyId]; + [decryptedMessage appendInt64:_sessionInfo.sessionId]; + [decryptedMessage appendInt32:expiresAt]; + + NSData *encryptedMessage = [self _manuallyEncryptedMessage:[decryptedMessage data] messageId:bindingMessageId authKey:_authInfo.persistentAuthKey]; + + MTBuffer *bindRequestData = [[MTBuffer alloc] init]; + + //auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int encrypted_message:bytes = Bool; + + [bindRequestData appendInt32:(int32_t)0xcdd42a05]; + [bindRequestData appendInt64:_authInfo.persistentAuthKey.authKeyId]; + [bindRequestData appendInt64:nonce]; + [bindRequestData appendInt32:expiresAt]; + [bindRequestData appendTLBytes:encryptedMessage]; + + NSData *messageData = bindRequestData.data; + + MTOutputStream *decryptedOs = [[MTOutputStream alloc] init]; + + if (MTLogEnabled()) { + MTLog(@"[MTProto#%p@%p sending temp key binding message (%" PRId64 "/%" PRId32 ") for %lld (%d)]", self, _context, bindingMessageId, bindingSeqNo, effectiveTempAuthKey.authKeyId, (int)tempAuthKeyType); + } + MTShortLog(@"[MTProto#%p@%p sending temp key binding message (%" PRId64 "/%" PRId32 ") for %lld (%d)]", self, _context, bindingMessageId, bindingSeqNo, effectiveTempAuthKey.authKeyId, (int)tempAuthKeyType); + + [decryptedOs writeInt64:[_authInfo authSaltForMessageId:bindingMessageId]]; + [decryptedOs writeInt64:_sessionInfo.sessionId]; + [decryptedOs writeInt64:bindingMessageId]; + [decryptedOs writeInt32:bindingSeqNo]; + + [decryptedOs writeInt32:(int32_t)messageData.length]; + [decryptedOs writeData:messageData]; + + NSData *decryptedData = [self paddedData:[decryptedOs currentBytes] extendedPadding:extendedPadding]; + + int xValue = 0; + NSMutableData *msgKeyLargeData = [[NSMutableData alloc] init]; + [msgKeyLargeData appendBytes:effectiveTempAuthKey.authKey.bytes + 88 + xValue length:32]; + [msgKeyLargeData appendData:decryptedData]; + + NSData *msgKeyLarge = MTSha256(msgKeyLargeData); + NSData *messageKey = [msgKeyLarge subdataWithRange:NSMakeRange(8, 16)]; + MTMessageEncryptionKey *encryptionKey = [MTMessageEncryptionKey messageEncryptionKeyV2ForAuthKey:effectiveTempAuthKey.authKey messageKey:messageKey toClient:false]; + + NSData *transactionData = nil; + + if (encryptionKey != nil) + { + NSMutableData *encryptedData = [[NSMutableData alloc] initWithCapacity:14 + decryptedData.length]; + [encryptedData appendData:decryptedData]; + MTAesEncryptInplace(encryptedData, encryptionKey.key, encryptionKey.iv); + + int64_t authKeyId = effectiveTempAuthKey.authKeyId; + [encryptedData replaceBytesInRange:NSMakeRange(0, 0) withBytes:&authKeyId length:8]; + [encryptedData replaceBytesInRange:NSMakeRange(8, 0) withBytes:messageKey.bytes length:messageKey.length]; + + transactionData = encryptedData; + } + + if (transactionReady != nil) + { + if (transactionData != nil) + { + __weak MTProto *weakSelf = self; + transactionReady(@[[[MTTransportTransaction alloc] initWithPayload:transactionData completion:^(bool success, id transactionId) { + [[MTProto managerQueue] dispatchOnQueue:^{ + if (success) { + if (transactionId != nil) { + _bindingTempAuthKeyContext = [[MTBindingTempAuthKeyContext alloc] initWithMessageId:bindingMessageId messageSeqNo:bindingSeqNo transactionId:transactionId]; + } + } + else + { + __strong MTProto *strongSelf = weakSelf; + [strongSelf requestTransportTransaction]; + } + }]; + } needsQuickAck:false expectsDataInResponse:true]]); + } + else + transactionReady(nil); + } + } else if (transactionReady != nil) transactionReady(nil); @@ -1406,8 +1503,19 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; }]; } -- (NSData *)_dataForEncryptedContainerWithMessages:(NSArray *)preparedMessages authKey:(MTDatacenterAuthKey *)authKey sessionInfo:(MTSessionInfo *)sessionInfo quickAckId:(int32_t *)quickAckId address:(MTDatacenterAddress *)address extendedPadding:(bool)extendedPadding { - MTDatacenterAuthKey *effectiveAuthKey = authKey; +- (NSData *)_dataForEncryptedContainerWithMessages:(NSArray *)preparedMessages sessionInfo:(MTSessionInfo *)sessionInfo quickAckId:(int32_t *)quickAckId address:(MTDatacenterAddress *)address extendedPadding:(bool)extendedPadding { + MTDatacenterAuthKey *effectiveAuthKey; + if (_useTempAuthKeys) { + MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; + if (address.preferForMedia) { + tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; + } + effectiveAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; + + NSAssert(effectiveAuthKey != nil, @"effectiveAuthKey == nil"); + } else { + effectiveAuthKey = [[MTDatacenterAuthKey alloc] initWithAuthKey:_authInfo.authKey authKeyId:_authInfo.authKeyId notBound:false]; + } NSMutableArray *containerMessageIds = [[NSMutableArray alloc] init]; @@ -1514,7 +1622,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; return messageData; } -+ (NSData *)paddedDataV1:(NSData *)data { +- (NSData *)paddedDataV1:(NSData *)data { NSMutableData *padded = [[NSMutableData alloc] initWithData:data]; uint8_t randomBytes[128]; arc4random_buf(randomBytes, 128); @@ -1561,7 +1669,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; return padded; } -+ (NSData *)_manuallyEncryptedMessage:(NSData *)preparedData messageId:(int64_t)messageId authKey:(MTDatacenterAuthKey *)authKey { +- (NSData *)_manuallyEncryptedMessage:(NSData *)preparedData messageId:(int64_t)messageId authKey:(MTDatacenterAuthKey *)authKey { MTOutputStream *decryptedOs = [[MTOutputStream alloc] init]; int64_t random1 = 0; @@ -1577,7 +1685,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; [decryptedOs writeInt32:(int32_t)preparedData.length]; [decryptedOs writeData:preparedData]; - NSData *decryptedData = [MTProto paddedDataV1:[decryptedOs currentBytes]]; + NSData *decryptedData = [self paddedDataV1:[decryptedOs currentBytes]]; NSData *messageKeyFull = MTSubdataSha1(decryptedData, 0, 32 + preparedData.length); NSData *messageKey = [[NSData alloc] initWithBytes:(((int8_t *)messageKeyFull.bytes) + messageKeyFull.length - 16) length:16]; @@ -1600,10 +1708,19 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; return nil; } -- (NSData *)_dataForEncryptedMessage:(MTPreparedMessage *)preparedMessage authKey:(MTDatacenterAuthKey *)authKey sessionInfo:(MTSessionInfo *)sessionInfo quickAckId:(int32_t *)quickAckId address:(MTDatacenterAddress *)address extendedPadding:(bool)extendedPadding +- (NSData *)_dataForEncryptedMessage:(MTPreparedMessage *)preparedMessage sessionInfo:(MTSessionInfo *)sessionInfo quickAckId:(int32_t *)quickAckId address:(MTDatacenterAddress *)address extendedPadding:(bool)extendedPadding { - MTDatacenterAuthKey *effectiveAuthKey = authKey; - NSAssert(effectiveAuthKey != nil, @"effectiveAuthKey == nil"); + MTDatacenterAuthKey *effectiveAuthKey; + if (_useTempAuthKeys) { + MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; + if (address.preferForMedia) { + tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; + } + effectiveAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; + NSAssert(effectiveAuthKey != nil, @"effectiveAuthKey == nil"); + } else { + effectiveAuthKey = [[MTDatacenterAuthKey alloc] initWithAuthKey:_authInfo.authKey authKeyId:_authInfo.authKeyId notBound:false]; + } MTOutputStream *decryptedOs = [[MTOutputStream alloc] init]; @@ -1657,6 +1774,12 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; requestTransaction = true; } + if (_bindingTempAuthKeyContext != nil && _bindingTempAuthKeyContext.transactionId != nil && [transactionIds containsObject:_bindingTempAuthKeyContext.transactionId]) + { + _bindingTempAuthKeyContext = nil; + requestTransaction = true; + } + for (NSInteger i = (NSInteger)_messageServices.count - 1; i >= 0; i--) { id messageService = _messageServices[(NSUInteger)i]; @@ -1683,6 +1806,12 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; requestTransaction = true; } + if (_bindingTempAuthKeyContext != nil && _bindingTempAuthKeyContext.transactionId != nil) + { + _bindingTempAuthKeyContext = nil; + requestTransaction = true; + } + for (NSInteger i = (NSInteger)_messageServices.count - 1; i >= 0; i--) { id messageService = _messageServices[(NSUInteger)i]; @@ -1719,18 +1848,24 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; if (transport != _transport || completion == nil) return; - MTDatacenterAuthKey *authKey = [self getAuthKeyForCurrentScheme:scheme createIfNeeded:false authInfoSelector:nil]; - if (authKey == nil) { - return; + MTDatacenterAuthKey *effectiveAuthKey; + if (_useTempAuthKeys) { + MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; + if (scheme.address.preferForMedia) { + tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; + } + + effectiveAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; + NSAssert(effectiveAuthKey != nil, @"effectiveAuthKey == nil"); + } else { + effectiveAuthKey = [[MTDatacenterAuthKey alloc] initWithAuthKey:_authInfo.authKey authKeyId:_authInfo.authKeyId notBound:false]; } - MTDatacenterAuthKey *effectiveAuthKey = authKey; - MTInputStream *is = [[MTInputStream alloc] initWithData:data]; int64_t keyId = [is readInt64]; - if (keyId == authKey.authKeyId) + if (keyId != 0 && _authInfo != nil) { NSData *messageKey = [is readData:16]; @@ -1935,7 +2070,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { } if (protocolErrorCode == -404) { - [self handleMissingKey:scheme]; + [self handleMissingKey:scheme.address]; } if (currentTransport == _transport) @@ -1946,17 +2081,10 @@ static NSString *dumpHexString(NSData *data, int maxLength) { NSData *decryptedData = nil; - int64_t embeddedAuthKeyId = 0; - MTDatacenterAuthInfoSelector authInfoSelector = MTDatacenterAuthInfoSelectorPersistent; - if (_useUnauthorizedMode) { + if (_useUnauthorizedMode) decryptedData = data; - } else { - MTDatacenterAuthKey *authKey = [self getAuthKeyForCurrentScheme:scheme createIfNeeded:false authInfoSelector:&authInfoSelector]; - if (authKey != nil) { - embeddedAuthKeyId = authKey.authKeyId; - decryptedData = [self _decryptIncomingTransportData:data address:scheme.address authKey:authKey]; - } - } + else + decryptedData = [self _decryptIncomingTransportData:data address:scheme.address]; if (decryptedData != nil) { @@ -1965,7 +2093,9 @@ static NSString *dumpHexString(NSData *data, int maxLength) { int64_t dataMessageId = 0; bool parseError = false; - NSArray *parsedMessages = [self _parseIncomingMessages:decryptedData dataMessageId:&dataMessageId embeddedAuthKeyId:embeddedAuthKeyId parseError:&parseError]; + NSArray *parsedMessages = [self _parseIncomingMessages:decryptedData dataMessageId:&dataMessageId parseError:&parseError]; + + for (MTIncomingMessage *message in parsedMessages) { if ([message.body isKindOfClass:[MTRpcResultMessage class]]) { @@ -1978,7 +2108,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { MTLog(@"[MTProto#%p@%p received AUTH_KEY_PERM_EMPTY]", self, _context); } MTShortLog(@"[MTProto#%p@%p received AUTH_KEY_PERM_EMPTY]", self, _context); - [self handleMissingKey:scheme]; + [self handleMissingKey:scheme.address]; [self requestSecureTransportReset]; return; @@ -2002,7 +2132,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { for (MTIncomingMessage *incomingMessage in parsedMessages) { - [self _processIncomingMessage:incomingMessage totalSize:(int)data.length withTransactionId:transactionId address:scheme.address authInfoSelector:authInfoSelector]; + [self _processIncomingMessage:incomingMessage totalSize:(int)data.length withTransactionId:transactionId address:scheme.address]; } if (requestTransactionAfterProcessing) @@ -2025,74 +2155,71 @@ static NSString *dumpHexString(NSData *data, int maxLength) { }]; } -- (void)handleMissingKey:(MTTransportScheme *)scheme { +- (void)handleMissingKey:(MTDatacenterAddress *)address { NSAssert([[MTProto managerQueue] isCurrentQueue], @"invalid queue"); - MTDatacenterAuthInfoSelector authInfoSelector; - [self getAuthKeyForCurrentScheme:scheme createIfNeeded:false authInfoSelector:&authInfoSelector]; - - if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p missing key %lld selector]", self, _context, _validAuthInfo.authInfo.authKeyId, authInfoSelector); - } - if (_useExplicitAuthKey != nil) { } else if (_cdn) { - _validAuthInfo = nil; - + _authInfo = nil; [_context performBatchUpdates:^{ - [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:nil selector:authInfoSelector]; - [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:true selector:authInfoSelector]; + [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:nil]; + [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:true]; }]; _mtState |= MTProtoStateAwaitingDatacenterAuthorization; - _awaitingAuthInfoForSelector = @(authInfoSelector); - } else { - MTDatacenterAuthInfoSelector authInfoSelector; - [self getAuthKeyForCurrentScheme:scheme createIfNeeded:false authInfoSelector:&authInfoSelector]; + } else if (_useTempAuthKeys) { + MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; + if (address.preferForMedia) { + tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; + } + MTDatacenterAuthKey *currentTempAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; + + _authInfo = [_authInfo withUpdatedTempAuthKeyWithType:tempAuthKeyType key:nil]; + _mtState |= MTProtoStateAwaitingDatacenterTempAuthKey; + + [_context performBatchUpdates:^{ + MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:_datacenterId]; + if ([authInfo tempAuthKeyWithType:tempAuthKeyType].authKeyId == currentTempAuthKey.authKeyId) { + authInfo = [authInfo withUpdatedTempAuthKeyWithType:tempAuthKeyType key:nil]; + [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:authInfo]; + [_context tempAuthKeyForDatacenterWithIdRequired:_datacenterId keyType:tempAuthKeyType]; + } + }]; + } else { if (_requiredAuthToken != nil && _authTokenMasterDatacenterId != _datacenterId) { - _validAuthInfo = nil; - + _authInfo = nil; [_context removeTokenForDatacenterWithId:_datacenterId]; [_context performBatchUpdates:^{ - [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:nil selector:authInfoSelector]; - [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:false selector:authInfoSelector]; + [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:nil]; + [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:false]; }]; _mtState |= MTProtoStateAwaitingDatacenterAuthorization; - _awaitingAuthInfoForSelector = @(authInfoSelector); } else if (_canResetAuthData) { - _validAuthInfo = nil; - + _authInfo = nil; [_context performBatchUpdates:^{ - [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:nil selector:authInfoSelector]; - [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:false selector:authInfoSelector]; + [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:nil]; + [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:false]; }]; _mtState |= MTProtoStateAwaitingDatacenterAuthorization; - _awaitingAuthInfoForSelector = @(authInfoSelector); } else { - switch (authInfoSelector) { - case MTDatacenterAuthInfoSelectorEphemeralMain: - case MTDatacenterAuthInfoSelectorEphemeralMedia: { - _validAuthInfo = nil; - - [_context performBatchUpdates:^{ - [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:nil selector:authInfoSelector]; - [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:false selector:authInfoSelector]; - }]; - _mtState |= MTProtoStateAwaitingDatacenterAuthorization; - _awaitingAuthInfoForSelector = @(authInfoSelector); - break; - } - default: - [_context checkIfLoggedOut:_datacenterId]; - break; - } + [_context checkIfLoggedOut:_datacenterId]; } } } -- (NSData *)_decryptIncomingTransportData:(NSData *)transportData address:(MTDatacenterAddress *)address authKey:(MTDatacenterAuthKey *)authKey +- (NSData *)_decryptIncomingTransportData:(NSData *)transportData address:(MTDatacenterAddress *)address { - MTDatacenterAuthKey *effectiveAuthKey = authKey; + MTDatacenterAuthKey *effectiveAuthKey; + if (_useTempAuthKeys) { + MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; + if (address.preferForMedia) { + tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; + } + + effectiveAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; + } else { + effectiveAuthKey = [[MTDatacenterAuthKey alloc] initWithAuthKey:_authInfo.authKey authKeyId:_authInfo.authKeyId notBound:false]; + } if (effectiveAuthKey == nil) return nil; @@ -2154,12 +2281,13 @@ static NSString *dumpHexString(NSData *data, int maxLength) { return [_context.serialization parseMessage:unwrappedData]; } -- (NSArray *)_parseIncomingMessages:(NSData *)data dataMessageId:(out int64_t *)dataMessageId embeddedAuthKeyId:(int64_t)embeddedAuthKeyId parseError:(out bool *)parseError +- (NSArray *)_parseIncomingMessages:(NSData *)data dataMessageId:(out int64_t *)dataMessageId parseError:(out bool *)parseError { MTInputStream *is = [[MTInputStream alloc] initWithData:data]; bool readError = false; + int64_t embeddedAuthKeyId = 0; int64_t embeddedSessionId = 0; int64_t embeddedMessageId = 0; int32_t embeddedSeqNo = 0; @@ -2175,6 +2303,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { *parseError = true; return nil; } + embeddedAuthKeyId = authKeyId; embeddedMessageId = [is readInt64:&readError]; if (readError) @@ -2197,6 +2326,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { } else { + embeddedAuthKeyId = _authInfo.authKeyId; embeddedSalt = [is readInt64:&readError]; if (readError) { @@ -2306,7 +2436,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { return messages; } -- (void)_processIncomingMessage:(MTIncomingMessage *)incomingMessage totalSize:(int)totalSize withTransactionId:(id)transactionId address:(MTDatacenterAddress *)address authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector +- (void)_processIncomingMessage:(MTIncomingMessage *)incomingMessage totalSize:(int)totalSize withTransactionId:(id)transactionId address:(MTDatacenterAddress *)address { if ([_sessionInfo messageProcessed:incomingMessage.messageId]) { @@ -2352,7 +2482,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { int64_t validSalt = ((MTBadServerSaltNotificationMessage *)badMsgNotification).nextServerSalt; NSTimeInterval timeDifference = incomingMessage.messageId / 4294967296.0 - [[NSDate date] timeIntervalSince1970]; [self completeTimeSync]; - [self timeSyncInfoChanged:timeDifference saltList:@[[[MTDatacenterSaltInfo alloc] initWithSalt:validSalt firstValidMessageId:incomingMessage.messageId lastValidMessageId:incomingMessage.messageId + (4294967296 * 30 * 60)]] authInfoSelector:authInfoSelector]; + [self timeSyncInfoChanged:timeDifference saltList:@[[[MTDatacenterSaltInfo alloc] initWithSalt:validSalt firstValidMessageId:incomingMessage.messageId lastValidMessageId:incomingMessage.messageId + (4294967296 * 30 * 60)]]]; } else [self initiateTimeSync]; @@ -2370,7 +2500,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { NSTimeInterval timeDifference = incomingMessage.messageId / 4294967296.0 - [[NSDate date] timeIntervalSince1970]; [self completeTimeSync]; - [self timeSyncInfoChanged:timeDifference saltList:nil authInfoSelector:authInfoSelector]; + [self timeSyncInfoChanged:timeDifference saltList:nil]; } else [self initiateTimeSync]; @@ -2412,6 +2542,11 @@ static NSString *dumpHexString(NSData *data, int maxLength) { } } + if (_bindingTempAuthKeyContext != nil && badMessageId == _bindingTempAuthKeyContext.messageId) + { + _bindingTempAuthKeyContext = nil; + } + if ([self canAskForTransactions] || [self canAskForServiceTransactions]) [self requestTransportTransaction]; } @@ -2489,8 +2624,8 @@ static NSString *dumpHexString(NSData *data, int maxLength) { { id messageService = _messageServices[(NSUInteger)i]; - if ([messageService respondsToSelector:@selector(mtProto:receivedMessage:authInfoSelector:)]) - [messageService mtProto:self receivedMessage:incomingMessage authInfoSelector:authInfoSelector]; + if ([messageService respondsToSelector:@selector(mtProto:receivedMessage:)]) + [messageService mtProto:self receivedMessage:incomingMessage]; } if (_timeFixContext != nil && [incomingMessage.body isKindOfClass:[MTPongMessage class]] && ((MTPongMessage *)incomingMessage.body).messageId == _timeFixContext.messageId) @@ -2501,6 +2636,71 @@ static NSString *dumpHexString(NSData *data, int maxLength) { if ([self canAskForTransactions] || [self canAskForServiceTransactions]) [self requestTransportTransaction]; } + + if (_bindingTempAuthKeyContext != nil && [incomingMessage.body isKindOfClass:[MTRpcResultMessage class]] && ((MTRpcResultMessage *)incomingMessage.body).requestMessageId == _bindingTempAuthKeyContext.messageId) { + MTRpcResultMessage *rpcResultMessage = (MTRpcResultMessage *)incomingMessage.body; + + _bindingTempAuthKeyContext = nil; + + id maybeInternalMessage = [MTInternalMessageParser parseMessage:rpcResultMessage.data]; + + id rpcResult = nil; + MTRpcError *rpcError = nil; + + if ([maybeInternalMessage isKindOfClass:[MTRpcError class]]) + rpcError = maybeInternalMessage; + else + { + if (rpcResultMessage.data.length >= 4) { + int32_t signature = 0; + [rpcResultMessage.data getBytes:&signature range:NSMakeRange(0, 4)]; + if (signature == (int32_t)0xbc799737) { + rpcResult = @true; + } else if (signature == (int32_t)0x997275b5) { + rpcResult = @false; + } + } + if (rpcResult == nil) { + rpcError = [[MTRpcError alloc] initWithErrorCode:500 errorDescription:@"TL_PARSING_ERROR"]; + } + } + + if ([rpcResult respondsToSelector:@selector(boolValue)]) { + MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; + if (address.preferForMedia) { + tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; + } + MTDatacenterAuthKey *effectiveTempAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; + if (effectiveTempAuthKey != nil) { + _authInfo = [_authInfo withUpdatedTempAuthKeyWithType:tempAuthKeyType key:[[MTDatacenterAuthKey alloc] initWithAuthKey:effectiveTempAuthKey.authKey authKeyId:effectiveTempAuthKey.authKeyId notBound:false]]; + NSMutableDictionary *authKeyAttributes = [[NSMutableDictionary alloc] initWithDictionary:_authInfo.authKeyAttributes]; + [authKeyAttributes removeObjectForKey:@"apiInitializationHash"]; + _authInfo = [_authInfo withUpdatedAuthKeyAttributes:authKeyAttributes]; + if (_useExplicitAuthKey == nil) { + [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:_authInfo]; + } + if (_tempAuthKeyBindingResultUpdated) { + _tempAuthKeyBindingResultUpdated(true); + } + } + _bindingTempAuthKeyId = 0; + if ((_mtState & MTProtoStateBindingTempAuthKey) != 0) { + [self setMtState:_mtState & (~MTProtoStateBindingTempAuthKey)]; + [self requestTransportTransaction]; + } + } else { + if (MTLogEnabled()) { + MTLog(@"[MTProto#%p@%p bindTempAuthKey error %@]", self, _context, rpcError); + } + MTShortLog(@"[MTProto#%p@%p bindTempAuthKey error %@]", self, _context, rpcError); + + [self requestTransportTransaction]; + + if (_tempAuthKeyBindingResultUpdated) { + _tempAuthKeyBindingResultUpdated(false); + } + } + } } } @@ -2522,27 +2722,18 @@ static NSString *dumpHexString(NSData *data, int maxLength) { }]; } -- (void)contextDatacenterAuthInfoUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo selector:(MTDatacenterAuthInfoSelector)selector +- (void)contextDatacenterAuthInfoUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo { [[MTProto managerQueue] dispatchOnQueue:^ { if (!_useUnauthorizedMode && context == _context && datacenterId == _datacenterId) { - if (_validAuthInfo != nil) { - if (_validAuthInfo.selector != selector) { - return; - } - } else if (_awaitingAuthInfoForSelector != nil) { - if ([_awaitingAuthInfoForSelector intValue] != selector) { - return; - } else if (authInfo != nil) { - _awaitingAuthInfoForSelector = nil; - } - } else { - return; + _authInfo = authInfo; + if (_useExplicitAuthKey != nil) { + _authInfo = [_authInfo withUpdatedTempAuthKeyWithType:MTDatacenterAuthTempKeyTypeMain key:_useExplicitAuthKey]; } - bool wasSuspended = _mtState & (MTProtoStateAwaitingDatacenterAuthorization); + bool wasSuspended = _mtState & (MTProtoStateAwaitingDatacenterAuthorization | MTProtoStateAwaitingDatacenterTempAuthKey); if (authInfo != nil) { if (_mtState & MTProtoStateAwaitingDatacenterAuthorization) { @@ -2550,8 +2741,38 @@ static NSString *dumpHexString(NSData *data, int maxLength) { } } + /*if (_transportScheme != nil && _useTempAuthKeys) { + MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; + if (_transportScheme.address.preferForMedia) { + tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; + } + if ([[_context authInfoForDatacenterWithId:_datacenterId] tempAuthKeyWithType:tempAuthKeyType] == nil) { + if ((_mtState & MTProtoStateAwaitingDatacenterTempAuthKey) == 0) { + [self setMtState:_mtState | MTProtoStateAwaitingDatacenterTempAuthKey]; + + [_context tempAuthKeyForDatacenterWithIdRequired:_datacenterId keyType:tempAuthKeyType]; + } + } + } + + if (_transportScheme != nil && (_mtState & MTProtoStateAwaitingDatacenterTempAuthKey)) + { + MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; + if (_transportScheme.address.preferForMedia) { + tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; + } + + if ([_authInfo tempAuthKeyWithType:tempAuthKeyType] != nil) { + [self setMtState:_mtState & (~MTProtoStateAwaitingDatacenterTempAuthKey)]; + } + } + + if (_transportScheme != nil) { + [self checkTempAuthKeyBinding:_transportScheme.address]; + }*/ + if (authInfo != nil) { - if ((_mtState & (MTProtoStateAwaitingDatacenterAuthorization)) == 0) { + if ((_mtState & (MTProtoStateAwaitingDatacenterAuthorization | MTProtoStateAwaitingDatacenterTempAuthKey)) == 0) { if (wasSuspended) { [self resetTransport]; [self requestTransportTransaction]; @@ -2564,6 +2785,38 @@ static NSString *dumpHexString(NSData *data, int maxLength) { }]; } +- (void)checkTempAuthKeyBinding:(MTDatacenterAddress *)address { + NSAssert([[MTProto managerQueue] isCurrentQueue], @"invalid queue"); + + MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; + if (address.preferForMedia) { + tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; + } + MTDatacenterAuthKey *effectiveTempAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; + + if (_useTempAuthKeys && effectiveTempAuthKey != nil && effectiveTempAuthKey.notBound) { + bool isAlreadyBinding = false; + if (_bindingTempAuthKeyId != 0) { + if (_bindingTempAuthKeyId == effectiveTempAuthKey.authKeyId) { + isAlreadyBinding = true; + } else { + _bindingTempAuthKeyId = 0; + _bindingTempAuthKeyContext = nil; + } + } + if (!isAlreadyBinding && (_mtState & MTProtoStateBindingTempAuthKey) == 0) { + [self bindToPersistentKey:address]; + } + } else { + _bindingTempAuthKeyId = 0; + _bindingTempAuthKeyContext = nil; + if ((_mtState & MTProtoStateBindingTempAuthKey) != 0) { + [self setMtState:_mtState & (~MTProtoStateBindingTempAuthKey)]; + [self requestTransportTransaction]; + } + } +} + - (void)contextDatacenterAuthTokenUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authToken:(id)authToken { [[MTProto managerQueue] dispatchOnQueue:^ @@ -2589,38 +2842,28 @@ static NSString *dumpHexString(NSData *data, int maxLength) { }]; } -- (void)timeSyncServiceCompleted:(MTTimeSyncMessageService *)timeSyncService timeDifference:(NSTimeInterval)timeDifference saltList:(NSArray *)saltList authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector +- (void)timeSyncServiceCompleted:(MTTimeSyncMessageService *)timeSyncService timeDifference:(NSTimeInterval)timeDifference saltList:(NSArray *)saltList { if ([_messageServices containsObject:timeSyncService]) { [self completeTimeSync]; [_messageServices removeObject:timeSyncService]; - [self timeSyncInfoChanged:timeDifference saltList:saltList authInfoSelector:authInfoSelector]; + [self timeSyncInfoChanged:timeDifference saltList:saltList]; } } -- (void)timeSyncInfoChanged:(NSTimeInterval)timeDifference saltList:(NSArray *)saltList authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector +- (void)timeSyncInfoChanged:(NSTimeInterval)timeDifference saltList:(NSArray *)saltList { [_context setGlobalTimeDifference:timeDifference]; if (saltList != nil) { - if (_useExplicitAuthKey) { - if (_validAuthInfo != nil && _validAuthInfo.selector == authInfoSelector) { - MTDatacenterAuthInfo *updatedAuthInfo = [_validAuthInfo.authInfo mergeSaltSet:saltList forTimestamp:[_context globalTime]]; - _validAuthInfo = [[MTProtoValidAuthInfo alloc] initWithAuthInfo:updatedAuthInfo selector:authInfoSelector]; - } - } else { - MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:_datacenterId selector:authInfoSelector]; - if (authInfo != nil) - { - MTDatacenterAuthInfo *updatedAuthInfo = [authInfo mergeSaltSet:saltList forTimestamp:[_context globalTime]]; - [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:updatedAuthInfo selector:authInfoSelector]; - if (_validAuthInfo != nil && _validAuthInfo.selector == authInfoSelector) { - _validAuthInfo = [[MTProtoValidAuthInfo alloc] initWithAuthInfo:updatedAuthInfo selector:authInfoSelector]; - } - } + MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:_datacenterId]; + if (authInfo != nil) + { + MTDatacenterAuthInfo *updatedAuthInfo = [authInfo mergeSaltSet:saltList forTimestamp:[_context globalTime]]; + [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:updatedAuthInfo]; } } @@ -2682,4 +2925,18 @@ static NSString *dumpHexString(NSData *data, int maxLength) { }]; } +- (void)bindToPersistentKey:(MTDatacenterAddress *)address { + [[MTProto managerQueue] dispatchOnQueue:^{ + MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; + if (address.preferForMedia) { + tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; + } + MTDatacenterAuthKey *effectiveTempAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; + + _bindingTempAuthKeyId = effectiveTempAuthKey.authKeyId; + _bindingTempAuthKeyContext = nil; + _mtState |= MTProtoStateBindingTempAuthKey; + }]; +} + @end diff --git a/submodules/MtProtoKit/Sources/MTProtoEngine.m b/submodules/MtProtoKit/Sources/MTProtoEngine.m deleted file mode 100644 index f48cc993cd..0000000000 --- a/submodules/MtProtoKit/Sources/MTProtoEngine.m +++ /dev/null @@ -1,52 +0,0 @@ -#import - -#import "Utils/MTQueueLocalObject.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface MTProtoEngineImpl : NSObject { - MTQueue *_queue; - id _persistenceInterface; -} - -@end - -@implementation MTProtoEngineImpl - -- (instancetype)initWithQueue:(MTQueue *)queue persistenceInterface:(id)persistenceInterface { - self = [super init]; - if (self != nil) { - _queue = queue; - _persistenceInterface = persistenceInterface; - } - return self; -} - -@end - -@interface MTProtoEngine () { - MTQueue *_queue; - MTQueueLocalObject *_impl; -} - -@end - -@implementation MTProtoEngine - -- (instancetype)initWithPersistenceInterface:(id)persistenceInterface { - self = [super init]; - if (self != nil) { - _queue = [[MTQueue alloc] init]; - __auto_type queue = _queue; - _impl = [[MTQueueLocalObject alloc] initWithQueue:queue generator:^MTProtoEngineImpl *{ - return [[MTProtoEngineImpl alloc] initWithQueue:queue persistenceInterface:persistenceInterface]; - }]; - } - return self; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/MtProtoKit/Sources/MTProtoInstance.m b/submodules/MtProtoKit/Sources/MTProtoInstance.m deleted file mode 100644 index ba5da46abf..0000000000 --- a/submodules/MtProtoKit/Sources/MTProtoInstance.m +++ /dev/null @@ -1,48 +0,0 @@ -#import - -#import -#import "Utils/MTQueueLocalObject.h" - -@interface MTProtoInstanceImpl : NSObject { - MTQueue *_queue; - MTProtoEngine *_engine; -} - -@end - -@implementation MTProtoInstanceImpl - -- (instancetype)initWithQueue:(MTQueue *)queue engine:(MTProtoEngine *)engine { - self = [super init]; - if (self != nil) { - _queue = queue; - _engine = engine; - } - return self; -} - -@end - -@interface MTProtoInstance () { - MTQueue *_queue; - MTQueueLocalObject *_impl; -} - -@end - -@implementation MTProtoInstance - -- (instancetype)initWithEngine:(MTProtoEngine *)engine { - self = [super init]; - if (self != nil) { - _queue = [[MTQueue alloc] init]; - __auto_type queue = _queue; - _impl = [[MTQueueLocalObject alloc] initWithQueue:queue generator:^MTProtoInstanceImpl *{ - return [[MTProtoInstanceImpl alloc] initWithQueue:queue engine:engine]; - }]; - } - return self; -} - -@end diff --git a/submodules/MtProtoKit/Sources/MTRequestMessageService.m b/submodules/MtProtoKit/Sources/MTRequestMessageService.m index fb85f6ff47..23f12dc625 100644 --- a/submodules/MtProtoKit/Sources/MTRequestMessageService.m +++ b/submodules/MtProtoKit/Sources/MTRequestMessageService.m @@ -391,12 +391,12 @@ return currentData; } -- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo +- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto { NSMutableArray *messages = nil; NSMutableDictionary *requestInternalIdToMessageInternalId = nil; - bool requestsWillInitializeApi = _apiEnvironment != nil && ![_apiEnvironment.apiInitializationHash isEqualToString:[_context authInfoForDatacenterWithId:mtProto.datacenterId selector:authInfoSelector].authKeyAttributes[@"apiInitializationHash"]]; + bool requestsWillInitializeApi = _apiEnvironment != nil && ![_apiEnvironment.apiInitializationHash isEqualToString:[_context authInfoForDatacenterWithId:mtProto.datacenterId].authKeyAttributes[@"apiInitializationHash"]]; CFAbsoluteTime currentTime = MTAbsoluteSystemTime(); @@ -561,7 +561,7 @@ return nil; } -- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector +- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)message { if ([message.body isKindOfClass:[MTRpcResultMessage class]]) { @@ -610,13 +610,13 @@ { rpcError = [[MTRpcError alloc] initWithErrorCode:500 errorDescription:@"TL_PARSING_ERROR"]; [_context performBatchUpdates:^{ - MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId selector:authInfoSelector]; + MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId]; NSMutableDictionary *authKeyAttributes = [[NSMutableDictionary alloc] initWithDictionary:authInfo.authKeyAttributes]; authKeyAttributes[@"apiInitializationHash"] = @""; authInfo = [authInfo withUpdatedAuthKeyAttributes:authKeyAttributes]; - [_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo selector:authInfoSelector]; + [_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo]; }]; } } @@ -636,7 +636,7 @@ if (rpcResult != nil && request.requestContext.willInitializeApi) { - MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId selector:authInfoSelector]; + MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId]; if (![_apiEnvironment.apiInitializationHash isEqualToString:authInfo.authKeyAttributes[@"apiInitializationHash"]]) { @@ -644,7 +644,7 @@ authKeyAttributes[@"apiInitializationHash"] = _apiEnvironment.apiInitializationHash; authInfo = [authInfo withUpdatedAuthKeyAttributes:authKeyAttributes]; - [_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo selector:authInfoSelector]; + [_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo]; } } @@ -726,13 +726,13 @@ { [_context performBatchUpdates:^ { - MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId selector:authInfoSelector]; + MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId]; NSMutableDictionary *authKeyAttributes = [[NSMutableDictionary alloc] initWithDictionary:authInfo.authKeyAttributes]; [authKeyAttributes removeObjectForKey:@"apiInitializationHash"]; authInfo = [authInfo withUpdatedAuthKeyAttributes:authKeyAttributes]; - [_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo selector:authInfoSelector]; + [_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo]; }]; } else if (rpcError.errorCode == 406) { if (_didReceiveSoftAuthResetError) { diff --git a/submodules/MtProtoKit/Sources/MTResendMessageService.m b/submodules/MtProtoKit/Sources/MTResendMessageService.m index 585ec29599..4e515ba39f 100644 --- a/submodules/MtProtoKit/Sources/MTResendMessageService.m +++ b/submodules/MtProtoKit/Sources/MTResendMessageService.m @@ -41,7 +41,7 @@ [mtProto requestTransportTransaction]; } -- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo +- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto { if (_currentRequestMessageId == 0 || _currentRequestTransactionId == nil) { @@ -121,7 +121,7 @@ } } -- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector +- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message { if (message.messageId == _messageId) { diff --git a/submodules/MtProtoKit/Sources/MTTcpTransport.m b/submodules/MtProtoKit/Sources/MTTcpTransport.m index 3b0883b3aa..6b4cc265f1 100644 --- a/submodules/MtProtoKit/Sources/MTTcpTransport.m +++ b/submodules/MtProtoKit/Sources/MTTcpTransport.m @@ -747,7 +747,7 @@ static const NSTimeInterval MTTcpTransportSleepWatchdogTimeout = 60.0; }]; } -- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)incomingMessage authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector +- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)incomingMessage { if ([incomingMessage.body isKindOfClass:[MTPongMessage class]]) { diff --git a/submodules/MtProtoKit/Sources/MTTimeSyncMessageService.m b/submodules/MtProtoKit/Sources/MTTimeSyncMessageService.m index 8672ed541d..6d8919c176 100644 --- a/submodules/MtProtoKit/Sources/MTTimeSyncMessageService.m +++ b/submodules/MtProtoKit/Sources/MTTimeSyncMessageService.m @@ -45,7 +45,7 @@ [mtProto requestTransportTransaction]; } -- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo +- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto { if (_currentTransactionId == nil) { @@ -127,7 +127,7 @@ } } -- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector +- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message { if ([message.body isKindOfClass:[MTFutureSaltsMessage class]] && ((MTFutureSaltsMessage *)message.body).requestMessageId == _currentMessageId) { diff --git a/submodules/MtProtoKit/Sources/Utils/MTQueueLocalObject.h b/submodules/MtProtoKit/Sources/Utils/MTQueueLocalObject.h deleted file mode 100644 index 63165bd89f..0000000000 --- a/submodules/MtProtoKit/Sources/Utils/MTQueueLocalObject.h +++ /dev/null @@ -1,14 +0,0 @@ -#import - -@class MTQueue; - -NS_ASSUME_NONNULL_BEGIN - -@interface MTQueueLocalObject<__covariant T> : NSObject - -- (instancetype)initWithQueue:(MTQueue *)queue generator:(T(^)())generator; -- (void)with:(void (^)(T))f; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/MtProtoKit/Sources/Utils/MTQueueLocalObject.m b/submodules/MtProtoKit/Sources/Utils/MTQueueLocalObject.m deleted file mode 100644 index a84da57845..0000000000 --- a/submodules/MtProtoKit/Sources/Utils/MTQueueLocalObject.m +++ /dev/null @@ -1,56 +0,0 @@ -#import "MTQueueLocalObject.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface MTQueueLocalObjectHolder : NSObject - -@property (nonatomic, assign) CFTypeRef impl; - -@end - -@implementation MTQueueLocalObjectHolder - -@end - -@interface MTQueueLocalObject () { - MTQueue *_queue; - MTQueueLocalObjectHolder *_holder; -} - -@end - -@implementation MTQueueLocalObject - -- (instancetype)initWithQueue:(MTQueue *)queue generator:(id(^)())generator { - self = [super init]; - if (self != nil) { - _queue = queue; - _holder = [[MTQueueLocalObjectHolder alloc] init]; - __auto_type holder = _holder; - [queue dispatchOnQueue:^{ - id value = generator(); - holder.impl = CFBridgingRetain(value); - } synchronous:false]; - } - return self; -} - -- (void)dealloc { - __auto_type holder = _holder; - [_queue dispatchOnQueue:^{ - CFBridgingRelease(holder.impl); - } synchronous:false]; -} - -- (void)with:(void (^)(id))f { - __auto_type holder = _holder; - [_queue dispatchOnQueue:^{ - id value = (__bridge id)holder.impl; - f(value); - } synchronous:false]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift index 5874c4672c..d2544ac4d5 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift @@ -487,8 +487,6 @@ private func stringForRight(strings: PresentationStrings, right: TelegramChatAdm return strings.Channel_EditAdmin_PermissionPinMessages } else if right.contains(.canAddAdmins) { return strings.Channel_EditAdmin_PermissionAddAdmins - } else if right.contains(.canBeAnonymous) { - return strings.Channel_AdminLog_CanBeAnonymous } else { return "" } @@ -511,8 +509,6 @@ private func rightDependencies(_ right: TelegramChatAdminRightsFlags) -> [Telegr return [] } else if right.contains(.canAddAdmins) { return [] - } else if right.contains(.canBeAnonymous) { - return [] } else { return [] } @@ -611,43 +607,11 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s .canBanUsers, .canInviteUsers, .canPinMessages, - .canBeAnonymous, .canAddAdmins ] } if isCreator { - if isGroup { - entries.append(.rightsTitle(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionsHeader)) - - let accountUserRightsFlags: TelegramChatAdminRightsFlags - if channel.flags.contains(.isCreator) { - accountUserRightsFlags = maskRightsFlags - } else if let adminRights = channel.adminRights { - accountUserRightsFlags = maskRightsFlags.intersection(adminRights.flags) - } else { - accountUserRightsFlags = [] - } - - let currentRightsFlags: TelegramChatAdminRightsFlags - if let updatedFlags = state.updatedFlags { - currentRightsFlags = updatedFlags - } else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminRights, _, _) = initialParticipant, let adminRights = maybeAdminRights { - currentRightsFlags = adminRights.rights.flags - } else if let initialParticipant = initialParticipant, case let .creator(_, maybeAdminRights, _) = initialParticipant, let adminRights = maybeAdminRights { - currentRightsFlags = adminRights.rights.flags - } else { - currentRightsFlags = accountUserRightsFlags.subtracting(.canAddAdmins).subtracting(.canBeAnonymous) - } - - var index = 0 - for right in rightsOrder { - if accountUserRightsFlags.contains(right) { - entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, defaultBannedRights: channel.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), right == .canBeAnonymous)) - index += 1 - } - } - } } else { entries.append(.rightsTitle(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionsHeader)) @@ -667,7 +631,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s } else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminRights, _, _) = initialParticipant, let adminRights = maybeAdminRights { currentRightsFlags = adminRights.rights.flags } else { - currentRightsFlags = accountUserRightsFlags.subtracting(.canAddAdmins).subtracting(.canBeAnonymous) + currentRightsFlags = accountUserRightsFlags.subtracting(.canAddAdmins) } var index = 0 @@ -767,7 +731,6 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s .canBanUsers, .canInviteUsers, .canPinMessages, - .canBeAnonymous, .canAddAdmins ] diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift index 54e6664322..3a6adeff07 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift @@ -696,7 +696,7 @@ public func channelAdminsController(context: AccountContext, peerId initialPeerI if let peer = peerView.peers[participant.peerId] { switch participant { case .creator: - result.append(RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer)) + result.append(RenderedChannelParticipant(participant: .creator(id: peer.id, rank: nil), peer: peer)) case .admin: var peers: [PeerId: Peer] = [:] peers[creator.id] = creator diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift index 93e5dbdf56..eea39f5c4c 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift @@ -166,7 +166,7 @@ private func channelDiscussionGroupSetupControllerEntries(presentationData: Pres var entries: [ChannelDiscussionGroupSetupControllerEntry] = [] - if case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId { + if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId { if let group = view.peers[linkedDiscussionPeerId] { if case .group = peer.info { entries.append(.header(presentationData.theme, presentationData.strings, group.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), true, presentationData.strings.Channel_DiscussionGroup_HeaderLabel)) @@ -299,7 +299,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI return } - if case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, maybeLinkedDiscussionPeerId == groupId { + if groupId == cachedData.linkedDiscussionPeerId { navigateToGroupImpl?(groupId) return } @@ -483,7 +483,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI let applyPeerId: PeerId if case .broadcast = peer.info { applyPeerId = peerId - } else if case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId { + } else if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId { applyPeerId = linkedDiscussionPeerId } else { return diff --git a/submodules/PeerInfoUI/Sources/ChannelInfoController.swift b/submodules/PeerInfoUI/Sources/ChannelInfoController.swift index a8d2e5aa20..80d960096e 100644 --- a/submodules/PeerInfoUI/Sources/ChannelInfoController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelInfoController.swift @@ -497,7 +497,7 @@ private func channelInfoEntries(account: Account, presentationData: Presentation let discussionGroupTitle: String if let cachedData = view.cachedData as? CachedChannelData { - if case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] { + if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] { if let addressName = peer.addressName, !addressName.isEmpty { discussionGroupTitle = "@\(addressName)" } else { @@ -532,7 +532,7 @@ private func channelInfoEntries(account: Account, presentationData: Presentation if let _ = state.editingState, let adminRights = peer.adminRights, !adminRights.isEmpty { let discussionGroupTitle: String? if let cachedData = view.cachedData as? CachedChannelData { - if case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] { + if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] { if let addressName = peer.addressName, !addressName.isEmpty { discussionGroupTitle = "@\(addressName)" } else { @@ -951,7 +951,7 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi if canEditChannel { hasSomethingToEdit = true } else if let adminRights = peer.adminRights, !adminRights.isEmpty { - if let cachedData = view.cachedData as? CachedChannelData, case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let _ = maybeLinkedDiscussionPeerId { + if let cachedData = view.cachedData as? CachedChannelData, let _ = cachedData.linkedDiscussionPeerId { hasSomethingToEdit = true } } diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift index 2e451d1400..e2f39cd063 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift @@ -867,7 +867,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon let renderedParticipant: RenderedChannelParticipant switch participant { case .creator: - renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer) + renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, rank: nil), peer: peer) case .admin: var peers: [PeerId: Peer] = [:] if let creator = creatorPeer { diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift index 9cf4386f48..874ede1852 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift @@ -216,7 +216,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { let renderedParticipant: RenderedChannelParticipant switch participant { case .creator: - renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer) + renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, rank: nil), peer: peer) case .admin: var peers: [PeerId: Peer] = [:] peers[creator.id] = creator diff --git a/submodules/PeerInfoUI/Sources/GroupInfoController.swift b/submodules/PeerInfoUI/Sources/GroupInfoController.swift index 14daf3c79a..a9b1029dfd 100644 --- a/submodules/PeerInfoUI/Sources/GroupInfoController.swift +++ b/submodules/PeerInfoUI/Sources/GroupInfoController.swift @@ -896,7 +896,7 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa } else { if cachedChannelData.flags.contains(.canChangeUsername) { entries.append(GroupInfoEntry.groupTypeSetup(presentationData.theme, presentationData.strings.GroupInfo_GroupType, isPublic ? presentationData.strings.Channel_Setup_TypePublic : presentationData.strings.Channel_Setup_TypePrivate)) - if case let .known(maybeLinkedDiscussionPeerId) = cachedChannelData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] { + if let linkedDiscussionPeerId = cachedChannelData.linkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] { let peerTitle: String if let addressName = peer.addressName, !addressName.isEmpty { peerTitle = "@\(addressName)" @@ -1069,7 +1069,7 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa let participant: ChannelParticipant switch sortedParticipants[i] { case .creator: - participant = .creator(id: sortedParticipants[i].peerId, adminInfo: nil, rank: nil) + participant = .creator(id: sortedParticipants[i].peerId, rank: nil) memberStatus = .owner(rank: nil) case .admin: participant = .member(id: sortedParticipants[i].peerId, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(flags: .groupSpecific), promotedBy: account.peerId, canBeEditedByAccountPeer: true), banInfo: nil, rank: nil) @@ -1201,7 +1201,7 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa let participant = participants[i] let memberStatus: GroupInfoMemberStatus switch participant.participant { - case let .creator(_, _, rank): + case let .creator(_, rank): memberStatus = .owner(rank: rank) case let .member(_, _, adminInfo, _, rank): if adminInfo != nil { diff --git a/submodules/Postbox/Sources/AdditionalMessageHistoryViewData.swift b/submodules/Postbox/Sources/AdditionalMessageHistoryViewData.swift index 50f874f213..abf31af054 100644 --- a/submodules/Postbox/Sources/AdditionalMessageHistoryViewData.swift +++ b/submodules/Postbox/Sources/AdditionalMessageHistoryViewData.swift @@ -10,7 +10,6 @@ public enum AdditionalMessageHistoryViewData { case preferencesEntry(ValueBoxKey) case peer(PeerId) case peerIsContact(PeerId) - case message(MessageId) } public enum AdditionalMessageHistoryViewDataEntry { @@ -23,5 +22,4 @@ public enum AdditionalMessageHistoryViewDataEntry { case preferencesEntry(ValueBoxKey, PreferencesEntry?) case peerIsContact(PeerId, Bool) case peer(PeerId, Peer?) - case message(MessageId, Message?) } diff --git a/submodules/Postbox/Sources/ChatListViewState.swift b/submodules/Postbox/Sources/ChatListViewState.swift index a3fc67730f..c535fa6317 100644 --- a/submodules/Postbox/Sources/ChatListViewState.swift +++ b/submodules/Postbox/Sources/ChatListViewState.swift @@ -96,7 +96,7 @@ private func updateMessagePeers(_ message: Message, updatedPeers: [PeerId: Peer] peers[peerId] = currentPeer } } - return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) + return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) } return nil } diff --git a/submodules/Postbox/Sources/ChatLocation.swift b/submodules/Postbox/Sources/ChatLocation.swift index bdf23981a7..aa10414475 100644 --- a/submodules/Postbox/Sources/ChatLocation.swift +++ b/submodules/Postbox/Sources/ChatLocation.swift @@ -1,12 +1,6 @@ import Foundation -import SwiftSignalKit -public enum ChatLocationInput { +public enum ChatLocation: Equatable { case peer(PeerId) - case external(PeerId, Signal) -} - -enum ResolvedChatLocationInput { - case peer(PeerId) - case external(MessageHistoryViewExternalInput) + //case group(PeerGroupId) } diff --git a/submodules/Postbox/Sources/GlobalMessageTagsView.swift b/submodules/Postbox/Sources/GlobalMessageTagsView.swift index 51bd14f19a..c52fa1f315 100644 --- a/submodules/Postbox/Sources/GlobalMessageTagsView.swift +++ b/submodules/Postbox/Sources/GlobalMessageTagsView.swift @@ -163,11 +163,11 @@ final class MutableGlobalMessageTagsView: MutablePostboxView { hasChanges = true } case let .intermediateMessage(message): - if self.add(.intermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: updatedTimestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: message.embeddedMediaData, referencedMedia: message.referencedMedia))) { + if self.add(.intermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: updatedTimestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: message.embeddedMediaData, referencedMedia: message.referencedMedia))) { hasChanges = true } case let .message(message): - if self.add(.message(Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: updatedTimestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds))) { + if self.add(.message(Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: updatedTimestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds))) { hasChanges = true } } diff --git a/submodules/Postbox/Sources/IntermediateMessage.swift b/submodules/Postbox/Sources/IntermediateMessage.swift index 65d29e403c..6835a546ec 100644 --- a/submodules/Postbox/Sources/IntermediateMessage.swift +++ b/submodules/Postbox/Sources/IntermediateMessage.swift @@ -34,7 +34,6 @@ class IntermediateMessage { let globallyUniqueId: Int64? let groupingKey: Int64? let groupInfo: MessageGroupInfo? - let threadId: Int64? let timestamp: Int32 let flags: MessageFlags let tags: MessageTags @@ -51,14 +50,13 @@ class IntermediateMessage { return MessageIndex(id: self.id, timestamp: self.timestamp) } - init(stableId: UInt32, stableVersion: UInt32, id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, groupInfo: MessageGroupInfo?, threadId: Int64?, timestamp: Int32, flags: MessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: IntermediateMessageForwardInfo?, authorId: PeerId?, text: String, attributesData: ReadBuffer, embeddedMediaData: ReadBuffer, referencedMedia: [MediaId]) { + init(stableId: UInt32, stableVersion: UInt32, id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, groupInfo: MessageGroupInfo?, timestamp: Int32, flags: MessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: IntermediateMessageForwardInfo?, authorId: PeerId?, text: String, attributesData: ReadBuffer, embeddedMediaData: ReadBuffer, referencedMedia: [MediaId]) { self.stableId = stableId self.stableVersion = stableVersion self.id = id self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey self.groupInfo = groupInfo - self.threadId = threadId self.timestamp = timestamp self.flags = flags self.tags = tags @@ -73,18 +71,18 @@ class IntermediateMessage { } func withUpdatedTimestamp(_ timestamp: Int32) -> IntermediateMessage { - return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) + return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) } func withUpdatedGroupingKey(_ groupingKey: Int64?) -> IntermediateMessage { - return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) + return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) } func withUpdatedGroupInfo(_ groupInfo: MessageGroupInfo?) -> IntermediateMessage { - return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) + return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) } func withUpdatedEmbeddedMedia(_ embeddedMedia: ReadBuffer) -> IntermediateMessage { - return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: embeddedMedia, referencedMedia: self.referencedMedia) + return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: embeddedMedia, referencedMedia: self.referencedMedia) } } diff --git a/submodules/Postbox/Sources/Message.swift b/submodules/Postbox/Sources/Message.swift index 6d476aa661..ebcce69cd8 100644 --- a/submodules/Postbox/Sources/Message.swift +++ b/submodules/Postbox/Sources/Message.swift @@ -107,6 +107,10 @@ public struct MessageIndex: Comparable, Hashable { return MessageIndex(id: MessageId(peerId: self.id.peerId, namespace: self.id.namespace, id: self.id.id == Int32.max ? self.id.id : (self.id.id + 1)), timestamp: self.timestamp) } + public var hashValue: Int { + return self.id.hashValue + } + public static func absoluteUpperBound() -> MessageIndex { return MessageIndex(id: MessageId(peerId: PeerId(namespace: Int32(Int8.max), id: Int32.max), namespace: Int32(Int8.max), id: Int32.max), timestamp: Int32.max) } @@ -488,7 +492,6 @@ public final class Message { public let globallyUniqueId: Int64? public let groupingKey: Int64? public let groupInfo: MessageGroupInfo? - public let threadId: Int64? public let timestamp: Int32 public let flags: MessageFlags public let tags: MessageTags @@ -507,14 +510,13 @@ public final class Message { return MessageIndex(id: self.id, timestamp: self.timestamp) } - public init(stableId: UInt32, stableVersion: UInt32, id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, groupInfo: MessageGroupInfo?, threadId: Int64?, timestamp: Int32, flags: MessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: MessageForwardInfo?, author: Peer?, text: String, attributes: [MessageAttribute], media: [Media], peers: SimpleDictionary, associatedMessages: SimpleDictionary, associatedMessageIds: [MessageId]) { + public init(stableId: UInt32, stableVersion: UInt32, id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, groupInfo: MessageGroupInfo?, timestamp: Int32, flags: MessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: MessageForwardInfo?, author: Peer?, text: String, attributes: [MessageAttribute], media: [Media], peers: SimpleDictionary, associatedMessages: SimpleDictionary, associatedMessageIds: [MessageId]) { self.stableId = stableId self.stableVersion = stableVersion self.id = id self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey self.groupInfo = groupInfo - self.threadId = threadId self.timestamp = timestamp self.flags = flags self.tags = tags @@ -531,23 +533,23 @@ public final class Message { } public func withUpdatedMedia(_ media: [Media]) -> Message { - return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) + return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) } public func withUpdatedPeers(_ peers: SimpleDictionary) -> Message { - return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) + return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) } public func withUpdatedFlags(_ flags: MessageFlags) -> Message { - return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) + return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) } func withUpdatedGroupInfo(_ groupInfo: MessageGroupInfo?) -> Message { - return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) + return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) } func withUpdatedAssociatedMessages(_ associatedMessages: SimpleDictionary) -> Message { - return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: associatedMessages, associatedMessageIds: self.associatedMessageIds) + return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: associatedMessages, associatedMessageIds: self.associatedMessageIds) } } @@ -635,22 +637,11 @@ public enum StoreMessageId { } } -public func makeMessageThreadId(_ messageId: MessageId) -> Int64 { - return (Int64(messageId.namespace) << 32) | Int64(bitPattern: UInt64(UInt32(bitPattern: messageId.id))) -} - -public func makeThreadIdMessageId(peerId: PeerId, threadId: Int64) -> MessageId { - let namespace = Int32((threadId >> 32) & 0x7fffffff) - let id = Int32(bitPattern: UInt32(threadId & 0xffffffff)) - return MessageId(peerId: peerId, namespace: namespace, id: id) -} - public final class StoreMessage { public let id: StoreMessageId public let timestamp: Int32 public let globallyUniqueId: Int64? public let groupingKey: Int64? - public let threadId: Int64? public let flags: StoreMessageFlags public let tags: MessageTags public let globalTags: GlobalMessageTags @@ -661,11 +652,10 @@ public final class StoreMessage { public let attributes: [MessageAttribute] public let media: [Media] - public init(id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, threadId: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { + public init(id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { self.id = .Id(id) self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey - self.threadId = threadId self.timestamp = timestamp self.flags = flags self.tags = tags @@ -678,12 +668,11 @@ public final class StoreMessage { self.media = media } - public init(peerId: PeerId, namespace: MessageId.Namespace, globallyUniqueId: Int64?, groupingKey: Int64?, threadId: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { + public init(peerId: PeerId, namespace: MessageId.Namespace, globallyUniqueId: Int64?, groupingKey: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { self.id = .Partial(peerId, namespace) self.timestamp = timestamp self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey - self.threadId = threadId self.flags = flags self.tags = tags self.globalTags = globalTags @@ -695,12 +684,11 @@ public final class StoreMessage { self.media = media } - public init(id: StoreMessageId, globallyUniqueId: Int64?, groupingKey: Int64?, threadId: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { + public init(id: StoreMessageId, globallyUniqueId: Int64?, groupingKey: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { self.id = id self.timestamp = timestamp self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey - self.threadId = threadId self.flags = flags self.tags = tags self.globalTags = globalTags @@ -724,19 +712,19 @@ public final class StoreMessage { if flags == self.flags { return self } else { - return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, threadId: self.threadId, timestamp: self.timestamp, flags: flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media) + return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media) } } public func withUpdatedAttributes(_ attributes: [MessageAttribute]) -> StoreMessage { - return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media) + return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media) } public func withUpdatedLocalTags(_ localTags: LocalMessageTags) -> StoreMessage { if localTags == self.localTags { return self } else { - return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: self.attributes, media: self.media) + return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: self.attributes, media: self.media) } } } @@ -746,7 +734,6 @@ final class InternalStoreMessage { let timestamp: Int32 let globallyUniqueId: Int64? let groupingKey: Int64? - let threadId: Int64? let flags: StoreMessageFlags let tags: MessageTags let globalTags: GlobalMessageTags @@ -761,12 +748,11 @@ final class InternalStoreMessage { return MessageIndex(id: self.id, timestamp: self.timestamp) } - init(id: MessageId, timestamp: Int32, globallyUniqueId: Int64?, groupingKey: Int64?, threadId: Int64?, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { + init(id: MessageId, timestamp: Int32, globallyUniqueId: Int64?, groupingKey: Int64?, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { self.id = id self.timestamp = timestamp self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey - self.threadId = threadId self.flags = flags self.tags = tags self.globalTags = globalTags diff --git a/submodules/Postbox/Sources/MessageHistoryHolesView.swift b/submodules/Postbox/Sources/MessageHistoryHolesView.swift index 503580e653..14c65efea0 100644 --- a/submodules/Postbox/Sources/MessageHistoryHolesView.swift +++ b/submodules/Postbox/Sources/MessageHistoryHolesView.swift @@ -37,40 +37,3 @@ public final class MessageHistoryHolesView { self.entries = mutableView.entries } } - -public struct MessageHistoryExternalHolesViewEntry: Equatable, Hashable { - public let hole: MessageHistoryViewHole - public let direction: MessageHistoryViewRelativeHoleDirection - public let count: Int - - public init(hole: MessageHistoryViewHole, direction: MessageHistoryViewRelativeHoleDirection, count: Int) { - self.hole = hole - self.direction = direction - self.count = count - } -} - -final class MutableMessageHistoryExternalHolesView { - fileprivate var entries = Set() - - init() { - } - - func update(_ holes: Set) -> Bool { - if self.entries != holes { - self.entries = holes - return true - } else { - return false - } - } -} - -public final class MessageHistoryExternalHolesView { - public let entries: Set - - init(_ mutableView: MutableMessageHistoryExternalHolesView) { - self.entries = mutableView.entries - } -} - diff --git a/submodules/Postbox/Sources/MessageHistoryTable.swift b/submodules/Postbox/Sources/MessageHistoryTable.swift index bd0c1988b1..4bf428c00a 100644 --- a/submodules/Postbox/Sources/MessageHistoryTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTable.swift @@ -50,7 +50,6 @@ private struct MessageDataFlags: OptionSet { static let hasGroupingKey = MessageDataFlags(rawValue: 1 << 2) static let hasGroupInfo = MessageDataFlags(rawValue: 1 << 3) static let hasLocalTags = MessageDataFlags(rawValue: 1 << 4) - static let hasThreadId = MessageDataFlags(rawValue: 1 << 5) } private func extractKey(_ key: ValueBoxKey) -> MessageIndex { @@ -72,7 +71,6 @@ final class MessageHistoryTable: Table { let unsentTable: MessageHistoryUnsentTable let failedTable: MessageHistoryFailedTable let tagsTable: MessageHistoryTagsTable - let threadsTable: MessageHistoryThreadsTable let globalTagsTable: GlobalMessageHistoryTagsTable let localTagsTable: LocalMessageHistoryTagsTable let readStateTable: MessageHistoryReadStateTable @@ -81,7 +79,7 @@ final class MessageHistoryTable: Table { let summaryTable: MessageHistoryTagsSummaryTable let pendingActionsTable: PendingMessageActionsTable - init(valueBox: ValueBox, table: ValueBoxTable, seedConfiguration: SeedConfiguration, messageHistoryIndexTable: MessageHistoryIndexTable, messageHistoryHoleIndexTable: MessageHistoryHoleIndexTable, messageMediaTable: MessageMediaTable, historyMetadataTable: MessageHistoryMetadataTable, globallyUniqueMessageIdsTable: MessageGloballyUniqueIdTable, unsentTable: MessageHistoryUnsentTable, failedTable: MessageHistoryFailedTable, tagsTable: MessageHistoryTagsTable, threadsTable: MessageHistoryThreadsTable, globalTagsTable: GlobalMessageHistoryTagsTable, localTagsTable: LocalMessageHistoryTagsTable, readStateTable: MessageHistoryReadStateTable, synchronizeReadStateTable: MessageHistorySynchronizeReadStateTable, textIndexTable: MessageHistoryTextIndexTable, summaryTable: MessageHistoryTagsSummaryTable, pendingActionsTable: PendingMessageActionsTable) { + init(valueBox: ValueBox, table: ValueBoxTable, seedConfiguration: SeedConfiguration, messageHistoryIndexTable: MessageHistoryIndexTable, messageHistoryHoleIndexTable: MessageHistoryHoleIndexTable, messageMediaTable: MessageMediaTable, historyMetadataTable: MessageHistoryMetadataTable, globallyUniqueMessageIdsTable: MessageGloballyUniqueIdTable, unsentTable: MessageHistoryUnsentTable, failedTable: MessageHistoryFailedTable, tagsTable: MessageHistoryTagsTable, globalTagsTable: GlobalMessageHistoryTagsTable, localTagsTable: LocalMessageHistoryTagsTable, readStateTable: MessageHistoryReadStateTable, synchronizeReadStateTable: MessageHistorySynchronizeReadStateTable, textIndexTable: MessageHistoryTextIndexTable, summaryTable: MessageHistoryTagsSummaryTable, pendingActionsTable: PendingMessageActionsTable) { self.seedConfiguration = seedConfiguration self.messageHistoryIndexTable = messageHistoryIndexTable self.messageHistoryHoleIndexTable = messageHistoryHoleIndexTable @@ -91,7 +89,6 @@ final class MessageHistoryTable: Table { self.unsentTable = unsentTable self.failedTable = failedTable self.tagsTable = tagsTable - self.threadsTable = threadsTable self.globalTagsTable = globalTagsTable self.localTagsTable = localTagsTable self.readStateTable = readStateTable @@ -271,9 +268,6 @@ final class MessageHistoryTable: Table { } } } - if let threadId = message.threadId { - self.threadsTable.add(threadId: threadId, index: message.index) - } let globalTags = message.globalTags.rawValue if globalTags != 0 { for i in 0 ..< 32 { @@ -380,10 +374,10 @@ final class MessageHistoryTable: Table { for message in messages { switch message.id { case let .Id(id): - internalStoreMessages.append(InternalStoreMessage(id: id, timestamp: message.timestamp, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributes: message.attributes, media: message.media)) + internalStoreMessages.append(InternalStoreMessage(id: id, timestamp: message.timestamp, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributes: message.attributes, media: message.media)) case let .Partial(peerId, namespace): let id = self.historyMetadataTable.getNextMessageIdAndIncrement(peerId, namespace: namespace) - internalStoreMessages.append(InternalStoreMessage(id: id, timestamp: message.timestamp, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributes: message.attributes, media: message.media)) + internalStoreMessages.append(InternalStoreMessage(id: id, timestamp: message.timestamp, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributes: message.attributes, media: message.media)) } } return internalStoreMessages @@ -985,9 +979,6 @@ final class MessageHistoryTable: Table { if !message.localTags.isEmpty { dataFlags.insert(.hasLocalTags) } - if message.threadId != nil { - dataFlags.insert(.hasThreadId) - } sharedBuffer.write(&dataFlags, offset: 0, length: 1) if let globallyUniqueId = message.globallyUniqueId { var globallyUniqueIdValue = globallyUniqueId @@ -1010,9 +1001,6 @@ final class MessageHistoryTable: Table { var localTagsValue: UInt32 = message.localTags.rawValue sharedBuffer.write(&localTagsValue, offset: 0, length: 4) } - if var threadId = message.threadId { - sharedBuffer.write(&threadId, length: 8) - } if self.seedConfiguration.peerNamespacesRequiringMessageTextIndex.contains(message.id.peerId.namespace) { var indexableText = message.text @@ -1172,7 +1160,7 @@ final class MessageHistoryTable: Table { self.valueBox.set(self.table, key: self.key(message.index, key: sharedKey), value: sharedBuffer) - let result = (IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: intermediateForwardInfo, authorId: message.authorId, text: message.text, attributesData: attributesBuffer.makeReadBufferAndReset(), embeddedMediaData: embeddedMediaBuffer.makeReadBufferAndReset(), referencedMedia: referencedMedia), updatedGroupInfos) + let result = (IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: groupInfo, timestamp: message.timestamp, flags: flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: intermediateForwardInfo, authorId: message.authorId, text: message.text, attributesData: attributesBuffer.makeReadBufferAndReset(), embeddedMediaData: embeddedMediaBuffer.makeReadBufferAndReset(), referencedMedia: referencedMedia), updatedGroupInfos) return result } @@ -1249,9 +1237,6 @@ final class MessageHistoryTable: Table { for tag in message.tags { self.tagsTable.remove(tags: tag, index: index, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) } - if let threadId = message.threadId { - self.threadsTable.remove(threadId: threadId, index: index) - } for tag in message.globalTags { self.globalTagsTable.remove(tag, index: index) } @@ -1346,7 +1331,7 @@ final class MessageHistoryTable: Table { } withExtendedLifetime(updatedEmbeddedMediaBuffer, { - self.storeIntermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: updatedEmbeddedMediaBuffer.readBufferNoCopy(), referencedMedia: message.referencedMedia), sharedKey: self.key(index)) + self.storeIntermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: updatedEmbeddedMediaBuffer.readBufferNoCopy(), referencedMedia: message.referencedMedia), sharedKey: self.key(index)) }) let operation: MessageHistoryOperation = .UpdateEmbeddedMedia(index, updatedEmbeddedMediaBuffer.makeReadBufferAndReset()) @@ -1434,14 +1419,6 @@ final class MessageHistoryTable: Table { self.tagsTable.add(tags: message.tags, index: message.index, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) } } - if previousMessage.threadId != message.threadId || index != message.index { - if let threadId = previousMessage.threadId { - self.threadsTable.remove(threadId: threadId, index: index) - } - if let threadId = message.threadId { - self.threadsTable.add(threadId: threadId, index: message.index) - } - } if !previousMessage.globalTags.isEmpty || !message.globalTags.isEmpty { if !previousMessage.globalTags.isEmpty { @@ -1565,9 +1542,6 @@ final class MessageHistoryTable: Table { if !updatedLocalTags.isEmpty { dataFlags.insert(.hasLocalTags) } - if message.threadId != nil { - dataFlags.insert(.hasThreadId) - } sharedBuffer.write(&dataFlags, offset: 0, length: 1) if let globallyUniqueId = message.globallyUniqueId { var globallyUniqueIdValue = globallyUniqueId @@ -1590,9 +1564,6 @@ final class MessageHistoryTable: Table { var localTagsValue: UInt32 = updatedLocalTags.rawValue sharedBuffer.write(&localTagsValue, offset: 0, length: 4) } - if var threadId = message.threadId { - sharedBuffer.write(&threadId, length: 8) - } var flags = MessageFlags(message.flags) sharedBuffer.write(&flags.rawValue, offset: 0, length: 4) @@ -1737,7 +1708,7 @@ final class MessageHistoryTable: Table { self.valueBox.set(self.table, key: self.key(message.index, key: sharedKey), value: sharedBuffer) - let result = (IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: flags, tags: tags, globalTags: message.globalTags, localTags: updatedLocalTags, forwardInfo: intermediateForwardInfo, authorId: message.authorId, text: message.text, attributesData: attributesBuffer.makeReadBufferAndReset(), embeddedMediaData: embeddedMediaBuffer.makeReadBufferAndReset(), referencedMedia: referencedMedia), previousMessage.tags) + let result = (IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: groupInfo, timestamp: message.timestamp, flags: flags, tags: tags, globalTags: message.globalTags, localTags: updatedLocalTags, forwardInfo: intermediateForwardInfo, authorId: message.authorId, text: message.text, attributesData: attributesBuffer.makeReadBufferAndReset(), embeddedMediaData: embeddedMediaBuffer.makeReadBufferAndReset(), referencedMedia: referencedMedia), previousMessage.tags) for media in mediaToUpdate { if let id = media.id { @@ -1815,7 +1786,7 @@ final class MessageHistoryTable: Table { let updatedIndex = MessageIndex(id: index.id, timestamp: timestamp) - let _ = self.justUpdate(index, message: InternalStoreMessage(id: previousMessage.id, timestamp: timestamp, globallyUniqueId: previousMessage.globallyUniqueId, groupingKey: previousMessage.groupingKey, threadId: previousMessage.threadId, flags: StoreMessageFlags(previousMessage.flags), tags: previousMessage.tags, globalTags: previousMessage.globalTags, localTags: previousMessage.localTags, forwardInfo: storeForwardInfo, authorId: previousMessage.authorId, text: previousMessage.text, attributes: parsedAttributes, media: parsedMedia), keepLocalTags: false, sharedKey: self.key(updatedIndex), sharedBuffer: WriteBuffer(), sharedEncoder: PostboxEncoder(), unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, updatedMedia: &updatedMedia) + let _ = self.justUpdate(index, message: InternalStoreMessage(id: previousMessage.id, timestamp: timestamp, globallyUniqueId: previousMessage.globallyUniqueId, groupingKey: previousMessage.groupingKey, flags: StoreMessageFlags(previousMessage.flags), tags: previousMessage.tags, globalTags: previousMessage.globalTags, localTags: previousMessage.localTags, forwardInfo: storeForwardInfo, authorId: previousMessage.authorId, text: previousMessage.text, attributes: parsedAttributes, media: parsedMedia), keepLocalTags: false, sharedKey: self.key(updatedIndex), sharedBuffer: WriteBuffer(), sharedEncoder: PostboxEncoder(), unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, updatedMedia: &updatedMedia) return (previousMessage.tags, previousMessage.globalTags) } else { return nil @@ -1855,7 +1826,7 @@ final class MessageHistoryTable: Table { var updatedReferencedMedia = message.referencedMedia updatedReferencedMedia.append(extractedMedia.id!) withExtendedLifetime(updatedEmbeddedMediaBuffer, { - self.storeIntermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: updatedEmbeddedMediaBuffer.readBufferNoCopy(), referencedMedia: updatedReferencedMedia), sharedKey: self.key(index)) + self.storeIntermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: updatedEmbeddedMediaBuffer.readBufferNoCopy(), referencedMedia: updatedReferencedMedia), sharedKey: self.key(index)) }) return extractedMedia @@ -1892,9 +1863,6 @@ final class MessageHistoryTable: Table { if !message.localTags.isEmpty { dataFlags.insert(.hasLocalTags) } - if message.threadId != nil { - dataFlags.insert(.hasThreadId) - } sharedBuffer.write(&dataFlags, offset: 0, length: 1) if let globallyUniqueId = message.globallyUniqueId { var globallyUniqueIdValue = globallyUniqueId @@ -1917,9 +1885,6 @@ final class MessageHistoryTable: Table { var localTagsValue: UInt32 = message.localTags.rawValue sharedBuffer.write(&localTagsValue, offset: 0, length: 4) } - if var threadId = message.threadId { - sharedBuffer.write(&threadId, length: 8) - } var flagsValue: UInt32 = message.flags.rawValue sharedBuffer.write(&flagsValue, offset: 0, length: 4) @@ -2122,13 +2087,6 @@ final class MessageHistoryTable: Table { localTags = LocalMessageTags(rawValue: localTagsValue) } - var threadId: Int64? - if dataFlags.contains(.hasThreadId) { - var threadIdValue: Int64 = 0 - value.read(&threadIdValue, offset: 0, length: 8) - threadId = threadIdValue - } - var flagsValue: UInt32 = 0 value.read(&flagsValue, offset: 0, length: 4) let flags = MessageFlags(rawValue: flagsValue) @@ -2235,7 +2193,7 @@ final class MessageHistoryTable: Table { referencedMediaIds.append(MediaId(namespace: idNamespace, id: idId)) } - return IntermediateMessageHistoryEntry(message: IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: index.id, globallyUniqueId: globallyUniqueId, groupingKey: groupingKey, groupInfo: groupInfo, threadId: threadId, timestamp: index.timestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: forwardInfo, authorId: authorId, text: text, attributesData: attributesData, embeddedMediaData: embeddedMediaData, referencedMedia: referencedMediaIds)) + return IntermediateMessageHistoryEntry(message: IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: index.id, globallyUniqueId: globallyUniqueId, groupingKey: groupingKey, groupInfo: groupInfo, timestamp: index.timestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: forwardInfo, authorId: authorId, text: text, attributesData: attributesData, embeddedMediaData: embeddedMediaData, referencedMedia: referencedMediaIds)) } else { preconditionFailure() } @@ -2384,7 +2342,7 @@ final class MessageHistoryTable: Table { } } - return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: parsedAttributes, media: parsedMedia, peers: peers, associatedMessages: associatedMessages, associatedMessageIds: associatedMessageIds) + return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: parsedAttributes, media: parsedMedia, peers: peers, associatedMessages: associatedMessages, associatedMessageIds: associatedMessageIds) } func renderMessagePeers(_ message: Message, peerTable: PeerTable) -> Message { @@ -2719,57 +2677,11 @@ final class MessageHistoryTable: Table { return (result, mediaRefs, count == 0 ? nil : lastIndex) } - func fetch(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags?, threadId: Int64?, from fromIndex: MessageIndex, includeFrom: Bool, to toIndex: MessageIndex, limit: Int) -> [IntermediateMessage] { + func fetch(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags?, from fromIndex: MessageIndex, includeFrom: Bool, to toIndex: MessageIndex, limit: Int) -> [IntermediateMessage] { precondition(fromIndex.id.peerId == toIndex.id.peerId) precondition(fromIndex.id.namespace == toIndex.id.namespace) var result: [IntermediateMessage] = [] - if let threadId = threadId { - var indices: [MessageIndex] = [] - var startIndex = fromIndex - var localIncludeFrom = includeFrom - while true { - let sliceIndices: [MessageIndex] - if fromIndex < toIndex { - sliceIndices = self.threadsTable.laterIndices(threadId: threadId, peerId: peerId, namespace: namespace, index: startIndex, includeFrom: localIncludeFrom, count: limit) - } else { - sliceIndices = self.threadsTable.earlierIndices(threadId: threadId, peerId: peerId, namespace: namespace, index: startIndex, includeFrom: localIncludeFrom, count: limit) - } - if sliceIndices.isEmpty { - break - } - startIndex = sliceIndices[sliceIndices.count - 1] - localIncludeFrom = false - - for index in sliceIndices { - if let tag = tag { - if self.tagsTable.entryExists(tag: tag, index: index) { - indices.append(index) - } - } else { - indices.append(index) - } - } - if indices.count >= limit { - break - } - } - for index in indices { - if fromIndex < toIndex { - if index < fromIndex || index > toIndex { - continue - } - } else { - if index < toIndex || index > fromIndex { - continue - } - } - if let message = self.getMessage(index) { - result.append(message) - } else { - assertionFailure() - } - } - } else if let tag = tag { + if let tag = tag { let indices: [MessageIndex] if fromIndex < toIndex { indices = self.tagsTable.laterIndices(tag: tag, peerId: peerId, namespace: namespace, index: fromIndex, includeFrom: includeFrom, count: limit) diff --git a/submodules/Postbox/Sources/MessageHistoryTagsTable.swift b/submodules/Postbox/Sources/MessageHistoryTagsTable.swift index a8102d32de..2af930855d 100644 --- a/submodules/Postbox/Sources/MessageHistoryTagsTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTagsTable.swift @@ -61,10 +61,6 @@ class MessageHistoryTagsTable: Table { } } - func entryExists(tag: MessageTags, index: MessageIndex) -> Bool { - return self.valueBox.exists(self.table, key: self.key(tag: tag, index: index, key: self.sharedKey)) - } - func entryLocation(at index: MessageIndex, tag: MessageTags) -> MessageHistoryEntryLocation? { if let _ = self.valueBox.get(self.table, key: self.key(tag: tag, index: index)) { var greaterCount = 0 diff --git a/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift b/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift deleted file mode 100644 index 776893cdcd..0000000000 --- a/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift +++ /dev/null @@ -1,98 +0,0 @@ -import Foundation - -private func extractKey(_ key: ValueBoxKey) -> MessageIndex { - return MessageIndex(id: MessageId(peerId: PeerId(key.getInt64(0)), namespace: key.getInt32(8 + 8), id: key.getInt32(8 + 8 + 4 + 4)), timestamp: key.getInt32(8 + 8 + 4)) -} - -class MessageHistoryThreadsTable: Table { - static func tableSpec(_ id: Int32) -> ValueBoxTable { - return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: true) - } - - private let sharedKey = ValueBoxKey(length: 8 + 8 + 4 + 4 + 4) - - override init(valueBox: ValueBox, table: ValueBoxTable) { - super.init(valueBox: valueBox, table: table) - } - - private func key(threadId: Int64, index: MessageIndex, key: ValueBoxKey = ValueBoxKey(length: 8 + 8 + 4 + 4 + 4)) -> ValueBoxKey { - key.setInt64(0, value: index.id.peerId.toInt64()) - key.setInt64(8, value: threadId) - key.setInt32(8 + 8, value: index.id.namespace) - key.setInt32(8 + 8 + 4, value: index.timestamp) - key.setInt32(8 + 8 + 4 + 4, value: index.id.id) - return key - } - - private func lowerBound(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace) -> ValueBoxKey { - let key = ValueBoxKey(length: 8 + 8 + 4) - key.setInt64(0, value: peerId.toInt64()) - key.setInt64(8, value: threadId) - key.setInt32(8 + 8, value: namespace) - return key - } - - private func upperBound(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace) -> ValueBoxKey { - return self.lowerBound(threadId: threadId, peerId: peerId, namespace: namespace).successor - } - - func add(threadId: Int64, index: MessageIndex) { - self.valueBox.set(self.table, key: self.key(threadId: threadId, index: index, key: self.sharedKey), value: MemoryBuffer()) - } - - func remove(threadId: Int64, index: MessageIndex) { - self.valueBox.remove(self.table, key: self.key(threadId: threadId, index: index, key: self.sharedKey), secure: false) - } - - func earlierIndices(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace, index: MessageIndex?, includeFrom: Bool, count: Int) -> [MessageIndex] { - var indices: [MessageIndex] = [] - let key: ValueBoxKey - if let index = index { - if includeFrom { - key = self.key(threadId: threadId, index: index).successor - } else { - key = self.key(threadId: threadId, index: index) - } - } else { - key = self.upperBound(threadId: threadId, peerId: peerId, namespace: namespace) - } - self.valueBox.range(self.table, start: key, end: self.lowerBound(threadId: threadId, peerId: peerId, namespace: namespace), keys: { key in - indices.append(extractKey(key)) - return true - }, limit: count) - return indices - } - - func laterIndices(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace, index: MessageIndex?, includeFrom: Bool, count: Int) -> [MessageIndex] { - var indices: [MessageIndex] = [] - let key: ValueBoxKey - if let index = index { - if includeFrom { - key = self.key(threadId: threadId, index: index).predecessor - } else { - key = self.key(threadId: threadId, index: index) - } - } else { - key = self.lowerBound(threadId: threadId, peerId: peerId, namespace: namespace) - } - self.valueBox.range(self.table, start: key, end: self.upperBound(threadId: threadId, peerId: peerId, namespace: namespace), keys: { key in - indices.append(extractKey(key)) - return true - }, limit: count) - return indices - } - - func getMessageCountInRange(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace, lowerBound: MessageIndex, upperBound: MessageIndex) -> Int { - precondition(lowerBound.id.namespace == namespace) - precondition(upperBound.id.namespace == namespace) - var lowerBoundKey = self.key(threadId: threadId, index: lowerBound) - if lowerBound.timestamp > 1 { - lowerBoundKey = lowerBoundKey.predecessor - } - var upperBoundKey = self.key(threadId: threadId, index: upperBound) - if upperBound.timestamp < Int32.max - 1 { - upperBoundKey = upperBoundKey.successor - } - return Int(self.valueBox.count(self.table, start: lowerBoundKey, end: upperBoundKey)) - } -} diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index a5aaef4ee8..87c2e0db6a 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -3,16 +3,9 @@ import Foundation public struct MessageHistoryViewPeerHole: Equatable, Hashable, CustomStringConvertible { public let peerId: PeerId public let namespace: MessageId.Namespace - public let threadId: Int64? - - public init(peerId: PeerId, namespace: MessageId.Namespace, threadId: Int64?) { - self.peerId = peerId - self.namespace = namespace - self.threadId = threadId - } public var description: String { - return "peerId: \(self.peerId), namespace: \(self.namespace), threadId: \(String(describing: self.threadId))" + return "peerId: \(self.peerId), namespace: \(self.namespace)" } } @@ -133,18 +126,18 @@ enum MutableMessageHistoryEntry { func updatedTimestamp(_ timestamp: Int32) -> MutableMessageHistoryEntry { switch self { case let .IntermediateMessageEntry(message, location, monthLocation): - let updatedMessage = IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: message.embeddedMediaData, referencedMedia: message.referencedMedia) + let updatedMessage = IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: message.embeddedMediaData, referencedMedia: message.referencedMedia) return .IntermediateMessageEntry(updatedMessage, location, monthLocation) case let .MessageEntry(value, reloadAssociatedMessages, reloadPeers): let message = value.message - let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) + let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers) } } func getAssociatedMessageIds() -> [MessageId] { switch self { - case .IntermediateMessageEntry: + case let .IntermediateMessageEntry(message, location, monthLocation): return [] case let .MessageEntry(value, _, _): return value.message.associatedMessageIds @@ -156,11 +149,6 @@ public struct MessageHistoryEntryLocation: Equatable { public let index: Int public let count: Int - public init(index: Int, count: Int) { - self.index = index - self.count = count - } - var predecessor: MessageHistoryEntryLocation? { if index == 0 { return nil @@ -248,45 +236,9 @@ public struct MessageHistoryViewOrderStatistics: OptionSet { public static let locationWithinMonth = MessageHistoryViewOrderStatistics(rawValue: 1 << 1) } -public final class MessageHistoryViewExternalInput: Equatable { - public let peerId: PeerId - public let threadId: Int64 - public let maxReadMessageId: MessageId? - public let holes: [MessageId.Namespace: IndexSet] - - public init( - peerId: PeerId, - threadId: Int64, - maxReadMessageId: MessageId?, - holes: [MessageId.Namespace: IndexSet] - ) { - self.peerId = peerId - self.threadId = threadId - self.maxReadMessageId = maxReadMessageId - self.holes = holes - } - - public static func ==(lhs: MessageHistoryViewExternalInput, rhs: MessageHistoryViewExternalInput) -> Bool { - if lhs === rhs { - return true - } - if lhs.peerId != rhs.peerId { - return false - } - if lhs.threadId != rhs.threadId { - return false - } - if lhs.holes != rhs.holes { - return false - } - return true - } -} - -public enum MessageHistoryViewInput: Equatable { +public enum MessageHistoryViewPeerIds: Equatable { case single(PeerId) case associated(PeerId, MessageId?) - case external(MessageHistoryViewExternalInput) } public enum MessageHistoryViewReadState { @@ -302,7 +254,7 @@ public enum HistoryViewInputAnchor: Equatable { } final class MutableMessageHistoryView { - private(set) var peerIds: MessageHistoryViewInput + private(set) var peerIds: MessageHistoryViewPeerIds let tag: MessageTags? let namespaces: MessageIdNamespaces private let orderStatistics: MessageHistoryViewOrderStatistics @@ -322,7 +274,7 @@ final class MutableMessageHistoryView { fileprivate var isAddedToChatList: Bool - init(postbox: Postbox, orderStatistics: MessageHistoryViewOrderStatistics, clipHoles: Bool, peerIds: MessageHistoryViewInput, anchor inputAnchor: HistoryViewInputAnchor, combinedReadStates: MessageHistoryViewReadState?, transientReadStates: MessageHistoryViewReadState?, tag: MessageTags?, namespaces: MessageIdNamespaces, count: Int, topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?], additionalDatas: [AdditionalMessageHistoryViewDataEntry], getMessageCountInRange: (MessageIndex, MessageIndex) -> Int32) { + init(postbox: Postbox, orderStatistics: MessageHistoryViewOrderStatistics, clipHoles: Bool, peerIds: MessageHistoryViewPeerIds, anchor inputAnchor: HistoryViewInputAnchor, combinedReadStates: MessageHistoryViewReadState?, transientReadStates: MessageHistoryViewReadState?, tag: MessageTags?, namespaces: MessageIdNamespaces, count: Int, topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?], additionalDatas: [AdditionalMessageHistoryViewDataEntry], getMessageCountInRange: (MessageIndex, MessageIndex) -> Int32) { self.anchor = inputAnchor self.orderStatistics = orderStatistics @@ -336,14 +288,14 @@ final class MutableMessageHistoryView { self.topTaggedMessages = topTaggedMessages self.additionalDatas = additionalDatas + let mainPeerId: PeerId switch peerIds { case let .associated(peerId, _): - self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: peerId) != nil + mainPeerId = peerId case let .single(peerId): - self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: peerId) != nil - case let .external(input): - self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: input.peerId) != nil + mainPeerId = peerId } + self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: mainPeerId) != nil self.state = HistoryViewState(postbox: postbox, inputAnchor: inputAnchor, tag: tag, namespaces: namespaces, statistics: self.orderStatistics, halfLimit: count + 1, locations: peerIds) if case let .loading(loadingState) = self.state { @@ -403,14 +355,12 @@ final class MutableMessageHistoryView { self.peerIds = .associated(peerId, updatedData.associatedHistoryMessageId) } } - case .external: - break } } func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool { var operations: [[MessageHistoryOperation]] = [] - var holePeerIdsSet = Set() + var peerIdsSet = Set() if !transaction.chatListOperations.isEmpty { let mainPeerId: PeerId @@ -419,67 +369,52 @@ final class MutableMessageHistoryView { mainPeerId = peerId case let .single(peerId): mainPeerId = peerId - case let .external(input): - mainPeerId = input.peerId } self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: mainPeerId) != nil } switch self.peerIds { case let .single(peerId): - holePeerIdsSet.insert(peerId) + peerIdsSet.insert(peerId) if let value = transaction.currentOperationsByPeerId[peerId] { operations.append(value) } case .associated: switch self.peerIds { - case .single, .external: + case .single: assertionFailure() case let .associated(mainPeerId, associatedPeerId): - holePeerIdsSet.insert(mainPeerId) + peerIdsSet.insert(mainPeerId) if let associatedPeerId = associatedPeerId { - holePeerIdsSet.insert(associatedPeerId.peerId) + peerIdsSet.insert(associatedPeerId.peerId) } } for (peerId, value) in transaction.currentOperationsByPeerId { - if holePeerIdsSet.contains(peerId) { + if peerIdsSet.contains(peerId) { operations.append(value) } } - case let .external(input): - if let value = transaction.currentOperationsByPeerId[input.peerId] { - operations.append(value) - } } var hasChanges = false let unwrappedTag: MessageTags = self.tag ?? [] - let threadId: Int64? - switch self.peerIds { - case .single, .associated: - threadId = nil - case let .external(input): - threadId = input.threadId - } switch self.state { case let .loading(loadingState): for (key, holeOperations) in transaction.currentPeerHoleOperations { var matchesSpace = false - if threadId == nil { - switch key.space { - case .everywhere: - matchesSpace = unwrappedTag.isEmpty - case let .tag(tag): - if let currentTag = self.tag, currentTag == tag { - matchesSpace = true - } + switch key.space { + case .everywhere: + matchesSpace = unwrappedTag.isEmpty + case let .tag(tag): + if let currentTag = self.tag, currentTag == tag { + matchesSpace = true } } if matchesSpace { - if holePeerIdsSet.contains(key.peerId) { + if peerIdsSet.contains(key.peerId) { for operation in holeOperations { switch operation { case let .insert(range): @@ -512,22 +447,13 @@ final class MutableMessageHistoryView { for operation in operationSet { switch operation { case let .InsertMessage(message): - var matches = false if unwrappedTag.isEmpty || message.tags.contains(unwrappedTag) { - if threadId == nil || message.threadId == threadId { - if self.namespaces.contains(message.id.namespace) { - matches = true - if loadedState.add(entry: .IntermediateMessageEntry(message, nil, nil)) { - hasChanges = true - } + if self.namespaces.contains(message.id.namespace) { + if loadedState.add(entry: .IntermediateMessageEntry(message, nil, nil)) { + hasChanges = true } } } - if !matches { - if loadedState.addAssociated(entry: .IntermediateMessageEntry(message, nil, nil)) { - hasChanges = true - } - } case let .Remove(indicesAndTags): for (index, _) in indicesAndTags { if self.namespaces.contains(index.id.namespace) { @@ -565,18 +491,16 @@ final class MutableMessageHistoryView { } for (key, holeOperations) in transaction.currentPeerHoleOperations { var matchesSpace = false - if threadId == nil { - switch key.space { - case .everywhere: - matchesSpace = unwrappedTag.isEmpty - case let .tag(tag): - if let currentTag = self.tag, currentTag == tag { - matchesSpace = true - } + switch key.space { + case .everywhere: + matchesSpace = unwrappedTag.isEmpty + case let .tag(tag): + if let currentTag = self.tag, currentTag == tag { + matchesSpace = true } } if matchesSpace { - if holePeerIdsSet.contains(key.peerId) { + if peerIdsSet.contains(key.peerId) { for operation in holeOperations { switch operation { case let .insert(range): @@ -655,50 +579,6 @@ final class MutableMessageHistoryView { } case .cachedPeerDataMessages: break - case let .message(id, _): - if let operations = transaction.currentOperationsByPeerId[id.peerId] { - var updateMessage = false - findOperation: for operation in operations { - switch operation { - case let .InsertMessage(message): - if message.id == id { - updateMessage = true - break findOperation - } - case let .Remove(indices): - for (index, _) in indices { - if index.id == id { - updateMessage = true - break findOperation - } - } - case let .UpdateEmbeddedMedia(index, _): - if index.id == id { - updateMessage = true - break findOperation - } - case let .UpdateGroupInfos(dict): - if dict[id] != nil { - updateMessage = true - break findOperation - } - case let .UpdateTimestamp(index, _): - if index.id == id { - updateMessage = true - break findOperation - } - case .UpdateReadState: - break - } - } - if updateMessage { - let message = postbox.messageHistoryIndexTable.getIndex(id).flatMap(postbox.messageHistoryTable.getMessage).flatMap { message in - postbox.messageHistoryTable.renderMessage(message, peerTable: postbox.peerTable) - } - self.additionalDatas[i] = .message(id, message) - hasChanges = true - } - } case let .peerChatState(peerId, _): if transaction.currentUpdatedPeerChatStates.contains(peerId) { self.additionalDatas[i] = .peerChatState(peerId, postbox.peerChatStateTable.get(peerId) as? PeerChatState) @@ -787,21 +667,19 @@ final class MutableMessageHistoryView { } if !transaction.currentPeerHoleOperations.isEmpty { - var holePeerIdsSet: [PeerId] = [] - switch self.peerIds { + var peerIdsSet: [PeerId] = [] + switch peerIds { case let .single(peerId): - holePeerIdsSet.append(peerId) + peerIdsSet.append(peerId) case let .associated(peerId, associatedId): - holePeerIdsSet.append(peerId) + peerIdsSet.append(peerId) if let associatedId = associatedId { - holePeerIdsSet.append(associatedId.peerId) + peerIdsSet.append(associatedId.peerId) } - case .external: - break } let space: MessageHistoryHoleSpace = self.tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere for key in transaction.currentPeerHoleOperations.keys { - if holePeerIdsSet.contains(key.peerId) && key.space == space { + if peerIdsSet.contains(key.peerId) && key.space == space { hasChanges = true } } @@ -829,8 +707,8 @@ final class MutableMessageHistoryView { switch loadingSample { case .ready: return nil - case let .loadHole(peerId, namespace, _, threadId, id): - return (.peer(MessageHistoryViewPeerHole(peerId: peerId, namespace: namespace, threadId: threadId)), .aroundId(MessageId(peerId: peerId, namespace: namespace, id: id)), self.fillCount * 2) + case let .loadHole(peerId, namespace, _, id): + return (.peer(MessageHistoryViewPeerHole(peerId: peerId, namespace: namespace)), .aroundId(MessageId(peerId: peerId, namespace: namespace, id: id)), self.fillCount * 2) } case let .loaded(loadedSample): if let hole = loadedSample.hole { @@ -840,7 +718,7 @@ final class MutableMessageHistoryView { } else { direction = .aroundId(MessageId(peerId: hole.peerId, namespace: hole.namespace, id: hole.startId)) } - return (.peer(MessageHistoryViewPeerHole(peerId: hole.peerId, namespace: hole.namespace, threadId: hole.threadId)), direction, self.fillCount * 2) + return (.peer(MessageHistoryViewPeerHole(peerId: hole.peerId, namespace: hole.namespace)), direction, self.fillCount * 2) } else { return nil } @@ -960,122 +838,66 @@ public final class MessageHistoryView { self.fixedReadStates = mutableView.combinedReadStates - switch mutableView.peerIds { - case .single, .associated: - if let combinedReadStates = mutableView.combinedReadStates { - switch combinedReadStates { - case let .peer(states): - var hasUnread = false - for (_, readState) in states { - if readState.count > 0 { - hasUnread = true - break - } + if let combinedReadStates = mutableView.combinedReadStates { + switch combinedReadStates { + case let .peer(states): + var hasUnread = false + for (_, readState) in states { + if readState.count > 0 { + hasUnread = true + break } - - var maxIndex: MessageIndex? - - if hasUnread { - var peerIds = Set() - for entry in entries { - peerIds.insert(entry.index.id.peerId) - } - for peerId in peerIds { - if let combinedReadState = states[peerId] { - for (namespace, state) in combinedReadState.states { - var maxNamespaceIndex: MessageIndex? - var index = entries.count - 1 - for entry in entries.reversed() { - if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace && state.isIncomingMessageIndexRead(entry.index) { - maxNamespaceIndex = entry.index - break - } - index -= 1 - } - if maxNamespaceIndex == nil && index == -1 && entries.count != 0 { - index = 0 - for entry in entries { - if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace { - maxNamespaceIndex = entry.index.predecessor() - break - } - index += 1 - } - } - if let _ = maxNamespaceIndex , index + 1 < entries.count { - for i in index + 1 ..< entries.count { - if entries[i].message.flags.intersection(.IsIncomingMask).isEmpty { - maxNamespaceIndex = entries[i].message.index - } else { - break - } - } - } - if let maxNamespaceIndex = maxNamespaceIndex , maxIndex == nil || maxIndex! < maxNamespaceIndex { - maxIndex = maxNamespaceIndex - } - } - } - } - } - self.maxReadIndex = maxIndex } - } else { - self.maxReadIndex = nil - } - case let .external(input): - if let maxReadMesageId = input.maxReadMessageId { + var maxIndex: MessageIndex? - let hasUnread = true if hasUnread { var peerIds = Set() for entry in entries { peerIds.insert(entry.index.id.peerId) } for peerId in peerIds { - if peerId != maxReadMesageId.peerId { - continue - } - let namespace = maxReadMesageId.namespace - - var maxNamespaceIndex: MessageIndex? - var index = entries.count - 1 - for entry in entries.reversed() { - if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace && entry.index.id <= maxReadMesageId { - maxNamespaceIndex = entry.index - break - } - index -= 1 - } - if maxNamespaceIndex == nil && index == -1 && entries.count != 0 { - index = 0 - for entry in entries { - if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace { - maxNamespaceIndex = entry.index.predecessor() - break + if let combinedReadState = states[peerId] { + for (namespace, state) in combinedReadState.states { + var maxNamespaceIndex: MessageIndex? + var index = entries.count - 1 + for entry in entries.reversed() { + if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace && state.isIncomingMessageIndexRead(entry.index) { + maxNamespaceIndex = entry.index + break + } + index -= 1 } - index += 1 - } - } - if let _ = maxNamespaceIndex , index + 1 < entries.count { - for i in index + 1 ..< entries.count { - if entries[i].message.flags.intersection(.IsIncomingMask).isEmpty { - maxNamespaceIndex = entries[i].message.index - } else { - break + if maxNamespaceIndex == nil && index == -1 && entries.count != 0 { + index = 0 + for entry in entries { + if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace { + maxNamespaceIndex = entry.index.predecessor() + break + } + index += 1 + } + } + if let _ = maxNamespaceIndex , index + 1 < entries.count { + for i in index + 1 ..< entries.count { + if entries[i].message.flags.intersection(.IsIncomingMask).isEmpty { + maxNamespaceIndex = entries[i].message.index + } else { + break + } + } + } + if let maxNamespaceIndex = maxNamespaceIndex , maxIndex == nil || maxIndex! < maxNamespaceIndex { + maxIndex = maxNamespaceIndex } } } - if let maxNamespaceIndex = maxNamespaceIndex , maxIndex == nil || maxIndex! < maxNamespaceIndex { - maxIndex = maxNamespaceIndex - } } } self.maxReadIndex = maxIndex - } else { - self.maxReadIndex = nil } + } else { + self.maxReadIndex = nil } self.entries = entries diff --git a/submodules/Postbox/Sources/MessageHistoryViewState.swift b/submodules/Postbox/Sources/MessageHistoryViewState.swift index 6134e3d7a8..7f46f97daa 100644 --- a/submodules/Postbox/Sources/MessageHistoryViewState.swift +++ b/submodules/Postbox/Sources/MessageHistoryViewState.swift @@ -1,43 +1,5 @@ import Foundation -public enum MessageHistoryInput: Equatable, Hashable { - case automatic(MessageTags?) - case external(MessageHistoryViewExternalInput, MessageTags?) - - public func hash(into hasher: inout Hasher) { - switch self { - case .automatic: - hasher.combine(1) - case .external: - hasher.combine(2) - } - } -} - -private extension MessageHistoryInput { - func fetch(postbox: Postbox, peerId: PeerId, namespace: MessageId.Namespace, from fromIndex: MessageIndex, includeFrom: Bool, to toIndex: MessageIndex, limit: Int) -> [IntermediateMessage] { - switch self { - case let .automatic(tag): - return postbox.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: tag, threadId: nil, from: fromIndex, includeFrom: includeFrom, to: toIndex, limit: limit) - case let .external(input, tag): - return postbox.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: tag, threadId: input.threadId, from: fromIndex, includeFrom: includeFrom, to: toIndex, limit: limit) - } - } - - func getMessageCountInRange(postbox: Postbox, peerId: PeerId, namespace: MessageId.Namespace, lowerBound: MessageIndex, upperBound: MessageIndex) -> Int { - switch self { - case let .automatic(tag): - if let tag = tag { - return postbox.messageHistoryTagsTable.getMessageCountInRange(tag: tag, peerId: peerId, namespace: namespace, lowerBound: lowerBound, upperBound: upperBound) - } else { - return 0 - } - case .external: - return 0 - } - } -} - public struct PeerIdAndNamespace: Hashable { public let peerId: PeerId public let namespace: MessageId.Namespace @@ -48,16 +10,11 @@ public struct PeerIdAndNamespace: Hashable { } } -private func canContainHoles(_ peerIdAndNamespace: PeerIdAndNamespace, input: MessageHistoryInput, seedConfiguration: SeedConfiguration) -> Bool { - switch input { - case .automatic: - guard let messageNamespaces = seedConfiguration.messageHoles[peerIdAndNamespace.peerId.namespace] else { - return false - } - return messageNamespaces[peerIdAndNamespace.namespace] != nil - case .external: - return true +private func canContainHoles(_ peerIdAndNamespace: PeerIdAndNamespace, seedConfiguration: SeedConfiguration) -> Bool { + guard let messageNamespaces = seedConfiguration.messageHoles[peerIdAndNamespace.peerId.namespace] else { + return false } + return messageNamespaces[peerIdAndNamespace.namespace] != nil } private struct MessageMonthIndex: Equatable { @@ -289,7 +246,6 @@ struct SampledHistoryViewHole: Equatable { let peerId: PeerId let namespace: MessageId.Namespace let tag: MessageTags? - let threadId: Int64? let indices: IndexSet let startId: MessageId.Id let endId: MessageId.Id? @@ -335,31 +291,22 @@ private func isIndex(index: MessageIndex, closerTo anchor: HistoryViewAnchor, th } } -private func sampleHoleRanges(input: MessageHistoryInput, orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries], holes: HistoryViewHoles, anchor: HistoryViewAnchor, halfLimit: Int, seedConfiguration: SeedConfiguration) -> (clipRanges: [ClosedRange], sampledHole: SampledHistoryViewHole?) { +private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries], holes: HistoryViewHoles, anchor: HistoryViewAnchor, tag: MessageTags?, halfLimit: Int, seedConfiguration: SeedConfiguration) -> (clipRanges: [ClosedRange], sampledHole: SampledHistoryViewHole?) { var clipRanges: [ClosedRange] = [] var sampledHole: (distanceFromAnchor: Int?, hole: SampledHistoryViewHole)? - var tag: MessageTags? - var threadId: Int64? - switch input { - case let .automatic(value): - tag = value - case let .external(value, _): - threadId = value.threadId - } - for (space, indices) in holes.holesBySpace { if indices.isEmpty { continue } - assert(canContainHoles(space, input: input, seedConfiguration: seedConfiguration)) + assert(canContainHoles(space, seedConfiguration: seedConfiguration)) switch anchor { case .lowerBound, .upperBound: break case let .index(index): if index.id.peerId == space.peerId && index.id.namespace == space.namespace { if indices.contains(Int(index.id.id)) { - return ([MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound()], SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: index.id.id, endId: nil)) + return ([MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound()], SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, indices: indices, startId: index.id.id, endId: nil)) } } } @@ -372,9 +319,9 @@ private func sampleHoleRanges(input: MessageHistoryInput, orderedEntriesBySpace: holeBounds = (Int32.max - 1, 1) } if case let .index(index) = anchor, index.id.peerId == space.peerId { - return ([MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound()], SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: holeBounds.startId, endId: holeBounds.endId)) + return ([MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound()], SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, indices: indices, startId: holeBounds.startId, endId: holeBounds.endId)) } else { - sampledHole = (nil, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: holeBounds.startId, endId: holeBounds.endId)) + sampledHole = (nil, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, indices: indices, startId: holeBounds.startId, endId: holeBounds.endId)) continue } } @@ -411,7 +358,7 @@ private func sampleHoleRanges(input: MessageHistoryInput, orderedEntriesBySpace: } else { holeStartIndex = indices[indices.endIndex] } - lowerOrAtAnchorHole = (items.lowerOrAtAnchor.count - i, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: Int32(holeStartIndex), endId: 1)) + lowerOrAtAnchorHole = (items.lowerOrAtAnchor.count - i, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, indices: indices, startId: Int32(holeStartIndex), endId: 1)) if i == -1 { if items.lowerOrAtAnchor.count == 0 { @@ -479,7 +426,7 @@ private func sampleHoleRanges(input: MessageHistoryInput, orderedEntriesBySpace: } else { holeStartIndex = indices[indices.startIndex] } - higherThanAnchorHole = (i, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: Int32(holeStartIndex), endId: Int32.max - 1)) + higherThanAnchorHole = (i, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, indices: indices, startId: Int32(holeStartIndex), endId: Int32.max - 1)) if i == items.higherThanAnchor.count { if items.higherThanAnchor.count == 0 { @@ -825,8 +772,8 @@ struct HistoryViewLoadedSample { final class HistoryViewLoadedState { let anchor: HistoryViewAnchor + let tag: MessageTags? let namespaces: MessageIdNamespaces - let input: MessageHistoryInput let statistics: MessageHistoryViewOrderStatistics let halfLimit: Int let seedConfiguration: SeedConfiguration @@ -834,9 +781,10 @@ final class HistoryViewLoadedState { var holes: HistoryViewHoles var spacesWithRemovals = Set() - init(anchor: HistoryViewAnchor, tag: MessageTags?, namespaces: MessageIdNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewInput, postbox: Postbox, holes: HistoryViewHoles) { + init(anchor: HistoryViewAnchor, tag: MessageTags?, namespaces: MessageIdNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewPeerIds, postbox: Postbox, holes: HistoryViewHoles) { precondition(halfLimit >= 3) self.anchor = anchor + self.tag = tag self.namespaces = namespaces self.statistics = statistics self.halfLimit = halfLimit @@ -845,22 +793,15 @@ final class HistoryViewLoadedState { self.holes = holes var peerIds: [PeerId] = [] - let input: MessageHistoryInput switch locations { case let .single(peerId): peerIds.append(peerId) - input = .automatic(tag) case let .associated(peerId, associatedId): peerIds.append(peerId) if let associatedId = associatedId { peerIds.append(associatedId.peerId) } - input = .automatic(tag) - case let .external(external): - peerIds.append(external.peerId) - input = .external(external, tag) } - self.input = input var spaces: [PeerIdAndNamespace] = [] for peerId in peerIds { @@ -908,7 +849,7 @@ final class HistoryViewLoadedState { } else { nextLowerIndex = (anchorIndex, true) } - lowerOrAtAnchorMessages.append(contentsOf: self.input.fetch(postbox: postbox, peerId: space.peerId, namespace: space.namespace, from: nextLowerIndex.index, includeFrom: nextLowerIndex.includeFrom, to: lowerBound, limit: self.halfLimit - lowerOrAtAnchorMessages.count).map(mapEntry)) + lowerOrAtAnchorMessages.append(contentsOf: postbox.messageHistoryTable.fetch(peerId: space.peerId, namespace: space.namespace, tag: self.tag, from: nextLowerIndex.index, includeFrom: nextLowerIndex.includeFrom, to: lowerBound, limit: self.halfLimit - lowerOrAtAnchorMessages.count).map(mapEntry)) } if higherThanAnchorMessages.count < self.halfLimit { let nextHigherIndex: MessageIndex @@ -917,7 +858,7 @@ final class HistoryViewLoadedState { } else { nextHigherIndex = anchorIndex } - higherThanAnchorMessages.append(contentsOf: self.input.fetch(postbox: postbox, peerId: space.peerId, namespace: space.namespace, from: nextHigherIndex, includeFrom: false, to: upperBound, limit: self.halfLimit - higherThanAnchorMessages.count).map(mapEntry)) + higherThanAnchorMessages.append(contentsOf: postbox.messageHistoryTable.fetch(peerId: space.peerId, namespace: space.namespace, tag: self.tag, from: nextHigherIndex, includeFrom: false, to: upperBound, limit: self.halfLimit - higherThanAnchorMessages.count).map(mapEntry)) } lowerOrAtAnchorMessages.reverse() @@ -927,10 +868,10 @@ final class HistoryViewLoadedState { var entries = OrderedHistoryViewEntries(lowerOrAtAnchor: lowerOrAtAnchorMessages, higherThanAnchor: higherThanAnchorMessages) - if case let .automatic(tagValue) = self.input, let _ = tagValue, self.statistics.contains(.combinedLocation), let first = entries.first { + if let tag = self.tag, self.statistics.contains(.combinedLocation), let first = entries.first { let messageIndex = first.index - let previousCount = self.input.getMessageCountInRange(postbox: postbox, peerId: space.peerId, namespace: space.namespace, lowerBound: MessageIndex.lowerBound(peerId: space.peerId, namespace: space.namespace), upperBound: messageIndex) - let nextCount = self.input.getMessageCountInRange(postbox: postbox, peerId: space.peerId, namespace: space.namespace, lowerBound: messageIndex, upperBound: MessageIndex.upperBound(peerId: space.peerId, namespace: space.namespace)) + let previousCount = postbox.messageHistoryTagsTable.getMessageCountInRange(tag: tag, peerId: space.peerId, namespace: space.namespace, lowerBound: MessageIndex.lowerBound(peerId: space.peerId, namespace: space.namespace), upperBound: messageIndex) + let nextCount = postbox.messageHistoryTagsTable.getMessageCountInRange(tag: tag, peerId: space.peerId, namespace: space.namespace, lowerBound: messageIndex, upperBound: MessageIndex.upperBound(peerId: space.peerId, namespace: space.namespace)) let initialLocation = MessageHistoryEntryLocation(index: previousCount - 1, count: previousCount + nextCount - 1) var nextLocation = initialLocation @@ -946,10 +887,10 @@ final class HistoryViewLoadedState { } } - if case let .automatic(tagValue) = self.input, let _ = tagValue, self.statistics.contains(.locationWithinMonth), let first = entries.first { + if let tag = self.tag, self.statistics.contains(.locationWithinMonth), let first = entries.first { let messageIndex = first.index let monthIndex = MessageMonthIndex(timestamp: messageIndex.timestamp) - let count = self.input.getMessageCountInRange(postbox: postbox, peerId: space.peerId, namespace: space.namespace, lowerBound: messageIndex, upperBound: monthUpperBoundIndex(peerId: space.peerId, namespace: space.namespace, index: monthIndex)) + let count = postbox.messageHistoryTagsTable.getMessageCountInRange(tag: tag, peerId: space.peerId, namespace: space.namespace, lowerBound: messageIndex, upperBound: monthUpperBoundIndex(peerId: space.peerId, namespace: space.namespace, index: monthIndex)) var nextLocation: (MessageMonthIndex, Int) = (monthIndex, count - 1) @@ -970,19 +911,19 @@ final class HistoryViewLoadedState { } } - if canContainHoles(space, input: self.input, seedConfiguration: self.seedConfiguration) { + if canContainHoles(space, seedConfiguration: self.seedConfiguration) { entries.fixMonotony() } self.orderedEntriesBySpace[space] = entries } func insertHole(space: PeerIdAndNamespace, range: ClosedRange) -> Bool { - assert(canContainHoles(space, input: self.input, seedConfiguration: self.seedConfiguration)) + assert(canContainHoles(space, seedConfiguration: self.seedConfiguration)) return self.holes.insertHole(space: space, range: range) } func removeHole(space: PeerIdAndNamespace, range: ClosedRange) -> Bool { - assert(canContainHoles(space, input: self.input, seedConfiguration: self.seedConfiguration)) + assert(canContainHoles(space, seedConfiguration: self.seedConfiguration)) return self.holes.removeHole(space: space, range: range) } @@ -1084,7 +1025,7 @@ final class HistoryViewLoadedState { messageMedia.append(media) } } - let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: messageMedia, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) + let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: messageMedia, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers) } case .IntermediateMessageEntry: @@ -1163,28 +1104,6 @@ final class HistoryViewLoadedState { } } - func addAssociated(entry: MutableMessageHistoryEntry) -> Bool { - var updated = false - - for (space, _) in self.orderedEntriesBySpace { - if let associatedIndices = self.orderedEntriesBySpace[space]!.indicesForAssociatedMessageId(entry.index.id) { - for associatedIndex in associatedIndices { - let _ = self.orderedEntriesBySpace[space]!.update(index: associatedIndex, { current in - switch current { - case .IntermediateMessageEntry: - return current - case let .MessageEntry(messageEntry, _, reloadPeers): - updated = true - return .MessageEntry(messageEntry, reloadAssociatedMessages: true, reloadPeers: reloadPeers) - } - }) - } - } - } - - return updated - } - func remove(index: MessageIndex) -> Bool { let space = PeerIdAndNamespace(peerId: index.id.peerId, namespace: index.id.namespace) if self.orderedEntriesBySpace[space] == nil { @@ -1195,7 +1114,7 @@ final class HistoryViewLoadedState { if let associatedIndices = self.orderedEntriesBySpace[space]!.indicesForAssociatedMessageId(index.id) { for associatedIndex in associatedIndices { - let _ = self.orderedEntriesBySpace[space]!.update(index: associatedIndex, { current in + self.orderedEntriesBySpace[space]!.update(index: associatedIndex, { current in switch current { case .IntermediateMessageEntry: return current @@ -1228,7 +1147,7 @@ final class HistoryViewLoadedState { self.spacesWithRemovals.removeAll() } let combinedSpacesAndIndicesByDirection = sampleEntries(orderedEntriesBySpace: self.orderedEntriesBySpace, anchor: self.anchor, halfLimit: self.halfLimit) - let (clipRanges, sampledHole) = sampleHoleRanges(input: self.input, orderedEntriesBySpace: self.orderedEntriesBySpace, holes: self.holes, anchor: self.anchor, halfLimit: self.halfLimit, seedConfiguration: self.seedConfiguration) + let (clipRanges, sampledHole) = sampleHoleRanges(orderedEntriesBySpace: self.orderedEntriesBySpace, holes: self.holes, anchor: self.anchor, tag: self.tag, halfLimit: self.halfLimit, seedConfiguration: self.seedConfiguration) var holesToLower = false var holesToHigher = false @@ -1310,7 +1229,8 @@ final class HistoryViewLoadedState { } } -private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewInput, tag: MessageTags?, namespaces: MessageIdNamespaces) -> [PeerIdAndNamespace: IndexSet] { +private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, namespaces: MessageIdNamespaces) -> [PeerIdAndNamespace: IndexSet] { + var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:] var peerIds: [PeerId] = [] switch locations { case let .single(peerId): @@ -1320,59 +1240,37 @@ private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewInput, ta if let associatedId = associatedId { peerIds.append(associatedId.peerId) } - case let .external(input): - peerIds.append(input.peerId) } - switch locations { - case .single, .associated: - var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:] - let holeSpace = tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere - for peerId in peerIds { - for namespace in postbox.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: holeSpace) { - if namespaces.contains(namespace) { - let indices = postbox.messageHistoryHoleIndexTable.closest(peerId: peerId, namespace: namespace, space: holeSpace, range: 1 ... (Int32.max - 1)) - if !indices.isEmpty { - let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace) - assert(canContainHoles(peerIdAndNamespace, input: .automatic(tag), seedConfiguration: postbox.seedConfiguration)) - holesBySpace[peerIdAndNamespace] = indices - } + let holeSpace = tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere + for peerId in peerIds { + for namespace in postbox.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: holeSpace) { + if namespaces.contains(namespace) { + let indices = postbox.messageHistoryHoleIndexTable.closest(peerId: peerId, namespace: namespace, space: holeSpace, range: 1 ... (Int32.max - 1)) + if !indices.isEmpty { + let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace) + assert(canContainHoles(peerIdAndNamespace, seedConfiguration: postbox.seedConfiguration)) + holesBySpace[peerIdAndNamespace] = indices } } } - return holesBySpace - case let .external(input): - var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:] - for peerId in peerIds { - for (namespace, indices) in input.holes { - if namespaces.contains(namespace) { - if !indices.isEmpty { - let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace) - assert(canContainHoles(peerIdAndNamespace, input: .external(input, tag), seedConfiguration: postbox.seedConfiguration)) - holesBySpace[peerIdAndNamespace] = indices - } - } - } - } - return holesBySpace } + return holesBySpace } enum HistoryViewLoadingSample { case ready(HistoryViewAnchor, HistoryViewHoles) - case loadHole(PeerId, MessageId.Namespace, MessageTags?, Int64?, MessageId.Id) + case loadHole(PeerId, MessageId.Namespace, MessageTags?, MessageId.Id) } final class HistoryViewLoadingState { var messageId: MessageId let tag: MessageTags? - let threadId: Int64? let halfLimit: Int var holes: HistoryViewHoles - init(postbox: Postbox, locations: MessageHistoryViewInput, tag: MessageTags?, threadId: Int64?, namespaces: MessageIdNamespaces, messageId: MessageId, halfLimit: Int) { + init(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, namespaces: MessageIdNamespaces, messageId: MessageId, halfLimit: Int) { self.messageId = messageId self.tag = tag - self.threadId = threadId self.halfLimit = halfLimit self.holes = HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces)) } @@ -1389,7 +1287,7 @@ final class HistoryViewLoadingState { while true { if let indices = self.holes.holesBySpace[PeerIdAndNamespace(peerId: self.messageId.peerId, namespace: self.messageId.namespace)] { if indices.contains(Int(messageId.id)) { - return .loadHole(messageId.peerId, messageId.namespace, self.tag, self.threadId, messageId.id) + return .loadHole(messageId.peerId, messageId.namespace, self.tag, messageId.id) } } @@ -1414,7 +1312,7 @@ enum HistoryViewState { case loaded(HistoryViewLoadedState) case loading(HistoryViewLoadingState) - init(postbox: Postbox, inputAnchor: HistoryViewInputAnchor, tag: MessageTags?, namespaces: MessageIdNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewInput) { + init(postbox: Postbox, inputAnchor: HistoryViewInputAnchor, tag: MessageTags?, namespaces: MessageIdNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewPeerIds) { switch inputAnchor { case let .index(index): self = .loaded(HistoryViewLoadedState(anchor: .index(index), tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces)))) @@ -1429,9 +1327,6 @@ enum HistoryViewState { anchorPeerId = peerId case let .associated(peerId, _): anchorPeerId = peerId - case .external: - self = .loaded(HistoryViewLoadedState(anchor: .upperBound, tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces)))) - return } if postbox.chatListIndexTable.get(peerId: anchorPeerId).includedIndex(peerId: anchorPeerId) != nil, let combinedState = postbox.readStateTable.getCombinedState(anchorPeerId) { var messageId: MessageId? @@ -1457,7 +1352,7 @@ enum HistoryViewState { } } if let messageId = messageId { - let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, threadId: nil, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit) + let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit) let sampledState = loadingState.checkAndSample(postbox: postbox) switch sampledState { case let .ready(anchor, holes): @@ -1472,14 +1367,7 @@ enum HistoryViewState { preconditionFailure() } case let .message(messageId): - var threadId: Int64? - switch locations { - case let .external(input): - threadId = input.threadId - default: - break - } - let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, threadId: threadId, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit) + let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit) let sampledState = loadingState.checkAndSample(postbox: postbox) switch sampledState { case let .ready(anchor, holes): diff --git a/submodules/Postbox/Sources/MessageOfInterestHolesView.swift b/submodules/Postbox/Sources/MessageOfInterestHolesView.swift index 79ebc808dc..caa7f03362 100644 --- a/submodules/Postbox/Sources/MessageOfInterestHolesView.swift +++ b/submodules/Postbox/Sources/MessageOfInterestHolesView.swift @@ -33,7 +33,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { private let count: Int private var anchor: HistoryViewInputAnchor private var wrappedView: MutableMessageHistoryView - private var peerIds: MessageHistoryViewInput + private var peerIds: MessageHistoryViewPeerIds fileprivate var closestHole: MessageOfInterestHole? fileprivate var closestLaterMedia: [HolesViewMedia] = [] @@ -43,11 +43,11 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { self.count = count let mainPeerId: PeerId - let peerIds: MessageHistoryViewInput + let peerIds: MessageHistoryViewPeerIds switch self.location { case let .peer(id): mainPeerId = id - peerIds = postbox.peerIdsForLocation(.peer(id)) + peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil) } self.peerIds = peerIds var anchor: HistoryViewInputAnchor = .upperBound @@ -127,10 +127,10 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { if self.anchor != anchor { self.anchor = anchor - let peerIds: MessageHistoryViewInput + let peerIds: MessageHistoryViewPeerIds switch self.location { case let .peer(id): - peerIds = postbox.peerIdsForLocation(.peer(id)) + peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil) } self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0}) return self.updateFromView() @@ -146,9 +146,6 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { if let attachedMessageId = attachedMessageId { allPeerIds.append(attachedMessageId.peerId) } - case .external: - allPeerIds = [] - break } for (key, _) in transaction.currentPeerHoleOperations { if allPeerIds.contains(key.peerId) { @@ -158,10 +155,10 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { } } if reloadView { - let peerIds: MessageHistoryViewInput + let peerIds: MessageHistoryViewPeerIds switch self.location { case let .peer(id): - peerIds = postbox.peerIdsForLocation(.peer(id)) + peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil) } self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0}) } diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 1254573c07..4dcf6d4dcc 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -38,14 +38,6 @@ public final class Transaction { } } - public func messageExists(id: MessageId) -> Bool { - if let postbox = self.postbox { - return postbox.messageHistoryIndexTable.exists(id) - } else { - return false - } - } - public func countIncomingMessage(id: MessageId) { assert(!self.disposed) if let postbox = self.postbox { @@ -948,14 +940,6 @@ public final class Transaction { return postbox.messageHistoryTagsTable.earlierIndices(tag: tag, peerId: peerId, namespace: namespace, index: nil, includeFrom: false, count: 1000) } - public func getMessagesWithThreadId(peerId: PeerId, namespace: MessageId.Namespace, threadId: Int64, from: MessageIndex, includeFrom: Bool, to: MessageIndex, limit: Int) -> [Message] { - assert(!self.disposed) - guard let postbox = self.postbox else { - return [] - } - return postbox.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: nil, threadId: threadId, from: from, includeFrom: includeFrom, to: to, limit: limit).map(postbox.renderIntermediateMessage(_:)) - } - public func scanMessages(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags, _ f: (Message) -> Bool) { assert(!self.disposed) self.postbox?.scanMessages(peerId: peerId, namespace: namespace, tag: tag, f) @@ -1236,7 +1220,6 @@ public final class Postbox { let messageHistoryUnsentTable: MessageHistoryUnsentTable let messageHistoryFailedTable: MessageHistoryFailedTable let messageHistoryTagsTable: MessageHistoryTagsTable - let messageHistoryThreadsTable: MessageHistoryThreadsTable let globalMessageHistoryTagsTable: GlobalMessageHistoryTagsTable let localMessageHistoryTagsTable: LocalMessageHistoryTagsTable let peerChatStateTable: PeerChatStateTable @@ -1319,7 +1302,6 @@ public final class Postbox { self.pendingMessageActionsMetadataTable = PendingMessageActionsMetadataTable(valueBox: self.valueBox, table: PendingMessageActionsMetadataTable.tableSpec(45)) self.pendingMessageActionsTable = PendingMessageActionsTable(valueBox: self.valueBox, table: PendingMessageActionsTable.tableSpec(46), metadataTable: self.pendingMessageActionsMetadataTable) self.messageHistoryTagsTable = MessageHistoryTagsTable(valueBox: self.valueBox, table: MessageHistoryTagsTable.tableSpec(12), seedConfiguration: self.seedConfiguration, summaryTable: self.messageHistoryTagsSummaryTable) - self.messageHistoryThreadsTable = MessageHistoryThreadsTable(valueBox: self.valueBox, table: MessageHistoryThreadsTable.tableSpec(62)) self.globalMessageHistoryTagsTable = GlobalMessageHistoryTagsTable(valueBox: self.valueBox, table: GlobalMessageHistoryTagsTable.tableSpec(39)) self.localMessageHistoryTagsTable = LocalMessageHistoryTagsTable(valueBox: self.valueBox, table: GlobalMessageHistoryTagsTable.tableSpec(52)) self.messageHistoryIndexTable = MessageHistoryIndexTable(valueBox: self.valueBox, table: MessageHistoryIndexTable.tableSpec(4), messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, globalMessageIdsTable: self.globalMessageIdsTable, metadataTable: self.messageHistoryMetadataTable, seedConfiguration: self.seedConfiguration) @@ -1331,7 +1313,7 @@ public final class Postbox { self.timestampBasedMessageAttributesTable = TimestampBasedMessageAttributesTable(valueBox: self.valueBox, table: TimestampBasedMessageAttributesTable.tableSpec(34), indexTable: self.timestampBasedMessageAttributesIndexTable) self.textIndexTable = MessageHistoryTextIndexTable(valueBox: self.valueBox, table: MessageHistoryTextIndexTable.tableSpec(41)) self.additionalChatListItemsTable = AdditionalChatListItemsTable(valueBox: self.valueBox, table: AdditionalChatListItemsTable.tableSpec(55)) - self.messageHistoryTable = MessageHistoryTable(valueBox: self.valueBox, table: MessageHistoryTable.tableSpec(7), seedConfiguration: seedConfiguration, messageHistoryIndexTable: self.messageHistoryIndexTable, messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, messageMediaTable: self.mediaTable, historyMetadataTable: self.messageHistoryMetadataTable, globallyUniqueMessageIdsTable: self.globallyUniqueMessageIdsTable, unsentTable: self.messageHistoryUnsentTable, failedTable: self.messageHistoryFailedTable, tagsTable: self.messageHistoryTagsTable, threadsTable: self.messageHistoryThreadsTable, globalTagsTable: self.globalMessageHistoryTagsTable, localTagsTable: self.localMessageHistoryTagsTable, readStateTable: self.readStateTable, synchronizeReadStateTable: self.synchronizeReadStateTable, textIndexTable: self.textIndexTable, summaryTable: self.messageHistoryTagsSummaryTable, pendingActionsTable: self.pendingMessageActionsTable) + self.messageHistoryTable = MessageHistoryTable(valueBox: self.valueBox, table: MessageHistoryTable.tableSpec(7), seedConfiguration: seedConfiguration, messageHistoryIndexTable: self.messageHistoryIndexTable, messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, messageMediaTable: self.mediaTable, historyMetadataTable: self.messageHistoryMetadataTable, globallyUniqueMessageIdsTable: self.globallyUniqueMessageIdsTable, unsentTable: self.messageHistoryUnsentTable, failedTable: self.messageHistoryFailedTable, tagsTable: self.messageHistoryTagsTable, globalTagsTable: self.globalMessageHistoryTagsTable, localTagsTable: self.localMessageHistoryTagsTable, readStateTable: self.readStateTable, synchronizeReadStateTable: self.synchronizeReadStateTable, textIndexTable: self.textIndexTable, summaryTable: self.messageHistoryTagsSummaryTable, pendingActionsTable: self.pendingMessageActionsTable) self.peerChatStateTable = PeerChatStateTable(valueBox: self.valueBox, table: PeerChatStateTable.tableSpec(13)) self.peerNameTokenIndexTable = ReverseIndexReferenceTable(valueBox: self.valueBox, table: ReverseIndexReferenceTable.tableSpec(26)) self.peerNameIndexTable = PeerNameIndexTable(valueBox: self.valueBox, table: PeerNameIndexTable.tableSpec(27), peerTable: self.peerTable, peerNameTokenIndexTable: self.peerNameTokenIndexTable) @@ -1373,7 +1355,6 @@ public final class Postbox { tables.append(self.messageHistoryUnsentTable) tables.append(self.messageHistoryFailedTable) tables.append(self.messageHistoryTagsTable) - tables.append(self.messageHistoryThreadsTable) tables.append(self.globalMessageHistoryTagsTable) tables.append(self.localMessageHistoryTagsTable) tables.append(self.messageHistoryIndexTable) @@ -1486,7 +1467,7 @@ public final class Postbox { if let forwardInfo = message.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, timestamp: message.timestamp, flags: flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: storeForwardInfo, authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media)) + return .update(StoreMessage(id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, timestamp: message.timestamp, flags: flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: storeForwardInfo, authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media)) } else { return .skip } @@ -1685,7 +1666,7 @@ public final class Postbox { } fileprivate func applyInteractiveReadMaxIndex(_ messageIndex: MessageIndex) -> [MessageId] { - let peerIds = self.peerIdsForLocation(.peer(messageIndex.id.peerId)) + let peerIds = self.peerIdsForLocation(.peer(messageIndex.id.peerId), tagMask: nil) switch peerIds { case let .associated(_, messageId): if let messageId = messageId, let readState = self.readStateTable.getCombinedState(messageId.peerId), readState.count != 0 { @@ -1701,7 +1682,7 @@ public final class Postbox { if let states = initialCombinedStates?.states { for (namespace, state) in states { if namespace != messageIndex.id.namespace && state.count != 0 { - if let item = self.messageHistoryTable.fetch(peerId: messageIndex.id.peerId, namespace: namespace, tag: nil, threadId: nil, from: MessageIndex(id: MessageId(peerId: messageIndex.id.peerId, namespace: namespace, id: 1), timestamp: messageIndex.timestamp), includeFrom: true, to: MessageIndex.lowerBound(peerId: messageIndex.id.peerId, namespace: namespace), limit: 1).first { + if let item = self.messageHistoryTable.fetch(peerId: messageIndex.id.peerId, namespace: namespace, tag: nil, from: MessageIndex(id: MessageId(peerId: messageIndex.id.peerId, namespace: namespace, id: 1), timestamp: messageIndex.timestamp), includeFrom: true, to: MessageIndex.lowerBound(peerId: messageIndex.id.peerId, namespace: namespace), limit: 1).first { resultIds.append(contentsOf: self.messageHistoryTable.applyInteractiveMaxReadIndex(postbox: self, messageIndex: item.index, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations)) } } @@ -2352,159 +2333,93 @@ public final class Postbox { } } - func resolvedChatLocationInput(chatLocation: ChatLocationInput) -> Signal<(ResolvedChatLocationInput, Bool), NoError> { - switch chatLocation { - case let .peer(peerId): - return .single((.peer(peerId), false)) - case let .external(_, input): - var isHoleFill = false - return input - |> map { value -> (ResolvedChatLocationInput, Bool) in - let wasHoleFill = isHoleFill - isHoleFill = true - return (.external(value), wasHoleFill) - } - } - } - - func peerIdsForLocation(_ chatLocation: ResolvedChatLocationInput) -> MessageHistoryViewInput { - var peerIds: MessageHistoryViewInput + func peerIdsForLocation(_ chatLocation: ChatLocation, tagMask: MessageTags?) -> MessageHistoryViewPeerIds { + var peerIds: MessageHistoryViewPeerIds switch chatLocation { case let .peer(peerId): peerIds = .single(peerId) if let associatedMessageId = self.cachedPeerDataTable.get(peerId)?.associatedHistoryMessageId, associatedMessageId.peerId != peerId { peerIds = .associated(peerId, associatedMessageId) } - case let .external(input): - peerIds = .external(input) } return peerIds } - public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocationInput, count: Int, clipHoles: Bool = true, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { - return self.resolvedChatLocationInput(chatLocation: chatLocation) - |> mapToSignal { chatLocationData in - let (chatLocation, isHoleFill) = chatLocationData + public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocation, count: Int, clipHoles: Bool = true, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + return self.transactionSignal(userInteractive: true, { subscriber, transaction in + let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask) - let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> = self.transactionSignal(userInteractive: true, { subscriber, transaction in - let peerIds = self.peerIdsForLocation(chatLocation) - - var anchor: HistoryViewInputAnchor = .upperBound - switch peerIds { - case let .single(peerId): - if self.chatListTable.getPeerChatListIndex(peerId: peerId) != nil { - if let combinedState = self.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 { - switch state.1 { - case let .idBased(maxIncomingReadId, _, _, _, _): - anchor = .message(MessageId(peerId: peerId, namespace: state.0, id: maxIncomingReadId)) - case let .indexBased(maxIncomingReadIndex, _, _, _): - anchor = .index(maxIncomingReadIndex) - } - } else if let scrollIndex = self.peerChatInterfaceStateTable.get(peerId)?.historyScrollMessageIndex { - anchor = .index(scrollIndex) + var anchor: HistoryViewInputAnchor = .upperBound + switch peerIds { + case let .single(peerId): + if self.chatListTable.getPeerChatListIndex(peerId: peerId) != nil { + if let combinedState = self.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 { + switch state.1 { + case let .idBased(maxIncomingReadId, _, _, _, _): + anchor = .message(MessageId(peerId: peerId, namespace: state.0, id: maxIncomingReadId)) + case let .indexBased(maxIncomingReadIndex, _, _, _): + anchor = .index(maxIncomingReadIndex) } - } - case let .associated(mainId, associatedId): - var ids: [PeerId] = [] - ids.append(mainId) - if let associatedId = associatedId { - ids.append(associatedId.peerId) - } - - var found = false - loop: for peerId in ids.reversed() { - if self.chatListTable.getPeerChatListIndex(peerId: mainId) != nil, let combinedState = self.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 { - found = true - switch state.1 { - case let .idBased(maxIncomingReadId, _, _, _, _): - anchor = .message(MessageId(peerId: peerId, namespace: state.0, id: maxIncomingReadId)) - case let .indexBased(maxIncomingReadIndex, _, _, _): - anchor = .index(maxIncomingReadIndex) - } - break loop - } - } - - if !found { - if let scrollIndex = self.peerChatInterfaceStateTable.get(mainId)?.historyScrollMessageIndex { - anchor = .index(scrollIndex) - } - } - case let .external(input): - if let maxReadMessageId = input.maxReadMessageId { - anchor = .message(maxReadMessageId) - } else { - anchor = .upperBound + } else if let scrollIndex = self.peerChatInterfaceStateTable.get(peerId)?.historyScrollMessageIndex { + anchor = .index(scrollIndex) } } - return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) - }) + case let .associated(mainId, associatedId): + var ids: [PeerId] = [] + ids.append(mainId) + if let associatedId = associatedId { + ids.append(associatedId.peerId) + } - return signal - |> map { (view, updateType, data) -> (MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?) in - if isHoleFill { - return (view, .FillHole, data) - } else { - return (view, updateType, data) + var found = false + loop: for peerId in ids.reversed() { + if self.chatListTable.getPeerChatListIndex(peerId: mainId) != nil, let combinedState = self.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 { + found = true + switch state.1 { + case let .idBased(maxIncomingReadId, _, _, _, _): + anchor = .message(MessageId(peerId: peerId, namespace: state.0, id: maxIncomingReadId)) + case let .indexBased(maxIncomingReadIndex, _, _, _): + anchor = .index(maxIncomingReadIndex) + } + break loop + } + } + + if !found { + if let scrollIndex = self.peerChatInterfaceStateTable.get(mainId)?.historyScrollMessageIndex { + anchor = .index(scrollIndex) + } } } + return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) + }) + } + + public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, clipHoles: Bool = true, messageId: MessageId, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + return self.transactionSignal { subscriber, transaction in + let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask) + return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: .message(messageId), fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) } } - public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocationInput, count: Int, clipHoles: Bool = true, messageId: MessageId, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { - return self.resolvedChatLocationInput(chatLocation: chatLocation) - |> mapToSignal { chatLocationData in - let (chatLocation, isHoleFill) = chatLocationData - let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> = self.transactionSignal { subscriber, transaction in - let peerIds = self.peerIdsForLocation(chatLocation) - return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: .message(messageId), fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) - } - - return signal - |> map { (view, updateType, data) -> (MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?) in - if isHoleFill { - return (view, .FillHole, data) - } else { - return (view, updateType, data) - } - } + public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, anchor: HistoryViewInputAnchor, count: Int, clipHoles: Bool = true, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + return self.transactionSignal { subscriber, transaction in + let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask) + + return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) } } - public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocationInput, anchor: HistoryViewInputAnchor, count: Int, clipHoles: Bool = true, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { - return self.resolvedChatLocationInput(chatLocation: chatLocation) - |> mapToSignal { chatLocationData -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> in - let (chatLocation, isHoleFill) = chatLocationData - let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> = self.transactionSignal { subscriber, transaction in - let peerIds = self.peerIdsForLocation(chatLocation) - - return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) - } - - return signal - |> map { viewData -> (MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?) in - let (view, updateType, data) = viewData - if isHoleFill { - return (view, .FillHole, data) - } else { - return (view, updateType, data) - } - } - } - } - - private func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewInput, count: Int, clipHoles: Bool, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable { + private func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewPeerIds, count: Int, clipHoles: Bool, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable { var topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?] = [:] - var mainPeerIdForTopTaggedMessages: PeerId? + var mainPeerId: PeerId? switch peerIds { case let .single(id): - mainPeerIdForTopTaggedMessages = id + mainPeerId = id case let .associated(id, _): - mainPeerIdForTopTaggedMessages = id - case .external: - mainPeerIdForTopTaggedMessages = nil + mainPeerId = id } - if let peerId = mainPeerIdForTopTaggedMessages { + if let peerId = mainPeerId { for namespace in topTaggedMessageIdNamespaces { if let messageId = self.peerChatTopTaggedMessageIdsTable.get(peerId: peerId, namespace: namespace) { if let index = self.messageHistoryIndexTable.getIndex(messageId) { @@ -2538,9 +2453,6 @@ public final class Postbox { } } additionalDataEntries.append(.cachedPeerDataMessages(peerId, messages)) - case let .message(id): - let message = self.getMessage(id) - additionalDataEntries.append(.message(id, message)) case let .peerChatState(peerId): additionalDataEntries.append(.peerChatState(peerId, self.peerChatStateTable.get(peerId) as? PeerChatState)) case .totalUnreadState: @@ -2579,8 +2491,6 @@ public final class Postbox { if let readState = self.readStateTable.getCombinedState(peerId) { transientReadStates = .peer([peerId: readState]) } - case .external: - transientReadStates = nil } if let fixedCombinedReadStates = fixedCombinedReadStates { @@ -2607,8 +2517,6 @@ public final class Postbox { initialData = self.initialMessageHistoryData(peerId: peerId) case let .associated(peerId, _): initialData = self.initialMessageHistoryData(peerId: peerId) - case let .external(input): - initialData = self.initialMessageHistoryData(peerId: input.peerId) } subscriber.putNext((MessageHistoryView(mutableView), initialUpdateType, initialData)) @@ -3270,7 +3178,7 @@ public final class Postbox { var remainingLimit = limit var index = MessageIndex.upperBound(peerId: peerId, namespace: namespace) while remainingLimit > 0 { - let messages = self.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: nil, threadId: nil, from: index, includeFrom: false, to: MessageIndex.lowerBound(peerId: peerId, namespace: namespace), limit: 32) + let messages = self.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: nil, from: index, includeFrom: false, to: MessageIndex.lowerBound(peerId: peerId, namespace: namespace), limit: 32) for message in messages { let attributes = MessageHistoryTable.renderMessageAttributes(message) if !f(message.id, attributes) { diff --git a/submodules/Postbox/Sources/ViewTracker.swift b/submodules/Postbox/Sources/ViewTracker.swift index 0abae1dd7d..6b7de365ee 100644 --- a/submodules/Postbox/Sources/ViewTracker.swift +++ b/submodules/Postbox/Sources/ViewTracker.swift @@ -29,9 +29,6 @@ final class ViewTracker { private let messageHistoryHolesView = MutableMessageHistoryHolesView() private let messageHistoryHolesViewSubscribers = Bag>() - private let externalMessageHistoryHolesView = MutableMessageHistoryExternalHolesView() - private let externalMessageHistoryHolesViewSubscribers = Bag>() - private let chatListHolesView = MutableChatListHolesView() private let chatListHolesViewSubscribers = Bag>() @@ -312,7 +309,7 @@ final class ViewTracker { case .associated: var ids = Set() switch mutableView.peerIds { - case .single, .external: + case .single: assertionFailure() case let .associated(mainPeerId, associatedId): ids.insert(mainPeerId) @@ -329,8 +326,6 @@ final class ViewTracker { } } } - case .external: - break } mutableView.updatePeerIds(transaction: transaction) diff --git a/submodules/PresentationDataUtils/BUCK b/submodules/PresentationDataUtils/BUCK index 84fa4c2fdc..533f10d829 100644 --- a/submodules/PresentationDataUtils/BUCK +++ b/submodules/PresentationDataUtils/BUCK @@ -14,7 +14,6 @@ static_library( "//submodules/ItemListUI:ItemListUI", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/OverlayStatusController:OverlayStatusController", - "//submodules/UrlWhitelist:UrlWhitelist", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/PresentationDataUtils/BUILD b/submodules/PresentationDataUtils/BUILD index 18415391b2..022f9f42c3 100644 --- a/submodules/PresentationDataUtils/BUILD +++ b/submodules/PresentationDataUtils/BUILD @@ -15,7 +15,6 @@ swift_library( "//submodules/ItemListUI:ItemListUI", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/OverlayStatusController:OverlayStatusController", - "//submodules/UrlWhitelist:UrlWhitelist", ], visibility = [ "//visibility:public", diff --git a/submodules/PresentationDataUtils/Sources/OpenUrl.swift b/submodules/PresentationDataUtils/Sources/OpenUrl.swift deleted file mode 100644 index 9a7f4ea544..0000000000 --- a/submodules/PresentationDataUtils/Sources/OpenUrl.swift +++ /dev/null @@ -1,62 +0,0 @@ -import Foundation -import Display -import SwiftSignalKit -import AccountContext -import OverlayStatusController -import UrlWhitelist - -public func openUserGeneratedUrl(context: AccountContext, url: String, concealed: Bool, present: @escaping (ViewController) -> Void, openResolved: @escaping (ResolvedUrl) -> Void) { - var concealed = concealed - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - let openImpl: () -> Void = { - let disposable = MetaDisposable() - var cancelImpl: (() -> Void)? - let progressSignal = Signal { subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { - cancelImpl?() - })) - present(controller) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = progressSignal.start() - - cancelImpl = { - disposable.dispose() - } - disposable.set((context.sharedContext.resolveUrl(account: context.account, url: url) - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - |> deliverOnMainQueue).start(next: { result in - openResolved(result) - })) - } - - let (parsedString, parsedConcealed) = parseUrl(url: url, wasConcealed: concealed) - concealed = parsedConcealed - - if concealed { - var rawDisplayUrl: String = parsedString - let maxLength = 180 - if rawDisplayUrl.count > maxLength { - rawDisplayUrl = String(rawDisplayUrl[.. }, opaque: false)?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0), topCapHeight: Int(diameter / 2.0)) } - -public struct SearchBarToken { - public struct Style { - public let backgroundColor: UIColor - public let foregroundColor: UIColor - public let strokeColor: UIColor - - public init(backgroundColor: UIColor, foregroundColor: UIColor, strokeColor: UIColor) { - self.backgroundColor = backgroundColor - self.foregroundColor = foregroundColor - self.strokeColor = strokeColor - } - } - - public let id: AnyHashable - public let icon: UIImage? - public let title: String - public let style: Style? - - public init(id: AnyHashable, icon: UIImage?, title: String, style: Style? = nil) { - self.id = id - self.icon = icon - self.title = title - self.style = style - } -} - -private final class TokenNode: ASDisplayNode { - var theme: SearchBarNodeTheme - let token: SearchBarToken - let iconNode: ASImageNode - let titleNode: ASTextNode - let backgroundNode: ASImageNode - - var isSelected: Bool = false - var isCollapsed: Bool = false - - var tapped: (() -> Void)? - - init(theme: SearchBarNodeTheme, token: SearchBarToken) { - self.theme = theme - self.token = token - self.iconNode = ASImageNode() - self.iconNode.displaysAsynchronously = false - self.iconNode.displayWithoutProcessing = true - self.titleNode = ASTextNode() - self.titleNode.isUserInteractionEnabled = false - self.titleNode.displaysAsynchronously = false - self.titleNode.maximumNumberOfLines = 1 - self.backgroundNode = ASImageNode() - self.backgroundNode.displaysAsynchronously = false - self.backgroundNode.displayWithoutProcessing = true - - super.init() - - self.clipsToBounds = true - - self.addSubnode(self.backgroundNode) - - let backgroundColor = token.style?.backgroundColor ?? theme.inputIcon - let strokeColor = token.style?.strokeColor ?? backgroundColor - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 8.0, color: backgroundColor, strokeColor: strokeColor, strokeWidth: UIScreenPixel, backgroundColor: nil) - - let foregroundColor = token.style?.foregroundColor ?? .white - self.iconNode.image = generateTintedImage(image: token.icon, color: foregroundColor) - self.addSubnode(self.iconNode) - - self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(17.0), textColor: foregroundColor) - self.addSubnode(self.titleNode) - } - - override func didLoad() { - super.didLoad() - - self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture))) - } - - @objc private func tapGesture() { - self.tapped?() - } - - func animateIn() { - let targetFrame = self.frame - self.layer.animateFrame(from: CGRect(origin: targetFrame.origin, size: CGSize(width: 1.0, height: targetFrame.height)), to: targetFrame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) - self.iconNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) - self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) - self.titleNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) - self.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) - } - - func animateOut() { - self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { [weak self] _ in - self?.removeFromSupernode() - }) - self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - } - - func update(theme: SearchBarNodeTheme, token: SearchBarToken, isSelected: Bool, isCollapsed: Bool) { - let wasSelected = self.isSelected - self.isSelected = isSelected - self.isCollapsed = isCollapsed - - if theme !== self.theme || isSelected != wasSelected { - let backgroundColor = isSelected ? self.theme.accent : (token.style?.backgroundColor ?? self.theme.inputIcon) - let strokeColor = isSelected ? backgroundColor : (token.style?.strokeColor ?? backgroundColor) - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 8.0, color: backgroundColor, strokeColor: strokeColor, strokeWidth: UIScreenPixel, backgroundColor: nil) - - let foregroundColor = isSelected ? .white : (token.style?.foregroundColor ?? .white) - - if let image = token.icon { - self.iconNode.image = generateTintedImage(image: image, color: foregroundColor) - } - self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(17.0), textColor: foregroundColor) - } - } - - func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - let height: CGFloat = 24.0 - - var leftInset: CGFloat = 3.0 - if let icon = self.iconNode.image { - leftInset += 1.0 - transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: leftInset, y: floor((height - icon.size.height) / 2.0)), size: icon.size)) - leftInset += icon.size.width + 3.0 - } - - let iconSize = self.token.icon?.size ?? CGSize() - let titleSize = self.titleNode.measure(CGSize(width: constrainedSize.width - 6.0, height: constrainedSize.height)) - var width = titleSize.width + 6.0 - if !iconSize.width.isZero { - width += iconSize.width + 7.0 - } - - let size = CGSize(width: self.isCollapsed ? height : width, height: height) - transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize)) - - return size - } -} - private class SearchBarTextField: UITextField { public var didDeleteBackwardWhileEmpty: (() -> Void)? @@ -178,164 +37,6 @@ private class SearchBarTextField: UITextField { } } - var tokenNodes: [AnyHashable: TokenNode] = [:] - var tokens: [SearchBarToken] = [] { - didSet { - self._selectedTokenIndex = nil - self.layoutTokens(transition: .animated(duration: 0.2, curve: .easeInOut)) - self.setNeedsLayout() - self.updateCursorColor() - } - } - - var _selectedTokenIndex: Int? - var selectedTokenIndex: Int? { - get { - return self._selectedTokenIndex - } - set { - _selectedTokenIndex = newValue - self.layoutTokens(transition: .animated(duration: 0.2, curve: .easeInOut)) - self.setNeedsLayout() - self.updateCursorColor() - } - } - - private func updateCursorColor() { - if self._selectedTokenIndex != nil { - super.tintColor = UIColor.clear - } else { - super.tintColor = self._tintColor - } - } - - var _tintColor: UIColor = .black - override var tintColor: UIColor! { - get { - return super.tintColor - } - set { - if newValue != UIColor.clear { - self._tintColor = newValue - - if self.selectedTokenIndex == nil { - super.tintColor = newValue - } - } - } - } - - var theme: SearchBarNodeTheme - - fileprivate func layoutTokens(transition: ContainedViewLayoutTransition = .immediate) { - for i in 0 ..< self.tokens.count { - let token = self.tokens[i] - - let tokenNode: TokenNode - if let current = self.tokenNodes[token.id] { - tokenNode = current - } else { - tokenNode = TokenNode(theme: self.theme, token: token) - self.tokenNodes[token.id] = tokenNode - } - tokenNode.tapped = { [weak self] in - self?.selectedTokenIndex = i - self?.becomeFirstResponder() - } - let isSelected = i == self.selectedTokenIndex - let isCollapsed = !isSelected && (i < self.tokens.count - 1 || !(self.text?.isEmpty ?? true)) - tokenNode.update(theme: self.theme, token: token, isSelected: isSelected, isCollapsed: isCollapsed) - } - var removeKeys: [AnyHashable] = [] - for (id, _) in self.tokenNodes { - if !self.tokens.contains(where: { $0.id == id }) { - removeKeys.append(id) - } - } - for id in removeKeys { - if let itemNode = self.tokenNodes.removeValue(forKey: id) { - if transition.isAnimated { - itemNode.animateOut() - } else { - itemNode.removeFromSupernode() - } - } - } - - var tokenSizes: [(AnyHashable, CGSize, TokenNode, Bool)] = [] - var totalRawTabSize: CGFloat = 0.0 - - for token in self.tokens { - guard let tokenNode = self.tokenNodes[token.id] else { - continue - } - let wasAdded = tokenNode.view.superview == nil - var tokenNodeTransition = transition - if wasAdded { - tokenNodeTransition = .immediate - self.addSubnode(tokenNode) - } - - let nodeSize = tokenNode.updateLayout(constrainedSize: self.bounds.size, transition: tokenNodeTransition) - tokenSizes.append((token.id, nodeSize, tokenNode, wasAdded)) - totalRawTabSize += nodeSize.width - } - - let minSpacing: CGFloat = 6.0 - - let resolvedSideInset: CGFloat = 10.0 - var leftOffset: CGFloat = 0.0 - if !tokenSizes.isEmpty { - leftOffset += resolvedSideInset - } - - var longTitlesWidth: CGFloat = resolvedSideInset - for i in 0 ..< tokenSizes.count { - let (_, paneNodeSize, _, _) = tokenSizes[i] - longTitlesWidth += paneNodeSize.width - if i != tokenSizes.count - 1 { - longTitlesWidth += minSpacing - } - } - longTitlesWidth += resolvedSideInset - - let verticalOffset: CGFloat = 0.0 - var horizontalOffset: CGFloat = 0.0 - for i in 0 ..< tokenSizes.count { - let (_, nodeSize, tokenNode, wasAdded) = tokenSizes[i] - let tokenNodeTransition = transition - - let nodeFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((self.frame.height - nodeSize.height) / 2.0) + verticalOffset), size: nodeSize) - - if wasAdded { - if horizontalOffset > 0.0 { - tokenNode.frame = nodeFrame.offsetBy(dx: horizontalOffset, dy: 0.0) - tokenNodeTransition.updatePosition(node: tokenNode, position: nodeFrame.center) - } else { - tokenNode.frame = nodeFrame - } - tokenNode.animateIn() - } else { - if nodeFrame.width < tokenNode.frame.width { - horizontalOffset += tokenNode.frame.width - nodeFrame.width - } - tokenNodeTransition.updateFrame(node: tokenNode, frame: nodeFrame) - } - - tokenNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -minSpacing / 2.0, bottom: 0.0, right: -minSpacing / 2.0) - - leftOffset += nodeSize.width + minSpacing - } - - if !tokenSizes.isEmpty { - leftOffset -= 6.0 - } - - self.tokensWidth = leftOffset - } - - private var tokensWidth: CGFloat = 0.0 - private let measurePrefixLabel: ImmediateTextNode let prefixLabel: ImmediateTextNode var prefixString: NSAttributedString? { @@ -346,9 +47,7 @@ private class SearchBarTextField: UITextField { } } - init(theme: SearchBarNodeTheme) { - self.theme = theme - + override init(frame: CGRect) { self.placeholderLabel = ImmediateTextNode() self.placeholderLabel.isUserInteractionEnabled = false self.placeholderLabel.displaysAsynchronously = false @@ -367,7 +66,7 @@ private class SearchBarTextField: UITextField { self.prefixLabel.maximumNumberOfLines = 1 self.prefixLabel.truncationMode = .byTruncatingTail - super.init(frame: CGRect()) + super.init(frame: frame) self.addSubnode(self.placeholderLabel) self.addSubnode(self.prefixLabel) @@ -397,8 +96,7 @@ private class SearchBarTextField: UITextField { if bounds.size.width.isZero { return CGRect(origin: CGPoint(), size: CGSize()) } - var rect = bounds.insetBy(dx: 7.0, dy: 4.0) - rect.origin.y += 1.0 + var rect = bounds.insetBy(dx: 4.0, dy: 4.0) let prefixSize = self.measurePrefixLabel.updateLayout(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height)) if !prefixSize.width.isZero { @@ -406,10 +104,6 @@ private class SearchBarTextField: UITextField { rect.origin.x += prefixOffset rect.size.width -= prefixOffset } - if !self.tokensWidth.isZero { - rect.origin.x += self.tokensWidth - rect.size.width -= self.tokensWidth - } rect.size.width = max(rect.size.width, 10.0) return rect } @@ -441,16 +135,7 @@ private class SearchBarTextField: UITextField { } override func deleteBackward() { - var processed = false - if let selectedRange = self.selectedTextRange { - let cursorPosition = self.offset(from: self.beginningOfDocument, to: selectedRange.start) - if cursorPosition == 0 && !self.tokens.isEmpty && self.selectedTokenIndex == nil { - self.selectedTokenIndex = self.tokens.count - 1 - processed = true - } - } - - if !processed && (self.text == nil || self.text!.isEmpty) { + if self.text == nil || self.text!.isEmpty { self.didDeleteBackwardWhileEmpty?() } super.deleteBackward() @@ -570,9 +255,6 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { public var textUpdated: ((String, String?) -> Void)? public var textReturned: ((String) -> Void)? public var clearPrefix: (() -> Void)? - public var clearTokens: (() -> Void)? - - public var tokensUpdated: (([SearchBarToken]) -> Void)? private let backgroundNode: ASDisplayNode private let separatorNode: ASDisplayNode @@ -591,15 +273,6 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { } } - public var tokens: [SearchBarToken] { - get { - return self.textField.tokens - } set { - self.textField.tokens = newValue - self.updateIsEmpty(animated: true) - } - } - public var prefixString: NSAttributedString? { get { return self.textField.prefixString @@ -689,7 +362,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.iconNode.displaysAsynchronously = false self.iconNode.displayWithoutProcessing = true - self.textField = SearchBarTextField(theme: theme) + self.textField = SearchBarTextField() self.textField.accessibilityTraits = .searchField self.textField.autocorrectionType = .no self.textField.returnKeyType = .search @@ -699,6 +372,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.clearButton.imageNode.displaysAsynchronously = false self.clearButton.imageNode.displayWithoutProcessing = true self.clearButton.displaysAsynchronously = false + self.clearButton.isHidden = true self.cancelButton = HighlightableButtonNode(pointerStyle: .default) self.cancelButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) @@ -719,24 +393,15 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.textField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged) self.textField.didDeleteBackwardWhileEmpty = { [weak self] in - guard let strongSelf = self else { - return - } - if let index = strongSelf.textField.selectedTokenIndex { - strongSelf.tokens.remove(at: index) - strongSelf.tokensUpdated?(strongSelf.tokens) - } else { - strongSelf.clearPressed() - } + self?.clearPressed() } self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside) self.clearButton.addTarget(self, action: #selector(self.clearPressed), forControlEvents: .touchUpInside) self.updateThemeAndStrings(theme: theme, strings: strings) - self.updateIsEmpty(animated: false) } - + public func updateThemeAndStrings(theme: SearchBarNodeTheme, strings: PresentationStrings) { if self.theme != theme || self.strings !== strings { self.cancelButton.setAttributedTitle(NSAttributedString(string: self.cancelText ?? strings.Common_Cancel, font: self.cancelText != nil ? Font.semibold(17.0) : Font.regular(17.0), textColor: theme.accent), for: []) @@ -787,16 +452,16 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { if let iconImage = self.iconNode.image { let iconSize = iconImage.size - transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 5.0, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - iconSize.height) / 2.0) - UIScreenPixel), size: iconSize)) + transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 8.0, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - iconSize.height) / 2.0)), size: iconSize)) } if let activityIndicator = self.activityIndicator { let indicatorSize = activityIndicator.measure(CGSize(width: 32.0, height: 32.0)) - transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 9.0 + UIScreenPixel, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - indicatorSize.height) / 2.0)), size: indicatorSize)) + transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 7.0, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - indicatorSize.height) / 2.0)), size: indicatorSize)) } let clearSize = self.clearButton.measure(CGSize(width: 100.0, height: 100.0)) - transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.maxX - 6.0 - clearSize.width, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - clearSize.height) / 2.0)), size: clearSize)) + transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.maxX - 8.0 - clearSize.width, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - clearSize.height) / 2.0)), size: clearSize)) self.textField.frame = textFrame } @@ -834,7 +499,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.textBackgroundNode.layer.animateFrame(from: initialTextBackgroundFrame, to: self.textBackgroundNode.frame, duration: duration, timingFunction: timingFunction) let textFieldFrame = self.textField.frame - let initialLabelNodeFrame = CGRect(origin: node.labelNode.frame.offsetBy(dx: initialTextBackgroundFrame.origin.x - 7.0, dy: initialTextBackgroundFrame.origin.y - 8.0).origin, size: textFieldFrame.size) + let initialLabelNodeFrame = CGRect(origin: node.labelNode.frame.offsetBy(dx: initialTextBackgroundFrame.origin.x - 4.0, dy: initialTextBackgroundFrame.origin.y - 7.0).origin, size: textFieldFrame.size) self.textField.layer.animateFrame(from: initialLabelNodeFrame, to: self.textField.frame, duration: duration, timingFunction: timingFunction) let iconFrame = self.iconNode.frame @@ -850,9 +515,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.textField.resignFirstResponder() if clear { self.textField.text = nil - self.textField.tokens = [] - self.textField.prefixString = nil - self.textField.placeholderLabel.alpha = 1.0 + self.textField.placeholderLabel.isHidden = false } } @@ -913,7 +576,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { transitionBackgroundNode.layer.animateFrame(from: self.textBackgroundNode.frame, to: targetTextBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) let textFieldFrame = self.textField.frame - let targetLabelNodeFrame = CGRect(origin: CGPoint(x: node.labelNode.frame.minX + targetTextBackgroundFrame.origin.x - 7.0, y: targetTextBackgroundFrame.minY + floorToScreenPixels((targetTextBackgroundFrame.size.height - textFieldFrame.size.height) / 2.0) - UIScreenPixel), size: textFieldFrame.size) + let targetLabelNodeFrame = CGRect(origin: CGPoint(x: node.labelNode.frame.minX + targetTextBackgroundFrame.origin.x - 4.0, y: targetTextBackgroundFrame.minY + floorToScreenPixels((targetTextBackgroundFrame.size.height - textFieldFrame.size.height) / 2.0)), size: textFieldFrame.size) self.textField.layer.animateFrame(from: self.textField.frame, to: targetLabelNodeFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { if let snapshot = node.labelNode.layer.snapshotContentTree() { @@ -963,42 +626,22 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { @objc private func textFieldDidChange(_ textField: UITextField) { self.updateIsEmpty() - if let _ = self.textField.selectedTokenIndex { - self.textField.selectedTokenIndex = nil - } if let textUpdated = self.textUpdated { textUpdated(textField.text ?? "", textField.textInputMode?.primaryLanguage) - self.textField.layoutTokens(transition: .animated(duration: 0.2, curve: .easeInOut)) } } - public func textFieldDidEndEditing(_ textField: UITextField) { - self.textField.selectedTokenIndex = nil - } - public func selectAll() { self.textField.becomeFirstResponder() self.textField.selectAll(nil) } - public func selectLastToken() { - if !self.textField.tokens.isEmpty { - self.textField.selectedTokenIndex = self.textField.tokens.count - 1 - self.textField.becomeFirstResponder() + private func updateIsEmpty() { + let isEmpty = !(self.textField.text?.isEmpty ?? true) + if isEmpty != self.textField.placeholderLabel.isHidden { + self.textField.placeholderLabel.isHidden = isEmpty } - } - - private func updateIsEmpty(animated: Bool = false) { - let isEmpty = (self.textField.text?.isEmpty ?? true) && self.tokens.isEmpty - - let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .spring) : .immediate - let placeholderTransition = !isEmpty ? .immediate : transition - placeholderTransition.updateAlpha(node: self.textField.placeholderLabel, alpha: isEmpty ? 1.0 : 0.0) - - let clearIsHidden = isEmpty && self.prefixString == nil - transition.updateAlpha(node: self.clearButton, alpha: clearIsHidden ? 0.0 : 1.0) - transition.updateTransformScale(node: self.clearButton, scale: clearIsHidden ? 0.2 : 1.0) - self.clearButton.isUserInteractionEnabled = !clearIsHidden + self.clearButton.isHidden = !isEmpty && self.prefixString == nil } @objc private func cancelPressed() { @@ -1012,9 +655,6 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { if self.prefixString != nil { self.clearPrefix?() } - if !self.tokens.isEmpty { - self.clearTokens?() - } } else { self.textField.text = "" self.textFieldDidChange(self.textField) diff --git a/submodules/SearchBarNode/Sources/SearchBarPlaceholderNode.swift b/submodules/SearchBarNode/Sources/SearchBarPlaceholderNode.swift index 499ee864fd..78ad5c0b3f 100644 --- a/submodules/SearchBarNode/Sources/SearchBarPlaceholderNode.swift +++ b/submodules/SearchBarNode/Sources/SearchBarPlaceholderNode.swift @@ -143,7 +143,7 @@ public class SearchBarPlaceholderNode: ASDisplayNode { var iconSize = CGSize() var totalWidth = labelLayoutResult.size.width - let spacing: CGFloat = 6.0 + let spacing: CGFloat = 8.0 if let iconImage = strongSelf.iconNode.image { iconSize = iconImage.size @@ -152,7 +152,7 @@ public class SearchBarPlaceholderNode: ASDisplayNode { } var textOffset: CGFloat = 0.0 if constrainedSize.height >= 36.0 { - textOffset += 1.0 + textOffset += UIScreenPixel } let labelFrame = CGRect(origin: CGPoint(x: floor((constrainedSize.width - totalWidth) / 2.0) + iconSize.width + spacing, y: floorToScreenPixels((height - labelLayoutResult.size.height) / 2.0) + textOffset), size: labelLayoutResult.size) transition.updateFrame(node: strongSelf.labelNode, frame: labelFrame) diff --git a/submodules/SearchUI/Sources/SearchDisplayController.swift b/submodules/SearchUI/Sources/SearchDisplayController.swift index 9843a4d8fd..e5bd24e29c 100644 --- a/submodules/SearchUI/Sources/SearchDisplayController.swift +++ b/submodules/SearchUI/Sources/SearchDisplayController.swift @@ -14,7 +14,6 @@ public enum SearchDisplayControllerMode { public final class SearchDisplayController { private let searchBar: SearchBarNode private let mode: SearchDisplayControllerMode - private let backgroundNode: ASDisplayNode public let contentNode: SearchDisplayControllerContentNode private var hasSeparator: Bool @@ -26,9 +25,6 @@ public final class SearchDisplayController { public init(presentationData: PresentationData, mode: SearchDisplayControllerMode = .navigation, placeholder: String? = nil, hasSeparator: Bool = false, contentNode: SearchDisplayControllerContentNode, cancel: @escaping () -> Void) { self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: hasSeparator), strings: presentationData.strings, fieldStyle: .modern, forceSeparator: hasSeparator) - self.backgroundNode = ASDisplayNode() - self.backgroundNode.backgroundColor = presentationData.theme.chatList.backgroundColor - self.mode = mode self.contentNode = contentNode self.hasSeparator = hasSeparator @@ -36,9 +32,6 @@ public final class SearchDisplayController { self.searchBar.textUpdated = { [weak contentNode] text, _ in contentNode?.searchTextUpdated(text: text) } - self.searchBar.tokensUpdated = { [weak contentNode] tokens in - contentNode?.searchTokensUpdated(tokens: tokens) - } self.searchBar.cancel = { [weak self] in self?.isDeactivating = true cancel() @@ -46,9 +39,6 @@ public final class SearchDisplayController { self.searchBar.clearPrefix = { [weak contentNode] in contentNode?.searchTextClearPrefix() } - self.searchBar.clearTokens = { [weak contentNode] in - contentNode?.searchTextClearTokens() - } self.contentNode.cancel = { [weak self] in self?.isDeactivating = true cancel() @@ -56,16 +46,9 @@ public final class SearchDisplayController { self.contentNode.dismissInput = { [weak self] in self?.searchBar.deactivate(clear: false) } - self.contentNode.setQuery = { [weak self] prefix, tokens, query in - if let strongSelf = self { - strongSelf.searchBar.prefixString = prefix - let previousTokens = strongSelf.searchBar.tokens - strongSelf.searchBar.tokens = tokens - if previousTokens.count < tokens.count { - strongSelf.searchBar.selectLastToken() - } - strongSelf.searchBar.text = query - } + self.contentNode.setQuery = { [weak self] prefix, query in + self?.searchBar.prefixString = prefix + self?.searchBar.text = query } if let placeholder = placeholder { self.searchBar.placeholderString = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: presentationData.theme.rootController.navigationSearchBar.inputPlaceholderTextColor) @@ -89,8 +72,6 @@ public final class SearchDisplayController { public func updatePresentationData(_ presentationData: PresentationData) { self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: self.hasSeparator), strings: presentationData.strings) self.contentNode.updatePresentationData(presentationData) - - self.backgroundNode.backgroundColor = presentationData.theme.chatList.backgroundColor } public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { @@ -119,9 +100,8 @@ public final class SearchDisplayController { self.containerLayout = (layout, navigationBarFrame.maxY) - transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: layout.size)) transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition) + self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarFrame.maxY, transition: transition) } public func activate(insertSubnode: (ASDisplayNode, Bool) -> Void, placeholder: SearchBarPlaceholderNode?) { @@ -129,7 +109,6 @@ public final class SearchDisplayController { return } - insertSubnode(self.backgroundNode, false) insertSubnode(self.contentNode, false) self.contentNode.frame = CGRect(origin: CGPoint(), size: layout.size) @@ -146,10 +125,8 @@ public final class SearchDisplayController { let contentNodePosition = self.contentNode.layer.position -// self.contentNode.layer.animatePosition(from: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (initialTextBackgroundFrame.maxY + 8.0 - contentNavigationBarHeight)), to: contentNodePosition, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) - self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) + self.contentNode.layer.animatePosition(from: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (initialTextBackgroundFrame.maxY + 8.0 - contentNavigationBarHeight)), to: contentNodePosition, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) - self.contentNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) self.searchBar.placeholderString = placeholder.placeholderString } @@ -201,7 +178,6 @@ public final class SearchDisplayController { }) } - let backgroundNode = self.backgroundNode let contentNode = self.contentNode if animated { if let placeholder = placeholder, let (layout, navigationBarHeight) = self.containerLayout { @@ -218,11 +194,7 @@ public final class SearchDisplayController { contentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak contentNode] _ in contentNode?.removeFromSupernode() }) - backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak backgroundNode] _ in - backgroundNode?.removeFromSupernode() - }) } else { - backgroundNode.removeFromSupernode() contentNode.removeFromSupernode() } } diff --git a/submodules/SearchUI/Sources/SearchDisplayControllerContentNode.swift b/submodules/SearchUI/Sources/SearchDisplayControllerContentNode.swift index e9b8fd1fb8..01e11f9db3 100644 --- a/submodules/SearchUI/Sources/SearchDisplayControllerContentNode.swift +++ b/submodules/SearchUI/Sources/SearchDisplayControllerContentNode.swift @@ -4,12 +4,11 @@ import AsyncDisplayKit import Display import SwiftSignalKit import TelegramPresentationData -import SearchBarNode open class SearchDisplayControllerContentNode: ASDisplayNode { public final var dismissInput: (() -> Void)? public final var cancel: (() -> Void)? - public final var setQuery: ((NSAttributedString?, [SearchBarToken], String) -> Void)? + public final var setQuery: ((NSAttributedString?, String) -> Void)? public final var setPlaceholder: ((String) -> Void)? open var isSearching: Signal { @@ -26,15 +25,9 @@ open class SearchDisplayControllerContentNode: ASDisplayNode { open func searchTextUpdated(text: String) { } - open func searchTokensUpdated(tokens: [SearchBarToken]) { - } - open func searchTextClearPrefix() { } - open func searchTextClearTokens() { - } - open func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { } diff --git a/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift b/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift index 77c5462bd3..02eee416c4 100644 --- a/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift +++ b/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift @@ -149,9 +149,6 @@ public final class SelectablePeerNode: ASDisplayNode { if peer.peerId == context.account.peerId { text = strings.DialogList_SavedMessages overrideImage = .savedMessagesIcon - } else if peer.peerId.isReplies { - text = strings.DialogList_Replies - overrideImage = .repliesIcon } else { text = mainPeer.compactDisplayTitle if mainPeer.isDeleted { diff --git a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift index 94145bbdb7..600f8741f7 100644 --- a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift +++ b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift @@ -170,22 +170,22 @@ private final class BubbleSettingsControllerNode: ASDisplayNode, UIScrollViewDel peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) - let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message2], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) let waveformBase64 = "DAAOAAkACQAGAAwADwAMABAADQAPABsAGAALAA0AGAAfABoAHgATABgAGQAYABQADAAVABEAHwANAA0ACQAWABkACQAOAAwACQAfAAAAGQAVAAAAEwATAAAACAAfAAAAHAAAABwAHwAAABcAGQAAABQADgAAABQAHwAAAB8AHwAAAAwADwAAAB8AEwAAABoAFwAAAB8AFAAAAAAAHwAAAAAAHgAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAA=" let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))] let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes) - let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message3], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local), tapMessage: nil, clickThroughMessage: nil)) - let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message4], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) let width: CGFloat diff --git a/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift b/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift index 30fecbd5b5..8fc2ba536c 100644 --- a/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift +++ b/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift @@ -71,13 +71,13 @@ public func cachedFaqInstantPage(context: AccountContext) -> Signal, suggestAccountDeletion: Bool) -> Signal<[SettingsSearchableItem], NoError> { +func faqSearchableItems(context: AccountContext, suggestAccountDeletion: Bool) -> Signal<[SettingsSearchableItem], NoError> { let strings = context.sharedContext.currentPresentationData.with { $0 }.strings - return resolvedUrl + return cachedFaqInstantPage(context: context) |> map { resolvedUrl -> [SettingsSearchableItem] in var results: [SettingsSearchableItem] = [] var nextIndex: Int32 = 2 - if let resolvedUrl = resolvedUrl, case let .instantView(webPage, _) = resolvedUrl { + if case let .instantView(webPage, _) = resolvedUrl { if case let .Loaded(content) = webPage.content, let instantPage = content.instantPage { var processingQuestions = false var currentSection: String? diff --git a/submodules/SettingsUI/Sources/DebugController.swift b/submodules/SettingsUI/Sources/DebugController.swift index 80e166ea34..4b505dae7d 100644 --- a/submodules/SettingsUI/Sources/DebugController.swift +++ b/submodules/SettingsUI/Sources/DebugController.swift @@ -468,7 +468,6 @@ private enum DebugControllerEntry: ItemListNodeEntry { actionSheet?.dismissAnimated() let databasePath = context.account.basePath + "/postbox/db" let _ = try? FileManager.default.removeItem(atPath: databasePath) - exit(0) preconditionFailure() }), ]), ActionSheetItemGroup(items: [ diff --git a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift index 47bdbbba0a..db6abc4344 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift @@ -159,7 +159,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode { let forwardInfo = MessageForwardInfo(author: item.linkEnabled ? peers[peerId] : nil, source: nil, sourceMessageId: nil, date: 0, authorSignature: item.linkEnabled ? nil : item.peerName, psaType: nil) - let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: forwardInfo, author: nil, text: item.strings.Privacy_Forwards_PreviewMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil) + let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: forwardInfo, author: nil, text: item.strings.Privacy_Forwards_PreviewMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil) var node: ListViewItemNode? if let current = currentNode { diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift index eed38f2aee..4aac556f72 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift @@ -74,7 +74,6 @@ final class SettingsSearchItem: ItemListControllerSearch { let presentController: (ViewController, Any?) -> Void let pushController: (ViewController) -> Void let getNavigationController: (() -> NavigationController?)? - let resolvedFaqUrl: Signal let exceptionsList: Signal let archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError> let privacySettings: Signal @@ -86,7 +85,7 @@ final class SettingsSearchItem: ItemListControllerSearch { private var activity: ValuePromise = ValuePromise(ignoreRepeated: false) private let activityDisposable = MetaDisposable() - init(context: AccountContext, theme: PresentationTheme, placeholder: String, activated: Bool, updateActivated: @escaping (Bool) -> Void, presentController: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, getNavigationController: (() -> NavigationController?)?, resolvedFaqUrl: Signal, exceptionsList: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal, hasWallet: Signal, activeSessionsContext: Signal, webSessionsContext: Signal) { + init(context: AccountContext, theme: PresentationTheme, placeholder: String, activated: Bool, updateActivated: @escaping (Bool) -> Void, presentController: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, getNavigationController: (() -> NavigationController?)?, exceptionsList: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal, hasWallet: Signal, activeSessionsContext: Signal, webSessionsContext: Signal) { self.context = context self.theme = theme self.placeholder = placeholder @@ -95,7 +94,6 @@ final class SettingsSearchItem: ItemListControllerSearch { self.presentController = presentController self.pushController = pushController self.getNavigationController = getNavigationController - self.resolvedFaqUrl = resolvedFaqUrl self.exceptionsList = exceptionsList self.archivedStickerPacks = archivedStickerPacks self.privacySettings = privacySettings @@ -165,7 +163,7 @@ final class SettingsSearchItem: ItemListControllerSearch { pushController(c) }, presentController: { c, a in presentController(c, a) - }, getNavigationController: self.getNavigationController, resolvedFaqUrl: self.resolvedFaqUrl, exceptionsList: self.exceptionsList, archivedStickerPacks: self.archivedStickerPacks, privacySettings: self.privacySettings, hasWallet: self.hasWallet, activeSessionsContext: self.activeSessionsContext, webSessionsContext: self.webSessionsContext) + }, getNavigationController: self.getNavigationController, exceptionsList: self.exceptionsList, archivedStickerPacks: self.archivedStickerPacks, privacySettings: self.privacySettings, hasWallet: self.hasWallet, activeSessionsContext: self.activeSessionsContext, webSessionsContext: self.webSessionsContext) } } } @@ -362,7 +360,7 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo private var presentationDataDisposable: Disposable? private let presentationDataPromise: Promise - public init(context: AccountContext, openResult: @escaping (SettingsSearchableItem) -> Void, resolvedFaqUrl: Signal, exceptionsList: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal, hasWallet: Signal, activeSessionsContext: Signal, webSessionsContext: Signal) { + public init(context: AccountContext, openResult: @escaping (SettingsSearchableItem) -> Void, exceptionsList: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal, hasWallet: Signal, activeSessionsContext: Signal, webSessionsContext: Signal) { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationDataPromise = Promise(self.presentationData) @@ -392,9 +390,9 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo searchableItems.set(settingsSearchableItems(context: context, notificationExceptionsList: exceptionsList, archivedStickerPacks: archivedStickerPacks, privacySettings: privacySettings, hasWallet: hasWallet, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext)) let faqItems = Promise<[SettingsSearchableItem]>() - faqItems.set(faqSearchableItems(context: context, resolvedUrl: resolvedFaqUrl, suggestAccountDeletion: false)) + faqItems.set(faqSearchableItems(context: context, suggestAccountDeletion: false)) - let queryAndFoundItems = combineLatest(searchableItems.get(), faqSearchableItems(context: context, resolvedUrl: resolvedFaqUrl, suggestAccountDeletion: true)) + let queryAndFoundItems = combineLatest(searchableItems.get(), faqSearchableItems(context: context, suggestAccountDeletion: true)) |> mapToSignal { searchableItems, faqSearchableItems -> Signal<(String, [SettingsSearchableItem])?, NoError> in return self.searchQuery.get() |> mapToSignal { query -> Signal<(String, [SettingsSearchableItem])?, NoError> in @@ -658,7 +656,6 @@ private final class SettingsSearchItemNode: ItemListControllerSearchNode { let pushController: (ViewController) -> Void let presentController: (ViewController, Any?) -> Void let getNavigationController: (() -> NavigationController?)? - let resolvedFaqUrl: Signal let exceptionsList: Signal let archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError> let privacySettings: Signal @@ -668,14 +665,13 @@ private final class SettingsSearchItemNode: ItemListControllerSearchNode { var cancel: () -> Void - init(context: AccountContext, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: (() -> NavigationController?)?, resolvedFaqUrl: Signal, exceptionsList: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal, hasWallet: Signal, activeSessionsContext: Signal, webSessionsContext: Signal) { + init(context: AccountContext, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: (() -> NavigationController?)?, exceptionsList: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal, hasWallet: Signal, activeSessionsContext: Signal, webSessionsContext: Signal) { self.context = context self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.cancel = cancel self.pushController = pushController self.presentController = presentController self.getNavigationController = getNavigationController - self.resolvedFaqUrl = resolvedFaqUrl self.exceptionsList = exceptionsList self.archivedStickerPacks = archivedStickerPacks self.privacySettings = privacySettings @@ -721,7 +717,7 @@ private final class SettingsSearchItemNode: ItemListControllerSearchNode { } }) } - }, resolvedFaqUrl: self.resolvedFaqUrl, exceptionsList: self.exceptionsList, archivedStickerPacks: self.archivedStickerPacks, privacySettings: self.privacySettings, hasWallet: self.hasWallet, activeSessionsContext: self.activeSessionsContext, webSessionsContext: self.webSessionsContext), cancel: { [weak self] in + }, exceptionsList: self.exceptionsList, archivedStickerPacks: self.archivedStickerPacks, privacySettings: self.privacySettings, hasWallet: self.hasWallet, activeSessionsContext: self.activeSessionsContext, webSessionsContext: self.webSessionsContext), cancel: { [weak self] in self?.cancel() }) diff --git a/submodules/SettingsUI/Sources/SettingsController.swift b/submodules/SettingsUI/Sources/SettingsController.swift index 66ec64ff69..19fb415862 100644 --- a/submodules/SettingsUI/Sources/SettingsController.swift +++ b/submodules/SettingsUI/Sources/SettingsController.swift @@ -1661,7 +1661,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM presentControllerImpl?(c, a) }, pushController: { c in pushControllerImpl?(c) - }, getNavigationController: getNavigationControllerImpl, resolvedFaqUrl: .complete(), exceptionsList: notifyExceptions.get(), archivedStickerPacks: archivedPacks.get(), privacySettings: privacySettings.get(), hasWallet: hasWallet, activeSessionsContext: activeSessionsContextAndCountSignal |> map { $0.0 } |> distinctUntilChanged(isEqual: { $0 === $1 }), webSessionsContext: activeSessionsContextAndCountSignal |> map { $0.2 } |> distinctUntilChanged(isEqual: { $0 === $1 })) + }, getNavigationController: getNavigationControllerImpl, exceptionsList: notifyExceptions.get(), archivedStickerPacks: archivedPacks.get(), privacySettings: privacySettings.get(), hasWallet: hasWallet, activeSessionsContext: activeSessionsContextAndCountSignal |> map { $0.0 } |> distinctUntilChanged(isEqual: { $0 === $1 }), webSessionsContext: activeSessionsContextAndCountSignal |> map { $0.2 } |> distinctUntilChanged(isEqual: { $0 === $1 })) let (hasWallet, hasPassport, hasWatchApp, enableQRLogin, enableFilters) = hasWalletPassportAndWatch let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: settingsEntries(account: context.account, presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, notifyExceptions: preferencesAndExceptions.1, notificationsAuthorizationStatus: preferencesAndExceptions.2, notificationsWarningSuppressed: preferencesAndExceptions.3, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, privacySettings: preferencesAndExceptions.4, hasWallet: hasWallet, hasPassport: hasPassport, hasWatchApp: hasWatchApp, accountsAndPeers: accountsAndPeers.1, inAppNotificationSettings: inAppNotificationSettings, experimentalUISettings: experimentalUISettings, displayPhoneNumberConfirmation: preferencesAndExceptions.5, otherSessionCount: otherSessionCount, enableQRLogin: enableQRLogin, enableFilters: enableFilters), style: .blocks, searchItem: searchItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)) diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index e98633d1cc..862a27987f 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -235,22 +235,22 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView let timestamp = self.referenceTimestamp let timestamp1 = timestamp + 120 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60) let timestamp2 = timestamp + 3660 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp3 = timestamp + 3200 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp4 = timestamp + 3000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp5 = timestamp + 1000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer5), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer5), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let width: CGFloat if case .regular = layout.metrics.widthClass { @@ -316,22 +316,22 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) - let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message2], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) let waveformBase64 = "DAAOAAkACQAGAAwADwAMABAADQAPABsAGAALAA0AGAAfABoAHgATABgAGQAYABQADAAVABEAHwANAA0ACQAWABkACQAOAAwACQAfAAAAGQAVAAAAEwATAAAACAAfAAAAHAAAABwAHwAAABcAGQAAABQADgAAABQAHwAAAB8AHwAAAAwADwAAAB8AEwAAABoAFwAAAB8AFAAAAAAAHwAAAAAAHgAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAA=" let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))] let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes) - let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message3], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local), tapMessage: nil, clickThroughMessage: nil)) - let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message4], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) let width: CGFloat diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 8cd04d4ec1..2830ef49ba 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -786,17 +786,17 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let timestamp = self.referenceTimestamp let timestamp1 = timestamp + 120 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60) let timestamp2 = timestamp + 3660 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp3 = timestamp + 3200 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp4 = timestamp + 3000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height) if let chatNodes = self.chatNodes { @@ -852,19 +852,19 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate var sampleMessages: [Message] = [] - let message1 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message1 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message1) - let message2 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message2 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message2) - let message3 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message3 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message3) - let message4 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message4 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message4) - let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) messages[message5.id] = message5 sampleMessages.append(message5) @@ -872,13 +872,13 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))] let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes) - let message6 = Message(stableId: 6, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message6 = Message(stableId: 6, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message6) - let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id, threadMessageId: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message7) - let message8 = Message(stableId: 8, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message8 = Message(stableId: 8, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message8) items = sampleMessages.reversed().map { message in diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchColorsItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchColorsItem.swift index c0dcf33d56..2fcc5a6656 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchColorsItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchColorsItem.swift @@ -9,6 +9,35 @@ import SyncCore import TelegramPresentationData import ListSectionHeaderNode +private func nodeColor(for color: WallpaperSearchColor) -> UIColor { + switch color { + case .blue: + return UIColor(rgb: 0x0076ff) + case .red: + return UIColor(rgb: 0xff0000) + case .orange: + return UIColor(rgb: 0xff8a00) + case .yellow: + return UIColor(rgb: 0xffca00) + case .green: + return UIColor(rgb: 0x00e432) + case .teal: + return UIColor(rgb: 0x1fa9ab) + case .purple: + return UIColor(rgb: 0x7300aa) + case .pink: + return UIColor(rgb: 0xf9bec5) + case .brown: + return UIColor(rgb: 0x734021) + case .black: + return UIColor(rgb: 0x000000) + case .gray: + return UIColor(rgb: 0x5c585f) + case .white: + return UIColor(rgb: 0xffffff) + } +} + private class ThemeGridColorNode: HighlightableButtonNode { let action: () -> Void @@ -25,7 +54,7 @@ private class ThemeGridColorNode: HighlightableButtonNode { } else if color == .black && dark { image = generateFilledCircleImage(diameter: 42.0, color: .black, strokeColor: strokeColor, strokeWidth: 1.0) } else { - image = generateFilledCircleImage(diameter: 42.0, color: color.displayColor) + image = generateFilledCircleImage(diameter: 42.0, color: nodeColor(for: color)) } self.setImage(image, for: .normal) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift index b5801db755..5574a938e9 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift @@ -12,7 +12,6 @@ import AccountContext import SearchUI import ChatListSearchItemHeader import WebSearchUI -import SearchBarNode enum WallpaperSearchColor: CaseIterable { case blue @@ -57,35 +56,6 @@ enum WallpaperSearchColor: CaseIterable { } } - var displayColor: UIColor { - switch self { - case .blue: - return UIColor(rgb: 0x0076ff) - case .red: - return UIColor(rgb: 0xff0000) - case .orange: - return UIColor(rgb: 0xff8a00) - case .yellow: - return UIColor(rgb: 0xffca00) - case .green: - return UIColor(rgb: 0x00e432) - case .teal: - return UIColor(rgb: 0x1fa9ab) - case .purple: - return UIColor(rgb: 0x7300aa) - case .pink: - return UIColor(rgb: 0xf9bec5) - case .brown: - return UIColor(rgb: 0x734021) - case .black: - return UIColor(rgb: 0x000000) - case .gray: - return UIColor(rgb: 0x5c585f) - case .white: - return UIColor(rgb: 0xffffff) - } - } - func localizedString(strings: PresentationStrings) -> String { switch self { case .blue: @@ -688,30 +658,23 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode { self.queryPromise.set(.single(query)) if updateInterface { - let tokens: [SearchBarToken] + let prefix: NSAttributedString? let text: String let placeholder: String switch query { case let .generic(query): - tokens = [] + prefix = nil text = query placeholder = self.presentationData.strings.Wallpaper_Search case let .color(color, query): - let backgroundColor = color.displayColor - let foregroundColor: UIColor - let strokeColor: UIColor - if color == .white { - foregroundColor = .black - strokeColor = self.presentationData.theme.rootController.navigationSearchBar.inputClearButtonColor - } else { - foregroundColor = .white - strokeColor = color.displayColor - } - tokens = [SearchBarToken(id: 0, icon: UIImage(bundleImageName: "Settings/WallpaperSearchColorIcon"), title: color.localizedString(strings: self.presentationData.strings), style: SearchBarToken.Style(backgroundColor: backgroundColor, foregroundColor: foregroundColor, strokeColor: strokeColor))] + let prefixString = NSMutableAttributedString() + prefixString.append(NSAttributedString(string: self.presentationData.strings.WallpaperSearch_ColorPrefix, font: Font.regular(17.0), textColor: self.presentationData.theme.rootController.navigationSearchBar.inputTextColor)) + prefixString.append(NSAttributedString(string: "\(color.localizedString(strings: self.presentationData.strings)) ", font: Font.regular(17.0), textColor: self.presentationData.theme.rootController.navigationSearchBar.accentColor)) + prefix = prefixString text = query placeholder = self.presentationData.strings.Wallpaper_SearchShort } - self.setQuery?(nil, tokens, text) + self.setQuery?(prefix, text) self.setPlaceholder?(placeholder) } } @@ -725,10 +688,6 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode { self.updateQuery({ $0.updatedWithColor(nil) }, updateInterface: true) } - override func searchTextClearTokens() { - self.updateQuery({ $0.updatedWithColor(nil) }, updateInterface: true) - } - private func enqueueRecentTransition(_ transition: ThemeGridSearchContainerRecentTransition, firstTime: Bool) { self.enqueuedRecentTransitions.append((transition, firstTime)) diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 13125e369d..e6a3736b1f 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -373,24 +373,24 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let timestamp = self.referenceTimestamp let timestamp1 = timestamp + 120 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60) let timestamp2 = timestamp + 3660 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp3 = timestamp + 3200 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp4 = timestamp + 3000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp5 = timestamp + 1000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer5), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer5), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer7.id, namespace: 0, id: 0), timestamp: timestamp - 420)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer7.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp - 420, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer7), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer7.id, namespace: 0, id: 0), timestamp: timestamp - 420)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer7.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 420, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer7), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let width: CGFloat if case .regular = layout.metrics.widthClass { @@ -457,19 +457,19 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { var sampleMessages: [Message] = [] - let message1 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message1 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message1) - let message2 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message2 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message2) - let message3 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message3 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message3) - let message4 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message4 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message4) - let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) messages[message5.id] = message5 sampleMessages.append(message5) @@ -477,13 +477,13 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))] let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes) - let message6 = Message(stableId: 6, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message6 = Message(stableId: 6, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message6) - let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id, threadMessageId: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message7) - let message8 = Message(stableId: 8, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message8 = Message(stableId: 8, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message8) items = sampleMessages.reversed().map { message in diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift index 2a24f6b579..df4f629682 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift @@ -161,10 +161,10 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) if let (author, text) = messageItem.reply { peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: author, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) } - let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: messageItem.outgoing ? otherPeerId : peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: messageItem.outgoing ? TelegramUser(id: otherPeerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) : nil, text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)] : [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: messageItem.outgoing ? otherPeerId : peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: messageItem.outgoing ? TelegramUser(id: otherPeerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) : nil, text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId)] : [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index c6abb1ff4e..d489119662 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -839,10 +839,10 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let theme = self.presentationData.theme.withUpdated(preview: true) - let message1 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: bottomMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message1 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: bottomMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: theme, strings: self.presentationData.strings, wallpaper: currentWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) - let message2 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: topMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message2 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: topMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message2], theme: theme, strings: self.presentationData.strings, wallpaper: currentWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height) diff --git a/submodules/SyncCore/Sources/CachedChannelData.swift b/submodules/SyncCore/Sources/CachedChannelData.swift index 4f7a4ac10e..79019c8c40 100644 --- a/submodules/SyncCore/Sources/CachedChannelData.swift +++ b/submodules/SyncCore/Sources/CachedChannelData.swift @@ -150,11 +150,6 @@ public struct PeerGeoLocation: PostboxCoding, Equatable { } public final class CachedChannelData: CachedPeerData { - public enum LinkedDiscussionPeerId: Equatable { - case unknown - case known(PeerId?) - } - public let isNotAccessible: Bool public let flags: CachedChannelFlags public let about: String? @@ -166,7 +161,7 @@ public final class CachedChannelData: CachedPeerData { public let stickerPack: StickerPackCollectionInfo? public let minAvailableMessageId: MessageId? public let migrationReference: ChannelMigrationReference? - public let linkedDiscussionPeerId: LinkedDiscussionPeerId + public let linkedDiscussionPeerId: PeerId? public let peerGeoLocation: PeerGeoLocation? public let slowModeTimeout: Int32? public let slowModeValidUntilTimestamp: Int32? @@ -195,7 +190,7 @@ public final class CachedChannelData: CachedPeerData { self.stickerPack = nil self.minAvailableMessageId = nil self.migrationReference = nil - self.linkedDiscussionPeerId = .unknown + self.linkedDiscussionPeerId = nil self.peerGeoLocation = nil self.slowModeTimeout = nil self.slowModeValidUntilTimestamp = nil @@ -205,7 +200,7 @@ public final class CachedChannelData: CachedPeerData { self.photo = nil } - public init(isNotAccessible: Bool, flags: CachedChannelFlags, about: String?, participantsSummary: CachedChannelParticipantsSummary, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, stickerPack: StickerPackCollectionInfo?, minAvailableMessageId: MessageId?, migrationReference: ChannelMigrationReference?, linkedDiscussionPeerId: LinkedDiscussionPeerId, peerGeoLocation: PeerGeoLocation?, slowModeTimeout: Int32?, slowModeValidUntilTimestamp: Int32?, hasScheduledMessages: Bool, statsDatacenterId: Int32, invitedBy: PeerId?, photo: TelegramMediaImage?) { + public init(isNotAccessible: Bool, flags: CachedChannelFlags, about: String?, participantsSummary: CachedChannelParticipantsSummary, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, stickerPack: StickerPackCollectionInfo?, minAvailableMessageId: MessageId?, migrationReference: ChannelMigrationReference?, linkedDiscussionPeerId: PeerId?, peerGeoLocation: PeerGeoLocation?, slowModeTimeout: Int32?, slowModeValidUntilTimestamp: Int32?, hasScheduledMessages: Bool, statsDatacenterId: Int32, invitedBy: PeerId?, photo: TelegramMediaImage?) { self.isNotAccessible = isNotAccessible self.flags = flags self.about = about @@ -231,10 +226,8 @@ public final class CachedChannelData: CachedPeerData { peerIds.insert(botInfo.peerId) } - if case let .known(linkedDiscussionPeerIdValue) = linkedDiscussionPeerId { - if let linkedDiscussionPeerIdValue = linkedDiscussionPeerIdValue { - peerIds.insert(linkedDiscussionPeerIdValue) - } + if let linkedDiscussionPeerId = linkedDiscussionPeerId { + peerIds.insert(linkedDiscussionPeerId) } if let invitedBy = invitedBy { @@ -295,7 +288,7 @@ public final class CachedChannelData: CachedPeerData { return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) } - public func withUpdatedLinkedDiscussionPeerId(_ linkedDiscussionPeerId: LinkedDiscussionPeerId) -> CachedChannelData { + public func withUpdatedLinkedDiscussionPeerId(_ linkedDiscussionPeerId: PeerId?) -> CachedChannelData { return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) } @@ -368,13 +361,9 @@ public final class CachedChannelData: CachedPeerData { } if let linkedDiscussionPeerId = decoder.decodeOptionalInt64ForKey("dgi") { - if linkedDiscussionPeerId == 0 { - self.linkedDiscussionPeerId = .known(nil) - } else { - self.linkedDiscussionPeerId = .known(PeerId(linkedDiscussionPeerId)) - } + self.linkedDiscussionPeerId = PeerId(linkedDiscussionPeerId) } else { - self.linkedDiscussionPeerId = .unknown + self.linkedDiscussionPeerId = nil } if let peerGeoLocation = decoder.decodeObjectForKey("pgl", decoder: { PeerGeoLocation(decoder: $0) }) as? PeerGeoLocation { @@ -396,10 +385,8 @@ public final class CachedChannelData: CachedPeerData { self.photo = nil } - if case let .known(linkedDiscussionPeerIdValue) = self.linkedDiscussionPeerId { - if let linkedDiscussionPeerIdValue = linkedDiscussionPeerIdValue { - peerIds.insert(linkedDiscussionPeerIdValue) - } + if let linkedDiscussionPeerId = self.linkedDiscussionPeerId { + peerIds.insert(linkedDiscussionPeerId) } self.peerIds = peerIds @@ -459,15 +446,10 @@ public final class CachedChannelData: CachedPeerData { } else { encoder.encodeNil(forKey: "mr") } - switch self.linkedDiscussionPeerId { - case .unknown: + if let linkedDiscussionPeerId = self.linkedDiscussionPeerId { + encoder.encodeInt64(linkedDiscussionPeerId.toInt64(), forKey: "dgi") + } else { encoder.encodeNil(forKey: "dgi") - case let .known(value): - if let value = value { - encoder.encodeInt64(value.toInt64(), forKey: "dgi") - } else { - encoder.encodeInt64(0, forKey: "dgi") - } } if let peerGeoLocation = self.peerGeoLocation { encoder.encodeObject(peerGeoLocation, forKey: "pgl") diff --git a/submodules/SyncCore/Sources/ReplyMessageAttribute.swift b/submodules/SyncCore/Sources/ReplyMessageAttribute.swift index 308c0ad9c1..cbcc36b5fa 100644 --- a/submodules/SyncCore/Sources/ReplyMessageAttribute.swift +++ b/submodules/SyncCore/Sources/ReplyMessageAttribute.swift @@ -3,45 +3,23 @@ import Postbox public class ReplyMessageAttribute: MessageAttribute { public let messageId: MessageId - public let threadMessageId: MessageId? public var associatedMessageIds: [MessageId] { return [self.messageId] } - public init(messageId: MessageId, threadMessageId: MessageId?) { + public init(messageId: MessageId) { self.messageId = messageId - self.threadMessageId = threadMessageId } required public init(decoder: PostboxDecoder) { let namespaceAndId: Int64 = decoder.decodeInt64ForKey("i", orElse: 0) self.messageId = MessageId(peerId: PeerId(decoder.decodeInt64ForKey("p", orElse: 0)), namespace: Int32(namespaceAndId & 0xffffffff), id: Int32((namespaceAndId >> 32) & 0xffffffff)) - - if let threadNamespaceAndId = decoder.decodeOptionalInt64ForKey("ti"), let threadPeerId = decoder.decodeOptionalInt64ForKey("tp") { - self.threadMessageId = MessageId(peerId: PeerId(threadPeerId), namespace: Int32(threadNamespaceAndId & 0xffffffff), id: Int32((threadNamespaceAndId >> 32) & 0xffffffff)) - } else { - self.threadMessageId = nil - } } public func encode(_ encoder: PostboxEncoder) { let namespaceAndId = Int64(self.messageId.namespace) | (Int64(self.messageId.id) << 32) encoder.encodeInt64(namespaceAndId, forKey: "i") encoder.encodeInt64(self.messageId.peerId.toInt64(), forKey: "p") - if let threadMessageId = self.threadMessageId { - let threadNamespaceAndId = Int64(threadMessageId.namespace) | (Int64(threadMessageId.id) << 32) - encoder.encodeInt64(threadNamespaceAndId, forKey: "ti") - encoder.encodeInt64(threadMessageId.peerId.toInt64(), forKey: "tp") - } - } -} - -public extension Message { - var effectiveReplyThreadMessageId: MessageId? { - if let threadId = self.threadId { - return makeThreadIdMessageId(peerId: self.id.peerId, threadId: threadId) - } - return nil } } diff --git a/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift b/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift deleted file mode 100644 index 7ef8c3f65c..0000000000 --- a/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation -import Postbox - -public class ReplyThreadMessageAttribute: MessageAttribute { - public let count: Int32 - public let latestUsers: [PeerId] - public let commentsPeerId: PeerId? - - public var associatedPeerIds: [PeerId] { - return self.latestUsers - } - - public init(count: Int32, latestUsers: [PeerId], commentsPeerId: PeerId?) { - self.count = count - self.latestUsers = latestUsers - self.commentsPeerId = commentsPeerId - } - - required public init(decoder: PostboxDecoder) { - self.count = decoder.decodeInt32ForKey("c", orElse: 0) - self.latestUsers = decoder.decodeInt64ArrayForKey("u").map(PeerId.init) - self.commentsPeerId = decoder.decodeOptionalInt64ForKey("cp").flatMap(PeerId.init) - } - - public func encode(_ encoder: PostboxEncoder) { - encoder.encodeInt32(self.count, forKey: "c") - encoder.encodeInt64Array(self.latestUsers.map { $0.toInt64() }, forKey: "u") - if let commentsPeerId = self.commentsPeerId { - encoder.encodeInt64(commentsPeerId.toInt64(), forKey: "cp") - } else { - encoder.encodeNil(forKey: "cp") - } - } -} diff --git a/submodules/SyncCore/Sources/TelegramChatAdminRights.swift b/submodules/SyncCore/Sources/TelegramChatAdminRights.swift index 1ff66bf62a..86abc3ec9f 100644 --- a/submodules/SyncCore/Sources/TelegramChatAdminRights.swift +++ b/submodules/SyncCore/Sources/TelegramChatAdminRights.swift @@ -19,7 +19,6 @@ public struct TelegramChatAdminRightsFlags: OptionSet { public static let canInviteUsers = TelegramChatAdminRightsFlags(rawValue: 1 << 5) public static let canPinMessages = TelegramChatAdminRightsFlags(rawValue: 1 << 7) public static let canAddAdmins = TelegramChatAdminRightsFlags(rawValue: 1 << 9) - public static let canBeAnonymous = TelegramChatAdminRightsFlags(rawValue: 1 << 10) public static var groupSpecific: TelegramChatAdminRightsFlags = [ .canChangeInfo, @@ -27,7 +26,6 @@ public struct TelegramChatAdminRightsFlags: OptionSet { .canBanUsers, .canInviteUsers, .canPinMessages, - .canBeAnonymous, .canAddAdmins ] diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index e8f963c62d..1ea6c3776d 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -255,8 +255,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[889491791] = { return Api.Update.parse_updateDialogFilters($0) } dict[643940105] = { return Api.Update.parse_updatePhoneCallSignalingData($0) } dict[1708307556] = { return Api.Update.parse_updateChannelParticipant($0) } - dict[1854571743] = { return Api.Update.parse_updateChannelMessageForwards($0) } - dict[295679367] = { return Api.Update.parse_updateReadDiscussion($0) } dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) } dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) } dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) } @@ -264,7 +262,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1557620115] = { return Api.ChannelParticipant.parse_channelParticipantSelf($0) } dict[470789295] = { return Api.ChannelParticipant.parse_channelParticipantBanned($0) } dict[-859915345] = { return Api.ChannelParticipant.parse_channelParticipantAdmin($0) } - dict[1149094475] = { return Api.ChannelParticipant.parse_channelParticipantCreator($0) } + dict[-2138237532] = { return Api.ChannelParticipant.parse_channelParticipantCreator($0) } dict[-1567730343] = { return Api.MessageUserVote.parse_messageUserVote($0) } dict[909603888] = { return Api.MessageUserVote.parse_messageUserVoteInputOption($0) } dict[244310238] = { return Api.MessageUserVote.parse_messageUserVoteMultiple($0) } @@ -389,7 +387,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1694474197] = { return Api.messages.Chats.parse_chats($0) } dict[-1663561404] = { return Api.messages.Chats.parse_chatsSlice($0) } dict[482797855] = { return Api.InputSingleMedia.parse_inputSingleMedia($0) } - dict[1163625789] = { return Api.MessageViews.parse_messageViews($0) } dict[218751099] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowContacts($0) } dict[407582158] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowAll($0) } dict[320652927] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowUsers($0) } @@ -435,7 +432,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-886477832] = { return Api.LabeledPrice.parse_labeledPrice($0) } dict[-438840932] = { return Api.messages.ChatFull.parse_chatFull($0) } dict[-618540889] = { return Api.InputSecureValue.parse_inputSecureValue($0) } - dict[-765481584] = { return Api.messages.DiscussionMessage.parse_discussionMessage($0) } dict[1722786150] = { return Api.help.DeepLinkInfo.parse_deepLinkInfoEmpty($0) } dict[1783556146] = { return Api.help.DeepLinkInfo.parse_deepLinkInfo($0) } dict[-313079300] = { return Api.account.WebAuthorizations.parse_webAuthorizations($0) } @@ -489,7 +485,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1556570557] = { return Api.EmojiKeywordsDifference.parse_emojiKeywordsDifference($0) } dict[1493171408] = { return Api.HighScore.parse_highScore($0) } dict[-305282981] = { return Api.TopPeer.parse_topPeer($0) } - dict[-1495959709] = { return Api.MessageReplyHeader.parse_messageReplyHeader($0) } dict[411017418] = { return Api.SecureValue.parse_secureValue($0) } dict[-316748368] = { return Api.SecureValueHash.parse_secureValueHash($0) } dict[1444661369] = { return Api.ContactBlocked.parse_contactBlocked($0) } @@ -523,7 +518,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-402498398] = { return Api.messages.SavedGifs.parse_savedGifsNotModified($0) } dict[772213157] = { return Api.messages.SavedGifs.parse_savedGifs($0) } dict[-914167110] = { return Api.CdnPublicKey.parse_cdnPublicKey($0) } - dict[1093204652] = { return Api.MessageReplies.parse_messageReplies($0) } dict[53231223] = { return Api.InputGame.parse_inputGameID($0) } dict[-1020139510] = { return Api.InputGame.parse_inputGameShortName($0) } dict[1107543535] = { return Api.help.CountryCode.parse_countryCode($0) } @@ -545,17 +539,16 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[480546647] = { return Api.InputChatPhoto.parse_inputChatPhotoEmpty($0) } dict[-1991004873] = { return Api.InputChatPhoto.parse_inputChatPhoto($0) } dict[-968723890] = { return Api.InputChatPhoto.parse_inputChatUploadedPhoto($0) } - dict[742337250] = { return Api.messages.MessageViews.parse_messageViews($0) } dict[-368917890] = { return Api.PaymentCharge.parse_paymentCharge($0) } dict[-1387279939] = { return Api.MessageInteractionCounters.parse_messageInteractionCounters($0) } dict[-1107852396] = { return Api.stats.BroadcastStats.parse_broadcastStats($0) } dict[-484987010] = { return Api.Updates.parse_updatesTooLong($0) } + dict[-1857044719] = { return Api.Updates.parse_updateShortMessage($0) } + dict[377562760] = { return Api.Updates.parse_updateShortChatMessage($0) } dict[2027216577] = { return Api.Updates.parse_updateShort($0) } dict[1918567619] = { return Api.Updates.parse_updatesCombined($0) } dict[1957577280] = { return Api.Updates.parse_updates($0) } dict[301019932] = { return Api.Updates.parse_updateShortSentMessage($0) } - dict[580309704] = { return Api.Updates.parse_updateShortMessage($0) } - dict[1076714939] = { return Api.Updates.parse_updateShortChatMessage($0) } dict[-276825834] = { return Api.stats.MegagroupStats.parse_megagroupStats($0) } dict[-884757282] = { return Api.StatsAbsValueAndPrev.parse_statsAbsValueAndPrev($0) } dict[1038967584] = { return Api.MessageMedia.parse_messageMediaEmpty($0) } @@ -606,8 +599,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[537022650] = { return Api.User.parse_userEmpty($0) } dict[-1820043071] = { return Api.User.parse_user($0) } dict[-2082087340] = { return Api.Message.parse_messageEmpty($0) } - dict[1487813065] = { return Api.Message.parse_message($0) } - dict[678405636] = { return Api.Message.parse_messageService($0) } + dict[-1642487306] = { return Api.Message.parse_messageService($0) } + dict[1160515173] = { return Api.Message.parse_message($0) } dict[831924812] = { return Api.StatsGroupTopInviter.parse_statsGroupTopInviter($0) } dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) } dict[586395571] = { return Api.messages.RecentStickers.parse_recentStickers($0) } @@ -664,7 +657,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1036396922] = { return Api.InputWebFileLocation.parse_inputWebFileLocation($0) } dict[1430205163] = { return Api.InputWebFileLocation.parse_inputWebFileGeoMessageLocation($0) } dict[-1625153079] = { return Api.InputWebFileLocation.parse_inputWebFileGeoPointLocation($0) } - dict[1601666510] = { return Api.MessageFwdHeader.parse_messageFwdHeader($0) } + dict[893020267] = { return Api.MessageFwdHeader.parse_messageFwdHeader($0) } dict[-1012849566] = { return Api.BaseTheme.parse_baseThemeClassic($0) } dict[-69724536] = { return Api.BaseTheme.parse_baseThemeDay($0) } dict[-1212997976] = { return Api.BaseTheme.parse_baseThemeNight($0) } @@ -692,7 +685,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[364538944] = { return Api.messages.Dialogs.parse_dialogs($0) } dict[1910543603] = { return Api.messages.Dialogs.parse_dialogsSlice($0) } dict[-253500010] = { return Api.messages.Dialogs.parse_dialogsNotModified($0) } - dict[-1986399595] = { return Api.stats.MessageStats.parse_messageStats($0) } dict[-709641735] = { return Api.EmojiKeyword.parse_emojiKeyword($0) } dict[594408994] = { return Api.EmojiKeyword.parse_emojiKeywordDeleted($0) } dict[-290921362] = { return Api.upload.CdnFile.parse_cdnFileReuploadNeeded($0) } @@ -1118,8 +1110,6 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.InputSingleMedia: _1.serialize(buffer, boxed) - case let _1 as Api.MessageViews: - _1.serialize(buffer, boxed) case let _1 as Api.InputPrivacyRule: _1.serialize(buffer, boxed) case let _1 as Api.messages.DhConfig: @@ -1148,8 +1138,6 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.InputSecureValue: _1.serialize(buffer, boxed) - case let _1 as Api.messages.DiscussionMessage: - _1.serialize(buffer, boxed) case let _1 as Api.help.DeepLinkInfo: _1.serialize(buffer, boxed) case let _1 as Api.account.WebAuthorizations: @@ -1194,8 +1182,6 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.TopPeer: _1.serialize(buffer, boxed) - case let _1 as Api.MessageReplyHeader: - _1.serialize(buffer, boxed) case let _1 as Api.SecureValue: _1.serialize(buffer, boxed) case let _1 as Api.SecureValueHash: @@ -1244,8 +1230,6 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.CdnPublicKey: _1.serialize(buffer, boxed) - case let _1 as Api.MessageReplies: - _1.serialize(buffer, boxed) case let _1 as Api.InputGame: _1.serialize(buffer, boxed) case let _1 as Api.help.CountryCode: @@ -1270,8 +1254,6 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.InputChatPhoto: _1.serialize(buffer, boxed) - case let _1 as Api.messages.MessageViews: - _1.serialize(buffer, boxed) case let _1 as Api.PaymentCharge: _1.serialize(buffer, boxed) case let _1 as Api.MessageInteractionCounters: @@ -1392,8 +1374,6 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.Dialogs: _1.serialize(buffer, boxed) - case let _1 as Api.stats.MessageStats: - _1.serialize(buffer, boxed) case let _1 as Api.EmojiKeyword: _1.serialize(buffer, boxed) case let _1 as Api.upload.CdnFile: diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index 1a0b5f1fd4..cb22e0f124 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -823,66 +823,6 @@ public struct messages { } } - } - public enum DiscussionMessage: TypeConstructorDescription { - case discussionMessage(message: Api.Message, readMaxId: Int32, chats: [Api.Chat], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .discussionMessage(let message, let readMaxId, let chats, let users): - if boxed { - buffer.appendInt32(-765481584) - } - message.serialize(buffer, true) - serializeInt32(readMaxId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .discussionMessage(let message, let readMaxId, let chats, let users): - return ("discussionMessage", [("message", message), ("readMaxId", readMaxId), ("chats", chats), ("users", users)]) - } - } - - public static func parse_discussionMessage(_ reader: BufferReader) -> DiscussionMessage? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _4: [Api.User]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.DiscussionMessage.discussionMessage(message: _1!, readMaxId: _2!, chats: _3!, users: _4!) - } - else { - return nil - } - } - } public enum SearchCounter: TypeConstructorDescription { case searchCounter(flags: Int32, filter: Api.MessagesFilter, count: Int32) @@ -1269,56 +1209,6 @@ public struct messages { } } - } - public enum MessageViews: TypeConstructorDescription { - case messageViews(views: [Api.MessageViews], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageViews(let views, let users): - if boxed { - buffer.appendInt32(742337250) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(views.count)) - for item in views { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .messageViews(let views, let users): - return ("messageViews", [("views", views), ("users", users)]) - } - } - - public static func parse_messageViews(_ reader: BufferReader) -> MessageViews? { - var _1: [Api.MessageViews]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageViews.self) - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.MessageViews.messageViews(views: _1!, users: _2!) - } - else { - return nil - } - } - } public enum PeerDialogs: TypeConstructorDescription { case peerDialogs(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], state: Api.updates.State) @@ -6148,8 +6038,6 @@ public extension Api { case updateDialogFilters case updatePhoneCallSignalingData(phoneCallId: Int64, data: Buffer) case updateChannelParticipant(flags: Int32, channelId: Int32, date: Int32, userId: Int32, prevParticipant: Api.ChannelParticipant?, newParticipant: Api.ChannelParticipant?, qts: Int32) - case updateChannelMessageForwards(channelId: Int32, id: Int32, forwards: Int32) - case updateReadDiscussion(peer: Api.Peer, msgId: Int32, readMaxId: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -6842,22 +6730,6 @@ public extension Api { if Int(flags) & Int(1 << 1) != 0 {newParticipant!.serialize(buffer, true)} serializeInt32(qts, buffer: buffer, boxed: false) break - case .updateChannelMessageForwards(let channelId, let id, let forwards): - if boxed { - buffer.appendInt32(1854571743) - } - serializeInt32(channelId, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt32(forwards, buffer: buffer, boxed: false) - break - case .updateReadDiscussion(let peer, let msgId, let readMaxId): - if boxed { - buffer.appendInt32(295679367) - } - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt32(readMaxId, buffer: buffer, boxed: false) - break } } @@ -7027,10 +6899,6 @@ public extension Api { return ("updatePhoneCallSignalingData", [("phoneCallId", phoneCallId), ("data", data)]) case .updateChannelParticipant(let flags, let channelId, let date, let userId, let prevParticipant, let newParticipant, let qts): return ("updateChannelParticipant", [("flags", flags), ("channelId", channelId), ("date", date), ("userId", userId), ("prevParticipant", prevParticipant), ("newParticipant", newParticipant), ("qts", qts)]) - case .updateChannelMessageForwards(let channelId, let id, let forwards): - return ("updateChannelMessageForwards", [("channelId", channelId), ("id", id), ("forwards", forwards)]) - case .updateReadDiscussion(let peer, let msgId, let readMaxId): - return ("updateReadDiscussion", [("peer", peer), ("msgId", msgId), ("readMaxId", readMaxId)]) } } @@ -8409,42 +8277,6 @@ public extension Api { return nil } } - public static func parse_updateChannelMessageForwards(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChannelMessageForwards(channelId: _1!, id: _2!, forwards: _3!) - } - else { - return nil - } - } - public static func parse_updateReadDiscussion(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateReadDiscussion(peer: _1!, msgId: _2!, readMaxId: _3!) - } - else { - return nil - } - } } public enum PopularContact: TypeConstructorDescription { @@ -8530,7 +8362,7 @@ public extension Api { case channelParticipantSelf(userId: Int32, inviterId: Int32, date: Int32) case channelParticipantBanned(flags: Int32, userId: Int32, kickedBy: Int32, date: Int32, bannedRights: Api.ChatBannedRights) case channelParticipantAdmin(flags: Int32, userId: Int32, inviterId: Int32?, promotedBy: Int32, date: Int32, adminRights: Api.ChatAdminRights, rank: String?) - case channelParticipantCreator(flags: Int32, userId: Int32, adminRights: Api.ChatAdminRights, rank: String?) + case channelParticipantCreator(flags: Int32, userId: Int32, rank: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -8571,13 +8403,12 @@ public extension Api { adminRights.serialize(buffer, true) if Int(flags) & Int(1 << 2) != 0 {serializeString(rank!, buffer: buffer, boxed: false)} break - case .channelParticipantCreator(let flags, let userId, let adminRights, let rank): + case .channelParticipantCreator(let flags, let userId, let rank): if boxed { - buffer.appendInt32(1149094475) + buffer.appendInt32(-2138237532) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(userId, buffer: buffer, boxed: false) - adminRights.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {serializeString(rank!, buffer: buffer, boxed: false)} break } @@ -8593,8 +8424,8 @@ public extension Api { return ("channelParticipantBanned", [("flags", flags), ("userId", userId), ("kickedBy", kickedBy), ("date", date), ("bannedRights", bannedRights)]) case .channelParticipantAdmin(let flags, let userId, let inviterId, let promotedBy, let date, let adminRights, let rank): return ("channelParticipantAdmin", [("flags", flags), ("userId", userId), ("inviterId", inviterId), ("promotedBy", promotedBy), ("date", date), ("adminRights", adminRights), ("rank", rank)]) - case .channelParticipantCreator(let flags, let userId, let adminRights, let rank): - return ("channelParticipantCreator", [("flags", flags), ("userId", userId), ("adminRights", adminRights), ("rank", rank)]) + case .channelParticipantCreator(let flags, let userId, let rank): + return ("channelParticipantCreator", [("flags", flags), ("userId", userId), ("rank", rank)]) } } @@ -8690,18 +8521,13 @@ public extension Api { _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: Api.ChatAdminRights? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights - } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.ChannelParticipant.channelParticipantCreator(flags: _1!, userId: _2!, adminRights: _3!, rank: _4) + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.ChannelParticipant.channelParticipantCreator(flags: _1!, userId: _2!, rank: _3) } else { return nil @@ -11896,54 +11722,6 @@ public extension Api { } } - } - public enum MessageViews: TypeConstructorDescription { - case messageViews(flags: Int32, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageViews(let flags, let views, let forwards, let replies): - if boxed { - buffer.appendInt32(1163625789) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(views!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(forwards!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {replies!.serialize(buffer, true)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .messageViews(let flags, let views, let forwards, let replies): - return ("messageViews", [("flags", flags), ("views", views), ("forwards", forwards), ("replies", replies)]) - } - } - - public static func parse_messageViews(_ reader: BufferReader) -> MessageViews? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } - var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } - var _4: Api.MessageReplies? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.MessageReplies - } } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageViews.messageViews(flags: _1!, views: _2, forwards: _3, replies: _4) - } - else { - return nil - } - } - } public enum InputPrivacyRule: TypeConstructorDescription { case inputPrivacyValueAllowContacts @@ -14098,54 +13876,6 @@ public extension Api { } } - } - public enum MessageReplyHeader: TypeConstructorDescription { - case messageReplyHeader(flags: Int32, replyToMsgId: Int32, replyToPeerId: Api.Peer?, replyToTopId: Int32?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyToTopId): - if boxed { - buffer.appendInt32(-1495959709) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(replyToMsgId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {replyToPeerId!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(replyToTopId!, buffer: buffer, boxed: false)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyToTopId): - return ("messageReplyHeader", [("flags", flags), ("replyToMsgId", replyToMsgId), ("replyToPeerId", replyToPeerId), ("replyToTopId", replyToTopId)]) - } - } - - public static func parse_messageReplyHeader(_ reader: BufferReader) -> MessageReplyHeader? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Peer? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer - } } - var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageReplyHeader.messageReplyHeader(flags: _1!, replyToMsgId: _2!, replyToPeerId: _3, replyToTopId: _4) - } - else { - return nil - } - } - } public enum SecureValue: TypeConstructorDescription { case secureValue(flags: Int32, type: Api.SecureValueType, data: Api.SecureData?, frontSide: Api.SecureFile?, reverseSide: Api.SecureFile?, selfie: Api.SecureFile?, translation: [Api.SecureFile]?, files: [Api.SecureFile]?, plainData: Api.SecurePlainData?, hash: Buffer) @@ -15194,70 +14924,6 @@ public extension Api { } } - } - public enum MessageReplies: TypeConstructorDescription { - case messageReplies(flags: Int32, replies: Int32, repliesPts: Int32, recentRepliers: [Api.Peer]?, channelId: Int32?, maxId: Int32?, readMaxId: Int32?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageReplies(let flags, let replies, let repliesPts, let recentRepliers, let channelId, let maxId, let readMaxId): - if boxed { - buffer.appendInt32(1093204652) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(replies, buffer: buffer, boxed: false) - serializeInt32(repliesPts, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(recentRepliers!.count)) - for item in recentRepliers! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(channelId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(maxId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(readMaxId!, buffer: buffer, boxed: false)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .messageReplies(let flags, let replies, let repliesPts, let recentRepliers, let channelId, let maxId, let readMaxId): - return ("messageReplies", [("flags", flags), ("replies", replies), ("repliesPts", repliesPts), ("recentRepliers", recentRepliers), ("channelId", channelId), ("maxId", maxId), ("readMaxId", readMaxId)]) - } - } - - public static func parse_messageReplies(_ reader: BufferReader) -> MessageReplies? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: [Api.Peer]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } } - var _5: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } - var _6: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_6 = reader.readInt32() } - var _7: Int32? - if Int(_1!) & Int(1 << 3) != 0 {_7 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.MessageReplies.messageReplies(flags: _1!, replies: _2!, repliesPts: _3!, recentRepliers: _4, channelId: _5, maxId: _6, readMaxId: _7) - } - else { - return nil - } - } - } public enum InputGame: TypeConstructorDescription { case inputGameID(id: Int64, accessHash: Int64) @@ -15849,12 +15515,12 @@ public extension Api { } public enum Updates: TypeConstructorDescription { case updatesTooLong + case updateShortMessage(flags: Int32, id: Int32, userId: Int32, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, entities: [Api.MessageEntity]?) + case updateShortChatMessage(flags: Int32, id: Int32, fromId: Int32, chatId: Int32, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, entities: [Api.MessageEntity]?) case updateShort(update: Api.Update, date: Int32) case updatesCombined(updates: [Api.Update], users: [Api.User], chats: [Api.Chat], date: Int32, seqStart: Int32, seq: Int32) case updates(updates: [Api.Update], users: [Api.User], chats: [Api.Chat], date: Int32, seq: Int32) case updateShortSentMessage(flags: Int32, id: Int32, pts: Int32, ptsCount: Int32, date: Int32, media: Api.MessageMedia?, entities: [Api.MessageEntity]?) - case updateShortMessage(flags: Int32, id: Int32, userId: Int32, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyTo: Api.MessageReplyHeader?, entities: [Api.MessageEntity]?) - case updateShortChatMessage(flags: Int32, id: Int32, fromId: Int32, chatId: Int32, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyTo: Api.MessageReplyHeader?, entities: [Api.MessageEntity]?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -15863,6 +15529,47 @@ public extension Api { buffer.appendInt32(-484987010) } + break + case .updateShortMessage(let flags, let id, let userId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyToMsgId, let entities): + if boxed { + buffer.appendInt32(-1857044719) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} + if Int(flags) & Int(1 << 11) != 0 {serializeInt32(viaBotId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + break + case .updateShortChatMessage(let flags, let id, let fromId, let chatId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyToMsgId, let entities): + if boxed { + buffer.appendInt32(377562760) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt32(fromId, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} + if Int(flags) & Int(1 << 11) != 0 {serializeInt32(viaBotId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} break case .updateShort(let update, let date): if boxed { @@ -15932,47 +15639,6 @@ public extension Api { item.serialize(buffer, true) }} break - case .updateShortMessage(let flags, let id, let userId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities): - if boxed { - buffer.appendInt32(580309704) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt32(userId, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} - if Int(flags) & Int(1 << 11) != 0 {serializeInt32(viaBotId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - break - case .updateShortChatMessage(let flags, let id, let fromId, let chatId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities): - if boxed { - buffer.appendInt32(1076714939) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt32(fromId, buffer: buffer, boxed: false) - serializeInt32(chatId, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} - if Int(flags) & Int(1 << 11) != 0 {serializeInt32(viaBotId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - break } } @@ -15980,6 +15646,10 @@ public extension Api { switch self { case .updatesTooLong: return ("updatesTooLong", []) + case .updateShortMessage(let flags, let id, let userId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyToMsgId, let entities): + return ("updateShortMessage", [("flags", flags), ("id", id), ("userId", userId), ("message", message), ("pts", pts), ("ptsCount", ptsCount), ("date", date), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("entities", entities)]) + case .updateShortChatMessage(let flags, let id, let fromId, let chatId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyToMsgId, let entities): + return ("updateShortChatMessage", [("flags", flags), ("id", id), ("fromId", fromId), ("chatId", chatId), ("message", message), ("pts", pts), ("ptsCount", ptsCount), ("date", date), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("entities", entities)]) case .updateShort(let update, let date): return ("updateShort", [("update", update), ("date", date)]) case .updatesCombined(let updates, let users, let chats, let date, let seqStart, let seq): @@ -15988,16 +15658,105 @@ public extension Api { return ("updates", [("updates", updates), ("users", users), ("chats", chats), ("date", date), ("seq", seq)]) case .updateShortSentMessage(let flags, let id, let pts, let ptsCount, let date, let media, let entities): return ("updateShortSentMessage", [("flags", flags), ("id", id), ("pts", pts), ("ptsCount", ptsCount), ("date", date), ("media", media), ("entities", entities)]) - case .updateShortMessage(let flags, let id, let userId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities): - return ("updateShortMessage", [("flags", flags), ("id", id), ("userId", userId), ("message", message), ("pts", pts), ("ptsCount", ptsCount), ("date", date), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyTo", replyTo), ("entities", entities)]) - case .updateShortChatMessage(let flags, let id, let fromId, let chatId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities): - return ("updateShortChatMessage", [("flags", flags), ("id", id), ("fromId", fromId), ("chatId", chatId), ("message", message), ("pts", pts), ("ptsCount", ptsCount), ("date", date), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyTo", replyTo), ("entities", entities)]) } } public static func parse_updatesTooLong(_ reader: BufferReader) -> Updates? { return Api.Updates.updatesTooLong } + public static func parse_updateShortMessage(_ reader: BufferReader) -> Updates? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Api.MessageFwdHeader? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader + } } + var _9: Int32? + if Int(_1!) & Int(1 << 11) != 0 {_9 = reader.readInt32() } + var _10: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_10 = reader.readInt32() } + var _11: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { + _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 11) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 3) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return Api.Updates.updateShortMessage(flags: _1!, id: _2!, userId: _3!, message: _4!, pts: _5!, ptsCount: _6!, date: _7!, fwdFrom: _8, viaBotId: _9, replyToMsgId: _10, entities: _11) + } + else { + return nil + } + } + public static func parse_updateShortChatMessage(_ reader: BufferReader) -> Updates? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + var _9: Api.MessageFwdHeader? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader + } } + var _10: Int32? + if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt32() } + var _11: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_11 = reader.readInt32() } + var _12: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { + _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1!) & Int(1 << 2) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.Updates.updateShortChatMessage(flags: _1!, id: _2!, fromId: _3!, chatId: _4!, message: _5!, pts: _6!, ptsCount: _7!, date: _8!, fwdFrom: _9, viaBotId: _10, replyToMsgId: _11, entities: _12) + } + else { + return nil + } + } public static func parse_updateShort(_ reader: BufferReader) -> Updates? { var _1: Api.Update? if let signature = reader.readInt32() { @@ -16108,103 +15867,6 @@ public extension Api { return nil } } - public static func parse_updateShortMessage(_ reader: BufferReader) -> Updates? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - _7 = reader.readInt32() - var _8: Api.MessageFwdHeader? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader - } } - var _9: Int32? - if Int(_1!) & Int(1 << 11) != 0 {_9 = reader.readInt32() } - var _10: Api.MessageReplyHeader? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader - } } - var _11: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 11) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 3) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.Updates.updateShortMessage(flags: _1!, id: _2!, userId: _3!, message: _4!, pts: _5!, ptsCount: _6!, date: _7!, fwdFrom: _8, viaBotId: _9, replyTo: _10, entities: _11) - } - else { - return nil - } - } - public static func parse_updateShortChatMessage(_ reader: BufferReader) -> Updates? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: String? - _5 = parseString(reader) - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - _7 = reader.readInt32() - var _8: Int32? - _8 = reader.readInt32() - var _9: Api.MessageFwdHeader? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader - } } - var _10: Int32? - if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt32() } - var _11: Api.MessageReplyHeader? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader - } } - var _12: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 2) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.Updates.updateShortChatMessage(flags: _1!, id: _2!, fromId: _3!, chatId: _4!, message: _5!, pts: _6!, ptsCount: _7!, date: _8!, fwdFrom: _9, viaBotId: _10, replyTo: _11, entities: _12) - } - else { - return nil - } - } } public enum StatsAbsValueAndPrev: TypeConstructorDescription { @@ -17433,8 +17095,8 @@ public extension Api { } public enum Message: TypeConstructorDescription { case messageEmpty(id: Int32) - case message(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?) - case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction) + case messageService(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, replyToMsgId: Int32?, date: Int32, action: Api.MessageAction) + case message(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -17444,17 +17106,29 @@ public extension Api { } serializeInt32(id, buffer: buffer, boxed: false) break - case .message(let flags, let id, let fromId, let peerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let restrictionReason): + case .messageService(let flags, let id, let fromId, let toId, let replyToMsgId, let date, let action): if boxed { - buffer.appendInt32(1487813065) + buffer.appendInt32(-1642487306) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 8) != 0 {fromId!.serialize(buffer, true)} - peerId.serialize(buffer, true) + if Int(flags) & Int(1 << 8) != 0 {serializeInt32(fromId!, buffer: buffer, boxed: false)} + toId.serialize(buffer, true) + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} + serializeInt32(date, buffer: buffer, boxed: false) + action.serialize(buffer, true) + break + case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let editDate, let postAuthor, let groupedId, let restrictionReason): + if boxed { + buffer.appendInt32(1160515173) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 8) != 0 {serializeInt32(fromId!, buffer: buffer, boxed: false)} + toId.serialize(buffer, true) if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} if Int(flags) & Int(1 << 11) != 0 {serializeInt32(viaBotId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} serializeInt32(date, buffer: buffer, boxed: false) serializeString(message, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 9) != 0 {media!.serialize(buffer, true)} @@ -17465,8 +17139,6 @@ public extension Api { item.serialize(buffer, true) }} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(views!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 10) != 0 {serializeInt32(forwards!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 23) != 0 {replies!.serialize(buffer, true)} if Int(flags) & Int(1 << 15) != 0 {serializeInt32(editDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 16) != 0 {serializeString(postAuthor!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 17) != 0 {serializeInt64(groupedId!, buffer: buffer, boxed: false)} @@ -17476,18 +17148,6 @@ public extension Api { item.serialize(buffer, true) }} break - case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action): - if boxed { - buffer.appendInt32(678405636) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 8) != 0 {fromId!.serialize(buffer, true)} - peerId.serialize(buffer, true) - if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} - serializeInt32(date, buffer: buffer, boxed: false) - action.serialize(buffer, true) - break } } @@ -17495,10 +17155,10 @@ public extension Api { switch self { case .messageEmpty(let id): return ("messageEmpty", [("id", id)]) - case .message(let flags, let id, let fromId, let peerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let restrictionReason): - return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("peerId", peerId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyTo", replyTo), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("forwards", forwards), ("replies", replies), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("restrictionReason", restrictionReason)]) - case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action): - return ("messageService", [("flags", flags), ("id", id), ("fromId", fromId), ("peerId", peerId), ("replyTo", replyTo), ("date", date), ("action", action)]) + case .messageService(let flags, let id, let fromId, let toId, let replyToMsgId, let date, let action): + return ("messageService", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("replyToMsgId", replyToMsgId), ("date", date), ("action", action)]) + case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let editDate, let postAuthor, let groupedId, let restrictionReason): + return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("restrictionReason", restrictionReason)]) } } @@ -17513,15 +17173,46 @@ public extension Api { return nil } } + public static func parse_messageService(_ reader: BufferReader) -> Message? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + if Int(_1!) & Int(1 << 8) != 0 {_3 = reader.readInt32() } + var _4: Api.Peer? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _5: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_5 = reader.readInt32() } + var _6: Int32? + _6 = reader.readInt32() + var _7: Api.MessageAction? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.MessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.Message.messageService(flags: _1!, id: _2!, fromId: _3, toId: _4!, replyToMsgId: _5, date: _6!, action: _7!) + } + else { + return nil + } + } public static func parse_message(_ reader: BufferReader) -> Message? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: Api.Peer? - if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer - } } + var _3: Int32? + if Int(_1!) & Int(1 << 8) != 0 {_3 = reader.readInt32() } var _4: Api.Peer? if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.Peer @@ -17532,10 +17223,8 @@ public extension Api { } } var _6: Int32? if Int(_1!) & Int(1 << 11) != 0 {_6 = reader.readInt32() } - var _7: Api.MessageReplyHeader? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader - } } + var _7: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_7 = reader.readInt32() } var _8: Int32? _8 = reader.readInt32() var _9: String? @@ -17555,20 +17244,14 @@ public extension Api { var _13: Int32? if Int(_1!) & Int(1 << 10) != 0 {_13 = reader.readInt32() } var _14: Int32? - if Int(_1!) & Int(1 << 10) != 0 {_14 = reader.readInt32() } - var _15: Api.MessageReplies? - if Int(_1!) & Int(1 << 23) != 0 {if let signature = reader.readInt32() { - _15 = Api.parse(reader, signature: signature) as? Api.MessageReplies - } } - var _16: Int32? - if Int(_1!) & Int(1 << 15) != 0 {_16 = reader.readInt32() } - var _17: String? - if Int(_1!) & Int(1 << 16) != 0 {_17 = parseString(reader) } - var _18: Int64? - if Int(_1!) & Int(1 << 17) != 0 {_18 = reader.readInt64() } - var _19: [Api.RestrictionReason]? + if Int(_1!) & Int(1 << 15) != 0 {_14 = reader.readInt32() } + var _15: String? + if Int(_1!) & Int(1 << 16) != 0 {_15 = parseString(reader) } + var _16: Int64? + if Int(_1!) & Int(1 << 17) != 0 {_16 = reader.readInt64() } + var _17: [Api.RestrictionReason]? if Int(_1!) & Int(1 << 22) != 0 {if let _ = reader.readInt32() { - _19 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) + _17 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) } } let _c1 = _1 != nil let _c2 = _2 != nil @@ -17583,51 +17266,12 @@ public extension Api { let _c11 = (Int(_1!) & Int(1 << 6) == 0) || _11 != nil let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil let _c13 = (Int(_1!) & Int(1 << 10) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 10) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 23) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 15) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 16) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 17) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 22) == 0) || _19 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 { - return Api.Message.message(flags: _1!, id: _2!, fromId: _3, peerId: _4!, fwdFrom: _5, viaBotId: _6, replyTo: _7, date: _8!, message: _9!, media: _10, replyMarkup: _11, entities: _12, views: _13, forwards: _14, replies: _15, editDate: _16, postAuthor: _17, groupedId: _18, restrictionReason: _19) - } - else { - return nil - } - } - public static func parse_messageService(_ reader: BufferReader) -> Message? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Peer? - if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer - } } - var _4: Api.Peer? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _5: Api.MessageReplyHeader? - if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader - } } - var _6: Int32? - _6 = reader.readInt32() - var _7: Api.MessageAction? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.MessageAction - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.Message.messageService(flags: _1!, id: _2!, fromId: _3, peerId: _4!, replyTo: _5, date: _6!, action: _7!) + let _c14 = (Int(_1!) & Int(1 << 15) == 0) || _14 != nil + let _c15 = (Int(_1!) & Int(1 << 16) == 0) || _15 != nil + let _c16 = (Int(_1!) & Int(1 << 17) == 0) || _16 != nil + let _c17 = (Int(_1!) & Int(1 << 22) == 0) || _17 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 { + return Api.Message.message(flags: _1!, id: _2!, fromId: _3, toId: _4!, fwdFrom: _5, viaBotId: _6, replyToMsgId: _7, date: _8!, message: _9!, media: _10, replyMarkup: _11, entities: _12, views: _13, editDate: _14, postAuthor: _15, groupedId: _16, restrictionReason: _17) } else { return nil @@ -19294,18 +18938,19 @@ public extension Api { } public enum MessageFwdHeader: TypeConstructorDescription { - case messageFwdHeader(flags: Int32, fromId: Api.Peer?, fromName: String?, date: Int32, channelPost: Int32?, postAuthor: String?, savedFromPeer: Api.Peer?, savedFromMsgId: Int32?, psaType: String?) + case messageFwdHeader(flags: Int32, fromId: Int32?, fromName: String?, date: Int32, channelId: Int32?, channelPost: Int32?, postAuthor: String?, savedFromPeer: Api.Peer?, savedFromMsgId: Int32?, psaType: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageFwdHeader(let flags, let fromId, let fromName, let date, let channelPost, let postAuthor, let savedFromPeer, let savedFromMsgId, let psaType): + case .messageFwdHeader(let flags, let fromId, let fromName, let date, let channelId, let channelPost, let postAuthor, let savedFromPeer, let savedFromMsgId, let psaType): if boxed { - buffer.appendInt32(1601666510) + buffer.appendInt32(893020267) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {fromId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(fromId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 5) != 0 {serializeString(fromName!, buffer: buffer, boxed: false)} serializeInt32(date, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(channelId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {serializeInt32(channelPost!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {serializeString(postAuthor!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 4) != 0 {savedFromPeer!.serialize(buffer, true)} @@ -19317,45 +18962,46 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messageFwdHeader(let flags, let fromId, let fromName, let date, let channelPost, let postAuthor, let savedFromPeer, let savedFromMsgId, let psaType): - return ("messageFwdHeader", [("flags", flags), ("fromId", fromId), ("fromName", fromName), ("date", date), ("channelPost", channelPost), ("postAuthor", postAuthor), ("savedFromPeer", savedFromPeer), ("savedFromMsgId", savedFromMsgId), ("psaType", psaType)]) + case .messageFwdHeader(let flags, let fromId, let fromName, let date, let channelId, let channelPost, let postAuthor, let savedFromPeer, let savedFromMsgId, let psaType): + return ("messageFwdHeader", [("flags", flags), ("fromId", fromId), ("fromName", fromName), ("date", date), ("channelId", channelId), ("channelPost", channelPost), ("postAuthor", postAuthor), ("savedFromPeer", savedFromPeer), ("savedFromMsgId", savedFromMsgId), ("psaType", psaType)]) } } public static func parse_messageFwdHeader(_ reader: BufferReader) -> MessageFwdHeader? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.Peer? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } } + var _2: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } var _3: String? if Int(_1!) & Int(1 << 5) != 0 {_3 = parseString(reader) } var _4: Int32? _4 = reader.readInt32() var _5: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() } - var _6: String? - if Int(_1!) & Int(1 << 3) != 0 {_6 = parseString(reader) } - var _7: Api.Peer? + if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() } + var _6: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_6 = reader.readInt32() } + var _7: String? + if Int(_1!) & Int(1 << 3) != 0 {_7 = parseString(reader) } + var _8: Api.Peer? if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Peer + _8 = Api.parse(reader, signature: signature) as? Api.Peer } } - var _8: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_8 = reader.readInt32() } - var _9: String? - if Int(_1!) & Int(1 << 6) != 0 {_9 = parseString(reader) } + var _9: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_9 = reader.readInt32() } + var _10: String? + if Int(_1!) & Int(1 << 6) != 0 {_10 = parseString(reader) } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 5) == 0) || _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.MessageFwdHeader.messageFwdHeader(flags: _1!, fromId: _2, fromName: _3, date: _4!, channelPost: _5, postAuthor: _6, savedFromPeer: _7, savedFromMsgId: _8, psaType: _9) + let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 6) == 0) || _10 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { + return Api.MessageFwdHeader.messageFwdHeader(flags: _1!, fromId: _2, fromName: _3, date: _4!, channelId: _5, channelPost: _6, postAuthor: _7, savedFromPeer: _8, savedFromMsgId: _9, psaType: _10) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index a47b7c7b83..aad07e69b9 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -810,42 +810,6 @@ public struct stats { } } - public enum MessageStats: TypeConstructorDescription { - case messageStats(viewsGraph: Api.StatsGraph) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageStats(let viewsGraph): - if boxed { - buffer.appendInt32(-1986399595) - } - viewsGraph.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .messageStats(let viewsGraph): - return ("messageStats", [("viewsGraph", viewsGraph)]) - } - } - - public static func parse_messageStats(_ reader: BufferReader) -> MessageStats? { - var _1: Api.StatsGraph? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - let _c1 = _1 != nil - if _c1 { - return Api.stats.MessageStats.messageStats(viewsGraph: _1!) - } - else { - return nil - } - } - - } } } public extension Api { diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 0f9ff2daa3..238ea08cbb 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -2210,6 +2210,26 @@ public extension Api { }) } + public static func getMessagesViews(peer: Api.InputPeer, id: [Int32], increment: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { + let buffer = Buffer() + buffer.appendInt32(-993483427) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + increment.serialize(buffer, true) + return (FunctionDescription(name: "messages.getMessagesViews", parameters: [("peer", peer), ("id", id), ("increment", increment)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in + let reader = BufferReader(buffer) + var result: [Int32]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + return result + }) + } + public static func editChatAdmin(chatId: Int32, userId: Api.InputUser, isAdmin: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(-1444503762) @@ -2885,6 +2905,32 @@ public extension Api { }) } + public static func search(flags: Int32, peer: Api.InputPeer, q: String, fromId: Api.InputUser?, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2045448344) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(q, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {fromId!.serialize(buffer, true)} + filter.serialize(buffer, true) + serializeInt32(minDate, buffer: buffer, boxed: false) + serializeInt32(maxDate, buffer: buffer, boxed: false) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.search", parameters: [("flags", flags), ("peer", peer), ("q", q), ("fromId", fromId), ("filter", filter), ("minDate", minDate), ("maxDate", maxDate), ("offsetId", offsetId), ("addOffset", addOffset), ("limit", limit), ("maxId", maxId), ("minId", minId), ("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } + public static func toggleDialogPin(flags: Int32, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(-1489903017) @@ -3289,6 +3335,26 @@ public extension Api { }) } + public static func searchGlobal(flags: Int32, folderId: Int32?, q: String, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1083038300) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} + serializeString(q, buffer: buffer, boxed: false) + serializeInt32(offsetRate, buffer: buffer, boxed: false) + offsetPeer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.searchGlobal", parameters: [("flags", flags), ("folderId", folderId), ("q", q), ("offsetRate", offsetRate), ("offsetPeer", offsetPeer), ("offsetId", offsetId), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } + public static func sendMessage(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(1376532592) @@ -3655,128 +3721,6 @@ public extension Api { return result }) } - - public static func getReplies(peer: Api.InputPeer, msgId: Int32, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-39505956) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getReplies", parameters: [("peer", peer), ("msgId", msgId), ("offsetId", offsetId), ("addOffset", addOffset), ("limit", limit), ("maxId", maxId), ("minId", minId), ("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } - - public static func getDiscussionMessage(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1147761405) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getDiscussionMessage", parameters: [("peer", peer), ("msgId", msgId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DiscussionMessage? in - let reader = BufferReader(buffer) - var result: Api.messages.DiscussionMessage? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.DiscussionMessage - } - return result - }) - } - - public static func readDiscussion(peer: Api.InputPeer, msgId: Int32, readMaxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-147740172) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt32(readMaxId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.readDiscussion", parameters: [("peer", peer), ("msgId", msgId), ("readMaxId", readMaxId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } - - public static func getMessagesViews(peer: Api.InputPeer, id: [Int32], increment: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1468322785) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - increment.serialize(buffer, true) - return (FunctionDescription(name: "messages.getMessagesViews", parameters: [("peer", peer), ("id", id), ("increment", increment)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageViews? in - let reader = BufferReader(buffer) - var result: Api.messages.MessageViews? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.MessageViews - } - return result - }) - } - - public static func searchGlobal(flags: Int32, folderId: Int32?, q: String, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1271290010) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} - serializeString(q, buffer: buffer, boxed: false) - filter.serialize(buffer, true) - serializeInt32(minDate, buffer: buffer, boxed: false) - serializeInt32(maxDate, buffer: buffer, boxed: false) - serializeInt32(offsetRate, buffer: buffer, boxed: false) - offsetPeer.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.searchGlobal", parameters: [("flags", flags), ("folderId", folderId), ("q", q), ("filter", filter), ("minDate", minDate), ("maxDate", maxDate), ("offsetRate", offsetRate), ("offsetPeer", offsetPeer), ("offsetId", offsetId), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } - - public static func search(flags: Int32, peer: Api.InputPeer, q: String, fromId: Api.InputUser?, topMsgId: Int32?, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1310163211) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeString(q, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {fromId!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} - filter.serialize(buffer, true) - serializeInt32(minDate, buffer: buffer, boxed: false) - serializeInt32(maxDate, buffer: buffer, boxed: false) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.search", parameters: [("flags", flags), ("peer", peer), ("q", q), ("fromId", fromId), ("topMsgId", topMsgId), ("filter", filter), ("minDate", minDate), ("maxDate", maxDate), ("offsetId", offsetId), ("addOffset", addOffset), ("limit", limit), ("maxId", maxId), ("minId", minId), ("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } } public struct channels { public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { @@ -4509,41 +4453,6 @@ public extension Api { return result }) } - - public static func getMessagePublicForwards(channel: Api.InputChannel, msgId: Int32, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1445996571) - channel.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt32(offsetRate, buffer: buffer, boxed: false) - offsetPeer.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stats.getMessagePublicForwards", parameters: [("channel", channel), ("msgId", msgId), ("offsetRate", offsetRate), ("offsetPeer", offsetPeer), ("offsetId", offsetId), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } - - public static func getMessageStats(flags: Int32, channel: Api.InputChannel, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1226791947) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stats.getMessageStats", parameters: [("flags", flags), ("channel", channel), ("msgId", msgId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.MessageStats? in - let reader = BufferReader(buffer) - var result: Api.stats.MessageStats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stats.MessageStats - } - return result - }) - } } public struct auth { public static func checkPhone(phoneNumber: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index d248455df0..b83bf9492b 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -28,7 +28,7 @@ public enum LocationBroadcastPanelSource { private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) { let presentImpl: (Message?) -> Void = { [weak controller] message in if let message = message, let strongController = controller { - let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongController.navigationController as? NavigationController, modal: true, dismissInput: { + let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongController.navigationController as? NavigationController, modal: true, dismissInput: { controller?.view.endEditing(true) }, present: { c, a in controller?.present(c, in: .window(.root), with: a, blockInteraction: true) @@ -557,7 +557,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { } if let id = state.id as? PeerMessagesMediaPlaylistItemId { if type == .music { - let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), context: strongSelf.context, chatLocation: .peer(id.messageId.peerId), chatLocationContextHolder: Atomic(value: nil), tagMask: MessageTags.music) + let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), account: account, chatLocation: .peer(id.messageId.peerId), tagMask: MessageTags.music) var cancelImpl: (() -> Void)? let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } @@ -594,7 +594,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { } else { controllerContext = strongSelf.context.sharedContext.makeTempAccountContext(account: account) } - let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, peerId: id.messageId.peerId, type: type, initialMessageId: id.messageId, initialOrder: order, isGlobalSearch: false, parentNavigationController: strongSelf.navigationController as? NavigationController) + let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, peerId: id.messageId.peerId, type: type, initialMessageId: id.messageId, initialOrder: order, parentNavigationController: strongSelf.navigationController as? NavigationController) strongSelf.displayNode.view.window?.endEditing(true) strongSelf.present(controller, in: .window(.root)) } else if index.1 { diff --git a/submodules/TelegramCore/Sources/Account.swift b/submodules/TelegramCore/Sources/Account.swift index cad791bbc4..24194959ca 100644 --- a/submodules/TelegramCore/Sources/Account.swift +++ b/submodules/TelegramCore/Sources/Account.swift @@ -103,8 +103,8 @@ public class UnauthorizedAccount { datacenterIds.append(contentsOf: [4]) } for id in datacenterIds { - if network.context.authInfoForDatacenter(withId: id, selector: .persistent) == nil { - network.context.authInfoForDatacenter(withIdRequired: id, isCdn: false, selector: .ephemeralMain) + if network.context.authInfoForDatacenter(withId: id) == nil { + network.context.authInfoForDatacenter(withIdRequired: id, isCdn: false) } } network.context.beginExplicitBackupAddressDiscovery() @@ -128,7 +128,7 @@ public class UnauthorizedAccount { } } |> mapToSignal { (localizationSettings, proxySettings, networkSettings) -> Signal in - return initializedNetwork(accountId: self.id, arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) + return initializedNetwork(arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) |> map { network in let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network) updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get()) @@ -243,7 +243,7 @@ public func accountWithId(accountManager: AccountManager, networkArguments: Netw let backupState = AuthorizedAccountState(isTestingEnvironment: beginWithTestingEnvironment, masterDatacenterId: backupData.masterDatacenterId, peerId: PeerId(backupData.peerId), state: nil) state = backupState let dict = NSMutableDictionary() - dict.setObject(MTDatacenterAuthInfo(authKey: backupData.masterDatacenterKey, authKeyId: backupData.masterDatacenterKeyId, saltSet: [], authKeyAttributes: [:]), forKey: backupData.masterDatacenterId as NSNumber) + dict.setObject(MTDatacenterAuthInfo(authKey: backupData.masterDatacenterKey, authKeyId: backupData.masterDatacenterKeyId, saltSet: [], authKeyAttributes: [:], mainTempAuthKey: nil, mediaTempAuthKey: nil), forKey: backupData.masterDatacenterId as NSNumber) let data = NSKeyedArchiver.archivedData(withRootObject: dict) transaction.setState(backupState) transaction.setKeychainEntry(data, forKey: "persistent:datacenterAuthInfoById") @@ -257,7 +257,7 @@ public func accountWithId(accountManager: AccountManager, networkArguments: Netw if let accountState = accountState { switch accountState { case let unauthorizedState as UnauthorizedAccountState: - return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) + return initializedNetwork(arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) |> map { network -> AccountResult in return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) } @@ -266,7 +266,7 @@ public func accountWithId(accountManager: AccountManager, networkArguments: Netw return (transaction.getPeer(authorizedState.peerId) as? TelegramUser)?.phone } |> mapToSignal { phoneNumber in - return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber) + return initializedNetwork(arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber) |> map { network -> AccountResult in return .authorized(Account(accountManager: accountManager, id: id, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, postbox: postbox, network: network, networkArguments: networkArguments, peerId: authorizedState.peerId, auxiliaryMethods: auxiliaryMethods, supplementary: supplementary)) } @@ -276,7 +276,7 @@ public func accountWithId(accountManager: AccountManager, networkArguments: Netw } } - return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) + return initializedNetwork(arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) |> map { network -> AccountResult in return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) } diff --git a/submodules/TelegramCore/Sources/AccountManager.swift b/submodules/TelegramCore/Sources/AccountManager.swift index e604d4f956..d2a4eea491 100644 --- a/submodules/TelegramCore/Sources/AccountManager.swift +++ b/submodules/TelegramCore/Sources/AccountManager.swift @@ -30,7 +30,6 @@ private var declaredEncodables: Void = { declareEncodable(InlineBotMessageAttribute.self, f: { InlineBotMessageAttribute(decoder: $0) }) declareEncodable(TextEntitiesMessageAttribute.self, f: { TextEntitiesMessageAttribute(decoder: $0) }) declareEncodable(ReplyMessageAttribute.self, f: { ReplyMessageAttribute(decoder: $0) }) - declareEncodable(ReplyThreadMessageAttribute.self, f: { ReplyThreadMessageAttribute(decoder: $0) }) declareEncodable(ReactionsMessageAttribute.self, f: { ReactionsMessageAttribute(decoder: $0) }) declareEncodable(PendingReactionsMessageAttribute.self, f: { PendingReactionsMessageAttribute(decoder: $0) }) declareEncodable(CloudDocumentMediaResource.self, f: { CloudDocumentMediaResource(decoder: $0) }) diff --git a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift index a25c5cf3a5..57c725bf36 100644 --- a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift @@ -975,7 +975,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo updatedState.authorizationListUpdated = true } - let message = StoreMessage(peerId: peerId, namespace: Namespaces.Message.Local, globallyUniqueId: nil, groupingKey: nil, threadId: nil, timestamp: date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: peerId, text: messageText, attributes: attributes, media: medias) + let message = StoreMessage(peerId: peerId, namespace: Namespaces.Message.Local, globallyUniqueId: nil, groupingKey: nil, timestamp: date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: peerId, text: messageText, attributes: attributes, media: medias) updatedState.addMessages([message], location: .UpperHistoryBlock) } } @@ -2274,44 +2274,9 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP var topUpperHistoryBlockMessages: [PeerIdAndMessageNamespace: MessageId.Id] = [:] - final class MessageThreadStatsRecord { - var count: Int = 0 - var peers: [PeerId] = [] - } - var messageThreadStatsDifferences: [MessageId: MessageThreadStatsRecord] = [:] - func addMessageThreadStatsDifference(threadMessageId: MessageId, add: Int, remove: Int, addedMessagePeer: PeerId?) { - if let value = messageThreadStatsDifferences[threadMessageId] { - value.count += add - remove - if let addedMessagePeer = addedMessagePeer { - value.peers.append(addedMessagePeer) - } - } else { - let value = MessageThreadStatsRecord() - messageThreadStatsDifferences[threadMessageId] = value - value.count = add - remove - if let addedMessagePeer = addedMessagePeer { - value.peers.append(addedMessagePeer) - } - } - } - for operation in optimizedOperations(finalState.state.operations) { switch operation { case let .AddMessages(messages, location): - if case .UpperHistoryBlock = location { - for message in messages { - if case let .Id(id) = message.id { - if let threadId = message.threadId { - let messageThreadId = makeThreadIdMessageId(peerId: message.id.peerId, threadId: threadId) - if id.peerId.namespace == Namespaces.Peer.CloudChannel { - if !transaction.messageExists(id: id) { - addMessageThreadStatsDifference(threadMessageId: messageThreadId, add: 1, remove: 0, addedMessagePeer: message.authorId) - } - } - } - } - } - } let _ = transaction.addMessages(messages, location: location) if case .UpperHistoryBlock = location { for message in messages { @@ -2419,9 +2384,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP let _ = mediaBox.removeCachedResources(Set(resourceIds)).start() } case let .DeleteMessages(ids): - deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids, manualAddMessageThreadStatsDifference: { id, add, remove in - addMessageThreadStatsDifference(threadMessageId: id, add: add, remove: remove, addedMessagePeer: nil) - }) + deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids) case let .UpdateMinAvailableMessage(id): if let message = transaction.getMessage(id) { updatePeerChatInclusionWithMinTimestamp(transaction: transaction, id: id.peerId, minTimestamp: message.timestamp, forceRootGroupIfNotExists: false) @@ -2812,7 +2775,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP break loop } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) case let .UpdateMessageForwardsCount(id, count): transaction.updateMessage(id, update: { currentMessage in @@ -2827,7 +2790,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP break loop } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) case let .UpdateInstalledStickerPacks(operation): stickerPackOperations.append(operation) @@ -2913,41 +2876,36 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP Logger.shared.log("State", "not adding hole for peer \(messageId.peerId), \(upperId) >= \(messageId.id) = false") } } -//TODO Please do not forget fix holes space. // could be the reason for unbounded slowdown, needs investigation -// for (peerIdAndNamespace, pts) in clearHolesFromPreviousStateForChannelMessagesWithPts { -// var upperMessageId: Int32? -// var lowerMessageId: Int32? -// transaction.scanMessageAttributes(peerId: peerIdAndNamespace.peerId, namespace: peerIdAndNamespace.namespace, limit: 200, { id, attributes in -// for attribute in attributes { -// if let attribute = attribute as? ChannelMessageStateVersionAttribute { -// if attribute.pts >= pts { -// if upperMessageId == nil { -// upperMessageId = id.id -// } -// if let lowerMessageIdValue = lowerMessageId { -// lowerMessageId = min(id.id, lowerMessageIdValue) -// } else { -// lowerMessageId = id.id -// } -// return true -// } else { -// return false -// } -// } -// } -// return false -// }) -// if let upperMessageId = upperMessageId, let lowerMessageId = lowerMessageId { -// if upperMessageId != lowerMessageId { -// transaction.removeHole(peerId: peerIdAndNamespace.peerId, namespace: peerIdAndNamespace.namespace, space: .everywhere, range: lowerMessageId ... upperMessageId) -// } -// } -// } - - for (threadMessageId, difference) in messageThreadStatsDifferences { - updateMessageThreadStats(transaction: transaction, threadMessageId: threadMessageId, difference: difference.count, addedMessagePeers: difference.peers) + for (peerIdAndNamespace, pts) in clearHolesFromPreviousStateForChannelMessagesWithPts { + var upperMessageId: Int32? + var lowerMessageId: Int32? + transaction.scanMessageAttributes(peerId: peerIdAndNamespace.peerId, namespace: peerIdAndNamespace.namespace, limit: 200, { id, attributes in + for attribute in attributes { + if let attribute = attribute as? ChannelMessageStateVersionAttribute { + if attribute.pts >= pts { + if upperMessageId == nil { + upperMessageId = id.id + } + if let lowerMessageIdValue = lowerMessageId { + lowerMessageId = min(id.id, lowerMessageIdValue) + } else { + lowerMessageId = id.id + } + return true + } else { + return false + } + } + } + return false + }) + if let upperMessageId = upperMessageId, let lowerMessageId = lowerMessageId { + if upperMessageId != lowerMessageId { + transaction.removeHole(peerId: peerIdAndNamespace.peerId, namespace: peerIdAndNamespace.namespace, space: .everywhere, range: lowerMessageId ... upperMessageId) + } + } } if !peerActivityTimestamps.isEmpty { diff --git a/submodules/TelegramCore/Sources/AccountViewTracker.swift b/submodules/TelegramCore/Sources/AccountViewTracker.swift index af888300f2..cbabb0e832 100644 --- a/submodules/TelegramCore/Sources/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/AccountViewTracker.swift @@ -180,7 +180,7 @@ private func fetchPoll(account: Account, messageId: MessageId) -> Signal [AdditionalMessageHistoryViewData] { +private func wrappedHistoryViewAdditionalData(chatLocation: ChatLocation, additionalData: [AdditionalMessageHistoryViewData]) -> [AdditionalMessageHistoryViewData] { var result = additionalData switch chatLocation { case let .peer(peerId): @@ -189,12 +189,6 @@ private func wrappedHistoryViewAdditionalData(chatLocation: ChatLocationInput, a result.append(.peerChatState(peerId)) } } - case let .external(peerId, _): - if peerId.namespace == Namespaces.Peer.CloudChannel { - if result.firstIndex(where: { if case .peerChatState = $0 { return true } else { return false } }) == nil { - result.append(.peerChatState(peerId)) - } - } } return result } @@ -381,7 +375,7 @@ public final class AccountViewTracker { break } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: media)) }) } } @@ -597,63 +591,35 @@ public final class AccountViewTracker { if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { return account.network.request(Api.functions.messages.getMessagesViews(peer: inputPeer, id: messageIds.map { $0.id }, increment: .boolTrue)) |> map(Optional.init) - |> `catch` { _ -> Signal in + |> `catch` { _ -> Signal<[Int32]?, NoError> in return .single(nil) } - |> mapToSignal { result -> Signal in - guard case let .messageViews(viewCounts, _)? = result else { - return .complete() - } - - return account.postbox.transaction { transaction -> Void in - for i in 0 ..< messageIds.count { - if i < viewCounts.count { - if case let .messageViews(_, views, forwards, replies) = viewCounts[i] { - transaction.updateMessage(messageIds[i], update: { currentMessage in - let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) - var attributes = currentMessage.attributes - var foundReplies = false - var commentsChannelId: PeerId? - var recentRepliersPeerIds: [PeerId]? - var repliesCount: Int32? - if let replies = replies { - switch replies { - case let .messageReplies(_, repliesCountValue, _, recentRepliers, channelId, _, _): - if let channelId = channelId { - commentsChannelId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) - } - repliesCount = repliesCountValue - if let recentRepliers = recentRepliers { - recentRepliersPeerIds = recentRepliers.map { $0.peerId } - } else { - recentRepliersPeerIds = nil - } - } - } - loop: for j in 0 ..< attributes.count { - if let attribute = attributes[j] as? ViewCountMessageAttribute { - if let views = views { + |> mapToSignal { viewCounts -> Signal in + if let viewCounts = viewCounts { + return account.postbox.transaction { transaction -> Void in + for i in 0 ..< messageIds.count { + if i < viewCounts.count { + /*if case let .messageViews(views, forwards) = viewCounts[i] {*/ + let views = viewCounts[i] + transaction.updateMessage(messageIds[i], update: { currentMessage in + let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) + var attributes = currentMessage.attributes + loop: for j in 0 ..< attributes.count { + if let attribute = attributes[j] as? ViewCountMessageAttribute { attributes[j] = ViewCountMessageAttribute(count: max(attribute.count, Int(views))) } - } else if let _ = attributes[j] as? ForwardCountMessageAttribute { - if let forwards = forwards { + /*if let _ = attributes[j] as? ForwardCountMessageAttribute { attributes[j] = ForwardCountMessageAttribute(count: Int(forwards)) - } - } else if let _ = attributes[j] as? ReplyThreadMessageAttribute { - foundReplies = true - if let repliesCount = repliesCount { - attributes[j] = ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsChannelId) - } + }*/ } - } - if !foundReplies, let repliesCount = repliesCount { - attributes.append(ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsChannelId)) - } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) - }) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + //} } } } + } else { + return .complete() } } } else { @@ -975,7 +941,7 @@ public final class AccountViewTracker { break loop } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) transaction.setPendingMessageAction(type: .consumeUnseenPersonalMessage, id: id, action: ConsumePersonalMessageAction()) @@ -1108,7 +1074,7 @@ public final class AccountViewTracker { } } - func wrappedMessageHistorySignal(chatLocation: ChatLocationInput, signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, addHoleIfNeeded: Bool) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + func wrappedMessageHistorySignal(chatLocation: ChatLocation, signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, addHoleIfNeeded: Bool) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { let history = withState(signal, { [weak self] () -> Int32 in if let strongSelf = self { return OSAtomicIncrement32(&strongSelf.nextViewId) @@ -1137,10 +1103,6 @@ public final class AccountViewTracker { if peerId.namespace == Namespaces.Peer.CloudChannel { strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil) } - case let .external(peerId, _): - if peerId.namespace == Namespaces.Peer.CloudChannel { - strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil) - } } } } @@ -1187,7 +1149,7 @@ public final class AccountViewTracker { } } - public func scheduledMessagesViewForLocation(_ chatLocation: ChatLocationInput, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func scheduledMessagesViewForLocation(_ chatLocation: ChatLocation, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, anchor: .upperBound, count: 200, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: nil, namespaces: .just(Namespaces.Message.allScheduled), orderStatistics: [], additionalData: additionalData) return withState(signal, { [weak self] () -> Int32 in @@ -1217,7 +1179,7 @@ public final class AccountViewTracker { } } - public func aroundMessageOfInterestHistoryViewForLocation(_ chatLocation: ChatLocationInput, count: Int, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func aroundMessageOfInterestHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { let signal = account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData)) return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal, addHoleIfNeeded: true) @@ -1226,7 +1188,7 @@ public final class AccountViewTracker { } } - public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocationInput, count: Int, messageId: MessageId, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, messageId: MessageId, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { let signal = account.postbox.aroundIdMessageHistoryViewForLocation(chatLocation, count: count, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData)) return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal, addHoleIfNeeded: false) @@ -1235,7 +1197,7 @@ public final class AccountViewTracker { } } - public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocationInput, index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, clipHoles: Bool = true, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, clipHoles: Bool = true, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { let inputAnchor: HistoryViewInputAnchor switch index { diff --git a/submodules/TelegramCore/Sources/ApplyMaxReadIndexInteractively.swift b/submodules/TelegramCore/Sources/ApplyMaxReadIndexInteractively.swift index 6de3383d3a..84974b2b75 100644 --- a/submodules/TelegramCore/Sources/ApplyMaxReadIndexInteractively.swift +++ b/submodules/TelegramCore/Sources/ApplyMaxReadIndexInteractively.swift @@ -32,7 +32,7 @@ func applyMaxReadIndexInteractively(transaction: Transaction, stateManager: Acco return currentAttribute } }) - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) }) transaction.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: id) } @@ -101,7 +101,7 @@ func applySecretOutgoingMessageReadActions(transaction: Transaction, id: Message return currentAttribute } }) - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) }) transaction.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: id) } diff --git a/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift index be58039f8e..9d09f0de6e 100644 --- a/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift @@ -42,7 +42,7 @@ func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox, force: } } -func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, message: Message, result: Api.Updates, accountPeerId: PeerId) -> Signal { +func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, message: Message, result: Api.Updates) -> Signal { return postbox.transaction { transaction -> Void in let messageId: Int32? var apiMessage: Api.Message? @@ -90,8 +90,6 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes transaction.offsetPendingMessagesTimestamps(lowerBound: message.id, excludeIds: Set([message.id]), timestamp: updatedTimestamp) } - var updatedMessage: StoreMessage? - transaction.updateMessage(message.id, update: { currentMessage in let updatedId: MessageId if let messageId = messageId { @@ -224,19 +222,8 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes } } - let updatedMessageValue = StoreMessage(id: updatedId, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: updatedTimestamp ?? currentMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: forwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media) - updatedMessage = updatedMessageValue - - return .update(updatedMessageValue) + return .update(StoreMessage(id: updatedId, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: updatedTimestamp ?? currentMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: forwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media)) }) - if let updatedMessage = updatedMessage, case let .Id(updatedId) = updatedMessage.id { - if message.id.namespace == Namespaces.Message.Local && updatedId.namespace == Namespaces.Message.Cloud && updatedId.peerId.namespace == Namespaces.Peer.CloudChannel { - if let threadId = updatedMessage.threadId { - let messageThreadId = makeThreadIdMessageId(peerId: updatedMessage.id.peerId, threadId: threadId) - updateMessageThreadStats(transaction: transaction, threadMessageId: messageThreadId, difference: 1, addedMessagePeers: [accountPeerId]) - } - } - } for file in sentStickers { transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20) } @@ -301,21 +288,20 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage var sentStickers: [TelegramMediaFile] = [] var sentGifs: [TelegramMediaFile] = [] - var updatedGroupingKey: [Int64 : [MessageId]] = [:] - for (message, _, updatedMessage) in mapping { - if let groupingKey = updatedMessage.groupingKey { - var ids = updatedGroupingKey[groupingKey] ?? [] - ids.append(message.id) - updatedGroupingKey[groupingKey] = ids + var updatedGroupingKey: Int64? + for (_, _, updatedMessage) in mapping { + if let updatedGroupingKey = updatedGroupingKey { + assert(updatedGroupingKey == updatedMessage.groupingKey) } + updatedGroupingKey = updatedMessage.groupingKey } if let latestPreviousId = latestPreviousId, let latestIndex = mapping.last?.1 { transaction.offsetPendingMessagesTimestamps(lowerBound: latestPreviousId, excludeIds: Set(mapping.map { $0.0.id }), timestamp: latestIndex.timestamp) } - for (key, ids) in updatedGroupingKey { - transaction.updateMessageGroupingKeysAtomically(ids, groupingKey: key) + if let updatedGroupingKey = updatedGroupingKey { + transaction.updateMessageGroupingKeysAtomically(mapping.map { $0.0.id }, groupingKey: updatedGroupingKey) } for (message, _, updatedMessage) in mapping { @@ -374,7 +360,7 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage let (tags, globalTags) = tagsForStoreMessage(incoming: currentMessage.flags.contains(.Incoming), attributes: attributes, media: media, textEntities: entitiesAttribute?.entities) - return .update(StoreMessage(id: updatedId, globallyUniqueId: nil, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: updatedMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media)) + return .update(StoreMessage(id: updatedId, globallyUniqueId: nil, groupingKey: currentMessage.groupingKey, timestamp: updatedMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media)) }) } diff --git a/submodules/TelegramCore/Sources/CachedChannelParticipants.swift b/submodules/TelegramCore/Sources/CachedChannelParticipants.swift index ea72e40a81..8b6b1b79d8 100644 --- a/submodules/TelegramCore/Sources/CachedChannelParticipants.swift +++ b/submodules/TelegramCore/Sources/CachedChannelParticipants.swift @@ -72,12 +72,12 @@ public struct ChannelParticipantBannedInfo: PostboxCoding, Equatable { } public enum ChannelParticipant: PostboxCoding, Equatable { - case creator(id: PeerId, adminInfo: ChannelParticipantAdminInfo?, rank: String?) + case creator(id: PeerId, rank: String?) case member(id: PeerId, invitedAt: Int32, adminInfo: ChannelParticipantAdminInfo?, banInfo: ChannelParticipantBannedInfo?, rank: String?) public var peerId: PeerId { switch self { - case let .creator(id, _, _): + case let .creator(id, _): return id case let .member(id, _, _, _, _): return id @@ -86,7 +86,7 @@ public enum ChannelParticipant: PostboxCoding, Equatable { public var rank: String? { switch self { - case let .creator(_, _, rank): + case let .creator(_, rank): return rank case let .member(_, _, _, _, rank): return rank @@ -116,8 +116,8 @@ public enum ChannelParticipant: PostboxCoding, Equatable { } else { return false } - case let .creator(id, adminInfo, rank): - if case .creator(id, adminInfo, rank) = rhs { + case let .creator(id, rank): + if case .creator(id, rank) = rhs { return true } else { return false @@ -130,7 +130,7 @@ public enum ChannelParticipant: PostboxCoding, Equatable { case ChannelParticipantValue.member.rawValue: self = .member(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), invitedAt: decoder.decodeInt32ForKey("t", orElse: 0), adminInfo: decoder.decodeObjectForKey("ai", decoder: { ChannelParticipantAdminInfo(decoder: $0) }) as? ChannelParticipantAdminInfo, banInfo: decoder.decodeObjectForKey("bi", decoder: { ChannelParticipantBannedInfo(decoder: $0) }) as? ChannelParticipantBannedInfo, rank: decoder.decodeOptionalStringForKey("rank")) case ChannelParticipantValue.creator.rawValue: - self = .creator(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), adminInfo: decoder.decodeObjectForKey("ai", decoder: { ChannelParticipantAdminInfo(decoder: $0) }) as? ChannelParticipantAdminInfo, rank: decoder.decodeOptionalStringForKey("rank")) + self = .creator(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), rank: decoder.decodeOptionalStringForKey("rank")) default: self = .member(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), invitedAt: decoder.decodeInt32ForKey("t", orElse: 0), adminInfo: nil, banInfo: nil, rank: nil) } @@ -157,14 +157,9 @@ public enum ChannelParticipant: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "rank") } - case let .creator(id, adminInfo, rank): + case let .creator(id, rank): encoder.encodeInt32(ChannelParticipantValue.creator.rawValue, forKey: "r") encoder.encodeInt64(id.toInt64(), forKey: "i") - if let adminInfo = adminInfo { - encoder.encodeObject(adminInfo, forKey: "ai") - } else { - encoder.encodeNil(forKey: "ai") - } if let rank = rank { encoder.encodeString(rank, forKey: "rank") } else { @@ -200,8 +195,8 @@ extension ChannelParticipant { switch apiParticipant { case let .channelParticipant(userId, date): self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), invitedAt: date, adminInfo: nil, banInfo: nil, rank: nil) - case let .channelParticipantCreator(_, userId, adminRights, rank): - self = .creator(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(apiAdminRights: adminRights), promotedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), canBeEditedByAccountPeer: true), rank: rank) + case let .channelParticipantCreator(_, userId, rank): + self = .creator(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), rank: rank) case let .channelParticipantBanned(flags, userId, restrictedBy, date, bannedRights): let hasLeft = (flags & (1 << 0)) != 0 let banInfo = ChannelParticipantBannedInfo(rights: TelegramChatBannedRights(apiBannedRights: bannedRights), restrictedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: restrictedBy), timestamp: date, isMember: !hasLeft) diff --git a/submodules/TelegramCore/Sources/ChannelAdmins.swift b/submodules/TelegramCore/Sources/ChannelAdmins.swift index 2557ff7ede..10d4591c3c 100644 --- a/submodules/TelegramCore/Sources/ChannelAdmins.swift +++ b/submodules/TelegramCore/Sources/ChannelAdmins.swift @@ -65,7 +65,7 @@ public func channelAdminIds(postbox: Postbox, network: Network, peerId: PeerId, switch participant { case let .channelParticipantAdmin(_, userId, _, _, _, _, _): return user.peerId.id == userId - case let .channelParticipantCreator(_, userId, _, _): + case let .channelParticipantCreator(_, userId, _): return user.peerId.id == userId default: return false diff --git a/submodules/TelegramCore/Sources/ChannelOwnershipTransfer.swift b/submodules/TelegramCore/Sources/ChannelOwnershipTransfer.swift index 8c5d7d4357..023130be15 100644 --- a/submodules/TelegramCore/Sources/ChannelOwnershipTransfer.swift +++ b/submodules/TelegramCore/Sources/ChannelOwnershipTransfer.swift @@ -92,7 +92,7 @@ public func updateChannelOwnership(account: Account, accountStateManager: Accoun flags = TelegramChatAdminRightsFlags.groupSpecific } - let updatedParticipant = ChannelParticipant.creator(id: user.id, adminInfo: nil, rank: currentParticipant?.rank) + let updatedParticipant = ChannelParticipant.creator(id: user.id, rank: currentParticipant?.rank) let updatedPreviousCreator = ChannelParticipant.member(id: accountUser.id, invitedAt: Int32(Date().timeIntervalSince1970), adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(flags: flags), promotedBy: accountUser.id, canBeEditedByAccountPeer: false), banInfo: nil, rank: currentCreator?.rank) let checkPassword = twoStepAuthData(account.network) diff --git a/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift b/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift index 5c3cbde19c..dfb6619eaa 100644 --- a/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift +++ b/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift @@ -102,8 +102,7 @@ private final class HistoryPreloadEntry: Comparable { |> mapToSignal { download -> Signal in switch hole.hole { case let .peer(peerHole): - return fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .download(download), postbox: postbox, peerId: peerHole.peerId, namespace: peerHole.namespace, direction: hole.direction, space: .everywhere, threadId: nil, count: 60) - |> ignoreValues + return fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .download(download), postbox: postbox, peerId: peerHole.peerId, namespace: peerHole.namespace, direction: hole.direction, space: .everywhere, count: 60) } } ) diff --git a/submodules/TelegramCore/Sources/DeleteMessages.swift b/submodules/TelegramCore/Sources/DeleteMessages.swift index c8c06b4de4..14dfab3fd3 100644 --- a/submodules/TelegramCore/Sources/DeleteMessages.swift +++ b/submodules/TelegramCore/Sources/DeleteMessages.swift @@ -23,7 +23,7 @@ func addMessageMediaResourceIdsToRemove(message: Message, resourceIds: inout [Wr } } -public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [MessageId], deleteMedia: Bool = true, manualAddMessageThreadStatsDifference: ((MessageId, Int, Int) -> Void)? = nil) { +public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [MessageId], deleteMedia: Bool = true) { var resourceIds: [WrappedMediaResourceId] = [] if deleteMedia { for id in ids { @@ -37,22 +37,6 @@ public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [M if !resourceIds.isEmpty { let _ = mediaBox.removeCachedResources(Set(resourceIds)).start() } - for id in ids { - if id.peerId.namespace == Namespaces.Peer.CloudChannel && id.namespace == Namespaces.Message.Cloud { - if let message = transaction.getMessage(id) { - if let threadId = message.threadId { - let messageThreadId = makeThreadIdMessageId(peerId: message.id.peerId, threadId: threadId) - if id.peerId.namespace == Namespaces.Peer.CloudChannel { - if let manualAddMessageThreadStatsDifference = manualAddMessageThreadStatsDifference { - manualAddMessageThreadStatsDifference(messageThreadId, 0, 1) - } else { - updateMessageThreadStats(transaction: transaction, threadMessageId: messageThreadId, difference: -1, addedMessagePeers: []) - } - } - } - } - } - } transaction.deleteMessages(ids, forEachMedia: { _ in }) } diff --git a/submodules/TelegramCore/Sources/DeleteMessagesInteractively.swift b/submodules/TelegramCore/Sources/DeleteMessagesInteractively.swift index e206ada74b..12a257f601 100644 --- a/submodules/TelegramCore/Sources/DeleteMessagesInteractively.swift +++ b/submodules/TelegramCore/Sources/DeleteMessagesInteractively.swift @@ -114,7 +114,7 @@ public func clearHistoryInteractively(postbox: Postbox, peerId: PeerId, type: In } if let topIndex = topIndex { if peerId.namespace == Namespaces.Peer.CloudUser { - let _ = transaction.addMessages([StoreMessage(id: topIndex.id, globallyUniqueId: nil, groupingKey: nil, threadId: nil, timestamp: topIndex.timestamp, flags: StoreMessageFlags(), tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: nil, text: "", attributes: [], media: [TelegramMediaAction(action: .historyCleared)])], location: .Random) + let _ = transaction.addMessages([StoreMessage(id: topIndex.id, globallyUniqueId: nil, groupingKey: nil, timestamp: topIndex.timestamp, flags: StoreMessageFlags(), tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: nil, text: "", attributes: [], media: [TelegramMediaAction(action: .historyCleared)])], location: .Random) } else { updatePeerChatInclusionWithMinTimestamp(transaction: transaction, id: peerId, minTimestamp: topIndex.timestamp, forceRootGroupIfNotExists: false) } diff --git a/submodules/TelegramCore/Sources/EnqueueMessage.swift b/submodules/TelegramCore/Sources/EnqueueMessage.swift index 56e46a2933..b1fc81bf18 100644 --- a/submodules/TelegramCore/Sources/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/EnqueueMessage.swift @@ -316,11 +316,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, attributes.append(contentsOf: filterMessageAttributesForOutgoingMessage(requestedAttributes)) if let replyToMessageId = replyToMessageId, replyToMessageId.peerId == peerId { - var threadMessageId: MessageId? - if let replyMessage = transaction.getMessage(replyToMessageId) { - threadMessageId = replyMessage.effectiveReplyThreadMessageId - } - attributes.append(ReplyMessageAttribute(messageId: replyToMessageId, threadMessageId: threadMessageId)) + attributes.append(ReplyMessageAttribute(messageId: replyToMessageId)) } var mediaList: [Media] = [] if let mediaReference = mediaReference { @@ -366,14 +362,8 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, } let authorId: PeerId? - if let peer = peer as? TelegramChannel { - if case .broadcast = peer.info { - authorId = peer.id - } else if case .group = peer.info, peer.hasPermission(.canBeAnonymous) { - authorId = peer.id - } else { - authorId = account.peerId - } + if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + authorId = peer.id } else { authorId = account.peerId } @@ -418,18 +408,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, } } - var threadId: Int64? - if let replyToMessageId = replyToMessageId { - if let message = transaction.getMessage(replyToMessageId) { - if let threadIdValue = message.threadId { - threadId = threadIdValue - } else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { - threadId = makeMessageThreadId(replyToMessageId) - } - } - } - - storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, threadId: threadId, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: mediaList)) + storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: mediaList)) case let .forward(source, grouping, requestedAttributes): let sourceMessage = transaction.getMessage(source) if let sourceMessage = sourceMessage, let author = sourceMessage.author ?? sourceMessage.peers[sourceMessage.id.peerId] { @@ -535,14 +514,8 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, } let authorId: PeerId? - if let peer = peer as? TelegramChannel { - if case .broadcast = peer.info { - authorId = peer.id - } else if case .group = peer.info, peer.hasPermission(.canBeAnonymous) { - authorId = peer.id - } else { - authorId = account.peerId - } + if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + authorId = peer.id } else { authorId = account.peerId } @@ -550,20 +523,16 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, var messageNamespace = Namespaces.Message.Local var entitiesAttribute: TextEntitiesMessageAttribute? var effectiveTimestamp = timestamp - var threadId: Int64? for attribute in attributes { if let attribute = attribute as? TextEntitiesMessageAttribute { entitiesAttribute = attribute - } else if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute { + } + if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute { if attribute.scheduleTime == scheduleWhenOnlineTimestamp, let presence = peerPresence as? TelegramUserPresence, case let .present(statusTimestamp) = presence.status, statusTimestamp >= timestamp { } else { messageNamespace = Namespaces.Message.ScheduledLocal effectiveTimestamp = attribute.scheduleTime } - } else if let attribute = attribute as? ReplyMessageAttribute { - if let threadMessageId = attribute.threadMessageId { - threadId = makeMessageThreadId(threadMessageId) - } } } @@ -599,7 +568,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, augmentedMediaList = augmentedMediaList.map(convertForwardedMediaForSecretChat) } - storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, threadId: threadId, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: sourceMessage.text, attributes: attributes, media: augmentedMediaList)) + storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: sourceMessage.text, attributes: attributes, media: augmentedMediaList)) } } } diff --git a/submodules/TelegramCore/Sources/ExportMessageLink.swift b/submodules/TelegramCore/Sources/ExportMessageLink.swift index a75088a9bb..75be0fce4c 100644 --- a/submodules/TelegramCore/Sources/ExportMessageLink.swift +++ b/submodules/TelegramCore/Sources/ExportMessageLink.swift @@ -3,37 +3,16 @@ import Postbox import TelegramApi import SwiftSignalKit -public func exportMessageLink(account: Account, peerId: PeerId, messageId: MessageId, threadMessageId: MessageId? = nil) -> Signal { - return account.postbox.transaction { transaction -> (Peer, MessageId)? in - var peer: Peer? - var messageId = messageId - if let threadMessageId = threadMessageId { - messageId = threadMessageId - if let message = transaction.getMessage(threadMessageId), let sourceReference = message.sourceReference { - peer = transaction.getPeer(sourceReference.messageId.peerId) - messageId = sourceReference.messageId - } - } else { - peer = transaction.getPeer(messageId.peerId) - } - if let peer = peer { - return (peer, messageId) - } else { - return nil - } +public func exportMessageLink(account: Account, peerId: PeerId, messageId: MessageId) -> Signal { + return account.postbox.transaction { transaction -> Peer? in + return transaction.getPeer(peerId) } - |> mapToSignal { data -> Signal in - guard let (peer, sourceMessageId) = data else { - return .single(nil) - } - if let input = apiInputChannel(peer) { - return account.network.request(Api.functions.channels.exportMessageLink(channel: input, id: sourceMessageId.id, grouped: .boolTrue)) |> mapError { _ in return } + |> mapToSignal { peer -> Signal in + if let peer = peer, let input = apiInputChannel(peer) { + return account.network.request(Api.functions.channels.exportMessageLink(channel: input, id: messageId.id, grouped: .boolTrue)) |> mapError { _ in return } |> map { res in switch res { case let .exportedMessageLink(link, _): - if let _ = threadMessageId { - return "\(link)?comment=\(messageId.id)" - } return link } } |> `catch` { _ -> Signal in diff --git a/submodules/TelegramCore/Sources/HistoryViewStateValidation.swift b/submodules/TelegramCore/Sources/HistoryViewStateValidation.swift index 590ad6d6d1..da433663bf 100644 --- a/submodules/TelegramCore/Sources/HistoryViewStateValidation.swift +++ b/submodules/TelegramCore/Sources/HistoryViewStateValidation.swift @@ -138,7 +138,7 @@ final class HistoryViewStateValidationContexts { self.accountPeerId = accountPeerId } - func updateView(id: Int32, view: MessageHistoryView?, location: ChatLocationInput? = nil) { + func updateView(id: Int32, view: MessageHistoryView?, location: ChatLocation? = nil) { assert(self.queue.isCurrent()) guard let view = view, view.tagMask == nil || view.tagMask == MessageTags.unseenPersonalMessage || view.tagMask == MessageTags.music else { if self.contexts[id] != nil { @@ -375,7 +375,7 @@ private func validateChannelMessagesBatch(postbox: Postbox, network: Network, ac if tag == MessageTags.unseenPersonalMessage { requestSignal = network.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1)) } else if let filter = messageFilterForTagMask(tag) { - requestSignal = network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, topMsgId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1, hash: hash)) + requestSignal = network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1, hash: hash)) } else { assertionFailure() requestSignal = .complete() @@ -600,7 +600,7 @@ private func validateBatch(postbox: Postbox, network: Network, transaction: Tran let updatedFlags = StoreMessageFlags(currentMessage.flags) - return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: updatedFlags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: updatedFlags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) } }) @@ -640,7 +640,7 @@ private func validateBatch(postbox: Postbox, network: Network, transaction: Tran default: break } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) } } @@ -673,7 +673,7 @@ private func validateBatch(postbox: Postbox, network: Network, transaction: Tran default: break } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) } else { deleteMessages(transaction: transaction, mediaBox: postbox.mediaBox, ids: [id]) @@ -708,7 +708,7 @@ private func validateBatch(postbox: Postbox, network: Network, transaction: Tran default: break } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) } } diff --git a/submodules/TelegramCore/Sources/Holes.swift b/submodules/TelegramCore/Sources/Holes.swift index ada0911e65..53c07bf180 100644 --- a/submodules/TelegramCore/Sources/Holes.swift +++ b/submodules/TelegramCore/Sources/Holes.swift @@ -42,8 +42,8 @@ enum FetchMessageHistoryHoleSource { } } -func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistoryHoleSource, peers: [PeerId: Peer], storeMessages: [StoreMessage], _ f: @escaping (Transaction, [Peer], [StoreMessage]) -> T) -> Signal { - return postbox.transaction { transaction -> Signal in +func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistoryHoleSource, peers: [PeerId: Peer], storeMessages: [StoreMessage], _ f: @escaping (Transaction, [Peer], [StoreMessage]) -> Void) -> Signal { + return postbox.transaction { transaction -> Signal in var storedIds = Set() var referencedIds = Set() for message in storeMessages { @@ -59,7 +59,8 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHis referencedIds.subtract(transaction.filterStoredMessageIds(referencedIds)) if referencedIds.isEmpty { - return .single(f(transaction, [], [])) + f(transaction, [], []) + return .complete() } else { var signals: [Signal<([Api.Message], [Api.Chat], [Api.User]), NoError>] = [] for (peerId, messageIds) in messagesIdsGroupedByPeerId(referencedIds) { @@ -96,7 +97,7 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHis let fetchMessages = combineLatest(signals) return fetchMessages - |> mapToSignal { results -> Signal in + |> mapToSignal { results -> Signal in var additionalPeers: [Peer] = [] var additionalMessages: [StoreMessage] = [] for (messages, chats, users) in results { @@ -116,16 +117,17 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHis additionalPeers.append(TelegramUser(user: user)) } } - return postbox.transaction { transaction -> T in - return f(transaction, additionalPeers, additionalMessages) + return postbox.transaction { transaction -> Void in + f(transaction, additionalPeers, additionalMessages) } + |> ignoreValues } } } |> switchToLatest } -func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryHoleSource, postbox: Postbox, peerId: PeerId, namespace: MessageId.Namespace, direction: MessageHistoryViewRelativeHoleDirection, space: MessageHistoryHoleSpace, threadId: MessageId?, count rawCount: Int) -> Signal { +func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryHoleSource, postbox: Postbox, peerId: PeerId, namespace: MessageId.Namespace, direction: MessageHistoryViewRelativeHoleDirection, space: MessageHistoryHoleSpace, count rawCount: Int) -> Signal { let count = min(100, rawCount) return postbox.stateView() @@ -137,50 +139,10 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH } } |> take(1) - |> mapToSignal { _ -> Signal in - return postbox.transaction { transaction -> (Peer?, Int32) in - var hash: Int32 = 0 - switch space { - case .everywhere: - if let threadId = threadId { - let offsetId: Int32 - let addOffset: Int32 - let selectedLimit = count - let maxId: Int32 - let minId: Int32 - - switch direction { - case let .range(start, end): - if start.id <= end.id { - offsetId = start.id <= 1 ? 1 : (start.id - 1) - addOffset = Int32(-selectedLimit) - maxId = end.id - minId = start.id - 1 - } else { - offsetId = start.id == Int32.max ? start.id : (start.id + 1) - addOffset = 0 - maxId = start.id == Int32.max ? start.id : (start.id + 1) - minId = end.id - } - case let .aroundId(id): - offsetId = id.id - addOffset = Int32(-selectedLimit / 2) - maxId = Int32.max - minId = 1 - } - - //request = source.request(Api.functions.messages.getReplies(peer: inputPeer, msgId: threadId.id, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0)) - } - default: - break - } - - return (transaction.getPeer(peerId), hash) - } - |> mapToSignal { (peer, hash) -> Signal in - guard let peer = peer else { - return .single(IndexSet()) - } + |> mapToSignal { _ -> Signal in + return postbox.loadedPeerWithId(peerId) + |> take(1) + |> mapToSignal { peer in if let inputPeer = forceApiInputPeer(peer) { print("fetchMessageHistoryHole for \(peer.id) \(peer.debugDisplayTitle) \(direction) space \(space)") Logger.shared.log("fetchMessageHistoryHole", "fetch for \(peer.id) \(peer.debugDisplayTitle) \(direction) space \(space)") @@ -190,102 +152,52 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH switch space { case .everywhere: - if let threadId = threadId { - let offsetId: Int32 - let addOffset: Int32 - let selectedLimit = count - let maxId: Int32 - let minId: Int32 - - switch direction { - case let .range(start, end): - if start.id <= end.id { - offsetId = start.id <= 1 ? 1 : (start.id - 1) - addOffset = Int32(-selectedLimit) - maxId = end.id - minId = start.id - 1 - - let rangeStartId = start.id - let rangeEndId = min(end.id, Int32.max - 1) - if rangeStartId <= rangeEndId { - minMaxRange = rangeStartId ... rangeEndId - } else { - minMaxRange = rangeStartId ... rangeStartId - assertionFailure() - } + let offsetId: Int32 + let addOffset: Int32 + let selectedLimit = count + let maxId: Int32 + let minId: Int32 + + switch direction { + case let .range(start, end): + if start.id <= end.id { + offsetId = start.id <= 1 ? 1 : (start.id - 1) + addOffset = Int32(-selectedLimit) + maxId = end.id + minId = start.id - 1 + + let rangeStartId = start.id + let rangeEndId = min(end.id, Int32.max - 1) + if rangeStartId <= rangeEndId { + minMaxRange = rangeStartId ... rangeEndId } else { - offsetId = start.id == Int32.max ? start.id : (start.id + 1) - addOffset = 0 - maxId = start.id == Int32.max ? start.id : (start.id + 1) - minId = end.id - - let rangeStartId = end.id - let rangeEndId = min(start.id, Int32.max - 1) - if rangeStartId <= rangeEndId { - minMaxRange = rangeStartId ... rangeEndId - } else { - minMaxRange = rangeStartId ... rangeStartId - assertionFailure() - } + minMaxRange = rangeStartId ... rangeStartId + assertionFailure() } - case let .aroundId(id): - offsetId = id.id - addOffset = Int32(-selectedLimit / 2) - maxId = Int32.max - minId = 1 - - minMaxRange = 1 ... (Int32.max - 1) - } - - request = source.request(Api.functions.messages.getReplies(peer: inputPeer, msgId: threadId.id, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: hash)) - } else { - let offsetId: Int32 - let addOffset: Int32 - let selectedLimit = count - let maxId: Int32 - let minId: Int32 - - switch direction { - case let .range(start, end): - if start.id <= end.id { - offsetId = start.id <= 1 ? 1 : (start.id - 1) - addOffset = Int32(-selectedLimit) - maxId = end.id - minId = start.id - 1 - - let rangeStartId = start.id - let rangeEndId = min(end.id, Int32.max - 1) - if rangeStartId <= rangeEndId { - minMaxRange = rangeStartId ... rangeEndId - } else { - minMaxRange = rangeStartId ... rangeStartId - assertionFailure() - } + } else { + offsetId = start.id == Int32.max ? start.id : (start.id + 1) + addOffset = 0 + maxId = start.id == Int32.max ? start.id : (start.id + 1) + minId = end.id + + let rangeStartId = end.id + let rangeEndId = min(start.id, Int32.max - 1) + if rangeStartId <= rangeEndId { + minMaxRange = rangeStartId ... rangeEndId } else { - offsetId = start.id == Int32.max ? start.id : (start.id + 1) - addOffset = 0 - maxId = start.id == Int32.max ? start.id : (start.id + 1) - minId = end.id - - let rangeStartId = end.id - let rangeEndId = min(start.id, Int32.max - 1) - if rangeStartId <= rangeEndId { - minMaxRange = rangeStartId ... rangeEndId - } else { - minMaxRange = rangeStartId ... rangeStartId - assertionFailure() - } + minMaxRange = rangeStartId ... rangeStartId + assertionFailure() } - case let .aroundId(id): - offsetId = id.id - addOffset = Int32(-selectedLimit / 2) - maxId = Int32.max - minId = 1 - minMaxRange = 1 ... Int32.max - 1 - } - - request = source.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: offsetId, offsetDate: 0, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0)) + } + case let .aroundId(id): + offsetId = id.id + addOffset = Int32(-selectedLimit / 2) + maxId = Int32.max + minId = 1 + minMaxRange = 1 ... Int32.max - 1 } + + request = source.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: offsetId, offsetDate: 0, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0)) case let .tag(tag): assert(tag.containsSingleElement) if tag == .unseenPersonalMessage { @@ -392,17 +304,17 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH minMaxRange = 1 ... (Int32.max - 1) } - request = source.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, topMsgId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0)) + request = source.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0)) } else { assertionFailure() minMaxRange = 1 ... 1 request = .never() - } + } } return request |> retryRequest - |> mapToSignal { result -> Signal in + |> mapToSignal { result -> Signal in let messages: [Api.Message] let chats: [Api.Chat] let users: [Api.User] @@ -456,10 +368,10 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH } } - return withResolvedAssociatedMessages(postbox: postbox, source: source, peers: Dictionary(peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages -> IndexSet in + return withResolvedAssociatedMessages(postbox: postbox, source: source, peers: Dictionary(peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages in let _ = transaction.addMessages(storeMessages, location: .Random) let _ = transaction.addMessages(additionalMessages, location: .Random) - var filledRange: ClosedRange + let filledRange: ClosedRange let ids = storeMessages.compactMap { message -> MessageId.Id? in switch message.id { case let .Id(id): @@ -484,11 +396,6 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH switch direction { case let .aroundId(aroundId): filledRange = min(aroundId.id, messageRange.lowerBound) ... max(aroundId.id, messageRange.upperBound) - if threadId != nil { - if ids.count <= count / 2 - 1 { - filledRange = minMaxRange - } - } case let .range(start, end): if start.id <= end.id { let minBound = start.id @@ -501,19 +408,16 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH } } } - - if threadId == nil { - transaction.removeHole(peerId: peerId, namespace: namespace, space: space, range: filledRange) - } + transaction.removeHole(peerId: peerId, namespace: namespace, space: space, range: filledRange) updatePeers(transaction: transaction, peers: peers + additionalPeers, update: { _, updated -> Peer in return updated }) updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences) - print("fetchMessageHistoryHole for \(peer.id) \(peer.debugDisplayTitle) space \(space) threadId: \(String(describing: threadId)) done") + print("fetchMessageHistoryHole for \(peer.id) \(peer.debugDisplayTitle) space \(space) done") - return IndexSet(integersIn: Int(filledRange.lowerBound) ... Int(filledRange.upperBound)) + return }) } } else { @@ -600,10 +504,7 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId for (groupId, summary) in fetchedChats.folderSummaries { transaction.resetPeerGroupSummary(groupId: groupId, namespace: Namespaces.Message.Cloud, summary: summary) } - - return }) - |> ignoreValues } } @@ -612,7 +513,7 @@ func fetchCallListHole(network: Network, postbox: Postbox, accountPeerId: PeerId offset = single((holeIndex.timestamp, min(holeIndex.id.id, Int32.max - 1) + 1, Api.InputPeer.inputPeerEmpty), NoError.self) return offset |> mapToSignal { (timestamp, id, peer) -> Signal in - let searchResult = network.request(Api.functions.messages.search(flags: 0, peer: .inputPeerEmpty, q: "", fromId: nil, topMsgId: nil, filter: .inputMessagesFilterPhoneCalls(flags: 0), minDate: 0, maxDate: holeIndex.timestamp, offsetId: 0, addOffset: 0, limit: limit, maxId: holeIndex.id.id, minId: 0, hash: 0)) + let searchResult = network.request(Api.functions.messages.search(flags: 0, peer: .inputPeerEmpty, q: "", fromId: nil, filter: .inputMessagesFilterPhoneCalls(flags: 0), minDate: 0, maxDate: holeIndex.timestamp, offsetId: 0, addOffset: 0, limit: limit, maxId: holeIndex.id.id, minId: 0, hash: 0)) |> retryRequest |> mapToSignal { result -> Signal in let messages: [Api.Message] diff --git a/submodules/TelegramCore/Sources/InstallInteractiveReadMessagesAction.swift b/submodules/TelegramCore/Sources/InstallInteractiveReadMessagesAction.swift index ec862edad0..d24d8915ef 100644 --- a/submodules/TelegramCore/Sources/InstallInteractiveReadMessagesAction.swift +++ b/submodules/TelegramCore/Sources/InstallInteractiveReadMessagesAction.swift @@ -49,7 +49,7 @@ public func installInteractiveReadMessagesAction(postbox: Postbox, stateManager: break loop } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) transaction.setPendingMessageAction(type: .consumeUnseenPersonalMessage, id: id, action: ConsumePersonalMessageAction()) diff --git a/submodules/TelegramCore/Sources/ManageChannelDiscussionGroup.swift b/submodules/TelegramCore/Sources/ManageChannelDiscussionGroup.swift index e8fc694411..ab99f17e84 100644 --- a/submodules/TelegramCore/Sources/ManageChannelDiscussionGroup.swift +++ b/submodules/TelegramCore/Sources/ManageChannelDiscussionGroup.swift @@ -72,30 +72,30 @@ public func updateGroupDiscussionForChannel(network: Network, postbox: Postbox, if result { return postbox.transaction { transaction in if let channelId = channelId { - var previousGroupId: CachedChannelData.LinkedDiscussionPeerId? + var previousGroupId: PeerId? transaction.updatePeerCachedData(peerIds: Set([channelId]), update: { (_, current) -> CachedPeerData? in let current: CachedChannelData = current as? CachedChannelData ?? CachedChannelData() previousGroupId = current.linkedDiscussionPeerId - return current.withUpdatedLinkedDiscussionPeerId(.known(groupId)) + return current.withUpdatedLinkedDiscussionPeerId(groupId) }) - if case let .known(maybeValue)? = previousGroupId, let value = maybeValue, value != groupId { - transaction.updatePeerCachedData(peerIds: Set([value]), update: { (_, current) -> CachedPeerData? in + if let previousGroupId = previousGroupId, previousGroupId != groupId { + transaction.updatePeerCachedData(peerIds: Set([previousGroupId]), update: { (_, current) -> CachedPeerData? in let cachedData = (current as? CachedChannelData ?? CachedChannelData()) - return cachedData.withUpdatedLinkedDiscussionPeerId(.known(nil)) + return cachedData.withUpdatedLinkedDiscussionPeerId(nil) }) } } if let groupId = groupId { - var previousChannelId: CachedChannelData.LinkedDiscussionPeerId? + var previousChannelId: PeerId? transaction.updatePeerCachedData(peerIds: Set([groupId]), update: { (_, current) -> CachedPeerData? in let current: CachedChannelData = current as? CachedChannelData ?? CachedChannelData() previousChannelId = current.linkedDiscussionPeerId - return current.withUpdatedLinkedDiscussionPeerId(.known(channelId)) + return current.withUpdatedLinkedDiscussionPeerId(channelId) }) - if case let .known(maybeValue)? = previousChannelId, let value = maybeValue, value != channelId { - transaction.updatePeerCachedData(peerIds: Set([value]), update: { (_, current) -> CachedPeerData? in + if let previousChannelId = previousChannelId, previousChannelId != channelId { + transaction.updatePeerCachedData(peerIds: Set([previousChannelId]), update: { (_, current) -> CachedPeerData? in let cachedData = (current as? CachedChannelData ?? CachedChannelData()) - return cachedData.withUpdatedLinkedDiscussionPeerId(.known(nil)) + return cachedData.withUpdatedLinkedDiscussionPeerId(nil) }) } } diff --git a/submodules/TelegramCore/Sources/ManagedAutoremoveMessageOperations.swift b/submodules/TelegramCore/Sources/ManagedAutoremoveMessageOperations.swift index 76c4ac24b6..fd094efc44 100644 --- a/submodules/TelegramCore/Sources/ManagedAutoremoveMessageOperations.swift +++ b/submodules/TelegramCore/Sources/ManagedAutoremoveMessageOperations.swift @@ -74,7 +74,7 @@ func managedAutoremoveMessageOperations(postbox: Postbox) -> Signal ignoreValues @@ -220,7 +220,7 @@ private func synchronizeMessageReactions(transaction: Transaction, postbox: Post break loop } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) } |> ignoreValues diff --git a/submodules/TelegramCore/Sources/MessageUtils.swift b/submodules/TelegramCore/Sources/MessageUtils.swift index 3c84ab7aa9..1f09a7143a 100644 --- a/submodules/TelegramCore/Sources/MessageUtils.swift +++ b/submodules/TelegramCore/Sources/MessageUtils.swift @@ -168,11 +168,7 @@ func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer]) -> Mes } } - var hash: Int32 = id.id - hash = hash &* 31 &+ id.peerId.id - let stableId = UInt32(clamping: hash) - - return Message(stableId: stableId, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + return Message(stableId: 0, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) } public extension Message { diff --git a/submodules/TelegramCore/Sources/Network.swift b/submodules/TelegramCore/Sources/Network.swift index 4e7a4a2c60..7d0313466e 100644 --- a/submodules/TelegramCore/Sources/Network.swift +++ b/submodules/TelegramCore/Sources/Network.swift @@ -424,17 +424,7 @@ public struct NetworkInitializationArguments { private let cloudDataContext = Atomic(value: nil) #endif -private final class SharedContextStore { - struct Key: Hashable { - var accountId: AccountRecordId - } - - var contexts: [Key: MTContext] = [:] -} - -private let sharedContexts = Atomic(value: SharedContextStore()) - -func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?) -> Signal { +func initializedNetwork(arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?) -> Signal { return Signal { subscriber in let queue = Queue() queue.async { @@ -474,22 +464,7 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa } } - var contextValue: MTContext? - sharedContexts.with { store in - let key = SharedContextStore.Key(accountId: accountId) - - let context: MTContext - if let current = store.contexts[key] { - context = current - context.updateApiEnvironment({ _ in return apiEnvironment}) - } else { - context = MTContext(serialization: serialization, encryptionProvider: arguments.encryptionProvider, apiEnvironment: apiEnvironment, isTestingEnvironment: testingEnvironment, useTempAuthKeys: true)! - store.contexts[key] = context - } - contextValue = context - } - - let context = contextValue! + let context = MTContext(serialization: serialization, encryptionProvider: arguments.encryptionProvider, apiEnvironment: apiEnvironment, isTestingEnvironment: testingEnvironment, useTempAuthKeys: false)! let seedAddressList: [Int: [String]] diff --git a/submodules/TelegramCore/Sources/PeerAdmins.swift b/submodules/TelegramCore/Sources/PeerAdmins.swift index 3ddfdf6dad..416a0596e0 100644 --- a/submodules/TelegramCore/Sources/PeerAdmins.swift +++ b/submodules/TelegramCore/Sources/PeerAdmins.swift @@ -165,13 +165,7 @@ public func updateChannelAdminRights(account: Account, peerId: PeerId, adminId: } updatedParticipant = .member(id: adminId, invitedAt: invitedAt, adminInfo: adminInfo, banInfo: nil, rank: rank) } else if let currentParticipant = currentParticipant, case .creator = currentParticipant { - let adminInfo: ChannelParticipantAdminInfo? - if !rights.flags.isEmpty { - adminInfo = ChannelParticipantAdminInfo(rights: rights, promotedBy: account.peerId, canBeEditedByAccountPeer: true) - } else { - adminInfo = nil - } - updatedParticipant = .creator(id: adminId, adminInfo: adminInfo, rank: rank) + updatedParticipant = .creator(id: adminId, rank: rank) } else { let adminInfo: ChannelParticipantAdminInfo? if !rights.flags.isEmpty { diff --git a/submodules/TelegramCore/Sources/PeerUtils.swift b/submodules/TelegramCore/Sources/PeerUtils.swift index 23f84731d4..28571aa2ea 100644 --- a/submodules/TelegramCore/Sources/PeerUtils.swift +++ b/submodules/TelegramCore/Sources/PeerUtils.swift @@ -231,24 +231,3 @@ public func isServicePeer(_ peer: Peer) -> Bool { } return false } - -public extension PeerId { - var isReplies: Bool { - if self.namespace == Namespaces.Peer.CloudUser { - if self.id == 708513 || self.id == 1271266957 { - return true - } - } - return false - } - - func isRepliesOrSavedMessages(accountPeerId: PeerId) -> Bool { - if accountPeerId == self { - return true - } else if self.isReplies { - return true - } else { - return false - } - } -} diff --git a/submodules/TelegramCore/Sources/PendingMessageManager.swift b/submodules/TelegramCore/Sources/PendingMessageManager.swift index 500e9f32e8..a1cc5d6fc5 100644 --- a/submodules/TelegramCore/Sources/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/PendingMessageManager.swift @@ -111,7 +111,7 @@ private func failMessages(postbox: Postbox, ids: [MessageId]) -> Signal if isForward { - if messages.contains(where: { $0.0.groupingKey != nil }) { + if !messages.contains(where: { $0.0.groupingKey == nil }) { flags |= (1 << 9) } @@ -906,7 +906,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } else { let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: message.id.peerId, operation: .sendMessage(layer: layer, id: message.id, file: secretFile), state: state) @@ -922,7 +922,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } } else { @@ -931,7 +931,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } } @@ -1072,7 +1072,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) }).start() } @@ -1086,7 +1086,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } } @@ -1114,7 +1114,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) } } @@ -1135,7 +1135,7 @@ public final class PendingMessageManager { namespace = id.namespace } - return applyUpdateMessage(postbox: postbox, stateManager: stateManager, message: message, result: result, accountPeerId: self.accountPeerId) + return applyUpdateMessage(postbox: postbox, stateManager: stateManager, message: message, result: result) |> afterDisposed { [weak self] in if let strongSelf = self { strongSelf.queue.async { diff --git a/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift index 9feef62447..16b490b314 100644 --- a/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift @@ -352,7 +352,7 @@ private func uploadedMediaImageContent(network: Network, postbox: Postbox, trans } else { updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: arc4random64(), flags: [.transformedMedia], acknowledged: false)) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) }) } return .done(media) @@ -641,7 +641,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili } else { updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: arc4random64(), flags: [.transformedMedia], acknowledged: false)) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) }) } return .done(media) diff --git a/submodules/TelegramCore/Sources/ProcessSecretChatIncomingDecryptedOperations.swift b/submodules/TelegramCore/Sources/ProcessSecretChatIncomingDecryptedOperations.swift index 431af1370b..b86d428398 100644 --- a/submodules/TelegramCore/Sources/ProcessSecretChatIncomingDecryptedOperations.swift +++ b/submodules/TelegramCore/Sources/ProcessSecretChatIncomingDecryptedOperations.swift @@ -512,7 +512,7 @@ extension StoreMessage { convenience init?(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi8.DecryptedMessage, file: SecretChatFileReference?) { switch apiMessage { case let .decryptedMessage(randomId, _, message, media): - self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: message, attributes: [], media: []) + self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: message, attributes: [], media: []) case let .decryptedMessageService(randomId, _, action): switch action { case let .decryptedMessageActionDeleteMessages(randomIds): @@ -524,9 +524,9 @@ extension StoreMessage { case let .decryptedMessageActionReadMessages(randomIds): return nil case .decryptedMessageActionScreenshotMessages: - self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]) + self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]) + self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]) } } } @@ -809,7 +809,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) { - attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)) + attributes.append(ReplyMessageAttribute(messageId: replyMessageId)) } var entitiesAttribute: TextEntitiesMessageAttribute? @@ -822,7 +822,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 let (tags, globalTags) = tagsForStoreMessage(incoming: true, attributes: attributes, media: parsedMedia, textEntities: entitiesAttribute?.entities) - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) case let .decryptedMessageService(randomId, action): switch action { case let .decryptedMessageActionDeleteMessages(randomIds): @@ -834,9 +834,9 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 case .decryptedMessageActionReadMessages: return nil case .decryptedMessageActionScreenshotMessages: - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) case .decryptedMessageActionResend: return nil case .decryptedMessageActionRequestKey: @@ -1041,7 +1041,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) { - attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)) + attributes.append(ReplyMessageAttribute(messageId: replyMessageId)) } var entitiesAttribute: TextEntitiesMessageAttribute? @@ -1054,7 +1054,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 let (tags, globalTags) = tagsForStoreMessage(incoming: true, attributes: attributes, media: parsedMedia, textEntities: entitiesAttribute?.entities) - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: groupingKey, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: groupingKey, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) case let .decryptedMessageService(randomId, action): switch action { case .decryptedMessageActionDeleteMessages: @@ -1066,9 +1066,9 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 case .decryptedMessageActionReadMessages: return nil case .decryptedMessageActionScreenshotMessages: - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) case .decryptedMessageActionResend: return nil case .decryptedMessageActionRequestKey: @@ -1279,7 +1279,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) { - attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)) + attributes.append(ReplyMessageAttribute(messageId: replyMessageId)) } var entitiesAttribute: TextEntitiesMessageAttribute? @@ -1292,7 +1292,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 let (tags, globalTags) = tagsForStoreMessage(incoming: true, attributes: attributes, media: parsedMedia, textEntities: entitiesAttribute?.entities) - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: groupingKey, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: groupingKey, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) case let .decryptedMessageService(randomId, action): switch action { case .decryptedMessageActionDeleteMessages: @@ -1304,9 +1304,9 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 case .decryptedMessageActionReadMessages: return nil case .decryptedMessageActionScreenshotMessages: - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) case .decryptedMessageActionResend: return nil case .decryptedMessageActionRequestKey: diff --git a/submodules/TelegramCore/Sources/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/ReplyThreadHistory.swift deleted file mode 100644 index 25507264eb..0000000000 --- a/submodules/TelegramCore/Sources/ReplyThreadHistory.swift +++ /dev/null @@ -1,236 +0,0 @@ -import Foundation -import SyncCore -import Postbox -import SwiftSignalKit -import TelegramApi - -private class ReplyThreadHistoryContextImpl { - private let queue: Queue - private let account: Account - private let messageId: MessageId - - private var currentHole: (MessageHistoryHolesViewEntry, Disposable)? - - struct State: Equatable { - var messageId: MessageId - var holeIndices: [MessageId.Namespace: IndexSet] - var maxReadMessageId: MessageId? - } - - let state = Promise() - private var stateValue: State { - didSet { - if self.stateValue != oldValue { - self.state.set(.single(self.stateValue)) - } - } - } - - private var holesDisposable: Disposable? - private let readDisposable = MetaDisposable() - - init(queue: Queue, account: Account, messageId: MessageId, maxReadMessageId: MessageId?) { - self.queue = queue - self.account = account - self.messageId = messageId - - self.stateValue = State(messageId: self.messageId, holeIndices: [Namespaces.Message.Cloud: IndexSet(integersIn: 1 ..< Int(Int32.max))], maxReadMessageId: maxReadMessageId) - self.state.set(.single(self.stateValue)) - - let threadId = makeMessageThreadId(messageId) - - self.holesDisposable = (account.postbox.messageHistoryHolesView() - |> map { view -> MessageHistoryHolesViewEntry? in - for entry in view.entries { - switch entry.hole { - case let .peer(hole): - if hole.threadId == threadId { - return entry - } - } - } - return nil - } - |> distinctUntilChanged - |> deliverOn(self.queue)).start(next: { [weak self] entry in - guard let strongSelf = self else { - return - } - strongSelf.setCurrentHole(entry: entry) - }) - } - - deinit { - self.holesDisposable?.dispose() - self.readDisposable.dispose() - } - - func setCurrentHole(entry: MessageHistoryHolesViewEntry?) { - if self.currentHole?.0 != entry { - self.currentHole?.1.dispose() - if let entry = entry { - self.currentHole = (entry, self.fetchHole(entry: entry).start(next: { [weak self] removedHoleIndices in - guard let strongSelf = self else { - return - } - if var currentHoles = strongSelf.stateValue.holeIndices[Namespaces.Message.Cloud] { - currentHoles.subtract(removedHoleIndices) - strongSelf.stateValue.holeIndices[Namespaces.Message.Cloud] = currentHoles - } - })) - } else { - self.currentHole = nil - } - } - } - - private func fetchHole(entry: MessageHistoryHolesViewEntry) -> Signal { - switch entry.hole { - case let .peer(hole): - let fetchCount = min(entry.count, 100) - return fetchMessageHistoryHole(accountPeerId: self.account.peerId, source: .network(self.account.network), postbox: self.account.postbox, peerId: hole.peerId, namespace: hole.namespace, direction: entry.direction, space: entry.space, threadId: hole.threadId.flatMap { makeThreadIdMessageId(peerId: self.messageId.peerId, threadId: $0) }, count: fetchCount) - } - } - - func applyMaxReadIndex(messageIndex: MessageIndex) { - let account = self.account - let messageId = self.messageId - - if messageIndex.id.namespace != messageId.namespace { - return - } - - let signal = self.account.postbox.transaction { transaction -> Api.InputPeer? in - return transaction.getPeer(messageIndex.id.peerId).flatMap(apiInputPeer) - } - |> mapToSignal { inputPeer -> Signal in - guard let inputPeer = inputPeer else { - return .complete() - } - return account.network.request(Api.functions.messages.readDiscussion(peer: inputPeer, msgId: messageId.id, readMaxId: messageIndex.id.id)) - |> `catch` { _ -> Signal in - return .single(.boolFalse) - } - |> ignoreValues - } - self.readDisposable.set(signal.start()) - } -} - -public class ReplyThreadHistoryContext { - fileprivate final class GuardReference { - private let deallocated: () -> Void - - init(deallocated: @escaping () -> Void) { - self.deallocated = deallocated - } - - deinit { - self.deallocated() - } - } - - private let queue = Queue() - private let impl: QueueLocalObject - - public var state: Signal { - return Signal { subscriber in - let disposable = MetaDisposable() - - self.impl.with { impl in - let stateDisposable = impl.state.get().start(next: { state in - subscriber.putNext(MessageHistoryViewExternalInput( - peerId: state.messageId.peerId, - threadId: makeMessageThreadId(state.messageId), - maxReadMessageId: state.maxReadMessageId, - holes: state.holeIndices - )) - }) - disposable.set(stateDisposable) - } - - return disposable - } - } - - public init(account: Account, peerId: PeerId, threadMessageId: MessageId, maxReadMessageId: MessageId?) { - let queue = self.queue - self.impl = QueueLocalObject(queue: queue, generate: { - return ReplyThreadHistoryContextImpl(queue: queue, account: account, messageId: threadMessageId, maxReadMessageId: maxReadMessageId) - }) - } - - public func applyMaxReadIndex(messageIndex: MessageIndex) { - self.impl.with { impl in - impl.applyMaxReadIndex(messageIndex: messageIndex) - } - } -} - -public struct ChatReplyThreadMessage { - public var messageId: MessageId - public var maxReadMessageId: MessageId? - - public init(messageId: MessageId, maxReadMessageId: MessageId?) { - self.messageId = messageId - self.maxReadMessageId = maxReadMessageId - } -} - -public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageId) -> Signal { - return account.postbox.transaction { transaction -> Api.InputPeer? in - return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) - } - |> mapToSignal { inputPeer -> Signal in - guard let inputPeer = inputPeer else { - return .single(nil) - } - return account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: messageId.id)) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignal { result -> Signal in - guard let result = result else { - return .single(nil) - } - return account.postbox.transaction { transaction -> ChatReplyThreadMessage? in - switch result { - case let .discussionMessage(message, readMaxId, chats, users): - guard let parsedMessage = StoreMessage(apiMessage: message), let parsedIndex = parsedMessage.index else { - return nil - } - - var peers: [Peer] = [] - var peerPresences: [PeerId: PeerPresence] = [:] - - for chat in chats { - if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { - peers.append(groupOrChannel) - } - } - for user in users { - let telegramUser = TelegramUser(user: user) - peers.append(telegramUser) - if let presence = TelegramUserPresence(apiUser: user) { - peerPresences[telegramUser.id] = presence - } - } - - let _ = transaction.addMessages([parsedMessage], location: .Random) - - updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in - return updated - }) - - updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences) - - return ChatReplyThreadMessage( - messageId: parsedIndex.id, - maxReadMessageId: MessageId(peerId: parsedIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: readMaxId) - ) - } - } - } - } -} diff --git a/submodules/TelegramCore/Sources/RequestEditMessage.swift b/submodules/TelegramCore/Sources/RequestEditMessage.swift index e266c1bc41..495e7401c0 100644 --- a/submodules/TelegramCore/Sources/RequestEditMessage.swift +++ b/submodules/TelegramCore/Sources/RequestEditMessage.swift @@ -295,7 +295,7 @@ public func requestEditLiveLocation(postbox: Postbox, network: Network, stateMan } var updatedLocalTags = currentMessage.localTags updatedLocalTags.remove(.OutgoingLiveLocation) - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: updatedLocalTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: updatedLocalTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } } else { diff --git a/submodules/TelegramCore/Sources/RequestUserPhotos.swift b/submodules/TelegramCore/Sources/RequestUserPhotos.swift index 5d4f40d963..c77b20abdf 100644 --- a/submodules/TelegramCore/Sources/RequestUserPhotos.swift +++ b/submodules/TelegramCore/Sources/RequestUserPhotos.swift @@ -61,7 +61,7 @@ public func requestPeerPhotos(postbox: Postbox, network: Network, peerId: PeerId } } } else if let peer = peer, let inputPeer = apiInputPeer(peer) { - return network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, topMsgId: nil, filter: .inputMessagesFilterChatPhotos, minDate: 0, maxDate: 0, offsetId: 0, addOffset: 0, limit: 1000, maxId: 0, minId: 0, hash: 0)) + return network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, filter: .inputMessagesFilterChatPhotos, minDate: 0, maxDate: 0, offsetId: 0, addOffset: 0, limit: 1000, maxId: 0, minId: 0, hash: 0)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -121,7 +121,7 @@ public func requestPeerPhotos(postbox: Postbox, network: Network, peerId: PeerId switch media.action { case let .photoUpdated(image): if let image = image { - photos.append(TelegramPeerPhoto(image: image, reference: image.reference, date: message.timestamp, index: index, totalCount: messages.count, messageId: message.id)) + photos.append(TelegramPeerPhoto(image: image, reference: nil, date: message.timestamp, index: index, totalCount: messages.count, messageId: message.id)) } default: break diff --git a/submodules/TelegramCore/Sources/SearchMessages.swift b/submodules/TelegramCore/Sources/SearchMessages.swift index 4e0ce26fb7..ac1ec30e2d 100644 --- a/submodules/TelegramCore/Sources/SearchMessages.swift +++ b/submodules/TelegramCore/Sources/SearchMessages.swift @@ -7,9 +7,9 @@ import MtProtoKit import SyncCore public enum SearchMessagesLocation: Equatable { - case general(tags: MessageTags?, minDate: Int32?, maxDate: Int32?) + case general(tags: MessageTags?) case group(PeerGroupId) - case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?, topMsgId: MessageId?, minDate: Int32?, maxDate: Int32?) + case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?) case publicForwards(messageId: MessageId, datacenterId: Int?) } @@ -47,13 +47,6 @@ public struct SearchMessagesResult { public let readStates: [PeerId: CombinedPeerReadState] public let totalCount: Int32 public let completed: Bool - - public init(messages: [Message], readStates: [PeerId: CombinedPeerReadState], totalCount: Int32, completed: Bool) { - self.messages = messages - self.readStates = readStates - self.totalCount = totalCount - self.completed = completed - } } public struct SearchMessagesState: Equatable { @@ -187,7 +180,7 @@ private func mergedResult(_ state: SearchMessagesState) -> SearchMessagesResult public func searchMessages(account: Account, location: SearchMessagesLocation, query: String, state: SearchMessagesState?, limit: Int32 = 100) -> Signal<(SearchMessagesResult, SearchMessagesState), NoError> { let remoteSearchResult: Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError> switch location { - case let .peer(peerId, fromId, tags, topMsgId, minDate, maxDate): + case let .peer(peerId, fromId, tags): if peerId.namespace == Namespaces.Peer.SecretChat { return account.postbox.transaction { transaction -> (SearchMessagesResult, SearchMessagesState) in var readStates: [PeerId: CombinedPeerReadState] = [:] @@ -229,15 +222,12 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q flags |= (1 << 0) } } - if let _ = topMsgId { - flags |= (1 << 1) - } let peerMessages: Signal if let completed = state?.main.completed, completed { peerMessages = .single(nil) } else { let lowerBound = state?.main.messages.last.flatMap({ $0.index }) - peerMessages = account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: query, fromId: fromInputUser, topMsgId: topMsgId?.id, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetId: lowerBound?.id.id ?? 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0)) + peerMessages = account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: query, fromId: fromInputUser, filter: filter, minDate: 0, maxDate: Int32.max - 1, offsetId: lowerBound?.id.id ?? 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -251,7 +241,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q additionalPeerMessages = .single(nil) } else if mainCompleted || !hasAdditional { let lowerBound = state?.additional?.messages.last.flatMap({ $0.index }) - additionalPeerMessages = account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: query, fromId: fromInputUser, topMsgId: topMsgId?.id, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetId: lowerBound?.id.id ?? 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0)) + additionalPeerMessages = account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: query, fromId: fromInputUser, filter: filter, minDate: 0, maxDate: Int32.max - 1, offsetId: lowerBound?.id.id ?? 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -266,7 +256,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q } case .group: remoteSearchResult = .single((nil, nil)) - case let .general(tags, minDate, maxDate): + case let .general(tags): let filter: Api.MessagesFilter = tags.flatMap { messageFilterForTagMask($0) } ?? .inputMessagesFilterEmpty remoteSearchResult = account.postbox.transaction { transaction -> (Int32, MessageIndex?, Api.InputPeer) in var lowerBound: MessageIndex? @@ -280,7 +270,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q } } |> mapToSignal { (nextRate, lowerBound, inputPeer) in - return account.network.request(Api.functions.messages.searchGlobal(flags: 0, folderId: nil, q: query, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit), automaticFloodWait: false) + return account.network.request(Api.functions.messages.searchGlobal(flags: 0, folderId: nil, q: query, offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit), automaticFloodWait: false) |> map { result -> (Api.messages.Messages?, Api.messages.Messages?) in return (result, nil) } @@ -303,12 +293,13 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q return (inputChannel, 0, lowerBound, .inputPeerEmpty) } } - |> mapToSignal { (inputChannel, nextRate, lowerBound, inputPeer) in + |> mapToSignal { (inputChannel, nextRate, lowerBound, inputPeer) -> Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError> in guard let inputChannel = inputChannel else { return .complete() } + return .single((nil, nil)) - let request = Api.functions.stats.getMessagePublicForwards(channel: inputChannel, msgId: messageId.id, offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit) + /*let request = Api.functions.stats.getMessagePublicForwards(channel: inputChannel, msgId: messageId.id, offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit) let signal: Signal if let datacenterId = datacenterId, account.network.datacenterId != datacenterId { signal = account.network.download(datacenterId: datacenterId, isMedia: false, tag: nil) @@ -325,7 +316,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q } |> `catch` { _ -> Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError> in return .single((nil, nil)) - } + }*/ } } @@ -333,7 +324,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q |> mapToSignal { result, additionalResult -> Signal<(SearchMessagesResult, SearchMessagesState), NoError> in return account.postbox.transaction { transaction -> (SearchMessagesResult, SearchMessagesState) in var additional: SearchMessagesPeerState? = mergedState(transaction: transaction, state: state?.additional, result: additionalResult) - if state?.additional == nil, case let .general(tags, _, _) = location { + if state?.additional == nil, case let .general(tags) = location { let secretMessages = transaction.searchMessages(peerId: nil, query: query, tags: tags) var readStates: [PeerId: CombinedPeerReadState] = [:] for message in secretMessages { diff --git a/submodules/TelegramCore/Sources/Serialization.swift b/submodules/TelegramCore/Sources/Serialization.swift index 94cd1882c6..9d7dec93c3 100644 --- a/submodules/TelegramCore/Sources/Serialization.swift +++ b/submodules/TelegramCore/Sources/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 119 + return 118 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/SplitTest.swift b/submodules/TelegramCore/Sources/SplitTest.swift index c0f8938470..10cac1b41e 100644 --- a/submodules/TelegramCore/Sources/SplitTest.swift +++ b/submodules/TelegramCore/Sources/SplitTest.swift @@ -25,7 +25,7 @@ extension SplitTest { public func addEvent(_ event: Self.Event, data: JSON = []) { if let bucket = self.bucket { //TODO: merge additional data - addAppLogEvent(postbox: self.postbox, type: event.rawValue, data: ["bucket": bucket]) + addAppLogEvent(postbox: self.postbox, time: Date().timeIntervalSince1970, type: event.rawValue, peerId: nil, data: ["bucket": bucket]) } } } diff --git a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift index edb29aae1b..0ae53cfaa9 100644 --- a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift @@ -110,33 +110,58 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { switch messsage { case let .message(message): - let chatPeerId = message.peerId - return chatPeerId.peerId + let flags = message.flags + let fromId = message.fromId + let toId = message.toId + switch toId { + case let .peerUser(userId): + return PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) + case let .peerChat(chatId): + return PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) + case let .peerChannel(channelId): + return PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + } case .messageEmpty: return nil - case let .messageService(flags, _, fromId, chatPeerId, _, _, _): - return chatPeerId.peerId + case let .messageService(flags, _, fromId, toId, _, _, _): + switch toId { + case let .peerUser(userId): + return PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) + case let .peerChat(chatId): + return PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) + case let .peerChannel(channelId): + return PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + } } } func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { switch message { - case let .message(flags, _, fromId, chatPeerId, fwdHeader, viaBotId, _, _, _, media, _, entities, _, _, _, _, _, _, _): - let peerId: PeerId = chatPeerId.peerId + case let .message(flags, _, fromId, toId, fwdHeader, viaBotId, _, _, _, media, _, entities, _, _, _, _, _): + let peerId: PeerId + switch toId { + case let .peerUser(userId): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) + case let .peerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) + case let .peerChannel(channelId): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + } var result = [peerId] - let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId - - if resolvedFromId != peerId { - result.append(resolvedFromId) + if let fromId = fromId, PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) != peerId { + result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId)) } if let fwdHeader = fwdHeader { switch fwdHeader { case let .messageFwdHeader(messageFwdHeader): + if let channelId = messageFwdHeader.channelId { + result.append(PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)) + } if let fromId = messageFwdHeader.fromId { - result.append(fromId.peerId) + result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId)) } if let savedFromPeer = messageFwdHeader.savedFromPeer { result.append(savedFromPeer.peerId) @@ -173,14 +198,20 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { return result case .messageEmpty: return [] - case let .messageService(flags, _, fromId, chatPeerId, _, _, action): - let peerId: PeerId = chatPeerId.peerId + case let .messageService(flags, _, fromId, toId, _, _, action): + let peerId: PeerId + switch toId { + case let .peerUser(userId): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) + case let .peerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) + case let .peerChannel(channelId): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + } var result = [peerId] - let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId - - if resolvedFromId != peerId { - result.append(resolvedFromId) + if let fromId = fromId, PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) != peerId { + result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId)) } switch action { @@ -210,23 +241,35 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { func apiMessageAssociatedMessageIds(_ message: Api.Message) -> [MessageId]? { switch message { - case let .message(flags, _, fromId, chatPeerId, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _): - if let replyTo = replyTo { - let peerId: PeerId = chatPeerId.peerId - - switch replyTo { - case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, _): - return [MessageId(peerId: replyToPeerId?.peerId ?? peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId)] + case let .message(flags, _, fromId, toId, _, _, replyToMsgId, _, _, _, _, _, _, _, _, _, _): + if let replyToMsgId = replyToMsgId { + let peerId: PeerId + switch toId { + case let .peerUser(userId): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) + case let .peerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) + case let .peerChannel(channelId): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) } + + return [MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId)] } case .messageEmpty: break - case let .messageService(flags, _, fromId, chatPeerId, replyHeader, _, _): - if let replyHeader = replyHeader { - switch replyHeader { - case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, _): - return [MessageId(peerId: replyToPeerId?.peerId ?? chatPeerId.peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId)] + case let .messageService(flags, _, fromId, toId, replyToMsgId, _, _): + if let replyToMsgId = replyToMsgId { + let peerId: PeerId + switch toId { + case let .peerUser(userId): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) + case let .peerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) + case let .peerChannel(channelId): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) } + + return [MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId)] } } return nil @@ -356,21 +399,31 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { - case let .message(flags, id, fromId, chatPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, restrictionReason): - let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId - + case let .message(flags, id, fromId, toId, fwdFrom, viaBotId, replyToMsgId, date, message, media, replyMarkup, entities, views, editDate, postAuthor, groupingId, restrictionReason): let peerId: PeerId var authorId: PeerId? - switch chatPeerId { - case .peerUser: - peerId = chatPeerId.peerId - authorId = resolvedFromId + switch toId { + case let .peerUser(userId): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) + if let fromId = fromId { + authorId = PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) + } else { + authorId = peerId + } case let .peerChat(chatId): peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) - authorId = resolvedFromId + if let fromId = fromId { + authorId = PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) + } else { + authorId = peerId + } case let .peerChannel(channelId): peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) - authorId = resolvedFromId + if let fromId = fromId { + authorId = PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) + } else { + authorId = peerId + } } var attributes: [MessageAttribute] = [] @@ -378,22 +431,20 @@ extension StoreMessage { var forwardInfo: StoreMessageForwardInfo? if let fwdFrom = fwdFrom { switch fwdFrom { - case let .messageFwdHeader(_, fromId, fromName, date, channelPost, postAuthor, savedFromPeer, savedFromMsgId, psaType): + case let .messageFwdHeader(_, fromId, fromName, date, channelId, channelPost, postAuthor, savedFromPeer, savedFromMsgId, psaType): var authorId: PeerId? var sourceId: PeerId? var sourceMessageId: MessageId? if let fromId = fromId { - switch fromId { - case .peerChannel: - let peerId = fromId.peerId - sourceId = peerId - - if let channelPost = channelPost { - sourceMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: channelPost) - } - default: - authorId = fromId.peerId + authorId = PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) + } + if let channelId = channelId { + let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + sourceId = peerId + + if let channelPost = channelPost { + sourceMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: channelPost) } } @@ -463,23 +514,8 @@ extension StoreMessage { attributes.append(InlineBotMessageAttribute(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: viaBotId), title: nil)) } - var threadId: Int64? - if let replyTo = replyTo { - var threadMessageId: MessageId? - switch replyTo { - case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyToTopId): - let replyPeerId = replyToPeerId?.peerId ?? peerId - if let replyToTopId = replyToTopId { - let threadIdValue = MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToTopId) - threadMessageId = threadIdValue - threadId = makeMessageThreadId(threadIdValue) - } else if peerId.namespace == Namespaces.Peer.CloudChannel { - let threadIdValue = MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId) - threadMessageId = threadIdValue - threadId = makeMessageThreadId(threadIdValue) - } - attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId)) - } + if let replyToMsgId = replyToMsgId { + attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId))) } if namespace != Namespaces.Message.ScheduledCloud { @@ -487,9 +523,9 @@ extension StoreMessage { attributes.append(ViewCountMessageAttribute(count: Int(views))) } - if let forwards = forwards { + /*if let forwards = forwards { attributes.append(ForwardCountMessageAttribute(count: Int(forwards))) - } + }*/ } if let editDate = editDate { @@ -528,22 +564,6 @@ extension StoreMessage { attributes.append(ReactionsMessageAttribute(apiReactions: reactions)) }*/ - if let replies = replies { - let recentRepliersPeerIds: [PeerId]? - switch replies { - case let .messageReplies(_, repliesCount, _, recentRepliers, channelId, _, _): - if let recentRepliers = recentRepliers { - recentRepliersPeerIds = recentRepliers.map { $0.peerId } - } else { - recentRepliersPeerIds = nil - } - - let commentsPeerId = channelId.flatMap { PeerId(namespace: Namespaces.Peer.CloudChannel, id: $0) } - - attributes.append(ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsPeerId)) - } - } - if let restrictionReason = restrictionReason { attributes.append(RestrictedContentMessageAttribute(rules: restrictionReason.map(RestrictionRule.init(apiReason:)))) } @@ -584,38 +604,46 @@ extension StoreMessage { storeFlags.insert(.CanBeGroupedIntoFeed) - self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: groupingId, threadId: threadId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: messageText, attributes: attributes, media: medias) + self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: groupingId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: messageText, attributes: attributes, media: medias) case .messageEmpty: return nil - case let .messageService(flags, id, fromId, chatPeerId, replyTo, date, action): - let peerId: PeerId = chatPeerId.peerId - var authorId: PeerId? = fromId?.peerId ?? chatPeerId.peerId + case let .messageService(flags, id, fromId, toId, replyToMsgId, date, action): + let peerId: PeerId + var authorId: PeerId? + switch toId { + case let .peerUser(userId): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) + if let fromId = fromId { + authorId = PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) + } else { + authorId = peerId + } + case let .peerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) + if let fromId = fromId { + authorId = PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) + } else { + authorId = peerId + } + case let .peerChannel(channelId): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + if let fromId = fromId { + authorId = PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) + } else { + authorId = peerId + } + } var attributes: [MessageAttribute] = [] - - var threadId: Int64? - if let replyTo = replyTo { - var threadMessageId: MessageId? - switch replyTo { - case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyToTopId): - let replyPeerId = replyToPeerId?.peerId ?? peerId - if let replyToTopId = replyToTopId { - let threadIdValue = MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToTopId) - threadMessageId = threadIdValue - threadId = makeMessageThreadId(threadIdValue) - } else if peerId.namespace == Namespaces.Peer.CloudChannel { - let threadIdValue = MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId) - threadMessageId = threadIdValue - threadId = makeMessageThreadId(threadIdValue) - } - attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId)) - } + if let replyToMsgId = replyToMsgId { + attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId))) } if (flags & (1 << 17)) != 0 { attributes.append(ContentRequiresValidationMessageAttribute()) } + var storeFlags = StoreMessageFlags() if (flags & 2) == 0 { let _ = storeFlags.insert(.Incoming) @@ -645,7 +673,7 @@ extension StoreMessage { storeFlags.insert(.WasScheduled) } - self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: nil, threadId: threadId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: attributes, media: media) + self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: nil, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: attributes, media: media) } } } diff --git a/submodules/TelegramCore/Sources/SynchronizeAppLogEventsOperation.swift b/submodules/TelegramCore/Sources/SynchronizeAppLogEventsOperation.swift index 541fb91a73..99669da865 100644 --- a/submodules/TelegramCore/Sources/SynchronizeAppLogEventsOperation.swift +++ b/submodules/TelegramCore/Sources/SynchronizeAppLogEventsOperation.swift @@ -5,7 +5,7 @@ import MtProtoKit import SyncCore -public func addAppLogEvent(postbox: Postbox, time: Double = Date().timeIntervalSince1970, type: String, peerId: PeerId? = nil, data: JSON = .dictionary([:])) { +public func addAppLogEvent(postbox: Postbox, time: Double, type: String, peerId: PeerId?, data: JSON) { let tag: PeerOperationLogTag = OperationLogTags.SynchronizeAppLogEvents let peerId = PeerId(namespace: 0, id: 0) let _ = (postbox.transaction { transaction in diff --git a/submodules/TelegramCore/Sources/SynchronizePeerReadState.swift b/submodules/TelegramCore/Sources/SynchronizePeerReadState.swift index 9817e3c28b..5f55a1bce3 100644 --- a/submodules/TelegramCore/Sources/SynchronizePeerReadState.swift +++ b/submodules/TelegramCore/Sources/SynchronizePeerReadState.swift @@ -34,18 +34,12 @@ private func inputSecretChat(postbox: Postbox, peerId: PeerId) -> Signal take(1) } -private func dialogTopMessage(network: Network, postbox: Postbox, peerId: PeerId) -> Signal<(Int32, Int32)?, PeerReadStateValidationError> { +private func dialogTopMessage(network: Network, postbox: Postbox, peerId: PeerId) -> Signal<(Int32, Int32), PeerReadStateValidationError> { return inputPeer(postbox: postbox, peerId: peerId) - |> mapToSignal { inputPeer -> Signal<(Int32, Int32)?, PeerReadStateValidationError> in + |> mapToSignal { inputPeer -> Signal<(Int32, Int32), PeerReadStateValidationError> in return network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: Int32.max, offsetDate: Int32.max, addOffset: 0, limit: 1, maxId: Int32.max, minId: 1, hash: 0)) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignalPromotingError { result -> Signal<(Int32, Int32)?, PeerReadStateValidationError> in - guard let result = result else { - return .single(nil) - } + |> retryRequest + |> mapToSignalPromotingError { result -> Signal<(Int32, Int32), PeerReadStateValidationError> in let apiMessages: [Api.Message] switch result { case let .channelMessages(_, _, _, messages, _, _): @@ -66,18 +60,14 @@ private func dialogTopMessage(network: Network, postbox: Postbox, peerId: PeerId } } -private func dialogReadState(network: Network, postbox: Postbox, peerId: PeerId) -> Signal<(PeerReadState, PeerReadStateMarker)?, PeerReadStateValidationError> { +private func dialogReadState(network: Network, postbox: Postbox, peerId: PeerId) -> Signal<(PeerReadState, PeerReadStateMarker), PeerReadStateValidationError> { return dialogTopMessage(network: network, postbox: postbox, peerId: peerId) - |> mapToSignal { topMessage -> Signal<(PeerReadState, PeerReadStateMarker)?, PeerReadStateValidationError> in - guard let _ = topMessage else { - return .single(nil) - } - + |> mapToSignal { topMessage -> Signal<(PeerReadState, PeerReadStateMarker), PeerReadStateValidationError> in return inputPeer(postbox: postbox, peerId: peerId) - |> mapToSignal { inputPeer -> Signal<(PeerReadState, PeerReadStateMarker)?, PeerReadStateValidationError> in + |> mapToSignal { inputPeer -> Signal<(PeerReadState, PeerReadStateMarker), PeerReadStateValidationError> in return network.request(Api.functions.messages.getPeerDialogs(peers: [.inputDialogPeer(peer: inputPeer)])) |> retryRequest - |> mapToSignalPromotingError { result -> Signal<(PeerReadState, PeerReadStateMarker)?, PeerReadStateValidationError> in + |> mapToSignalPromotingError { result -> Signal<(PeerReadState, PeerReadStateMarker), PeerReadStateValidationError> in switch result { case let .peerDialogs(dialogs, _, _, _, state): if let dialog = dialogs.filter({ $0.peerId == peerId }).first { @@ -160,16 +150,10 @@ enum PeerReadStateValidationError { private func validatePeerReadState(network: Network, postbox: Postbox, stateManager: AccountStateManager, peerId: PeerId) -> Signal { let readStateWithInitialState = dialogReadState(network: network, postbox: postbox, peerId: peerId) + |> map { ($0.0, $0.1) } let maybeAppliedReadState = readStateWithInitialState - |> mapToSignal { data -> Signal in - guard let (readState, _) = data else { - return postbox.transaction { transaction -> Void in - transaction.confirmSynchronizedIncomingReadState(peerId) - } - |> castError(PeerReadStateValidationError.self) - |> ignoreValues - } + |> mapToSignal { (readState, finalMarker) -> Signal in return stateManager.addCustomOperation(postbox.transaction { transaction -> PeerReadStateValidationError? in if let currentReadState = transaction.getCombinedPeerReadState(peerId) { loop: for (namespace, currentState) in currentReadState.states { diff --git a/submodules/TelegramCore/Sources/TelegramChannel.swift b/submodules/TelegramCore/Sources/TelegramChannel.swift index 3961526d83..e3c12f4686 100644 --- a/submodules/TelegramCore/Sources/TelegramChannel.swift +++ b/submodules/TelegramCore/Sources/TelegramChannel.swift @@ -12,19 +12,11 @@ public enum TelegramChannelPermission { case banMembers case addAdmins case changeInfo - case canBeAnonymous } public extension TelegramChannel { func hasPermission(_ permission: TelegramChannelPermission) -> Bool { if self.flags.contains(.isCreator) { - if case .canBeAnonymous = permission { - if let adminRights = self.adminRights { - return adminRights.flags.contains(.canBeAnonymous) - } else { - return false - } - } return true } switch permission { @@ -124,11 +116,6 @@ public extension TelegramChannel { return true } return false - case .canBeAnonymous: - if let adminRights = self.adminRights, adminRights.flags.contains(.canBeAnonymous) { - return true - } - return false } } diff --git a/submodules/TelegramCore/Sources/UnauthorizedAccountStateManager.swift b/submodules/TelegramCore/Sources/UnauthorizedAccountStateManager.swift index b9993cf1f9..19a87d91f3 100644 --- a/submodules/TelegramCore/Sources/UnauthorizedAccountStateManager.swift +++ b/submodules/TelegramCore/Sources/UnauthorizedAccountStateManager.swift @@ -27,7 +27,7 @@ private final class UnauthorizedUpdateMessageService: NSObject, MTMessageService self.pipe.putNext(updates) } - func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!, authInfoSelector: MTDatacenterAuthInfoSelector) { + func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!) { if let updates = (message.body as? BoxedMessage)?.body as? Api.Updates { self.addUpdates(updates) } diff --git a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift index e94467f96f..493276fbae 100644 --- a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift @@ -498,7 +498,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI .withUpdatedStickerPack(stickerPack) .withUpdatedMinAvailableMessageId(minAvailableMessageId) .withUpdatedMigrationReference(migrationReference) - .withUpdatedLinkedDiscussionPeerId(.known(linkedDiscussionPeerId)) + .withUpdatedLinkedDiscussionPeerId(linkedDiscussionPeerId) .withUpdatedPeerGeoLocation(peerGeoLocation) .withUpdatedSlowModeTimeout(slowmodeSeconds) .withUpdatedSlowModeValidUntilTimestamp(slowmodeNextSendDate) diff --git a/submodules/TelegramCore/Sources/UpdateMessageMedia.swift b/submodules/TelegramCore/Sources/UpdateMessageMedia.swift index 5e90e06f8f..62e433f3f2 100644 --- a/submodules/TelegramCore/Sources/UpdateMessageMedia.swift +++ b/submodules/TelegramCore/Sources/UpdateMessageMedia.swift @@ -24,68 +24,7 @@ func updateMessageMedia(transaction: Transaction, id: MediaId, media: Media?) { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } } - -func updateMessageThreadStats(transaction: Transaction, threadMessageId: MessageId, difference: Int, addedMessagePeers: [PeerId]) { - updateMessageThreadStatsInternal(transaction: transaction, threadMessageId: threadMessageId, difference: difference, addedMessagePeers: addedMessagePeers, allowChannel: false) -} - -private func updateMessageThreadStatsInternal(transaction: Transaction, threadMessageId: MessageId, difference: Int, addedMessagePeers: [PeerId], allowChannel: Bool) { - guard let channel = transaction.getPeer(threadMessageId.peerId) as? TelegramChannel else { - return - } - var isGroup = true - if case .broadcast = channel.info { - isGroup = false - if !allowChannel { - return - } - } - - var channelThreadMessageId: MessageId? - - func mergeLatestUsers(current: [PeerId], added: [PeerId], isGroup: Bool, isEmpty: Bool) -> [PeerId] { - if isEmpty { - return [] - } - if isGroup { - return current - } - var current = current - for i in 0 ..< min(3, added.count) { - let peerId = added[added.count - 1 - i] - if let index = current.firstIndex(of: peerId) { - current.remove(at: index) - current.insert(peerId, at: 0) - } else { - if current.count >= 3 { - current.removeLast() - } - current.insert(peerId, at: 0) - } - } - return current - } - - transaction.updateMessage(threadMessageId, update: { currentMessage in - let countDifference = Int32(difference) - - var attributes = currentMessage.attributes - loop: for j in 0 ..< attributes.count { - if let attribute = attributes[j] as? ReplyThreadMessageAttribute { - let count = max(0, attribute.count + countDifference) - attributes[j] = ReplyThreadMessageAttribute(count: count, latestUsers: mergeLatestUsers(current: attribute.latestUsers, added: addedMessagePeers, isGroup: isGroup, isEmpty: count == 0), commentsPeerId: attribute.commentsPeerId) - } else if let attribute = attributes[j] as? SourceReferenceMessageAttribute { - channelThreadMessageId = attribute.messageId - } - } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) - }) - - if let channelThreadMessageId = channelThreadMessageId { - updateMessageThreadStatsInternal(transaction: transaction, threadMessageId: channelThreadMessageId, difference: difference, addedMessagePeers: addedMessagePeers, allowChannel: true) - } -} diff --git a/submodules/TelegramCore/Sources/UpdateMessageService.swift b/submodules/TelegramCore/Sources/UpdateMessageService.swift index bca8a5a76e..34cd793682 100644 --- a/submodules/TelegramCore/Sources/UpdateMessageService.swift +++ b/submodules/TelegramCore/Sources/UpdateMessageService.swift @@ -34,7 +34,7 @@ class UpdateMessageService: NSObject, MTMessageService { self.pipe.putNext(groups) } - func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!, authInfoSelector: MTDatacenterAuthInfoSelector) { + func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!) { if let updates = (message.body as? BoxedMessage)?.body as? Api.Updates { self.addUpdates(updates) } @@ -57,24 +57,25 @@ class UpdateMessageService: NSObject, MTMessageService { if groups.count != 0 { self.putNext(groups) } - case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities): - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: .peerChat(chatId: fromId), peerId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) + case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyToMsgId, entities): + let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: fromId, toId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { self.putNext(groups) } - case let .updateShortMessage(flags, id, userId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities): - let generatedFromId: Api.Peer - if (Int(flags) & 1 << 1) != 0 { - generatedFromId = Api.Peer.peerUser(userId: self.peerId.id) + case let .updateShortMessage(flags, id, userId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyToMsgId, entities): + let generatedFromId: Int32 + let generatedToId: Api.Peer + if (Int(flags) & 2) != 0 { + generatedFromId = self.peerId.id + generatedToId = Api.Peer.peerUser(userId: userId) } else { - generatedFromId = Api.Peer.peerUser(userId: userId) + generatedFromId = userId + generatedToId = Api.Peer.peerUser(userId: self.peerId.id) } - let generatedPeerId = Api.Peer.peerUser(userId: userId) - - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, peerId: generatedPeerId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) + let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, toId: generatedToId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { diff --git a/submodules/TelegramCore/Sources/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/UpdatesApiUtils.swift index 0b80c1de20..e457e6aa1e 100644 --- a/submodules/TelegramCore/Sources/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/UpdatesApiUtils.swift @@ -112,13 +112,36 @@ extension Api.Message { let flags = message.flags let id = message.id let fromId = message.fromId + let toId = message.toId - let peerId: PeerId = message.peerId.peerId + let peerId: PeerId + switch toId { + case let .peerUser(userId): + let id: PeerId.Id + if namespace == Namespaces.Message.ScheduledCloud { + id = userId + } else { + id = (flags & Int32(2)) != 0 ? userId : (fromId ?? userId) + } + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: id) + case let .peerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) + case let .peerChannel(channelId): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + } return MessageId(peerId: peerId, namespace: namespace, id: id) case .messageEmpty: return nil - case let .messageService(flags, id, fromId, chatPeerId, _, _, _): - let peerId: PeerId = chatPeerId.peerId + case let .messageService(flags, id, fromId, toId, _, _, _): + let peerId: PeerId + switch toId { + case let .peerUser(userId): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) + case let .peerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) + case let .peerChannel(channelId): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + } return MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id) } } diff --git a/submodules/TelegramCore/Sources/WebpagePreview.swift b/submodules/TelegramCore/Sources/WebpagePreview.swift index 5c9085b550..8fcf5fe9dc 100644 --- a/submodules/TelegramCore/Sources/WebpagePreview.swift +++ b/submodules/TelegramCore/Sources/WebpagePreview.swift @@ -83,6 +83,7 @@ public func actualizedWebpage(postbox: Postbox, network: Network, webpage: Teleg return .single(nil) } |> mapToSignal { result -> Signal in + if let result = result, let updatedWebpage = telegramMediaWebpageFromApiWebpage(result, url: nil), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId == webpage.webpageId { return postbox.transaction { transaction -> TelegramMediaWebpage in updateMessageMedia(transaction: transaction, id: webpage.webpageId, media: updatedWebpage) diff --git a/submodules/TelegramPresentationData/Sources/ChatPresentationData.swift b/submodules/TelegramPresentationData/Sources/ChatPresentationData.swift deleted file mode 100644 index 614ea5416e..0000000000 --- a/submodules/TelegramPresentationData/Sources/ChatPresentationData.swift +++ /dev/null @@ -1,71 +0,0 @@ -import Foundation -import UIKit -import Display -import TelegramCore -import SyncCore -import TelegramPresentationData -import TelegramUIPreferences - -public final class ChatPresentationThemeData: Equatable { - public let theme: PresentationTheme - public let wallpaper: TelegramWallpaper - - public init(theme: PresentationTheme, wallpaper: TelegramWallpaper) { - self.theme = theme - self.wallpaper = wallpaper - } - - public static func ==(lhs: ChatPresentationThemeData, rhs: ChatPresentationThemeData) -> Bool { - return lhs.theme === rhs.theme && lhs.wallpaper == rhs.wallpaper - } -} - -public final class ChatPresentationData { - public let theme: ChatPresentationThemeData - public let fontSize: PresentationFontSize - public let strings: PresentationStrings - public let dateTimeFormat: PresentationDateTimeFormat - public let nameDisplayOrder: PresentationPersonNameOrder - public let disableAnimations: Bool - public let largeEmoji: Bool - public let chatBubbleCorners: PresentationChatBubbleCorners - public let animatedEmojiScale: CGFloat - public let isPreview: Bool - - public let messageFont: UIFont - public let messageEmojiFont: UIFont - public let messageBoldFont: UIFont - public let messageItalicFont: UIFont - public let messageBoldItalicFont: UIFont - public let messageFixedFont: UIFont - public let messageBlockQuoteFont: UIFont - - public init(theme: ChatPresentationThemeData, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool, largeEmoji: Bool, chatBubbleCorners: PresentationChatBubbleCorners, animatedEmojiScale: CGFloat = 1.0, isPreview: Bool = false) { - self.theme = theme - self.fontSize = fontSize - self.strings = strings - self.dateTimeFormat = dateTimeFormat - self.nameDisplayOrder = nameDisplayOrder - self.disableAnimations = disableAnimations - self.chatBubbleCorners = chatBubbleCorners - self.largeEmoji = largeEmoji - self.isPreview = isPreview - - let baseFontSize = fontSize.baseDisplaySize - self.messageFont = Font.regular(baseFontSize) - self.messageEmojiFont = Font.regular(53.0) - self.messageBoldFont = Font.bold(baseFontSize) - self.messageItalicFont = Font.italic(baseFontSize) - self.messageBoldItalicFont = Font.semiboldItalic(baseFontSize) - self.messageFixedFont = Font.monospace(baseFontSize) - self.messageBlockQuoteFont = Font.regular(baseFontSize - 1.0) - - self.animatedEmojiScale = animatedEmojiScale - } -} - -extension ChatPresentationData { - public convenience init(presentationData: PresentationData) { - self.init(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper), fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations, largeEmoji: presentationData.largeEmoji, chatBubbleCorners: presentationData.chatBubbleCorners) - } -} diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index c4b9484c1e..d3b258eaa4 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -354,7 +354,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio controlColor: UIColor(rgb: 0x7e8791), accentTextColor: UIColor(rgb: 0x007ee5), backgroundColor: UIColor(rgb: 0xf7f7f7), - separatorColor: UIColor(rgb: 0xc8c7cc), + separatorColor: UIColor(rgb: 0xb1b1b1), badgeBackgroundColor: UIColor(rgb: 0xff3b30), badgeStrokeColor: UIColor(rgb: 0xff3b30), badgeTextColor: UIColor(rgb: 0xffffff), @@ -374,7 +374,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio inputPlaceholderTextColor: UIColor(rgb: 0x8e8e93), inputIconColor: UIColor(rgb: 0x8e8e93), inputClearButtonColor: UIColor(rgb: 0x7b7b81), - separatorColor: UIColor(rgb: 0xc8c7cc) + separatorColor: UIColor(rgb: 0xb1b1b1) ) let rootController = PresentationThemeRootController( @@ -685,7 +685,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio let historyNavigation = PresentationThemeChatHistoryNavigation( fillColor: UIColor(rgb: 0xf7f7f7), - strokeColor: UIColor(rgb: 0xc8c7cc), + strokeColor: UIColor(rgb: 0xb1b1b1), foregroundColor: UIColor(rgb: 0x88888d), badgeBackgroundColor: UIColor(rgb: 0x007ee5), badgeStrokeColor: UIColor(rgb: 0x007ee5), @@ -751,7 +751,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio backgroundColor: UIColor(rgb: 0xffffff), primaryTextColor: UIColor(rgb: 0x000000), controlColor: UIColor(rgb: 0x7e8791), - separatorColor: UIColor(rgb: 0xc8c7cc) + separatorColor: UIColor(rgb: 0xb1b1b1) ) ) ) diff --git a/submodules/TelegramPresentationData/Sources/PresentationStrings.swift b/submodules/TelegramPresentationData/Sources/PresentationStrings.swift index 090d06bb8a..d820c37425 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationStrings.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationStrings.swift @@ -187,5528 +187,5507 @@ public final class PresentationStrings: Equatable { private let _s: [Int: String] private let _r: [Int: [(Int, NSRange)]] private let _ps: [Int: String] - public var SocksProxySetup_Secret: String { return self._s[0]! } - public var Channel_AdminLog_EmptyTitle: String { return self._s[2]! } - public var Contacts_PermissionsText: String { return self._s[3]! } - public var SettingsSearch_Synonyms_Notifications_MessageNotificationsSound: String { return self._s[4]! } - public var Map_Work: String { return self._s[5]! } - public var Channel_AddBotAsAdmin: String { return self._s[6]! } - public func Wallet_Updated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[7]!, self._r[7]!, [_0]) + public var CallFeedback_ReasonSilentLocal: String { return self._s[0]! } + public var StickerPack_ShowStickers: String { return self._s[1]! } + public var Map_PullUpForPlaces: String { return self._s[2]! } + public var Channel_Status: String { return self._s[4]! } + public var Wallet_Updated_JustNow: String { return self._s[5]! } + public var TwoStepAuth_ChangePassword: String { return self._s[7]! } + public var Map_LiveLocationFor1Hour: String { return self._s[8]! } + public var CheckoutInfo_ShippingInfoAddress2Placeholder: String { return self._s[9]! } + public var Settings_AppleWatch: String { return self._s[10]! } + public var Login_InvalidCountryCode: String { return self._s[11]! } + public var WebSearch_RecentSectionTitle: String { return self._s[12]! } + public var UserInfo_DeleteContact: String { return self._s[13]! } + public var ShareFileTip_CloseTip: String { return self._s[14]! } + public var UserInfo_Invite: String { return self._s[15]! } + public var Passport_Identity_MiddleName: String { return self._s[16]! } + public var Passport_Identity_FrontSideHelp: String { return self._s[17]! } + public var Month_GenDecember: String { return self._s[19]! } + public var Common_Yes: String { return self._s[20]! } + public func EncryptionKey_Description(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[21]!, self._r[21]!, [_1, _2]) } - public var Call_CallInProgressTitle: String { return self._s[8]! } - public var TwoFactorSetup_Done_Action: String { return self._s[9]! } - public var Compose_NewChannel_Members: String { return self._s[10]! } - public var FastTwoStepSetup_PasswordPlaceholder: String { return self._s[11]! } - public func Channel_AdminLog_MessageInvitedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[12]!, self._r[12]!, [_1]) + public var Channel_AdminLogFilter_EventsLeaving: String { return self._s[22]! } + public var WallpaperPreview_PreviewBottomText: String { return self._s[23]! } + public func Notification_PinnedStickerMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[24]!, self._r[24]!, [_0]) } - public func Notification_MessageLifetimeRemoved(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[15]!, self._r[15]!, [_1]) + public var Passport_Address_ScansHelp: String { return self._s[25]! } + public var FastTwoStepSetup_PasswordHelp: String { return self._s[26]! } + public var SettingsSearch_Synonyms_Notifications_Title: String { return self._s[27]! } + public var StickerPacksSettings_AnimatedStickers: String { return self._s[28]! } + public var Wallet_WordCheck_IncorrectText: String { return self._s[29]! } + public func Items_NOfM(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[30]!, self._r[30]!, [_1, _2]) } - public var Undo_DeletedGroup: String { return self._s[17]! } - public var ChatListFolder_CategoryNonContacts: String { return self._s[18]! } - public var Gif_NoGifsPlaceholder: String { return self._s[19]! } - public var Conversation_ShareInlineBotLocationConfirmation: String { return self._s[20]! } - public var AutoNightTheme_ScheduleSection: String { return self._s[22]! } - public var Map_LiveLocationTitle: String { return self._s[23]! } - public var Wallet_Receive_AddressHeader: String { return self._s[24]! } - public var Wallet_Navigation_Cancel: String { return self._s[25]! } - public var Passport_PasswordCreate: String { return self._s[26]! } - public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[27]! } - public var Settings_ProxyConnected: String { return self._s[28]! } - public func PUSH_PINNED_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[29]!, self._r[29]!, [_1, _2]) + public var AutoDownloadSettings_Files: String { return self._s[31]! } + public var TextFormat_AddLinkPlaceholder: String { return self._s[32]! } + public var Stats_MessagePublicForwardsTitle: String { return self._s[33]! } + public var ChatList_GenericPsaLabel: String { return self._s[38]! } + public var LastSeen_Lately: String { return self._s[39]! } + public func PUSH_CHANNEL_MESSAGE_VIDEOS(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[40]!, self._r[40]!, [_1, _2]) } - public var Channel_Management_LabelOwner: String { return self._s[30]! } - public var ApplyLanguage_ApplySuccess: String { return self._s[31]! } - public var Group_Setup_HistoryHidden: String { return self._s[32]! } - public var Month_ShortNovember: String { return self._s[33]! } - public var Call_ReportIncludeLog: String { return self._s[34]! } - public var ChatList_RemoveFolder: String { return self._s[35]! } - public var PrivacyPhoneNumberSettings_CustomHelp: String { return self._s[36]! } - public var Appearance_ThemePreview_ChatList_5_Text: String { return self._s[37]! } - public var Checkout_Receipt_Title: String { return self._s[38]! } - public func Conversation_ClearChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[39]!, self._r[39]!, [_0]) + public var Camera_Discard: String { return self._s[41]! } + public var Channel_EditAdmin_PermissinAddAdminOff: String { return self._s[42]! } + public var Login_InvalidPhoneError: String { return self._s[44]! } + public var SettingsSearch_Synonyms_Privacy_AuthSessions: String { return self._s[45]! } + public var GroupInfo_LabelOwner: String { return self._s[46]! } + public var Conversation_Moderate_Delete: String { return self._s[47]! } + public var ClearCache_ClearCache: String { return self._s[48]! } + public var Conversation_DeleteMessagesForEveryone: String { return self._s[49]! } + public var WatchRemote_AlertOpen: String { return self._s[50]! } + public func MediaPicker_Nof(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[51]!, self._r[51]!, [_0]) } - public var AuthSessions_LogOutApplicationsHelp: String { return self._s[40]! } - public var SearchImages_Title: String { return self._s[41]! } - public var Notification_PaymentSent: String { return self._s[42]! } - public var Appearance_TintAllColors: String { return self._s[43]! } - public var Group_Setup_TypePublicHelp: String { return self._s[44]! } - public var ChatSettings_Cache: String { return self._s[45]! } - public var Login_InvalidLastNameError: String { return self._s[46]! } - public var PeerInfo_PaneMedia: String { return self._s[47]! } - public var Wallet_Month_GenNovember: String { return self._s[48]! } - public var Wallet_Month_GenApril: String { return self._s[49]! } - public var Wallet_Weekday_Today: String { return self._s[50]! } - public var GroupPermission_PermissionGloballyDisabled: String { return self._s[51]! } - public var LiveLocationUpdated_JustNow: String { return self._s[52]! } - public func Map_LiveLocationPrivateDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[53]!, self._r[53]!, [_0]) + public var ChatState_ConnectingToProxy: String { return self._s[52]! } + public var EditTheme_Expand_Preview_IncomingReplyName: String { return self._s[53]! } + public var AutoDownloadSettings_MediaTypes: String { return self._s[55]! } + public var Watch_GroupInfo_Title: String { return self._s[56]! } + public var Passport_Identity_AddPersonalDetails: String { return self._s[57]! } + public var Channel_Info_Members: String { return self._s[58]! } + public var LoginPassword_InvalidPasswordError: String { return self._s[60]! } + public var Conversation_LiveLocation: String { return self._s[61]! } + public var Wallet_Month_ShortNovember: String { return self._s[62]! } + public var PrivacyLastSeenSettings_CustomShareSettingsHelp: String { return self._s[63]! } + public var NetworkUsageSettings_BytesReceived: String { return self._s[66]! } + public var Stickers_Search: String { return self._s[68]! } + public var NotificationsSound_Synth: String { return self._s[69]! } + public var LogoutOptions_LogOutInfo: String { return self._s[70]! } + public func VoiceOver_Chat_ForwardedFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[72]!, self._r[72]!, [_0]) } - public var Channel_Info_Members: String { return self._s[54]! } - public var Common_edit: String { return self._s[55]! } - public var ChatList_DeleteSavedMessagesConfirmationText: String { return self._s[57]! } - public var OldChannels_GroupEmptyFormat: String { return self._s[58]! } - public func PUSH_PINNED_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[59]!, self._r[59]!, [_1]) + public var NetworkUsageSettings_MediaAudioDataSection: String { return self._s[73]! } + public var ChatListFolder_NameBots: String { return self._s[74]! } + public var ChatList_EmptyChatListFilterText: String { return self._s[76]! } + public var Call_IncomingVoiceCall: String { return self._s[78]! } + public var ChatList_Context_HideArchive: String { return self._s[79]! } + public var AutoNightTheme_UseSunsetSunrise: String { return self._s[80]! } + public var FastTwoStepSetup_Title: String { return self._s[81]! } + public var EditTheme_Create_Preview_IncomingReplyText: String { return self._s[82]! } + public var Channel_Info_BlackList: String { return self._s[83]! } + public var Channel_AdminLog_InfoPanelTitle: String { return self._s[84]! } + public var Conversation_OpenFile: String { return self._s[86]! } + public var SecretTimer_ImageDescription: String { return self._s[87]! } + public var PrivacySettings_AutoArchive: String { return self._s[88]! } + public var StickerSettings_ContextInfo: String { return self._s[89]! } + public var TwoStepAuth_GenericHelp: String { return self._s[91]! } + public var AutoDownloadSettings_Unlimited: String { return self._s[92]! } + public var PrivacyLastSeenSettings_NeverShareWith_Title: String { return self._s[93]! } + public var AutoDownloadSettings_DataUsageHigh: String { return self._s[94]! } + public func PUSH_CHAT_MESSAGE_VIDEO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[95]!, self._r[95]!, [_1, _2]) } - public var Passport_DiscardMessageAction: String { return self._s[60]! } - public var Passport_FieldOneOf_FinalDelimeter: String { return self._s[61]! } - public var Stickers_SuggestNone: String { return self._s[62]! } - public var Channel_AdminLog_CanPinMessages: String { return self._s[64]! } - public var Stickers_Search: String { return self._s[66]! } - public var Passport_Identity_EditPersonalDetails: String { return self._s[67]! } - public var NotificationSettings_ShowNotificationsAllAccounts: String { return self._s[68]! } - public var Login_ContinueWithLocalization: String { return self._s[69]! } - public var Privacy_ProfilePhoto_NeverShareWith_Title: String { return self._s[70]! } - public var TelegramWallet_Intro_TermsUrl: String { return self._s[72]! } - public var ChatList_Search_NoResultsFitlerLinks: String { return self._s[74]! } - public var TextFormat_Italic: String { return self._s[75]! } - public var Stickers_GroupChooseStickerPack: String { return self._s[76]! } - public var Notification_MessageLifetime1w: String { return self._s[77]! } - public var Channel_Management_AddModerator: String { return self._s[78]! } - public var Conversation_UnsupportedMediaPlaceholder: String { return self._s[79]! } - public var Gif_Search: String { return self._s[80]! } - public var Checkout_ErrorGeneric: String { return self._s[81]! } - public var Conversation_ContextMenuSendMessage: String { return self._s[82]! } - public var Map_SetThisLocation: String { return self._s[83]! } - public var Wallet_Info_ReceiveGrams: String { return self._s[84]! } - public var Notifications_ExceptionsDefaultSound: String { return self._s[85]! } - public var PrivacySettings_AutoArchiveInfo: String { return self._s[86]! } - public var Stats_NotificationsTitle: String { return self._s[87]! } - public var Conversation_ClearSecretHistory: String { return self._s[89]! } - public func Notification_CallFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[90]!, self._r[90]!, [_1, _2]) + public var AuthSessions_AddDevice_ScanInfo: String { return self._s[96]! } + public var Notifications_AddExceptionTitle: String { return self._s[97]! } + public var Watch_MessageView_Reply: String { return self._s[98]! } + public var Tour_Text6: String { return self._s[99]! } + public var TwoStepAuth_SetupPasswordEnterPasswordChange: String { return self._s[100]! } + public func Notification_PinnedAnimationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[101]!, self._r[101]!, [_0]) } - public var Wallet_TransactionInfo_Title: String { return self._s[91]! } - public var ChatListFolder_DiscardDiscard: String { return self._s[92]! } - public var PrivacyLastSeenSettings_AlwaysShareWith: String { return self._s[93]! } - public var Contacts_InviteFriends: String { return self._s[94]! } - public var Group_LinkedChannel: String { return self._s[95]! } - public var Notification_PassportValuePhone: String { return self._s[97]! } - public func InviteText_SingleContact(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[98]!, self._r[98]!, [_0]) + public func ShareFileTip_Text(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[102]!, self._r[102]!, [_0]) } - public var UserInfo_BotHelp: String { return self._s[100]! } - public var Passport_Identity_MainPage: String { return self._s[102]! } - public var LogoutOptions_ContactSupportText: String { return self._s[103]! } - public func VoiceOver_Chat_Title(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[104]!, self._r[104]!, [_0]) + public var Wallet_Configuration_BlockchainIdPlaceholder: String { return self._s[103]! } + public var AccessDenied_LocationDenied: String { return self._s[104]! } + public var CallSettings_RecentCalls: String { return self._s[105]! } + public var ConversationProfile_LeaveDeleteAndExit: String { return self._s[106]! } + public var Conversation_Dice_u1F3C0: String { return self._s[107]! } + public var Channel_Members_AddAdminErrorBlacklisted: String { return self._s[109]! } + public var Passport_Authorize: String { return self._s[110]! } + public var StickerPacksSettings_ArchivedMasks_Info: String { return self._s[111]! } + public var AutoDownloadSettings_Videos: String { return self._s[112]! } + public var TwoStepAuth_ReEnterPasswordTitle: String { return self._s[113]! } + public var Wallet_Info_Send: String { return self._s[114]! } + public var AuthSessions_AddDevice_UrlLoginHint: String { return self._s[115]! } + public var Wallet_TransactionInfo_SendGrams: String { return self._s[116]! } + public var Tour_StartButton: String { return self._s[117]! } + public var Watch_AppName: String { return self._s[119]! } + public var Settings_AddAnotherAccount: String { return self._s[120]! } + public var StickerPack_ErrorNotFound: String { return self._s[121]! } + public var Channel_Info_Subscribers: String { return self._s[122]! } + public func Channel_AdminLog_MessageGroupPreHistoryVisible(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[123]!, self._r[123]!, [_0]) } - public var StickerPack_ShowStickers: String { return self._s[106]! } - public var AttachmentMenu_PhotoOrVideo: String { return self._s[107]! } - public var Map_Satellite: String { return self._s[108]! } - public var Passport_Identity_MainPageHelp: String { return self._s[109]! } - public var Profile_About: String { return self._s[111]! } - public var Group_Setup_TypePrivate: String { return self._s[112]! } - public var Notifications_ChannelNotifications: String { return self._s[113]! } - public func Login_WillCallYou(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[114]!, self._r[114]!, [_0]) + public func DialogList_PinLimitError(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[124]!, self._r[124]!, [_0]) } - public var WallpaperPreview_Motion: String { return self._s[115]! } - public var Message_VideoMessage: String { return self._s[116]! } - public var SharedMedia_CategoryOther: String { return self._s[117]! } - public var Passport_FieldIdentityUploadHelp: String { return self._s[118]! } - public var PUSH_REMINDER_TITLE: String { return self._s[119]! } - public var Appearance_ThemePreview_Chat_3_Text: String { return self._s[121]! } - public var Login_ResetAccountProtected_Reset: String { return self._s[123]! } - public var Passport_Identity_TypeInternalPassportUploadScan: String { return self._s[124]! } - public var ChatList_PeerTypeContact: String { return self._s[125]! } - public var Stickers_SuggestAll: String { return self._s[127]! } - public var EmptyGroupInfo_Line3: String { return self._s[128]! } - public var Login_InvalidPhoneError: String { return self._s[129]! } - public var MediaPicker_GroupDescription: String { return self._s[130]! } - public var NetworkUsageSettings_MediaDocumentDataSection: String { return self._s[131]! } - public var Conversation_PrivateChannelTimeLimitedAlertText: String { return self._s[132]! } - public var PrivateDataSettings_Title: String { return self._s[133]! } - public var SecretChat_Title: String { return self._s[134]! } - public var Privacy_ChatsTitle: String { return self._s[135]! } - public var EditProfile_NameAndPhotoHelp: String { return self._s[136]! } - public var Watch_MessageView_Forward: String { return self._s[138]! } - public var ChannelMembers_WhoCanAddMembers_AllMembers: String { return self._s[139]! } - public func PUSH_PINNED_QUIZ(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[140]!, self._r[140]!, [_1, _2]) + public var Appearance_RemoveTheme: String { return self._s[125]! } + public func Wallet_Info_TransactionBlockchainFee(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[126]!, self._r[126]!, [_0]) } - public var PhotoEditor_DiscardChanges: String { return self._s[141]! } - public var SocksProxySetup_AdNoticeHelp: String { return self._s[142]! } - public var Date_DialogDateFormat: String { return self._s[143]! } - public var SettingsSearch_Synonyms_Proxy_Title: String { return self._s[144]! } - public var Notifications_AlertTones: String { return self._s[145]! } - public var Permissions_SiriAllow_v0: String { return self._s[146]! } - public var Tour_StartButton: String { return self._s[147]! } - public var Stats_InstantViewInteractionsTitle: String { return self._s[148]! } - public var UserInfo_ScamUserWarning: String { return self._s[150]! } - public var NotificationsSound_Chime: String { return self._s[151]! } - public var Update_Skip: String { return self._s[152]! } - public func ChannelInfo_ChannelForbidden(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[153]!, self._r[153]!, [_0]) + public var Conversation_StopLiveLocation: String { return self._s[129]! } + public var Channel_AdminLogFilter_EventsAll: String { return self._s[130]! } + public var GroupInfo_InviteLink_CopyAlert_Success: String { return self._s[132]! } + public var Username_LinkCopied: String { return self._s[134]! } + public var GroupRemoved_Title: String { return self._s[135]! } + public var SecretVideo_Title: String { return self._s[136]! } + public func PUSH_PINNED_VIDEO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[137]!, self._r[137]!, [_1]) } - public var SettingsSearch_Synonyms_EditProfile_PhoneNumber: String { return self._s[154]! } - public var Notifications_PermissionsTitle: String { return self._s[155]! } - public var Channel_AdminLog_BanSendMedia: String { return self._s[156]! } - public var Wallet_Receive_CommentHeader: String { return self._s[157]! } - public var Notifications_Badge_CountUnreadMessages: String { return self._s[158]! } - public var Appearance_AppIcon: String { return self._s[159]! } - public var Passport_Identity_FilesUploadNew: String { return self._s[160]! } - public func Passport_Email_UseTelegramEmail(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[161]!, self._r[161]!, [_0]) + public var AccessDenied_PhotosAndVideos: String { return self._s[138]! } + public var Appearance_ThemePreview_Chat_1_Text: String { return self._s[139]! } + public func PUSH_CHANNEL_MESSAGE_GEOLIVE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[141]!, self._r[141]!, [_1]) } - public var CreatePoll_QuizTitle: String { return self._s[162]! } - public var DialogList_DeleteConversationConfirmation: String { return self._s[163]! } - public var NotificationsSound_Calypso: String { return self._s[164]! } - public var ChannelMembers_GroupAdminsTitle: String { return self._s[165]! } - public var Checkout_NewCard_PaymentCard: String { return self._s[166]! } - public var Wallpaper_SetCustomBackground: String { return self._s[168]! } - public var Conversation_ContextMenuOpenProfile: String { return self._s[169]! } - public func PUSH_MESSAGE_VIDEO_SECRET(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[171]!, self._r[171]!, [_1]) + public var Map_OpenInGoogleMaps: String { return self._s[143]! } + public var Conversation_Dice_u26BD: String { return self._s[144]! } + public func Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[145]!, self._r[145]!, [_1, _2, _3]) } - public var AuthSessions_Terminate: String { return self._s[172]! } - public var ShareFileTip_CloseTip: String { return self._s[173]! } - public var ChatSettings_DownloadInBackgroundInfo: String { return self._s[174]! } - public var Channel_Moderator_AccessLevelRevoke: String { return self._s[175]! } - public var Channel_AdminLogFilter_EventsDeletedMessages: String { return self._s[176]! } - public var Passport_Language_fr: String { return self._s[177]! } - public func Watch_Time_ShortTodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[179]!, self._r[179]!, [_0]) + public func Channel_AdminLog_MessageKickedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[146]!, self._r[146]!, [_1, _2]) } - public var Passport_Identity_TypeIdentityCard: String { return self._s[180]! } - public func Conversation_OpenBotLinkAllowMessages(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[181]!, self._r[181]!, [_0]) + public var Call_StatusRinging: String { return self._s[147]! } + public var SettingsSearch_Synonyms_EditProfile_Username: String { return self._s[148]! } + public var Group_Username_InvalidStartsWithNumber: String { return self._s[149]! } + public var UserInfo_NotificationsEnabled: String { return self._s[150]! } + public var PeopleNearby_MakeVisibleDescription: String { return self._s[151]! } + public var Settings_RemoveConfirmation: String { return self._s[152]! } + public var ChatListFolder_CategoryRead: String { return self._s[153]! } + public var Map_Search: String { return self._s[154]! } + public var ClearCache_StorageFree: String { return self._s[156]! } + public var Login_TermsOfServiceHeader: String { return self._s[157]! } + public func Notification_PinnedVideoMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[158]!, self._r[158]!, [_0]) } - public var ReportPeer_ReasonCopyright: String { return self._s[182]! } - public var Permissions_PeopleNearbyText_v0: String { return self._s[184]! } - public var Channel_Stickers_NotFoundHelp: String { return self._s[185]! } - public var Passport_Identity_AddDriversLicense: String { return self._s[186]! } - public func Wallet_Sent_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[187]!, self._r[187]!, [_0]) + public func Channel_AdminLog_MessageToggleSignaturesOn(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[160]!, self._r[160]!, [_0]) } - public var Permissions_SiriAllowInSettings_v0: String { return self._s[188]! } - public var AutoDownloadSettings_AutodownloadFiles: String { return self._s[189]! } - public var ApplyLanguage_ChangeLanguageTitle: String { return self._s[190]! } - public var Map_LocatingError: String { return self._s[192]! } - public var ChatSettings_AutoDownloadSettings_TypePhoto: String { return self._s[193]! } - public func VoiceOver_Chat_MusicFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[195]!, self._r[195]!, [_0]) + public var ChatList_GenericPsaAlert: String { return self._s[161]! } + public var Wallet_Sent_Title: String { return self._s[162]! } + public var TwoStepAuth_SetupPasswordConfirmPassword: String { return self._s[163]! } + public var Weekday_Today: String { return self._s[164]! } + public var Stats_InstantViewInteractionsTitle: String { return self._s[165]! } + public func InstantPage_AuthorAndDateTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[167]!, self._r[167]!, [_1, _2]) } - public func Contacts_AccessDeniedHelpLandscape(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[196]!, self._r[196]!, [_0]) + public func Conversation_MessageDialogRetryAll(_ _1: Int) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[168]!, self._r[168]!, ["\(_1)"]) } - public var Channel_AdminLog_EmptyFilterText: String { return self._s[197]! } - public var Login_SmsRequestState2: String { return self._s[198]! } - public var Conversation_Unmute: String { return self._s[200]! } - public var TwoFactorSetup_Intro_Text: String { return self._s[201]! } - public var Channel_AdminLog_BanSendMessages: String { return self._s[202]! } - public func Channel_Management_RemovedBy(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[203]!, self._r[203]!, [_0]) + public var Notification_PassportValuePersonalDetails: String { return self._s[170]! } + public var ProfilePhoto_SearchWeb: String { return self._s[171]! } + public var Channel_AdminLog_MessagePreviousLink: String { return self._s[172]! } + public var ChangePhoneNumberNumber_NewNumber: String { return self._s[173]! } + public var ApplyLanguage_LanguageNotSupportedError: String { return self._s[174]! } + public var TwoStepAuth_ChangePasswordDescription: String { return self._s[175]! } + public var PhotoEditor_BlurToolLinear: String { return self._s[176]! } + public var Contacts_PermissionsAllowInSettings: String { return self._s[177]! } + public var Weekday_ShortMonday: String { return self._s[178]! } + public var Cache_KeepMedia: String { return self._s[179]! } + public var Passport_FieldIdentitySelfieHelp: String { return self._s[180]! } + public func PUSH_PINNED_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[181]!, self._r[181]!, [_1, _2]) } - public var AccessDenied_LocationDenied: String { return self._s[204]! } - public var Share_AuthTitle: String { return self._s[205]! } - public var Month_ShortAugust: String { return self._s[206]! } - public func Notification_PinnedDeletedMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[207]!, self._r[207]!, [_0]) + public func Chat_SlowmodeTooltip(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[182]!, self._r[182]!, [_0]) } - public var SettingsSearch_Synonyms_Data_DownloadInBackground: String { return self._s[209]! } - public func PUSH_CONTACT_JOINED(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[210]!, self._r[210]!, [_1]) + public var Wallet_Receive_ShareUrlInfo: String { return self._s[183]! } + public var Conversation_ClousStorageInfo_Description4: String { return self._s[184]! } + public var Wallet_RestoreFailed_Title: String { return self._s[185]! } + public var Passport_Language_ru: String { return self._s[186]! } + public func Notification_CreatedChatWithTitle(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[187]!, self._r[187]!, [_0, _1]) } - public var Channel_BanUser_PermissionSendMedia: String { return self._s[211]! } - public var WallpaperSearch_ColorTitle: String { return self._s[213]! } - public var Wallpaper_Search: String { return self._s[214]! } - public var ClearCache_StorageUsage: String { return self._s[215]! } - public var CreatePoll_TextPlaceholder: String { return self._s[216]! } - public var Conversation_EditingMessagePanelTitle: String { return self._s[217]! } - public var Channel_EditAdmin_PermissionBanUsers: String { return self._s[218]! } - public var OldChannels_NoticeCreateText: String { return self._s[219]! } - public var ProfilePhoto_MainVideo: String { return self._s[220]! } - public var UserInfo_NotificationsDisabled: String { return self._s[221]! } - public var Wallet_Info_WalletCreated: String { return self._s[222]! } - public var Map_Unknown: String { return self._s[223]! } - public var Notifications_MessageNotificationsAlert: String { return self._s[224]! } - public var Conversation_StopQuiz: String { return self._s[225]! } - public var Checkout_LiabilityAlertTitle: String { return self._s[226]! } - public func Username_UsernameIsAvailable(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[227]!, self._r[227]!, [_0]) + public var WallpaperPreview_PatternIntensity: String { return self._s[188]! } + public var ChatList_EditFolder: String { return self._s[191]! } + public var ChatList_AutoarchiveSuggestion_Title: String { return self._s[192]! } + public var WebBrowser_InAppSafari: String { return self._s[193]! } + public var TwoStepAuth_RecoveryUnavailable: String { return self._s[194]! } + public var EnterPasscode_TouchId: String { return self._s[195]! } + public var PhotoEditor_QualityVeryHigh: String { return self._s[198]! } + public var Checkout_NewCard_SaveInfo: String { return self._s[200]! } + public var Gif_NoGifsPlaceholder: String { return self._s[202]! } + public func Notification_InvitedMultiple(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[204]!, self._r[204]!, [_0, _1]) } - public var CreatePoll_OptionPlaceholder: String { return self._s[228]! } - public var Wallet_Month_GenJanuary: String { return self._s[229]! } - public var Conversation_RestrictedStickers: String { return self._s[230]! } - public var MemberSearch_BotSection: String { return self._s[232]! } - public var Channel_Management_AddModeratorHelp: String { return self._s[233]! } - public var MaskStickerSettings_Title: String { return self._s[234]! } - public var ShareMenu_Comment: String { return self._s[235]! } - public var GroupInfo_Notifications: String { return self._s[236]! } - public var CheckoutInfo_ReceiverInfoTitle: String { return self._s[237]! } - public func DialogList_EncryptedChatStartedOutgoing(_ _0: String) -> (String, [(Int, NSRange)]) { + public var ChatSettings_AutoDownloadEnabled: String { return self._s[205]! } + public var NetworkUsageSettings_BytesSent: String { return self._s[206]! } + public var Checkout_PasswordEntry_Pay: String { return self._s[207]! } + public var AuthSessions_TerminateSession: String { return self._s[208]! } + public var Message_File: String { return self._s[209]! } + public var MediaPicker_VideoMuteDescription: String { return self._s[210]! } + public var SocksProxySetup_ProxyStatusConnected: String { return self._s[211]! } + public var TwoStepAuth_RecoveryCode: String { return self._s[212]! } + public var EnterPasscode_EnterCurrentPasscode: String { return self._s[213]! } + public func TwoStepAuth_EnterPasswordHint(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[214]!, self._r[214]!, [_0]) + } + public var Conversation_Moderate_Report: String { return self._s[216]! } + public var TwoStepAuth_EmailInvalid: String { return self._s[217]! } + public var Passport_Language_ms: String { return self._s[218]! } + public var Channel_Edit_AboutItem: String { return self._s[220]! } + public var DialogList_SearchSectionGlobal: String { return self._s[224]! } + public var AttachmentMenu_WebSearch: String { return self._s[225]! } + public var ChatState_WaitingForNetwork: String { return self._s[226]! } + public var Channel_BanUser_Title: String { return self._s[227]! } + public var PasscodeSettings_TurnPasscodeOn: String { return self._s[228]! } + public var WallpaperPreview_SwipeTopText: String { return self._s[229]! } + public var ChatList_DeleteSavedMessagesConfirmationText: String { return self._s[230]! } + public var ArchivedChats_IntroText2: String { return self._s[231]! } + public var ChatSearch_SearchPlaceholder: String { return self._s[233]! } + public var Conversation_OpenBotLinkTitle: String { return self._s[234]! } + public var Passport_FieldAddressTranslationHelp: String { return self._s[235]! } + public var NotificationsSound_Aurora: String { return self._s[236]! } + public var Notification_Exceptions_DeleteAll: String { return self._s[237]! } + public func FileSize_GB(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[238]!, self._r[238]!, [_0]) } - public var Conversation_ContextMenuCopyLink: String { return self._s[239]! } - public var Wallet_Send_NetworkErrorTitle: String { return self._s[241]! } - public var ChatListFolder_CategoryMuted: String { return self._s[242]! } - public var TwoStepAuth_AddHintDescription: String { return self._s[243]! } - public func VoiceOver_Chat_Duration(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[244]!, self._r[244]!, [_0]) + public var AuthSessions_LoggedInWithTelegram: String { return self._s[241]! } + public func Privacy_GroupsAndChannels_InviteToGroupError(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[242]!, self._r[242]!, [_0, _1]) } - public var Conversation_ClousStorageInfo_Description3: String { return self._s[245]! } - public var Contacts_SortByPresence: String { return self._s[246]! } - public var Watch_Location_Access: String { return self._s[247]! } - public var WallpaperPreview_CustomColorTopText: String { return self._s[248]! } - public var Passport_Address_TypeBankStatement: String { return self._s[249]! } - public var Group_Username_RevokeExistingUsernamesInfo: String { return self._s[250]! } - public var Conversation_ClearPrivateHistory: String { return self._s[251]! } - public var ChatList_Mute: String { return self._s[254]! } - public var Channel_AdminLog_CanDeleteMessagesOfOthers: String { return self._s[255]! } - public var Stats_PostsTitle: String { return self._s[256]! } - public var Paint_Masks: String { return self._s[258]! } - public var PasscodeSettings_TryAgainIn1Minute: String { return self._s[260]! } - public var Chat_AttachmentLimitReached: String { return self._s[261]! } - public var StickerPackActionInfo_ArchivedTitle: String { return self._s[262]! } - public var Watch_Stickers_StickerPacks: String { return self._s[263]! } - public var Channel_Setup_Title: String { return self._s[264]! } - public var GroupInfo_Administrators: String { return self._s[265]! } - public var NotificationSettings_ShowNotificationsAllAccountsInfoOff: String { return self._s[267]! } - public var Conversation_ContextMenuDiscuss: String { return self._s[268]! } - public var StickerPack_BuiltinPackName: String { return self._s[269]! } - public var TwoStepAuth_RecoveryEmailChangeDescription: String { return self._s[271]! } - public var Checkout_ShippingMethod: String { return self._s[273]! } - public var ClearCache_FreeSpace: String { return self._s[274]! } - public var EditTheme_Expand_Preview_IncomingReplyText: String { return self._s[275]! } - public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsSound: String { return self._s[277]! } - public func TwoStepAuth_ConfirmEmailDescription(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[278]!, self._r[278]!, [_1]) + public var Passport_PasswordNext: String { return self._s[243]! } + public var Bot_GroupStatusReadsHistory: String { return self._s[244]! } + public var EmptyGroupInfo_Line2: String { return self._s[245]! } + public func Channel_AdminLog_MessageTransferedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[246]!, self._r[246]!, [_1, _2]) } - public var Conversation_typing: String { return self._s[279]! } - public func PrivacySettings_LastSeenContactsMinus(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[281]!, self._r[281]!, [_0]) + public var VoiceOver_Chat_SeenByRecipients: String { return self._s[247]! } + public var Settings_FAQ_Intro: String { return self._s[250]! } + public var PrivacySettings_PasscodeAndTouchId: String { return self._s[252]! } + public var FeaturedStickerPacks_Title: String { return self._s[253]! } + public var TwoStepAuth_PasswordRemoveConfirmation: String { return self._s[255]! } + public var Username_Title: String { return self._s[256]! } + public func Message_StickerText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[257]!, self._r[257]!, [_0]) } - public var WebSearch_RecentSectionTitle: String { return self._s[282]! } - public var ChatList_UnhideAction: String { return self._s[283]! } - public var PasscodeSettings_6DigitCode: String { return self._s[284]! } - public var CallFeedback_AddComment: String { return self._s[285]! } - public var LoginPassword_PasswordHelp: String { return self._s[286]! } - public var Call_Flip: String { return self._s[287]! } - public var Weekday_ShortWednesday: String { return self._s[289]! } - public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[290]! } - public var VoiceOver_Chat_PollFinalResults: String { return self._s[291]! } - public var PeerInfo_ButtonAddMember: String { return self._s[292]! } - public var Call_Decline: String { return self._s[294]! } - public var Join_ChannelsTooMuch: String { return self._s[295]! } - public func PUSH_CHANNEL_MESSAGE_NOTEXT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[297]!, self._r[297]!, [_1]) + public var PeerInfo_PaneFiles: String { return self._s[258]! } + public var PasscodeSettings_AlphanumericCode: String { return self._s[259]! } + public var Localization_LanguageOther: String { return self._s[260]! } + public var Stickers_SuggestStickers: String { return self._s[261]! } + public func Channel_AdminLog_MessageRemovedGroupUsername(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[262]!, self._r[262]!, [_0]) } - public var Passport_Identity_Selfie: String { return self._s[298]! } - public var Privacy_ContactsTitle: String { return self._s[299]! } - public var GroupInfo_InviteLink_Title: String { return self._s[301]! } - public var TwoFactorSetup_Password_PlaceholderPassword: String { return self._s[302]! } - public var Conversation_OpenFile: String { return self._s[303]! } - public var Map_SetThisPlace: String { return self._s[304]! } - public var Channel_Info_Management: String { return self._s[305]! } - public var Passport_Language_hr: String { return self._s[306]! } - public var EditTheme_Edit_Preview_IncomingText: String { return self._s[308]! } - public var Conversation_SecretChatContextBotAlert: String { return self._s[310]! } - public var GroupInfo_Permissions_SlowmodeValue_Off: String { return self._s[311]! } - public var Privacy_Calls_P2PContacts: String { return self._s[312]! } - public var Appearance_PickAccentColor: String { return self._s[313]! } - public var MediaPicker_TapToUngroupDescription: String { return self._s[314]! } - public var Localization_EnglishLanguageName: String { return self._s[315]! } - public var Stickers_SuggestStickers: String { return self._s[316]! } - public var Passport_Language_ko: String { return self._s[317]! } - public var Settings_ProxyDisabled: String { return self._s[318]! } - public var PrivacySettings_PasscodeOff: String { return self._s[319]! } - public var Undo_LeftChannel: String { return self._s[320]! } - public var Appearance_AutoNightThemeDisabled: String { return self._s[321]! } - public var TextFormat_Bold: String { return self._s[322]! } - public var Login_InfoTitle: String { return self._s[323]! } - public var Channel_BanUser_PermissionSendPolls: String { return self._s[324]! } - public var Settings_AddAnotherAccount: String { return self._s[325]! } - public var GroupPermission_NewTitle: String { return self._s[326]! } - public var Login_SelectCountry_Title: String { return self._s[327]! } - public var Cache_ServiceFiles: String { return self._s[328]! } - public var Passport_Language_nl: String { return self._s[329]! } - public var Contacts_TopSection: String { return self._s[330]! } - public var Passport_Identity_DateOfBirthPlaceholder: String { return self._s[331]! } - public var Conversation_ContextMenuReport: String { return self._s[333]! } - public func Login_BannedPhoneBody(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[334]!, self._r[334]!, [_0]) + public var NotificationSettings_ShowNotificationsFromAccountsSection: String { return self._s[263]! } + public var Channel_AdminLogFilter_EventsAdmins: String { return self._s[264]! } + public var Conversation_DefaultRestrictedStickers: String { return self._s[265]! } + public func Notification_PinnedDeletedMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[266]!, self._r[266]!, [_0]) } - public var Conversation_Search: String { return self._s[335]! } - public var Group_Setup_HistoryVisibleHelp: String { return self._s[337]! } - public var ReportPeer_AlertSuccess: String { return self._s[339]! } - public var AutoNightTheme_Title: String { return self._s[341]! } - public func Notification_PinnedTextMessage(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[343]!, self._r[343]!, [_0, _1]) - } - public func Conversation_OpenBotLinkText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[344]!, self._r[344]!, [_0]) - } - public var Conversation_ShareBotContactConfirmation: String { return self._s[345]! } - public var TwoStepAuth_RecoveryCode: String { return self._s[346]! } - public var SocksProxySetup_ConnectAndSave: String { return self._s[347]! } - public func MESSAGE_INVOICE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[348]!, self._r[348]!, [_1, _2]) - } - public func Channel_AdminLog_MessageChangedGroupUsername(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[349]!, self._r[349]!, [_0]) - } - public func Notification_GroupInviter(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[350]!, self._r[350]!, [_0]) - } - public var Conversation_InfoGroup: String { return self._s[351]! } - public func Map_AccurateTo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[353]!, self._r[353]!, [_0]) - } - public var Conversation_ChatBackground: String { return self._s[354]! } - public var PhotoEditor_Set: String { return self._s[355]! } - public func Channel_Management_PromotedBy(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[357]!, self._r[357]!, [_0]) - } - public var IntentsSettings_SuggestedChatsContacts: String { return self._s[358]! } - public var Passport_Phone_Title: String { return self._s[360]! } - public var Conversation_EditingMessageMediaChange: String { return self._s[361]! } - public var Channel_LinkItem: String { return self._s[362]! } - public func PUSH_CHAT_DELETE_MEMBER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[363]!, self._r[363]!, [_1, _2, _3]) - } - public var Conversation_DeleteManyMessages: String { return self._s[364]! } - public var Notifications_Badge_IncludeMutedChats: String { return self._s[365]! } - public var AuthSessions_AddedDeviceTitle: String { return self._s[368]! } - public var Privacy_Calls_NeverAllow_Placeholder: String { return self._s[369]! } - public var Settings_ProxyConnecting: String { return self._s[370]! } - public var Theme_Colors_Accent: String { return self._s[371]! } - public var Theme_Colors_ColorWallpaperWarning: String { return self._s[372]! } - public func PUSH_PHONE_CALL_MISSED(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[373]!, self._r[373]!, [_1]) - } - public var Passport_Language_lo: String { return self._s[374]! } - public var Wallet_WordCheck_Continue: String { return self._s[375]! } - public func Watch_Time_ShortWeekdayAt(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[377]!, self._r[377]!, [_1, _2]) - } - public var Permissions_NotificationsText_v0: String { return self._s[378]! } - public var ChatList_Context_RemoveFromRecents: String { return self._s[379]! } - public var Watch_GroupInfo_Title: String { return self._s[380]! } - public var Settings_AddDevice: String { return self._s[382]! } - public var WallpaperPreview_SwipeColorsTopText: String { return self._s[383]! } - public func PUSH_CHANNEL_ALBUM(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[384]!, self._r[384]!, [_1]) - } - public var TwoStepAuth_Disable: String { return self._s[386]! } - public func Conversation_AddNameToContacts(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[387]!, self._r[387]!, [_0]) - } - public func Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[388]!, self._r[388]!, [_1, _2, _3]) - } - public func Login_WillSendSms(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[389]!, self._r[389]!, [_0]) - } - public var Channel_AdminLog_BanReadMessages: String { return self._s[390]! } - public var Undo_ChatDeleted: String { return self._s[391]! } - public var ContactInfo_URLLabelHomepage: String { return self._s[392]! } - public func PUSH_CHAT_MESSAGE_STICKER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[393]!, self._r[393]!, [_1, _2, _3]) - } - public var FastTwoStepSetup_EmailHelp: String { return self._s[394]! } - public var Contacts_SelectAll: String { return self._s[395]! } - public var Privacy_ContactsReset: String { return self._s[396]! } - public var AttachmentMenu_File: String { return self._s[398]! } - public var PasscodeSettings_EncryptData: String { return self._s[399]! } - public var EditTheme_ThemeTemplateAlertText: String { return self._s[400]! } - public func Wallet_Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[402]!, self._r[402]!, [_1, _2, _3]) - } - public func Privacy_GroupsAndChannels_InviteToChannelError(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[403]!, self._r[403]!, [_0, _1]) - } - public func Profile_CreateEncryptedChatOutdatedError(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[404]!, self._r[404]!, [_0, _1]) - } - public var PhotoEditor_ShadowsTint: String { return self._s[406]! } - public var GroupInfo_ChatAdmins: String { return self._s[407]! } - public var ArchivedChats_IntroTitle2: String { return self._s[408]! } - public var Cache_LowDiskSpaceText: String { return self._s[409]! } - public var CreatePoll_Anonymous: String { return self._s[410]! } - public var Wallet_Created_ExportErrorText: String { return self._s[411]! } - public var Checkout_PaymentMethod_New: String { return self._s[412]! } - public var Wallet_Info_RefreshErrorText: String { return self._s[413]! } - public var Invitation_JoinGroup: String { return self._s[414]! } - public func Time_MonthOfYear_m4(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[417]!, self._r[417]!, [_0]) - } - public var CheckoutInfo_SaveInfoHelp: String { return self._s[418]! } - public var Notification_Reply: String { return self._s[420]! } - public var Wallet_Month_GenSeptember: String { return self._s[421]! } - public func Login_PhoneBannedEmailSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[422]!, self._r[422]!, [_0]) - } - public var Login_PhoneTitle: String { return self._s[423]! } - public var VoiceOver_Media_PlaybackRateNormal: String { return self._s[424]! } - public func PUSH_CHAT_MESSAGE_INVOICE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[425]!, self._r[425]!, [_1, _2, _3]) - } - public var Appearance_TextSize_Title: String { return self._s[426]! } - public var NetworkUsageSettings_MediaImageDataSection: String { return self._s[428]! } - public var VoiceOver_Navigation_Compose: String { return self._s[429]! } - public var Passport_InfoText: String { return self._s[430]! } - public var ApplyLanguage_ApplyLanguageAction: String { return self._s[431]! } - public var MessagePoll_LabelClosed: String { return self._s[433]! } - public var AttachmentMenu_SendAsFiles: String { return self._s[434]! } - public var KeyCommand_FocusOnInputField: String { return self._s[435]! } - public var Privacy_SecretChatsLinkPreviews: String { return self._s[437]! } - public var Permissions_PeopleNearbyAllow_v0: String { return self._s[438]! } - public var Conversation_ContextMenuMention: String { return self._s[440]! } - public var CreatePoll_QuizInfo: String { return self._s[441]! } - public var Appearance_ThemePreview_ChatList_2_Name: String { return self._s[442]! } - public var Username_LinkCopied: String { return self._s[443]! } - public var IntentsSettings_SuggestedAndSpotlightChatsInfo: String { return self._s[444]! } - public var TwoStepAuth_ChangePassword: String { return self._s[445]! } - public var Watch_Suggestion_Thanks: String { return self._s[446]! } - public var Channel_TitleInfo: String { return self._s[447]! } - public var ChatList_ChatTypesSection: String { return self._s[448]! } - public func Watch_LastSeen_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[449]!, self._r[449]!, [_0]) - } - public func Channel_AdminLog_PollStopped(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[450]!, self._r[450]!, [_0]) - } - public var AuthSessions_AddDevice_InvalidQRCode: String { return self._s[451]! } - public func Call_MicrophoneOff(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[452]!, self._r[452]!, [_0]) - } - public var Channel_AdminLogFilter_ChannelEventsInfo: String { return self._s[453]! } - public var Profile_MessageLifetimeForever: String { return self._s[454]! } - public var ArchivedChats_IntroText1: String { return self._s[455]! } - public var Notifications_ChannelNotificationsPreview: String { return self._s[456]! } - public var Map_PullUpForPlaces: String { return self._s[458]! } - public var UserInfo_TelegramCall: String { return self._s[459]! } - public var Conversation_ShareMyContactInfo: String { return self._s[460]! } - public var ChatList_Tabs_All: String { return self._s[461]! } - public var Notification_PassportValueEmail: String { return self._s[462]! } - public var Notification_VideoCallIncoming: String { return self._s[463]! } - public var SettingsSearch_Synonyms_Appearance_AutoNightTheme: String { return self._s[464]! } - public var Channel_Username_InvalidTaken: String { return self._s[465]! } - public var GroupPermission_EditingDisabled: String { return self._s[466]! } - public var ChatContextMenu_TextSelectionTip: String { return self._s[467]! } - public var Passport_Language_pl: String { return self._s[469]! } - public var Call_Accept: String { return self._s[470]! } - public var ChatListFolder_NameSectionHeader: String { return self._s[471]! } - public func Passport_Identity_NativeNameTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[472]!, self._r[472]!, [_0]) - } - public var ClearCache_Forever: String { return self._s[473]! } - public func ChannelInfo_AddParticipantConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[475]!, self._r[475]!, [_0]) - } - public var Group_EditAdmin_RankAdminPlaceholder: String { return self._s[476]! } - public var Calls_SubmitRating: String { return self._s[477]! } - public func ChatList_AddedToFolderTooltip(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[478]!, self._r[478]!, [_1, _2]) - } - public var IntentsSettings_MainAccountInfo: String { return self._s[479]! } - public var Map_Hybrid: String { return self._s[481]! } - public var ChatList_Context_Archive: String { return self._s[482]! } - public var Message_PinnedDocumentMessage: String { return self._s[483]! } - public var State_ConnectingToProxyInfo: String { return self._s[484]! } - public var Wallet_Month_GenDecember: String { return self._s[485]! } - public var Passport_Identity_NativeNameGenericTitle: String { return self._s[487]! } - public var Settings_AppLanguage: String { return self._s[488]! } - public func Checkout_SavePasswordTimeoutAndFaceId(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[489]!, self._r[489]!, [_0]) - } - public var Notifications_PermissionsEnable: String { return self._s[491]! } - public var CheckoutInfo_ShippingInfoAddress1Placeholder: String { return self._s[492]! } - public func UserInfo_BlockActionTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[493]!, self._r[493]!, [_0]) - } - public func AuthSessions_Message(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[494]!, self._r[494]!, [_0]) - } - public var NotificationsSound_Aurora: String { return self._s[497]! } - public var ScheduledMessages_ClearAll: String { return self._s[500]! } - public func CancelResetAccount_TextSMS(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[501]!, self._r[501]!, [_0]) - } - public var Settings_BlockedUsers: String { return self._s[503]! } - public func UserInfo_StartSecretChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[505]!, self._r[505]!, [_0]) - } - public var Passport_Language_hu: String { return self._s[506]! } + public var Wallet_TransactionInfo_CopyAddress: String { return self._s[268]! } + public var Group_UpgradeConfirmation: String { return self._s[270]! } + public var DialogList_Unpin: String { return self._s[271]! } + public var Passport_Identity_DateOfBirth: String { return self._s[273]! } + public var Month_ShortOctober: String { return self._s[274]! } + public var SettingsSearch_Synonyms_Privacy_Data_ContactsSync: String { return self._s[275]! } + public var TwoFactorSetup_Done_Text: String { return self._s[276]! } + public var Notification_CallCanceledShort: String { return self._s[277]! } + public var Conversation_StopQuiz: String { return self._s[278]! } + public var Passport_Phone_Help: String { return self._s[279]! } + public var Passport_Language_az: String { return self._s[281]! } + public var CreatePoll_TextPlaceholder: String { return self._s[283]! } + public var VoiceOver_Chat_AnonymousPoll: String { return self._s[284]! } + public var Passport_Identity_DocumentNumber: String { return self._s[285]! } + public var PhotoEditor_CurvesRed: String { return self._s[287]! } + public var PhoneNumberHelp_Alert: String { return self._s[289]! } + public var Stats_GroupTopPostersTitle: String { return self._s[290]! } + public var SocksProxySetup_Port: String { return self._s[291]! } + public var Checkout_PayNone: String { return self._s[292]! } + public var AutoDownloadSettings_WiFi: String { return self._s[293]! } + public var GroupInfo_GroupType: String { return self._s[294]! } + public var StickerSettings_ContextHide: String { return self._s[295]! } + public var Passport_Address_OneOfTypeTemporaryRegistration: String { return self._s[296]! } + public var Group_Setup_HistoryTitle: String { return self._s[298]! } + public var Passport_Identity_FilesUploadNew: String { return self._s[299]! } + public var PasscodeSettings_AutoLock: String { return self._s[300]! } + public var Passport_Title: String { return self._s[301]! } + public var VoiceOver_Chat_ContactPhoneNumber: String { return self._s[302]! } + public var Channel_AdminLogFilter_EventsNewSubscribers: String { return self._s[303]! } + public var GroupPermission_NoSendGifs: String { return self._s[304]! } + public var PrivacySettings_PasscodeOn: String { return self._s[305]! } public func Conversation_ScheduleMessage_SendTomorrow(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[507]!, self._r[507]!, [_0]) + return formatWithArgumentRanges(self._s[306]!, self._r[306]!, [_0]) } - public var StickerPack_Share: String { return self._s[508]! } - public var Checkout_NewCard_SaveInfoEnableHelp: String { return self._s[509]! } - public func ForwardedAuthors2(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[510]!, self._r[510]!, [_0, _1]) + public var ChatList_PeerTypeNonContact: String { return self._s[309]! } + public var State_WaitingForNetwork: String { return self._s[310]! } + public func Notification_Invited(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[311]!, self._r[311]!, [_0, _1]) } - public var Privacy_ContactsResetConfirmation: String { return self._s[511]! } - public var AppleWatch_ReplyPresets: String { return self._s[512]! } - public var Bot_GenericBotStatus: String { return self._s[513]! } - public var Appearance_ShareThemeColor: String { return self._s[514]! } - public var AuthSessions_AddDevice_UrlLoginHint: String { return self._s[515]! } - public var ReportGroupLocation_Title: String { return self._s[516]! } - public func Activity_RemindAboutUser(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[517]!, self._r[517]!, [_0]) + public var Calls_NotNow: String { return self._s[313]! } + public func Channel_DiscussionGroup_HeaderSet(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[314]!, self._r[314]!, [_0]) } - public var Profile_CreateEncryptedChatError: String { return self._s[518]! } - public var Channel_EditAdmin_TransferOwnership: String { return self._s[519]! } - public var Wallpaper_ErrorNotFound: String { return self._s[520]! } - public var Bot_GenericSupportStatus: String { return self._s[521]! } - public var Activity_UploadingPhoto: String { return self._s[523]! } - public var Watch_UserInfo_Title: String { return self._s[525]! } - public var SocksProxySetup_ProxyTelegram: String { return self._s[526]! } - public var Appearance_ThemeDay: String { return self._s[527]! } - public func ApplyLanguage_ChangeLanguageOfficialText(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[528]!, self._r[528]!, [_1]) + public var UserInfo_SendMessage: String { return self._s[315]! } + public var PhotoEditor_SelectCoverFrame: String { return self._s[316]! } + public var ChatList_AutoarchiveSuggestion_Text: String { return self._s[317]! } + public var TwoStepAuth_PasswordSet: String { return self._s[318]! } + public var Passport_DeleteDocument: String { return self._s[319]! } + public var SocksProxySetup_AddProxyTitle: String { return self._s[320]! } + public func PUSH_MESSAGE_VIDEO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[321]!, self._r[321]!, [_1]) } - public func FileSize_B(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[529]!, self._r[529]!, [_0]) + public var AuthSessions_AddedDeviceTitle: String { return self._s[322]! } + public var GroupRemoved_Remove: String { return self._s[323]! } + public var Passport_FieldIdentity: String { return self._s[324]! } + public var Group_Setup_TypePrivateHelp: String { return self._s[325]! } + public var Conversation_Processing: String { return self._s[328]! } + public var Wallet_Settings_BackupWallet: String { return self._s[330]! } + public var ChatListFolder_NameNonMuted: String { return self._s[331]! } + public var ChatSettings_AutoPlayAnimations: String { return self._s[332]! } + public var AuthSessions_LogOutApplicationsHelp: String { return self._s[335]! } + public var Forward_ErrorPublicQuizDisabledInChannels: String { return self._s[336]! } + public var Month_GenFebruary: String { return self._s[337]! } + public var ChatListFilter_AddChatsTitle: String { return self._s[338]! } + public var Wallet_Send_NetworkErrorTitle: String { return self._s[339]! } + public var Stats_GroupTopPoster_History: String { return self._s[341]! } + public func Login_InvalidPhoneEmailBody(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[342]!, self._r[342]!, [_1, _2, _3, _4, _5]) } - public var Passport_Title: String { return self._s[532]! } - public func Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[534]!, self._r[534]!, [_1, _2, _3]) + public var Passport_Identity_TypeIdentityCard: String { return self._s[343]! } + public var Wallet_Month_ShortJune: String { return self._s[345]! } + public var AutoDownloadSettings_DataUsageMedium: String { return self._s[346]! } + public var GroupInfo_AddParticipant: String { return self._s[347]! } + public var KeyCommand_SendMessage: String { return self._s[348]! } + public var VoiceOver_Chat_YourContact: String { return self._s[350]! } + public var Map_LiveLocationShowAll: String { return self._s[351]! } + public var WallpaperSearch_ColorOrange: String { return self._s[353]! } + public var Appearance_AppIconDefaultX: String { return self._s[354]! } + public var Checkout_Receipt_Title: String { return self._s[355]! } + public var Group_OwnershipTransfer_ErrorPrivacyRestricted: String { return self._s[356]! } + public var WallpaperPreview_PreviewTopText: String { return self._s[357]! } + public var Message_Contact: String { return self._s[359]! } + public var Call_StatusIncoming: String { return self._s[360]! } + public var Wallet_TransactionInfo_StorageFeeInfo: String { return self._s[361]! } + public func Channel_AdminLog_MessageKickedName(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[362]!, self._r[362]!, [_1]) } - public var Wallet_Sent_Title: String { return self._s[535]! } - public var CheckoutInfo_ShippingInfoCountryPlaceholder: String { return self._s[536]! } - public var SocksProxySetup_ShareLink: String { return self._s[539]! } - public var AuthSessions_OtherDevices: String { return self._s[540]! } - public var IntentsSettings_SuggestedChatsGroups: String { return self._s[541]! } - public var Watch_MessageView_Reply: String { return self._s[542]! } - public var Camera_FlashOn: String { return self._s[544]! } - public func PUSH_MESSAGE_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[545]!, self._r[545]!, [_1, _2]) + public func PUSH_ENCRYPTED_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[364]!, self._r[364]!, [_1]) } - public var Channel_EditAdmin_PermissionEditMessages: String { return self._s[547]! } - public var Privacy_Calls_NeverAllow: String { return self._s[548]! } - public var SharedMedia_CategoryLinks: String { return self._s[549]! } - public var Conversation_PinMessageAlertGroup: String { return self._s[552]! } - public var Passport_Identity_ScansHelp: String { return self._s[553]! } - public var ShareMenu_CopyShareLink: String { return self._s[554]! } - public var StickerSettings_MaskContextInfo: String { return self._s[555]! } - public var SocksProxySetup_ProxyStatusChecking: String { return self._s[556]! } - public var Conversation_WalletRequiredText: String { return self._s[557]! } - public var AutoDownloadSettings_AutodownloadPhotos: String { return self._s[559]! } - public var Checkout_ErrorPrecheckoutFailed: String { return self._s[561]! } - public var NotificationsSound_Popcorn: String { return self._s[562]! } - public var FeatureDisabled_Oops: String { return self._s[563]! } - public func Channel_AdminLog_MessageChangedChannelAbout(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[564]!, self._r[564]!, [_0]) + public var VoiceOver_Media_PlaybackRate: String { return self._s[365]! } + public var Passport_FieldIdentityDetailsHelp: String { return self._s[366]! } + public var Conversation_ViewChannel: String { return self._s[367]! } + public func Time_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[368]!, self._r[368]!, [_0]) } - public var Notification_PinnedMessage: String { return self._s[565]! } - public var Tour_Title4: String { return self._s[566]! } - public var Watch_Suggestion_OK: String { return self._s[567]! } - public var Compose_TokenListPlaceholder: String { return self._s[568]! } - public var EditTheme_Edit_TopInfo: String { return self._s[569]! } - public var Gif_NoGifsFound: String { return self._s[570]! } - public var Login_InvalidCountryCode: String { return self._s[571]! } - public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsExceptions: String { return self._s[572]! } - public func PUSH_LOCKED_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[574]!, self._r[574]!, [_1]) + public var Theme_Colors_Accent: String { return self._s[369]! } + public var Paint_Arrow: String { return self._s[370]! } + public var Passport_Language_nl: String { return self._s[372]! } + public var Camera_Retake: String { return self._s[373]! } + public func UserInfo_BlockActionTitle(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[374]!, self._r[374]!, [_0]) } - public var Profile_CreateNewContact: String { return self._s[575]! } - public var AutoDownloadSettings_DataUsageLow: String { return self._s[576]! } - public var SettingsSearch_Synonyms_Notifications_InAppNotificationsPreview: String { return self._s[577]! } - public var Group_Setup_TypePublic: String { return self._s[578]! } - public var Weekday_ShortSaturday: String { return self._s[579]! } - public func Time_MonthOfYear_m12(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[580]!, self._r[580]!, [_0]) + public var AuthSessions_LogOutApplications: String { return self._s[375]! } + public var ApplyLanguage_ApplySuccess: String { return self._s[376]! } + public var Tour_Title6: String { return self._s[377]! } + public var Map_ChooseAPlace: String { return self._s[378]! } + public var CallSettings_Never: String { return self._s[380]! } + public func Notification_ChangedGroupPhoto(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[381]!, self._r[381]!, [_0]) } - public var LiveLocation_MenuStopAll: String { return self._s[581]! } - public func DialogList_EncryptedChatStartedIncoming(_ _0: String) -> (String, [(Int, NSRange)]) { + public var ChannelRemoved_RemoveInfo: String { return self._s[382]! } + public func AutoDownloadSettings_PreloadVideoInfo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[383]!, self._r[383]!, [_0]) + } + public var SettingsSearch_Synonyms_Notifications_MessageNotificationsExceptions: String { return self._s[384]! } + public func Conversation_ClearChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[385]!, self._r[385]!, [_0]) + } + public var GroupInfo_InviteLink_Title: String { return self._s[386]! } + public func Channel_AdminLog_MessageUnkickedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[387]!, self._r[387]!, [_1, _2]) + } + public var KeyCommand_ScrollUp: String { return self._s[388]! } + public var ContactInfo_URLLabelHomepage: String { return self._s[389]! } + public var Channel_OwnershipTransfer_ChangeOwner: String { return self._s[390]! } + public func Channel_AdminLog_DisabledSlowmode(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[391]!, self._r[391]!, [_0]) + } + public var TwoFactorSetup_Done_Title: String { return self._s[392]! } + public func Conversation_EncryptedPlaceholderTitleOutgoing(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[393]!, self._r[393]!, [_0]) + } + public var CallFeedback_ReasonDistortedSpeech: String { return self._s[394]! } + public var Watch_LastSeen_WithinAWeek: String { return self._s[395]! } + public var ContactList_Context_SendMessage: String { return self._s[397]! } + public var Weekday_Tuesday: String { return self._s[398]! } + public var Wallet_Created_Title: String { return self._s[400]! } + public var ScheduledMessages_Delete: String { return self._s[401]! } + public var UserInfo_StartSecretChat: String { return self._s[402]! } + public var Passport_Identity_FilesTitle: String { return self._s[403]! } + public var Permissions_NotificationsAllow_v0: String { return self._s[404]! } + public var DialogList_DeleteConversationConfirmation: String { return self._s[406]! } + public var ChatList_UndoArchiveRevealedTitle: String { return self._s[407]! } + public func Wallet_Configuration_ApplyErrorTextURLUnreachable(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[408]!, self._r[408]!, [_0]) + } + public var AuthSessions_Sessions: String { return self._s[409]! } + public var Conversation_PeerNearbyText: String { return self._s[410]! } + public func Settings_KeepPhoneNumber(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[413]!, self._r[413]!, [_0]) + } + public var TwoStepAuth_RecoveryEmailChangeDescription: String { return self._s[414]! } + public var Call_StatusWaiting: String { return self._s[415]! } + public var CreateGroup_SoftUserLimitAlert: String { return self._s[416]! } + public var FastTwoStepSetup_HintHelp: String { return self._s[417]! } + public var WallpaperPreview_CustomColorBottomText: String { return self._s[418]! } + public var EditTheme_Expand_Preview_OutgoingText: String { return self._s[419]! } + public var LogoutOptions_AddAccountText: String { return self._s[420]! } + public var PasscodeSettings_6DigitCode: String { return self._s[421]! } + public var Settings_LogoutConfirmationText: String { return self._s[422]! } + public var ProfilePhoto_OpenGallery: String { return self._s[423]! } + public var Passport_Identity_TypePassport: String { return self._s[425]! } + public var Map_Work: String { return self._s[428]! } + public func PUSH_MESSAGE_VIDEOS(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[429]!, self._r[429]!, [_1, _2]) + } + public var SocksProxySetup_SaveProxy: String { return self._s[430]! } + public var AccessDenied_SaveMedia: String { return self._s[431]! } + public var Checkout_ErrorInvoiceAlreadyPaid: String { return self._s[433]! } + public var CreatePoll_MultipleChoice: String { return self._s[434]! } + public var Settings_Title: String { return self._s[436]! } + public var VoiceOver_Chat_RecordModeVideoMessageInfo: String { return self._s[437]! } + public var Contacts_InviteSearchLabel: String { return self._s[439]! } + public var PrivacySettings_WebSessions: String { return self._s[440]! } + public var ConvertToSupergroup_Title: String { return self._s[441]! } + public func Channel_AdminLog_CaptionEdited(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[442]!, self._r[442]!, [_0]) + } + public var TwoFactorSetup_Hint_Text: String { return self._s[443]! } + public var InfoPlist_NSSiriUsageDescription: String { return self._s[444]! } + public func PUSH_MESSAGE_CHANNEL_MESSAGE_GAME_SCORE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[445]!, self._r[445]!, [_1, _2, _3]) + } + public var ChatSettings_AutomaticPhotoDownload: String { return self._s[446]! } + public var UserInfo_BotHelp: String { return self._s[447]! } + public var PrivacySettings_LastSeenEverybody: String { return self._s[448]! } + public var Checkout_Name: String { return self._s[449]! } + public var AutoDownloadSettings_DataUsage: String { return self._s[450]! } + public var Channel_BanUser_BlockFor: String { return self._s[451]! } + public var Checkout_ShippingAddress: String { return self._s[452]! } + public var AutoDownloadSettings_MaxVideoSize: String { return self._s[453]! } + public var Privacy_PaymentsClearInfoDoneHelp: String { return self._s[454]! } + public var Privacy_Forwards: String { return self._s[455]! } + public var Channel_BanUser_PermissionSendPolls: String { return self._s[456]! } + public var Appearance_ThemeCarouselNewNight: String { return self._s[457]! } + public func SecretVideo_NotViewedYet(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[460]!, self._r[460]!, [_0]) + } + public var Contacts_SortedByName: String { return self._s[461]! } + public var Group_OwnershipTransfer_Title: String { return self._s[462]! } + public var PeerInfo_BioExpand: String { return self._s[464]! } + public var VoiceOver_Chat_OpenHint: String { return self._s[465]! } + public var Group_LeaveGroup: String { return self._s[466]! } + public var Settings_UsernameEmpty: String { return self._s[467]! } + public func Notification_PinnedPollMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[468]!, self._r[468]!, [_0]) + } + public func TwoStepAuth_ConfirmEmailDescription(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[469]!, self._r[469]!, [_1]) + } + public func Channel_OwnershipTransfer_DescriptionInfo(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[470]!, self._r[470]!, [_1, _2]) + } + public var Message_ImageExpired: String { return self._s[471]! } + public var TwoStepAuth_RecoveryFailed: String { return self._s[473]! } + public var EditTheme_Edit_Preview_OutgoingText: String { return self._s[474]! } + public var UserInfo_AddToExisting: String { return self._s[475]! } + public var TwoStepAuth_EnabledSuccess: String { return self._s[476]! } + public var Wallet_Send_SyncInProgress: String { return self._s[477]! } + public var ChatListFolderSettings_RecommendedFoldersSection: String { return self._s[478]! } + public var ChatListFolder_IncludeSectionInfo: String { return self._s[479]! } + public var SettingsSearch_Synonyms_Appearance_ChatBackground_SetColor: String { return self._s[480]! } + public func PUSH_CHANNEL_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[481]!, self._r[481]!, [_1]) + } + public var Notifications_GroupNotificationsAlert: String { return self._s[482]! } + public var Passport_Language_km: String { return self._s[483]! } + public var SocksProxySetup_AdNoticeHelp: String { return self._s[485]! } + public var VoiceOver_Media_PlaybackPlay: String { return self._s[486]! } + public var Notification_CallMissedShort: String { return self._s[487]! } + public var Wallet_Info_YourBalance: String { return self._s[488]! } + public var ReportPeer_ReasonOther_Send: String { return self._s[490]! } + public var Watch_Compose_Send: String { return self._s[491]! } + public var Passport_Identity_TypeInternalPassportUploadScan: String { return self._s[494]! } + public var TwoFactorSetup_Email_Action: String { return self._s[495]! } + public var Conversation_HoldForVideo: String { return self._s[496]! } + public var Wallet_Configuration_ApplyErrorTextURLInvalidData: String { return self._s[497]! } + public var AuthSessions_OtherDevices: String { return self._s[498]! } + public var Wallet_TransactionInfo_CommentHeader: String { return self._s[499]! } + public var CheckoutInfo_ErrorCityInvalid: String { return self._s[501]! } + public var Appearance_AutoNightThemeDisabled: String { return self._s[503]! } + public var Channel_LinkItem: String { return self._s[504]! } + public func PrivacySettings_LastSeenContactsMinusPlus(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[505]!, self._r[505]!, [_0, _1]) + } + public func Passport_Identity_NativeNameTitle(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[508]!, self._r[508]!, [_0]) + } + public var VoiceOver_Recording_StopAndPreview: String { return self._s[509]! } + public var Passport_Language_dv: String { return self._s[510]! } + public var Undo_LeftChannel: String { return self._s[511]! } + public var Notifications_ExceptionsMuted: String { return self._s[512]! } + public var ChatList_UnhideAction: String { return self._s[513]! } + public var Conversation_ContextMenuShare: String { return self._s[515]! } + public var Conversation_ContextMenuStickerPackInfo: String { return self._s[516]! } + public var ShareFileTip_Title: String { return self._s[517]! } + public var NotificationsSound_Chord: String { return self._s[518]! } + public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[519]! } + public func PUSH_CHAT_RETURNED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[520]!, self._r[520]!, [_1, _2]) + } + public var PeerInfo_ButtonVideoCall: String { return self._s[521]! } + public var Passport_Address_EditTemporaryRegistration: String { return self._s[522]! } + public func Notification_Joined(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[523]!, self._r[523]!, [_0]) + } + public func Wallet_Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[524]!, self._r[524]!, [_1, _2, _3]) + } + public var Wallet_Settings_ConfigurationInfo: String { return self._s[525]! } + public var Wallpaper_ErrorNotFound: String { return self._s[526]! } + public var Notification_CallOutgoingShort: String { return self._s[528]! } + public var Wallet_WordImport_IncorrectText: String { return self._s[529]! } + public func Watch_Time_ShortFullAt(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[530]!, self._r[530]!, [_1, _2]) + } + public var Passport_Address_TypeUtilityBill: String { return self._s[531]! } + public var Privacy_Forwards_LinkIfAllowed: String { return self._s[532]! } + public var ReportPeer_Report: String { return self._s[533]! } + public var SettingsSearch_Synonyms_Proxy_Title: String { return self._s[534]! } + public var GroupInfo_DeactivatedStatus: String { return self._s[535]! } + public func VoiceOver_Chat_MusicTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[536]!, self._r[536]!, [_1, _2]) + } + public var StickerPack_Send: String { return self._s[537]! } + public var Login_CodeSentInternal: String { return self._s[538]! } + public var Wallet_Month_GenJanuary: String { return self._s[539]! } + public var GroupInfo_InviteLink_LinkSection: String { return self._s[541]! } + public func Channel_AdminLog_MessageDeleted(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[542]!, self._r[542]!, [_0]) + } + public func Conversation_EncryptionWaiting(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[544]!, self._r[544]!, [_0]) + } + public var Channel_BanUser_PermissionSendStickersAndGifs: String { return self._s[545]! } + public func PUSH_PINNED_GAME(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[546]!, self._r[546]!, [_1]) + } + public var ReportPeer_ReasonViolence: String { return self._s[548]! } + public var Appearance_ShareThemeColor: String { return self._s[549]! } + public var Map_Locating: String { return self._s[550]! } + public func VoiceOver_Chat_VideoFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[551]!, self._r[551]!, [_0]) + } + public func PUSH_ALBUM(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[552]!, self._r[552]!, [_1]) + } + public var ChatListFolderSettings_FoldersSection: String { return self._s[553]! } + public var AutoDownloadSettings_GroupChats: String { return self._s[555]! } + public var CheckoutInfo_SaveInfo: String { return self._s[556]! } + public var ChatList_ChatTypesSection: String { return self._s[557]! } + public var SharedMedia_EmptyLinksText: String { return self._s[559]! } + public var Passport_Address_CityPlaceholder: String { return self._s[560]! } + public var CheckoutInfo_ErrorStateInvalid: String { return self._s[561]! } + public var Privacy_ProfilePhoto_CustomHelp: String { return self._s[562]! } + public var Wallet_Send_OwnAddressAlertTitle: String { return self._s[564]! } + public var Channel_AdminLog_CanAddAdmins: String { return self._s[565]! } + public func PUSH_CHANNEL_MESSAGE_FWD(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[566]!, self._r[566]!, [_1]) + } + public func Time_MonthOfYear_m8(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[567]!, self._r[567]!, [_0]) + } + public var InfoPlist_NSLocationWhenInUseUsageDescription: String { return self._s[568]! } + public var GroupInfo_InviteLink_RevokeAlert_Success: String { return self._s[569]! } + public var ChangePhoneNumberCode_Code: String { return self._s[570]! } + public var Appearance_CreateTheme: String { return self._s[571]! } + public func UserInfo_NotificationsDefaultSound(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[572]!, self._r[572]!, [_0]) + } + public var TwoStepAuth_SetupEmail: String { return self._s[573]! } + public var HashtagSearch_AllChats: String { return self._s[574]! } + public var MediaPlayer_UnknownTrack: String { return self._s[575]! } + public var SettingsSearch_Synonyms_Data_AutoDownloadUsingCellular: String { return self._s[577]! } + public func ChatList_DeleteForEveryone(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[578]!, self._r[578]!, [_0]) + } + public var Chat_Gifs_SavedSectionHeader: String { return self._s[579]! } + public var PhotoEditor_QualityHigh: String { return self._s[581]! } + public func Passport_Phone_UseTelegramNumber(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[582]!, self._r[582]!, [_0]) } - public var ChatListFolder_NamePlaceholder: String { return self._s[583]! } - public var Channel_OwnershipTransfer_ErrorPublicChannelsTooMuch: String { return self._s[584]! } - public func PUSH_CHAT_MESSAGE_GAME(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[585]!, self._r[585]!, [_1, _2, _3]) + public var ApplyLanguage_ApplyLanguageAction: String { return self._s[583]! } + public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsPreview: String { return self._s[584]! } + public var Message_LiveLocation: String { return self._s[585]! } + public var Cache_LowDiskSpaceText: String { return self._s[586]! } + public var Wallet_Receive_ShareAddress: String { return self._s[587]! } + public var EditTheme_ErrorLinkTaken: String { return self._s[589]! } + public var Conversation_SendMessage: String { return self._s[590]! } + public var AuthSessions_EmptyTitle: String { return self._s[591]! } + public var Privacy_PhoneNumber: String { return self._s[592]! } + public var PeopleNearby_CreateGroup: String { return self._s[593]! } + public var Stats_SharesPerPost: String { return self._s[595]! } + public var CallSettings_UseLessData: String { return self._s[596]! } + public var NetworkUsageSettings_MediaDocumentDataSection: String { return self._s[597]! } + public var Stickers_AddToFavorites: String { return self._s[598]! } + public var Wallet_WordImport_Title: String { return self._s[599]! } + public var PhotoEditor_QualityLow: String { return self._s[600]! } + public var Watch_UserInfo_Unblock: String { return self._s[601]! } + public var Settings_Logout: String { return self._s[602]! } + public func PUSH_MESSAGE_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[603]!, self._r[603]!, [_1]) } - public func Wallet_SecureStorageChanged_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[587]!, self._r[587]!, [_0]) + public var ContactInfo_PhoneLabelWork: String { return self._s[604]! } + public var ChannelInfo_Stats: String { return self._s[605]! } + public var TextFormat_Link: String { return self._s[606]! } + public func Date_ChatDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[607]!, self._r[607]!, [_1, _2]) } - public var Chat_GenericPsaTooltip: String { return self._s[588]! } - public func Message_ForwardedMessageShort(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[589]!, self._r[589]!, [_0]) + public var Paint_Framed: String { return self._s[608]! } + public var Wallet_TransactionInfo_Title: String { return self._s[609]! } + public func Message_ForwardedMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[610]!, self._r[610]!, [_0]) } - public var PrivacyLastSeenSettings_AlwaysShareWith_Placeholder: String { return self._s[590]! } - public var Login_PhoneAndCountryHelp: String { return self._s[591]! } - public var SaveIncomingPhotosSettings_From: String { return self._s[592]! } - public var Conversation_JumpToDate: String { return self._s[593]! } - public var AuthSessions_AddDevice: String { return self._s[594]! } - public var Settings_FAQ: String { return self._s[596]! } - public var Username_Title: String { return self._s[597]! } - public var DialogList_Read: String { return self._s[598]! } - public var Conversation_InstantPagePreview: String { return self._s[599]! } - public var Login_ResetAccountProtected_Title: String { return self._s[601]! } - public var CallFeedback_ReasonDistortedSpeech: String { return self._s[602]! } - public var Channel_EditAdmin_PermissionChangeInfo: String { return self._s[603]! } - public func Channel_AdminLog_MessageRankUsername(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[604]!, self._r[604]!, [_1, _2, _3]) + public var Watch_Notification_Joined: String { return self._s[611]! } + public var Group_Setup_TypePublicHelp: String { return self._s[612]! } + public var Passport_Scans_UploadNew: String { return self._s[613]! } + public var Checkout_LiabilityAlertTitle: String { return self._s[614]! } + public var DialogList_Title: String { return self._s[617]! } + public var NotificationSettings_ContactJoined: String { return self._s[618]! } + public var GroupInfo_LabelAdmin: String { return self._s[619]! } + public var KeyCommand_ChatInfo: String { return self._s[620]! } + public var Conversation_EditingCaptionPanelTitle: String { return self._s[621]! } + public var Call_ReportIncludeLog: String { return self._s[622]! } + public func Notifications_ExceptionsChangeSound(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[625]!, self._r[625]!, [_0]) } - public var WallpaperPreview_PreviewBottomText: String { return self._s[606]! } - public var Privacy_SecretChatsTitle: String { return self._s[609]! } - public func Notification_PassportValuesSentMessage(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[610]!, self._r[610]!, [_1, _2]) + public var Stats_Followers: String { return self._s[626]! } + public var Stats_GroupLanguagesTitle: String { return self._s[627]! } + public var Cache_NoLimit: String { return self._s[628]! } + public var Channel_AdminLog_InfoPanelChannelAlertText: String { return self._s[629]! } + public var ChatAdmins_AllMembersAreAdmins: String { return self._s[630]! } + public var LocalGroup_IrrelevantWarning: String { return self._s[631]! } + public var Conversation_DefaultRestrictedInline: String { return self._s[632]! } + public var Message_Sticker: String { return self._s[633]! } + public var LastSeen_JustNow: String { return self._s[635]! } + public var Passport_Email_EmailPlaceholder: String { return self._s[637]! } + public var SettingsSearch_Synonyms_AppLanguage: String { return self._s[638]! } + public var Channel_AdminLogFilter_EventsEditedMessages: String { return self._s[640]! } + public var Channel_EditAdmin_PermissionsHeader: String { return self._s[641]! } + public var TwoStepAuth_Email: String { return self._s[642]! } + public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsSound: String { return self._s[643]! } + public var PhotoEditor_BlurToolOff: String { return self._s[644]! } + public var Message_PinnedStickerMessage: String { return self._s[645]! } + public var ContactInfo_PhoneLabelPager: String { return self._s[646]! } + public var SettingsSearch_Synonyms_Appearance_TextSize: String { return self._s[647]! } + public var Passport_DiscardMessageTitle: String { return self._s[648]! } + public var Privacy_PaymentsTitle: String { return self._s[649]! } + public var EditTheme_Edit_Preview_IncomingReplyName: String { return self._s[650]! } + public var ClearCache_StorageCache: String { return self._s[651]! } + public var Cache_KeepMediaHelp: String { return self._s[652]! } + public var Appearance_TextSizeSetting: String { return self._s[653]! } + public var Channel_DiscussionGroup_Header: String { return self._s[655]! } + public var VoiceOver_Chat_OptionSelected: String { return self._s[656]! } + public var Appearance_ColorTheme: String { return self._s[657]! } + public var UserInfo_ShareContact: String { return self._s[658]! } + public var Passport_Address_TypePassportRegistration: String { return self._s[659]! } + public var Common_More: String { return self._s[660]! } + public var Watch_Message_Call: String { return self._s[661]! } + public var Profile_EncryptionKey: String { return self._s[664]! } + public var Privacy_TopPeers: String { return self._s[665]! } + public var Conversation_StopPollConfirmation: String { return self._s[666]! } + public var Wallet_Words_NotDoneText: String { return self._s[668]! } + public var Privacy_TopPeersWarning: String { return self._s[670]! } + public var SettingsSearch_Synonyms_Data_DownloadInBackground: String { return self._s[671]! } + public var SettingsSearch_Synonyms_Data_Storage_KeepMedia: String { return self._s[672]! } + public var Media_SendWithTimer: String { return self._s[675]! } + public var Wallet_RestoreFailed_EnterWords: String { return self._s[676]! } + public var DialogList_SearchSectionMessages: String { return self._s[677]! } + public var ChatList_Context_AddToFolder: String { return self._s[678]! } + public var Notifications_ChannelNotifications: String { return self._s[679]! } + public var CheckoutInfo_ShippingInfoAddress1Placeholder: String { return self._s[680]! } + public var Notification_MessageLifetime1h: String { return self._s[681]! } + public var Passport_Language_sk: String { return self._s[682]! } + public var Wallpaper_ResetWallpapersInfo: String { return self._s[683]! } + public var Appearance_ThemePreview_Chat_5_Text: String { return self._s[684]! } + public var PeerInfo_PaneGifs: String { return self._s[685]! } + public var Call_ReportSkip: String { return self._s[687]! } + public var Cache_ServiceFiles: String { return self._s[688]! } + public var Group_ErrorAddTooMuchAdmins: String { return self._s[689]! } + public var VoiceOver_Chat_YourFile: String { return self._s[690]! } + public var Map_Hybrid: String { return self._s[691]! } + public var Contacts_SearchUsersAndGroupsLabel: String { return self._s[693]! } + public func PUSH_MESSAGE_QUIZ(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[694]!, self._r[694]!, [_1]) } - public var Checkout_NewCard_SaveInfoHelp: String { return self._s[611]! } - public var Conversation_ClousStorageInfo_Description4: String { return self._s[612]! } - public var PasscodeSettings_TurnPasscodeOn: String { return self._s[613]! } - public var Message_ReplyActionButtonShowReceipt: String { return self._s[614]! } - public func PrivacyPolicy_AgeVerificationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[615]!, self._r[615]!, [_0]) + public var ChatSettings_AutoDownloadVideos: String { return self._s[696]! } + public var Channel_BanUser_PermissionEmbedLinks: String { return self._s[697]! } + public var InfoPlist_NSLocationAlwaysAndWhenInUseUsageDescription: String { return self._s[698]! } + public var SocksProxySetup_ProxyTelegram: String { return self._s[701]! } + public func PUSH_MESSAGE_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[702]!, self._r[702]!, [_1]) } - public var GroupInfo_DeleteAndExitConfirmation: String { return self._s[617]! } - public var TwoStepAuth_ConfirmationAbort: String { return self._s[618]! } - public var PrivacySettings_LastSeenEverybody: String { return self._s[619]! } - public var CallFeedback_ReasonDropped: String { return self._s[620]! } - public func ScheduledMessages_ScheduledDate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[621]!, self._r[621]!, [_0]) + public var Channel_Username_CreatePrivateLinkHelp: String { return self._s[704]! } + public var ScheduledMessages_ScheduledToday: String { return self._s[705]! } + public func PUSH_CHAT_TITLE_EDITED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[706]!, self._r[706]!, [_1, _2]) } - public var WebSearch_Images: String { return self._s[622]! } - public var Passport_Identity_Surname: String { return self._s[623]! } - public var Channel_Stickers_CreateYourOwn: String { return self._s[624]! } - public var TwoFactorSetup_Email_Title: String { return self._s[625]! } - public var Cache_ClearEmpty: String { return self._s[626]! } - public var AuthSessions_AddDeviceIntro_Action: String { return self._s[627]! } - public var Theme_Context_Apply: String { return self._s[628]! } - public var GroupInfo_Permissions_SearchPlaceholder: String { return self._s[629]! } - public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[630]! } - public var AutoDownloadSettings_DocumentsTitle: String { return self._s[631]! } - public func NetworkUsageSettings_CellularUsageSince(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[632]!, self._r[632]!, [_0]) + public var Conversation_LiveLocationYou: String { return self._s[707]! } + public var SettingsSearch_Synonyms_Privacy_Calls: String { return self._s[708]! } + public var SettingsSearch_Synonyms_Notifications_MessageNotificationsPreview: String { return self._s[709]! } + public var UserInfo_ShareBot: String { return self._s[712]! } + public func PUSH_AUTH_REGION(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[713]!, self._r[713]!, [_1, _2]) } - public var Call_StatusRinging: String { return self._s[633]! } + public var Conversation_ClearCache: String { return self._s[714]! } + public var PhotoEditor_ShadowsTint: String { return self._s[715]! } + public var ChatListFolderSettings_EditFoldersInfo: String { return self._s[716]! } + public var Message_Audio: String { return self._s[717]! } + public var Passport_Language_lt: String { return self._s[718]! } + public func Message_PinnedTextMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[719]!, self._r[719]!, [_0]) + } + public var Permissions_SiriText_v0: String { return self._s[720]! } + public var Conversation_FileICloudDrive: String { return self._s[721]! } + public var ChatList_DeleteForEveryoneConfirmationTitle: String { return self._s[722]! } + public var Notifications_Badge_IncludeMutedChats: String { return self._s[723]! } + public func Notification_NewAuthDetected(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String, _ _6: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[724]!, self._r[724]!, [_1, _2, _3, _4, _5, _6]) + } + public var DialogList_ProxyConnectionIssuesTooltip: String { return self._s[725]! } + public func Time_MonthOfYear_m5(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[726]!, self._r[726]!, [_0]) + } + public var Channel_SignMessages: String { return self._s[727]! } + public func PUSH_MESSAGE_NOTEXT(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[728]!, self._r[728]!, [_1]) + } + public var Compose_ChannelTokenListPlaceholder: String { return self._s[729]! } + public var Passport_ScanPassport: String { return self._s[730]! } + public var Watch_Suggestion_Thanks: String { return self._s[731]! } + public var BlockedUsers_AddNew: String { return self._s[732]! } + public func PUSH_CHAT_MESSAGE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[733]!, self._r[733]!, [_1, _2]) + } + public var Watch_Message_Invoice: String { return self._s[734]! } + public var SettingsSearch_Synonyms_Privacy_LastSeen: String { return self._s[735]! } + public var Month_GenJuly: String { return self._s[736]! } + public var CreatePoll_QuizInfo: String { return self._s[737]! } + public var UserInfo_StartSecretChatStart: String { return self._s[738]! } + public var SocksProxySetup_ProxySocks5: String { return self._s[739]! } + public var IntentsSettings_SuggestByShare: String { return self._s[741]! } + public var Notification_Exceptions_DeleteAllConfirmation: String { return self._s[742]! } + public var Notification_ChannelInviterSelf: String { return self._s[743]! } + public var CheckoutInfo_ReceiverInfoEmail: String { return self._s[744]! } + public func ApplyLanguage_ChangeLanguageUnofficialText(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[745]!, self._r[745]!, [_1, _2]) + } + public var Stats_FollowersTitle: String { return self._s[746]! } + public var CheckoutInfo_Title: String { return self._s[747]! } + public var Watch_Stickers_RecentPlaceholder: String { return self._s[748]! } public func Map_DistanceAway(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[634]!, self._r[634]!, [_0]) - } - public func DialogList_SingleTypingSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[635]!, self._r[635]!, [_0]) - } - public var Cache_ClearNone: String { return self._s[636]! } - public var Wallet_Receive_CopyAddress: String { return self._s[637]! } - public var PrivacyPolicy_Accept: String { return self._s[638]! } - public var Wallet_TransactionInfo_SenderHeader: String { return self._s[639]! } - public var Contacts_PhoneNumber: String { return self._s[640]! } - public var Passport_Identity_OneOfTypePassport: String { return self._s[641]! } - public var PhotoEditor_HighlightsTint: String { return self._s[643]! } - public var AutoDownloadSettings_AutodownloadVideos: String { return self._s[644]! } - public var Checkout_PaymentMethod_Title: String { return self._s[647]! } - public var Month_GenAugust: String { return self._s[649]! } - public var DialogList_Draft: String { return self._s[650]! } - public var ChatList_EmptyChatListFilterText: String { return self._s[651]! } - public var PeopleNearby_Description: String { return self._s[652]! } - public var WallpaperPreview_SwipeColorsBottomText: String { return self._s[653]! } - public var AppWallet_TransactionInfo_FeeInfoURL: String { return self._s[654]! } - public var SettingsSearch_Synonyms_Privacy_Data_TopPeers: String { return self._s[656]! } - public var Watch_Message_ForwardedFrom: String { return self._s[657]! } - public var Wallet_Words_NotDoneOk: String { return self._s[658]! } - public var Notification_Mute1h: String { return self._s[659]! } - public var Appearance_ThemePreview_Chat_3_TextWithLink: String { return self._s[660]! } - public var SettingsSearch_Synonyms_Privacy_AuthSessions: String { return self._s[662]! } - public var Channel_Edit_LinkItem: String { return self._s[663]! } - public var Presence_online: String { return self._s[664]! } - public var AutoDownloadSettings_Title: String { return self._s[665]! } - public var Conversation_MessageDialogRetry: String { return self._s[666]! } - public var SettingsSearch_Synonyms_ChatSettings_OpenLinksIn: String { return self._s[668]! } - public var Channel_About_Placeholder: String { return self._s[669]! } - public var Passport_Language_sl: String { return self._s[670]! } - public var AppleWatch_Title: String { return self._s[672]! } - public var Settings_ViewPhoto: String { return self._s[674]! } - public var ChatList_DeleteSavedMessagesConfirmation: String { return self._s[675]! } - public var Cache_ClearProgress: String { return self._s[676]! } - public var Cache_Music: String { return self._s[677]! } - public var Conversation_ContextMenuShare: String { return self._s[679]! } - public var AutoDownloadSettings_Unlimited: String { return self._s[680]! } - public var Channel_OwnershipTransfer_ErrorPrivacyRestricted: String { return self._s[681]! } - public var Contacts_PermissionsAllow: String { return self._s[682]! } - public var Passport_Language_vi: String { return self._s[684]! } - public func PUSH_MESSAGE_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[687]!, self._r[687]!, [_1, _2]) - } - public var Passport_Language_de: String { return self._s[688]! } - public var Notifications_PermissionsText: String { return self._s[690]! } - public var GroupRemoved_AddToGroup: String { return self._s[691]! } - public var Appearance_ThemePreview_ChatList_4_Text: String { return self._s[692]! } - public var ChangePhoneNumberCode_RequestingACall: String { return self._s[693]! } - public var Login_TermsOfServiceAgree: String { return self._s[694]! } - public var VoiceOver_Navigation_ProxySettings: String { return self._s[695]! } - public func PUSH_CHAT_JOINED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[696]!, self._r[696]!, [_1, _2]) - } - public var SettingsSearch_Synonyms_Data_CallsUseLessData: String { return self._s[698]! } - public var ChatListFolder_NameGroups: String { return self._s[699]! } - public var SocksProxySetup_ProxyDetailsTitle: String { return self._s[700]! } - public func Channel_AdminLog_MessageChangedLinkedGroup(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[701]!, self._r[701]!, [_1, _2]) - } - public var Watch_Suggestion_TalkLater: String { return self._s[702]! } - public var Checkout_ShippingOption_Title: String { return self._s[703]! } - public var CreatePoll_TextHeader: String { return self._s[704]! } - public var VoiceOver_Chat_Message: String { return self._s[706]! } - public var InfoPlist_NSLocationWhenInUseUsageDescription: String { return self._s[707]! } - public var ContactInfo_Note: String { return self._s[709]! } - public var Channel_AdminLog_InfoPanelAlertText: String { return self._s[710]! } - public var Wallet_Receive_AmountHeader: String { return self._s[711]! } - public var Checkout_NewCard_CardholderNameTitle: String { return self._s[712]! } - public var AutoDownloadSettings_Photos: String { return self._s[713]! } - public var UserInfo_NotificationsDefaultDisabled: String { return self._s[714]! } - public var Channel_Info_Subscribers: String { return self._s[715]! } - public var ChatList_DeleteForCurrentUser: String { return self._s[716]! } - public var ChatListFolderSettings_FoldersSection: String { return self._s[717]! } - public func Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[721]!, self._r[721]!, [_1, _2, _3]) - } - public var AutoNightTheme_System: String { return self._s[722]! } - public var Call_StatusWaiting: String { return self._s[723]! } - public var GroupInfo_GroupHistoryHidden: String { return self._s[724]! } - public func CHAT_MESSAGE_INVOICE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[725]!, self._r[725]!, [_1, _2, _3]) - } - public var Conversation_ContextMenuCopy: String { return self._s[727]! } - public var Notifications_MessageNotificationsPreview: String { return self._s[728]! } - public var Notifications_InAppNotificationsVibrate: String { return self._s[729]! } - public func Conversation_RestrictedTextTimed(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[730]!, self._r[730]!, [_0]) - } - public var Group_Status: String { return self._s[732]! } - public var Group_Setup_HistoryVisible: String { return self._s[733]! } - public var Conversation_DiscardVoiceMessageAction: String { return self._s[734]! } - public var Paint_Edit: String { return self._s[735]! } - public var Channel_EditAdmin_CannotEdit: String { return self._s[737]! } - public var Username_InvalidTooShort: String { return self._s[738]! } - public var ClearCache_StorageOtherApps: String { return self._s[739]! } - public var Wallet_Configuration_SourceJSON: String { return self._s[740]! } - public var Conversation_ViewMessage: String { return self._s[741]! } - public var GroupInfo_PublicLinkAdd: String { return self._s[743]! } - public func Notification_RemovedGroupPhoto(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[744]!, self._r[744]!, [_0]) - } - public var CallSettings_Title: String { return self._s[745]! } - public func Conversation_BotInteractiveUrlAlert(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[746]!, self._r[746]!, [_0]) - } - public func VoiceOver_Chat_ContactFrom(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[749]!, self._r[749]!, [_0]) } - public var PUSH_SENDER_YOU: String { return self._s[752]! } - public var Profile_ShareContactButton: String { return self._s[753]! } - public var GroupInfo_Permissions_SectionTitle: String { return self._s[754]! } - public var Map_ShareLiveLocation: String { return self._s[755]! } - public var ChatListFolder_TitleEdit: String { return self._s[756]! } - public var Wallet_Send_ErrorInvalidAddress: String { return self._s[757]! } - public var Passport_Address_Address: String { return self._s[759]! } - public var LastSeen_JustNow: String { return self._s[761]! } - public func SecretImage_NotViewedYet(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[762]!, self._r[762]!, [_0]) - } - public var ContactInfo_PhoneLabelOther: String { return self._s[763]! } - public var PasscodeSettings_DoNotMatch: String { return self._s[764]! } - public var Weekday_Today: String { return self._s[767]! } - public var DialogList_Title: String { return self._s[768]! } - public var SettingsSearch_Synonyms_Notifications_MessageNotificationsPreview: String { return self._s[769]! } - public var Cache_ClearCache: String { return self._s[770]! } - public var CreatePoll_ExplanationInfo: String { return self._s[771]! } - public var Notifications_ResetAllNotificationsHelp: String { return self._s[773]! } - public var Stats_MessageTitle: String { return self._s[774]! } - public var Passport_Address_Street: String { return self._s[776]! } - public var Wallet_Receive_ShareUrlInfo: String { return self._s[777]! } - public func Channel_AdminLog_MessageRemovedGroupUsername(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[778]!, self._r[778]!, [_0]) - } - public var Channel_AdminLog_ChannelEmptyText: String { return self._s[779]! } - public func Login_PhoneGenericEmailSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[780]!, self._r[780]!, [_0]) - } - public var TwoStepAuth_Email: String { return self._s[782]! } - public var Wallet_Words_Text: String { return self._s[783]! } - public var Conversation_SecretLinkPreviewAlert: String { return self._s[784]! } - public var PrivacySettings_PasscodeOn: String { return self._s[785]! } - public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[787]! } - public var Wallet_Send_AddressInfo: String { return self._s[788]! } - public var Camera_SquareMode: String { return self._s[789]! } - public var Wallet_Month_ShortJuly: String { return self._s[790]! } - public var SocksProxySetup_Port: String { return self._s[791]! } - public var Watch_LastSeen_JustNow: String { return self._s[793]! } - public func PUSH_MESSAGE_GAME(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[794]!, self._r[794]!, [_1, _2]) - } - public func Watch_LastSeen_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[795]!, self._r[795]!, [_0]) - } - public var EditTheme_Expand_Preview_OutgoingText: String { return self._s[796]! } - public var Channel_AdminLogFilter_EventsTitle: String { return self._s[797]! } - public var Wallet_AccessDenied_Settings: String { return self._s[799]! } - public var Watch_Suggestion_HoldOn: String { return self._s[801]! } - public func PUSH_CHANNEL_MESSAGE_GEOLIVE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[802]!, self._r[802]!, [_1]) - } - public var CallSettings_TabIcon: String { return self._s[803]! } - public var ScheduledMessages_SendNow: String { return self._s[804]! } - public var Stats_GroupTopWeekdaysTitle: String { return self._s[805]! } - public var Wallet_WordImport_IncorrectTitle: String { return self._s[806]! } - public var UserInfo_PhoneCall: String { return self._s[807]! } - public var Month_GenMarch: String { return self._s[808]! } - public var Camera_Discard: String { return self._s[809]! } - public var InfoPlist_NSFaceIDUsageDescription: String { return self._s[810]! } - public var Passport_RequestedInformation: String { return self._s[811]! } - public var Passport_Language_ro: String { return self._s[813]! } - public func PUSH_CHAT_MESSAGE_DOC(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[814]!, self._r[814]!, [_1, _2]) - } - public var AutoDownloadSettings_ResetHelp: String { return self._s[815]! } - public var Passport_Identity_DocumentDetails: String { return self._s[817]! } - public var Passport_Address_ScansHelp: String { return self._s[818]! } - public var ClearCache_StorageCache: String { return self._s[819]! } - public var Theme_Colors_ColorWallpaperWarningProceed: String { return self._s[820]! } - public var Conversation_RestrictedText: String { return self._s[821]! } - public var Notifications_MessageNotifications: String { return self._s[823]! } - public var Passport_Scans: String { return self._s[824]! } - public var TwoStepAuth_SetupHintTitle: String { return self._s[826]! } - public var LogoutOptions_ContactSupportTitle: String { return self._s[827]! } - public var Passport_Identity_SelfieHelp: String { return self._s[828]! } - public var Permissions_NotificationsUnreachableText_v0: String { return self._s[829]! } - public var Privacy_PaymentsClear_PaymentInfo: String { return self._s[830]! } - public var ShareMenu_CopyShareLinkGame: String { return self._s[831]! } - public var PeerInfo_ButtonSearch: String { return self._s[832]! } - public var SettingsSearch_Synonyms_Privacy_Data_ClearPaymentsInfo: String { return self._s[835]! } - public var Passport_FieldIdentityTranslationHelp: String { return self._s[837]! } - public var Conversation_InputTextSilentBroadcastPlaceholder: String { return self._s[838]! } - public var Month_GenSeptember: String { return self._s[839]! } - public func Call_GroupFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[841]!, self._r[841]!, [_1, _2]) - } - public var StickerPacksSettings_ArchivedPacks: String { return self._s[842]! } - public func Channel_Username_LinkHint(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[844]!, self._r[844]!, [_0]) - } - public func PUSH_PINNED_CONTACT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[846]!, self._r[846]!, [_1, _2]) - } - public var LogoutOptions_LogOutWalletInfo: String { return self._s[847]! } - public func PUSH_MESSAGE_VIDEOS(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[848]!, self._r[848]!, [_1, _2]) - } - public var Calls_NotNow: String { return self._s[850]! } - public var Wallet_Completed_Text: String { return self._s[853]! } - public var Settings_ChatFolders: String { return self._s[855]! } - public var Login_PadPhoneHelpTitle: String { return self._s[856]! } - public var TwoStepAuth_EnterPasswordInvalid: String { return self._s[857]! } - public var Settings_ChatBackground: String { return self._s[858]! } - public func PUSH_CHAT_MESSAGE_CONTACT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[860]!, self._r[860]!, [_1, _2]) - } - public var ProxyServer_VoiceOver_Active: String { return self._s[861]! } - public var Call_StatusBusy: String { return self._s[862]! } - public var Conversation_MessageDeliveryFailed: String { return self._s[863]! } - public var Login_NetworkError: String { return self._s[865]! } - public var TwoStepAuth_SetupPasswordDescription: String { return self._s[866]! } - public var Privacy_Calls_Integration: String { return self._s[867]! } - public var DialogList_SearchSectionMessages: String { return self._s[868]! } - public var AutoDownloadSettings_VideosTitle: String { return self._s[869]! } - public var Preview_DeletePhoto: String { return self._s[870]! } - public var PrivacySettings_PhoneNumber: String { return self._s[872]! } - public var Forward_ErrorDisabledForChat: String { return self._s[873]! } - public var Watch_Compose_CurrentLocation: String { return self._s[874]! } - public var Wallet_Info_TransactionFrom: String { return self._s[875]! } - public var Settings_CallSettings: String { return self._s[876]! } - public var AutoDownloadSettings_TypePrivateChats: String { return self._s[877]! } - public var ChatList_Context_MarkAllAsRead: String { return self._s[878]! } - public var ChatSettings_AutoPlayAnimations: String { return self._s[879]! } - public var SaveIncomingPhotosSettings_Title: String { return self._s[880]! } - public var OwnershipTransfer_SecurityRequirements: String { return self._s[881]! } - public var Map_LiveLocationFor1Hour: String { return self._s[882]! } - public func Privacy_GroupsAndChannels_InviteToGroupError(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[883]!, self._r[883]!, [_0, _1]) - } - public func Notification_PinnedLiveLocationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[884]!, self._r[884]!, [_0]) - } - public var Conversation_UnvotePoll: String { return self._s[885]! } - public var TwoStepAuth_EnterEmailCode: String { return self._s[886]! } - public func LOCAL_MESSAGE_FWDS(_ _1: String, _ _2: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[887]!, self._r[887]!, [_1, "\(_2)"]) - } - public var Passport_InfoTitle: String { return self._s[888]! } - public func Conversation_Bytes(_ _0: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[889]!, self._r[889]!, ["\(_0)"]) - } - public var AccentColor_Title: String { return self._s[890]! } - public func PUSH_MESSAGE_INVOICE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[891]!, self._r[891]!, [_1, _2]) - } - public func Notification_JoinedChannel(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[894]!, self._r[894]!, [_0]) - } - public var AutoDownloadSettings_DataUsageCustom: String { return self._s[895]! } - public var Conversation_ShareBotLocationConfirmation: String { return self._s[896]! } - public var PrivacyPhoneNumberSettings_WhoCanSeeMyPhoneNumber: String { return self._s[897]! } - public var VoiceOver_Editing_ClearText: String { return self._s[898]! } - public var Conversation_Unarchive: String { return self._s[899]! } - public var Notification_CallOutgoing: String { return self._s[900]! } - public var Channel_Setup_PublicNoLink: String { return self._s[901]! } - public var Passport_Identity_GenderPlaceholder: String { return self._s[902]! } - public var Message_Animation: String { return self._s[903]! } - public var SettingsSearch_Synonyms_Appearance_Animations: String { return self._s[904]! } - public var ChatSettings_ConnectionType_Title: String { return self._s[905]! } - public func Watch_Time_ShortFullAt(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[906]!, self._r[906]!, [_1, _2]) - } - public var Notification_CallBack: String { return self._s[908]! } - public var Appearance_Title: String { return self._s[910]! } - public var NotificationsSound_Glass: String { return self._s[912]! } - public var AutoDownloadSettings_CellularTitle: String { return self._s[914]! } - public var Notifications_PermissionsSuppressWarningTitle: String { return self._s[916]! } - public var ChatSearch_SearchPlaceholder: String { return self._s[917]! } - public var Passport_Identity_AddPassport: String { return self._s[918]! } - public func Wallet_Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[920]!, self._r[920]!, [_1, _2, _3]) - } - public var GroupPermission_NoAddMembers: String { return self._s[921]! } - public var ContactList_Context_SendMessage: String { return self._s[922]! } - public var PhotoEditor_GrainTool: String { return self._s[923]! } - public var Settings_CopyPhoneNumber: String { return self._s[924]! } - public var Passport_Address_City: String { return self._s[925]! } - public var ChannelRemoved_RemoveInfo: String { return self._s[926]! } - public var SocksProxySetup_Password: String { return self._s[928]! } - public var Wallet_Configuration_ApplyErrorTextJSONInvalidData: String { return self._s[929]! } - public var Settings_Passport: String { return self._s[930]! } - public var Channel_MessagePhotoUpdated: String { return self._s[932]! } - public var Stats_LanguagesTitle: String { return self._s[933]! } - public var ChatList_PeerTypeGroup: String { return self._s[934]! } - public var Privacy_Calls_P2PHelp: String { return self._s[935]! } - public var VoiceOver_Chat_PollNoVotes: String { return self._s[936]! } - public var Embed_PlayingInPIP: String { return self._s[937]! } - public var BlockedUsers_BlockUser: String { return self._s[939]! } - public var Login_CancelPhoneVerificationContinue: String { return self._s[940]! } - public func PUSH_CHANNEL_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[941]!, self._r[941]!, [_1]) - } - public var AuthSessions_LoggedIn: String { return self._s[942]! } - public var Channel_AdminLog_MessagePreviousCaption: String { return self._s[943]! } - public var Wallet_SecureStorageReset_PasscodeText: String { return self._s[944]! } - public var Activity_UploadingDocument: String { return self._s[945]! } - public var PeopleNearby_NoMembers: String { return self._s[946]! } - public var SettingsSearch_Synonyms_Stickers_Masks: String { return self._s[949]! } - public var ChatSettings_AutoPlayVideos: String { return self._s[950]! } - public var VoiceOver_Chat_OpenLinkHint: String { return self._s[951]! } - public var Settings_ViewVideo: String { return self._s[953]! } - public var Map_ShowPlaces: String { return self._s[955]! } - public var Passport_Phone_UseTelegramNumberHelp: String { return self._s[956]! } - public var SettingsSearch_Synonyms_Appearance_ChatBackground_Custom: String { return self._s[957]! } - public func PrivacySettings_LastSeenContactsPlus(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[958]!, self._r[958]!, [_0]) - } - public var Wallet_Month_ShortNovember: String { return self._s[959]! } - public var Conversation_StatusLeftGroup: String { return self._s[960]! } - public var Theme_Colors_Messages: String { return self._s[961]! } - public var AuthSessions_EmptyText: String { return self._s[962]! } - public func PUSH_MESSAGE_CONTACT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[963]!, self._r[963]!, [_1]) - } - public var UserInfo_StartSecretChat: String { return self._s[964]! } - public var ChatListFolderSettings_EditFoldersInfo: String { return self._s[965]! } - public var Channel_Edit_PrivatePublicLinkAlert: String { return self._s[966]! } - public var Conversation_ReportSpamGroupConfirmation: String { return self._s[967]! } - public var Conversation_PrivateMessageLinkCopied: String { return self._s[969]! } - public var PeerInfo_PaneFiles: String { return self._s[970]! } - public var PrivacySettings_AutoArchive: String { return self._s[971]! } - public var Camera_VideoMode: String { return self._s[972]! } - public var NotificationsSound_Alert: String { return self._s[973]! } - public var Privacy_Forwards_NeverAllow_Title: String { return self._s[974]! } - public var Appearance_AutoNightTheme: String { return self._s[975]! } - public var Passport_Language_he: String { return self._s[976]! } - public var Wallet_Configuration_BlockchainNameChangedText: String { return self._s[977]! } - public var Passport_InvalidPasswordError: String { return self._s[978]! } - public var Conversation_PinMessageAlert_OnlyPin: String { return self._s[979]! } - public var UserInfo_InviteBotToGroup: String { return self._s[980]! } - public var Conversation_SilentBroadcastTooltipOff: String { return self._s[981]! } - public var Common_TakePhoto: String { return self._s[982]! } - public var Passport_Email_UseTelegramEmailHelp: String { return self._s[983]! } - public var Wallet_Configuration_ApplyErrorTextURLInvalid: String { return self._s[984]! } - public var ChatList_Context_JoinChannel: String { return self._s[985]! } - public var MediaPlayer_UnknownArtist: String { return self._s[986]! } - public var KeyCommand_JumpToPreviousUnreadChat: String { return self._s[989]! } - public var Channel_OwnershipTransfer_Title: String { return self._s[990]! } - public var EditTheme_UploadEditedTheme: String { return self._s[991]! } - public var Settings_SetProfilePhotoOrVideo: String { return self._s[993]! } - public var Passport_FieldOneOf_Delimeter: String { return self._s[994]! } - public var MessagePoll_ViewResults: String { return self._s[995]! } - public var Group_Setup_TypePrivateHelp: String { return self._s[996]! } - public var Passport_Address_OneOfTypeUtilityBill: String { return self._s[997]! } - public var ChatList_Search_ShowLess: String { return self._s[998]! } - public var UserInfo_ShareBot: String { return self._s[999]! } - public var Privacy_Calls_P2P: String { return self._s[1001]! } - public var WebBrowser_InAppSafari: String { return self._s[1002]! } - public var SharedMedia_EmptyFilesText: String { return self._s[1003]! } - public var Channel_AdminLog_MessagePreviousMessage: String { return self._s[1005]! } - public var GroupInfo_SetSound: String { return self._s[1006]! } - public var Permissions_PeopleNearbyAllowInSettings_v0: String { return self._s[1007]! } - public var Channel_AdminLog_MessagePreviousDescription: String { return self._s[1008]! } - public var Channel_AdminLogFilter_EventsAll: String { return self._s[1009]! } - public var CallSettings_UseLessData: String { return self._s[1010]! } - public var InfoPlist_NSCameraUsageDescription: String { return self._s[1011]! } - public var NotificationsSound_Chord: String { return self._s[1012]! } - public var PhotoEditor_CurvesTool: String { return self._s[1013]! } - public var Appearance_ThemePreview_Chat_2_Text: String { return self._s[1014]! } - public var Resolve_ErrorNotFound: String { return self._s[1015]! } - public var Activity_PlayingGame: String { return self._s[1016]! } - public var Wallet_Send_UninitializedText: String { return self._s[1019]! } - public var StickerPacksSettings_AnimatedStickersInfo: String { return self._s[1020]! } - public func PUSH_CHANNEL_MESSAGE_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1021]!, self._r[1021]!, [_1]) - } - public var Conversation_ShareBotContactConfirmationTitle: String { return self._s[1022]! } - public var Notification_CallIncoming: String { return self._s[1023]! } - public var Stats_EnabledNotifications: String { return self._s[1024]! } - public var Notifications_PermissionsOpenSettings: String { return self._s[1025]! } - public var Checkout_ErrorProviderAccountTimeout: String { return self._s[1026]! } - public func Activity_RemindAboutChannel(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1027]!, self._r[1027]!, [_0]) - } - public var VoiceOver_Chat_ReplyToYourMessage: String { return self._s[1028]! } - public var Channel_DiscussionGroup_MakeHistoryPublic: String { return self._s[1029]! } - public var StickerPacksSettings_Title: String { return self._s[1030]! } - public func Channel_AdminLog_MessageGroupPreHistoryVisible(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1031]!, self._r[1031]!, [_0]) - } - public var Watch_NoConnection: String { return self._s[1032]! } - public var EncryptionKey_Title: String { return self._s[1033]! } - public var Widget_AuthRequired: String { return self._s[1034]! } - public func PUSH_MESSAGE_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1035]!, self._r[1035]!, [_1]) - } - public var Notifications_ExceptionsTitle: String { return self._s[1036]! } - public var EditTheme_Expand_TopInfo: String { return self._s[1037]! } - public func Contacts_AddPhoneNumber(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1038]!, self._r[1038]!, [_0]) - } - public var Channel_AdminLogFilter_EventsRestrictions: String { return self._s[1040]! } - public var Notifications_GroupNotificationsSound: String { return self._s[1041]! } - public var Passport_Email_EnterOtherEmail: String { return self._s[1042]! } - public var Conversation_AddToContacts: String { return self._s[1045]! } - public var AutoDownloadSettings_DataUsageMedium: String { return self._s[1046]! } - public var AuthSessions_LogOutApplications: String { return self._s[1048]! } - public var ChatList_Context_Unpin: String { return self._s[1049]! } - public var PeopleNearby_DiscoverDescription: String { return self._s[1050]! } - public var Notification_MessageLifetime1d: String { return self._s[1051]! } - public var PrivacyLastSeenSettings_NeverShareWith_Title: String { return self._s[1052]! } - public var ChatListFolder_CategoryChannels: String { return self._s[1053]! } - public var VoiceOver_Chat_SeenByRecipient: String { return self._s[1054]! } - public var Notifications_PermissionsAllow: String { return self._s[1055]! } - public var Undo_ScheduledMessagesCleared: String { return self._s[1056]! } - public var AutoDownloadSettings_PrivateChats: String { return self._s[1058]! } - public var ApplyLanguage_ChangeLanguageAction: String { return self._s[1059]! } - public func PrivacySettings_LastSeenNobodyPlus(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1060]!, self._r[1060]!, [_0]) - } - public var Wallet_WordImport_Title: String { return self._s[1061]! } - public var Notifications_MessageNotificationsHelp: String { return self._s[1064]! } - public var WallpaperSearch_ColorPink: String { return self._s[1065]! } - public var ContactInfo_PhoneNumberHidden: String { return self._s[1066]! } - public var Passport_Identity_IssueDate: String { return self._s[1068]! } - public func PUSH_CHAT_MESSAGE_GIF(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1069]!, self._r[1069]!, [_1, _2]) - } - public var PrivacySettings_DeleteAccountIfAwayFor: String { return self._s[1070]! } - public var Channel_Info_Description: String { return self._s[1071]! } - public var Common_Back: String { return self._s[1072]! } - public var Weekday_ShortTuesday: String { return self._s[1073]! } - public var ChatListFolder_AddChats: String { return self._s[1075]! } - public var Common_Close: String { return self._s[1077]! } - public var Map_OpenIn: String { return self._s[1078]! } - public var Group_Setup_HistoryTitle: String { return self._s[1079]! } - public func Wallet_Info_TransactionDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1080]!, self._r[1080]!, [_1, _2, _3]) - } - public var SettingsSearch_Synonyms_Data_AutoDownloadUsingWifi: String { return self._s[1081]! } - public var Notification_MessageLifetime1h: String { return self._s[1082]! } - public func CancelResetAccount_Success(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1083]!, self._r[1083]!, [_0]) - } - public var Watch_Contacts_NoResults: String { return self._s[1085]! } - public var TwoStepAuth_SetupResendEmailCode: String { return self._s[1086]! } - public var Checkout_Phone: String { return self._s[1087]! } - public var OwnershipTransfer_ComeBackLater: String { return self._s[1088]! } - public func DialogList_MultipleTypingSuffix(_ _0: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1089]!, self._r[1089]!, ["\(_0)"]) - } - public var ChatAdmins_Title: String { return self._s[1090]! } - public var Appearance_ThemePreview_Chat_7_Text: String { return self._s[1091]! } - public func PUSH_CHANNEL_MESSAGE_POLL(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1092]!, self._r[1092]!, [_1]) - } - public var Common_Done: String { return self._s[1093]! } - public var Wallet_Send_AddressHeader: String { return self._s[1096]! } - public func PUSH_PINNED_VIDEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1098]!, self._r[1098]!, [_1]) - } - public var Appearance_ThemeCarouselNight: String { return self._s[1099]! } - public var Preview_OpenInInstagram: String { return self._s[1101]! } - public var Wallpaper_SetColor: String { return self._s[1105]! } - public var VoiceOver_Media_PlaybackRate: String { return self._s[1106]! } - public var ChatSettings_Groups: String { return self._s[1107]! } - public func VoiceOver_Chat_VoiceMessageFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1108]!, self._r[1108]!, [_0]) - } - public var Contacts_SortedByName: String { return self._s[1109]! } - public var SettingsSearch_Synonyms_Notifications_ContactJoined: String { return self._s[1110]! } - public var Wallet_Send_Title: String { return self._s[1111]! } - public var Channel_Management_LabelCreator: String { return self._s[1112]! } - public var Contacts_PermissionsSuppressWarningTitle: String { return self._s[1113]! } - public func PrivacySettings_LastSeenContactsMinusPlus(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1114]!, self._r[1114]!, [_0, _1]) - } - public var Group_PublicLink_Title: String { return self._s[1115]! } - public var Channel_OwnershipTransfer_ErrorAdminsTooMuch: String { return self._s[1116]! } - public var VoiceOver_Chat_Photo: String { return self._s[1117]! } - public var TwoFactorSetup_EmailVerification_Placeholder: String { return self._s[1118]! } - public var IntentsSettings_SuggestBy: String { return self._s[1119]! } - public var Privacy_Calls_AlwaysAllow_Placeholder: String { return self._s[1120]! } - public var Appearance_ThemePreview_ChatList_1_Name: String { return self._s[1121]! } - public var PhoneNumberHelp_ChangeNumber: String { return self._s[1122]! } - public var LogoutOptions_SetPasscodeText: String { return self._s[1123]! } - public var Map_OpenInMaps: String { return self._s[1124]! } - public var ContactInfo_PhoneLabelWorkFax: String { return self._s[1125]! } - public var BlockedUsers_Unblock: String { return self._s[1126]! } - public func Settings_ApplyProxyAlert(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1127]!, self._r[1127]!, [_1, _2]) - } - public func Channel_AdminLog_MessageRestrictedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1128]!, self._r[1128]!, [_1, _2]) - } - public var Conversation_Block: String { return self._s[1130]! } - public var Passport_Scans_UploadNew: String { return self._s[1131]! } - public var Share_Title: String { return self._s[1132]! } - public var Wallet_Send_SendAnyway: String { return self._s[1133]! } - public func Wallet_Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1134]!, self._r[1134]!, [_1, _2, _3]) - } - public var Conversation_ApplyLocalization: String { return self._s[1135]! } - public var SharedMedia_EmptyLinksText: String { return self._s[1136]! } - public var Settings_NotificationsAndSounds: String { return self._s[1137]! } - public var Stats_ViewsByHoursTitle: String { return self._s[1138]! } - public var PhotoEditor_QualityMedium: String { return self._s[1139]! } - public var Conversation_ContextMenuCancelSending: String { return self._s[1140]! } - public func PUSH_CHANNEL_MESSAGE_GAME(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1141]!, self._r[1141]!, [_1, _2]) - } - public var Conversation_RestrictedInline: String { return self._s[1142]! } - public var Passport_Language_tr: String { return self._s[1143]! } - public var Call_Mute: String { return self._s[1144]! } - public func Conversation_NoticeInvitedByInGroup(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1145]!, self._r[1145]!, [_0]) - } - public var Passport_Language_bn: String { return self._s[1146]! } - public var AccessDenied_LocationTracking: String { return self._s[1148]! } - public var Month_ShortOctober: String { return self._s[1149]! } - public var AutoDownloadSettings_WiFi: String { return self._s[1150]! } - public var ProfilePhoto_SetMainPhoto: String { return self._s[1152]! } - public var ChangePhoneNumberNumber_NewNumber: String { return self._s[1153]! } - public func Time_MonthOfYear_m3(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1154]!, self._r[1154]!, [_0]) - } - public var Watch_ChannelInfo_Title: String { return self._s[1155]! } - public var State_Updating: String { return self._s[1156]! } - public var Conversation_UnblockUser: String { return self._s[1157]! } - public var Notifications_ChannelNotificationsSound: String { return self._s[1158]! } - public var Map_GetDirections: String { return self._s[1159]! } - public var Watch_Compose_AddContact: String { return self._s[1161]! } - public var Conversation_Dice_u26BD: String { return self._s[1162]! } - public var AccessDenied_PhotosRestricted: String { return self._s[1163]! } - public func Channel_AdminLog_MessageRank(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1164]!, self._r[1164]!, [_1]) - } - public var Wallet_UnknownError: String { return self._s[1166]! } - public var Map_LoadError: String { return self._s[1167]! } - public var SettingsSearch_Synonyms_Privacy_Calls: String { return self._s[1168]! } - public var PhotoEditor_CropAuto: String { return self._s[1169]! } - public var Wallet_Month_ShortApril: String { return self._s[1172]! } - public func Target_ShareGameConfirmationPrivate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1173]!, self._r[1173]!, [_0]) - } - public func PUSH_PINNED_GAME(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1175]!, self._r[1175]!, [_1]) - } - public var Username_TooManyPublicUsernamesError: String { return self._s[1176]! } - public var Settings_PhoneNumber: String { return self._s[1177]! } - public func Channel_AdminLog_MessageTransferedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1178]!, self._r[1178]!, [_1]) - } - public var Month_GenJune: String { return self._s[1180]! } - public var Notifications_ExceptionsGroupPlaceholder: String { return self._s[1181]! } - public var ChatListFolder_CategoryRead: String { return self._s[1182]! } - public var LoginPassword_ResetAccount: String { return self._s[1183]! } - public func DialogList_SingleUploadingFileSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1184]!, self._r[1184]!, [_0]) - } - public var Call_CameraConfirmationConfirm: String { return self._s[1185]! } - public var Notification_RenamedChannel: String { return self._s[1186]! } - public func Channel_AdminLog_MessageUnpinned(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1187]!, self._r[1187]!, [_0]) - } - public var Channel_AdminLogFilter_EventsAdmins: String { return self._s[1188]! } - public var IntentsSettings_Title: String { return self._s[1190]! } - public var Settings_AppleWatch: String { return self._s[1191]! } - public var DialogList_NoMessagesText: String { return self._s[1192]! } - public var GroupPermission_NoChangeInfo: String { return self._s[1193]! } - public var Channel_ErrorAccessDenied: String { return self._s[1195]! } - public var ScheduledMessages_EmptyPlaceholder: String { return self._s[1196]! } - public func Message_StickerText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1197]!, self._r[1197]!, [_0]) - } - public var AuthSessions_TerminateOtherSessionsHelp: String { return self._s[1198]! } - public var StickerPacksSettings_AnimatedStickers: String { return self._s[1199]! } - public var Month_ShortJanuary: String { return self._s[1200]! } - public var Conversation_UnreadMessages: String { return self._s[1201]! } - public var Conversation_PrivateChannelTooltip: String { return self._s[1203]! } - public var PrivacySettings_DeleteAccountTitle: String { return self._s[1205]! } - public var Channel_Members_AddBannedErrorAdmin: String { return self._s[1206]! } - public func Conversation_ShareMyPhoneNumberConfirmation(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1209]!, self._r[1209]!, [_1, _2]) - } - public var Widget_ApplicationLocked: String { return self._s[1210]! } - public func TextFormat_AddLinkText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1211]!, self._r[1211]!, [_0]) - } - public var Common_TakePhotoOrVideo: String { return self._s[1212]! } - public var Passport_Language_ru: String { return self._s[1213]! } - public var MediaPicker_VideoMuteDescription: String { return self._s[1214]! } - public var EditTheme_ErrorLinkTaken: String { return self._s[1215]! } - public func Group_EditAdmin_RankInfo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1217]!, self._r[1217]!, [_0]) - } - public var Channel_Members_AddAdminErrorBlacklisted: String { return self._s[1218]! } - public var Conversation_Owner: String { return self._s[1220]! } - public var Settings_FAQ_Intro: String { return self._s[1221]! } - public var PhotoEditor_QualityLow: String { return self._s[1223]! } - public var Call_End: String { return self._s[1224]! } - public var StickerPacksSettings_FeaturedPacks: String { return self._s[1226]! } - public var Privacy_ContactsSyncHelp: String { return self._s[1227]! } - public var OldChannels_NoticeUpgradeText: String { return self._s[1231]! } - public var Conversation_Call: String { return self._s[1232]! } - public var Watch_MessageView_Title: String { return self._s[1233]! } - public func Notification_RenamedChat(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1234]!, self._r[1234]!, [_0]) - } - public var Passport_PasswordCompleteSetup: String { return self._s[1235]! } - public func Notification_ChangedGroupVideo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1236]!, self._r[1236]!, [_0]) - } - public func TwoFactorSetup_EmailVerification_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1238]!, self._r[1238]!, [_0]) - } - public var Map_Location: String { return self._s[1239]! } - public var Watch_MessageView_ViewOnPhone: String { return self._s[1240]! } - public var Login_CountryCode: String { return self._s[1241]! } - public var Wallet_Settings_ConfigurationInfo: String { return self._s[1242]! } - public var Channel_DiscussionGroup_PrivateGroup: String { return self._s[1243]! } - public var ChatState_ConnectingToProxy: String { return self._s[1244]! } - public var Login_CallRequestState3: String { return self._s[1245]! } - public var NetworkUsageSettings_MediaAudioDataSection: String { return self._s[1247]! } - public var SocksProxySetup_ProxyStatusConnecting: String { return self._s[1248]! } - public var PrivacyLastSeenSettings_NeverShareWith_Placeholder: String { return self._s[1251]! } - public var Call_StatusEnded: String { return self._s[1252]! } - public var MusicPlayer_VoiceNote: String { return self._s[1255]! } - public func PUSH_CHANNEL_MESSAGE_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1256]!, self._r[1256]!, [_1, _2]) - } - public var VoiceOver_MessageContextShare: String { return self._s[1257]! } - public var ProfilePhoto_SearchWeb: String { return self._s[1258]! } - public var EditProfile_Title: String { return self._s[1259]! } - public func Notification_PinnedQuizMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1260]!, self._r[1260]!, [_0]) - } - public var ChangePhoneNumberCode_CodePlaceholder: String { return self._s[1261]! } - public var NetworkUsageSettings_ResetStats: String { return self._s[1263]! } - public var Wallet_Qr_ScanCode: String { return self._s[1264]! } - public var NetworkUsageSettings_GeneralDataSection: String { return self._s[1265]! } - public var StickerPackActionInfo_AddedTitle: String { return self._s[1266]! } - public var Channel_BanUser_PermissionSendStickersAndGifs: String { return self._s[1267]! } - public func Call_ParticipantVideoVersionOutdatedError(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1268]!, self._r[1268]!, [_0]) - } - public var AuthSessions_AddDeviceIntro_Text1: String { return self._s[1270]! } - public var Passport_Identity_LatinNameHelp: String { return self._s[1272]! } - public var AuthSessions_AddDeviceIntro_Text2: String { return self._s[1273]! } - public var Stats_GroupMembersTitle: String { return self._s[1274]! } - public var AuthSessions_AddDeviceIntro_Text3: String { return self._s[1275]! } - public var Contacts_PermissionsSuppressWarningText: String { return self._s[1276]! } - public var Wallet_Info_Address: String { return self._s[1277]! } - public var Settings_SetUsername: String { return self._s[1278]! } - public var GroupInfo_ActionRestrict: String { return self._s[1279]! } - public func Wallet_Configuration_ApplyErrorTextURLUnreachable(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1280]!, self._r[1280]!, [_0]) - } - public var SettingsSearch_Synonyms_SavedMessages: String { return self._s[1281]! } - public func Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1282]!, self._r[1282]!, [_1, _2, _3]) - } - public var Notifications_DisplayNamesOnLockScreenInfoWithLink: String { return self._s[1283]! } - public var Notification_Exceptions_AlwaysOff: String { return self._s[1284]! } - public var Conversation_ContextMenuDelete: String { return self._s[1285]! } - public var Privacy_Calls_WhoCanCallMe: String { return self._s[1286]! } - public var ChatList_PsaAlert_covid: String { return self._s[1289]! } - public var DialogList_Pin: String { return self._s[1290]! } - public var PrivacySettings_SecurityTitle: String { return self._s[1291]! } - public var GroupPermission_NotAvailableInPublicGroups: String { return self._s[1292]! } - public var PeopleNearby_Groups: String { return self._s[1293]! } - public var Message_File: String { return self._s[1294]! } - public var Calls_NoCallsPlaceholder: String { return self._s[1295]! } - public var ChatList_GenericPsaLabel: String { return self._s[1297]! } - public var UserInfo_LastNamePlaceholder: String { return self._s[1298]! } - public var IntentsSettings_Reset: String { return self._s[1300]! } - public var Call_ConnectionErrorTitle: String { return self._s[1301]! } - public var PhotoEditor_SaturationTool: String { return self._s[1302]! } - public var ChatSettings_AutomaticVideoMessageDownload: String { return self._s[1303]! } - public var SettingsSearch_Synonyms_Stickers_ArchivedPacks: String { return self._s[1304]! } - public var Conversation_SearchNoResults: String { return self._s[1305]! } - public var Map_OpenInWaze: String { return self._s[1306]! } - public var WallpaperPreview_Title: String { return self._s[1307]! } - public func Passport_AcceptHelp(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1309]!, self._r[1309]!, [_1, _2]) - } - public var AuthSessions_AddDeviceIntro_Title: String { return self._s[1310]! } - public var VoiceOver_Chat_RecordModeVideoMessageInfo: String { return self._s[1311]! } - public var Wallet_Month_ShortMay: String { return self._s[1312]! } - public var Wallet_Send_OwnAddressAlertTitle: String { return self._s[1313]! } - public var Passport_Identity_OneOfTypeInternalPassport: String { return self._s[1314]! } - public var Notifications_PermissionsUnreachableTitle: String { return self._s[1316]! } - public var Stats_Total: String { return self._s[1319]! } - public var Stats_GroupMessages: String { return self._s[1320]! } - public var TwoFactorSetup_Email_SkipAction: String { return self._s[1321]! } - public var CheckoutInfo_ErrorPhoneInvalid: String { return self._s[1322]! } - public var Wallet_TransactionInfo_OtherFeeInfoUrl: String { return self._s[1323]! } - public var Passport_Identity_Translation: String { return self._s[1324]! } - public var Notifications_TextTone: String { return self._s[1326]! } - public var Settings_RemoveConfirmation: String { return self._s[1328]! } - public var ScheduledMessages_Delete: String { return self._s[1329]! } - public var Channel_AdminLog_BanEmbedLinks: String { return self._s[1330]! } - public var Passport_PasswordNext: String { return self._s[1331]! } - public func PUSH_ENCRYPTED_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1332]!, self._r[1332]!, [_1]) - } - public var Passport_Address_EditBankStatement: String { return self._s[1333]! } - public var PhotoEditor_ShadowsTool: String { return self._s[1334]! } - public var Notification_VideoCallMissed: String { return self._s[1335]! } - public var Wallet_WordCheck_IncorrectText: String { return self._s[1336]! } - public var AccessDenied_CameraDisabled: String { return self._s[1337]! } - public var AuthSessions_AddDevice_ScanInfo: String { return self._s[1338]! } - public var Notifications_ExceptionsMuted: String { return self._s[1339]! } - public var Conversation_ScheduleMessage_SendWhenOnline: String { return self._s[1340]! } - public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[1341]! } - public var Channel_BlackList_Title: String { return self._s[1342]! } - public var PasscodeSettings_4DigitCode: String { return self._s[1343]! } - public var NotificationsSound_Bamboo: String { return self._s[1344]! } - public var PrivacySettings_LastSeenContacts: String { return self._s[1345]! } - public var Passport_Address_TypeUtilityBill: String { return self._s[1346]! } - public var Passport_Address_CountryPlaceholder: String { return self._s[1347]! } - public var GroupPermission_SectionTitle: String { return self._s[1348]! } - public func Notification_InvitedMultiple(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1349]!, self._r[1349]!, [_0, _1]) - } - public var CheckoutInfo_ShippingInfoStatePlaceholder: String { return self._s[1350]! } - public var Channel_LeaveChannel: String { return self._s[1351]! } - public var Watch_Notification_Joined: String { return self._s[1352]! } - public var PeerInfo_ButtonMore: String { return self._s[1353]! } - public var Passport_FieldEmailHelp: String { return self._s[1354]! } - public var ChatList_Context_Pin: String { return self._s[1355]! } - public func Time_MonthOfYear_m9(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1356]!, self._r[1356]!, [_0]) - } - public var Group_Location_CreateInThisPlace: String { return self._s[1357]! } - public var PhotoEditor_QualityVeryHigh: String { return self._s[1358]! } - public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[1359]! } - public var Wallet_TransactionInfo_StorageFeeInfoUrl: String { return self._s[1360]! } - public func PUSH_CHAT_MESSAGE_FWD(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1361]!, self._r[1361]!, [_1, _2]) - } - public var Tour_Title5: String { return self._s[1362]! } - public var Wallet_Navigation_Back: String { return self._s[1363]! } - public var Passport_Language_en: String { return self._s[1364]! } - public var Checkout_Name: String { return self._s[1365]! } - public func NetworkUsageSettings_WifiUsageSince(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1366]!, self._r[1366]!, [_0]) - } - public var Wallet_Send_Confirmation: String { return self._s[1367]! } - public var PhotoEditor_EnhanceTool: String { return self._s[1368]! } - public func PUSH_CHAT_DELETE_YOU(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1369]!, self._r[1369]!, [_1, _2]) - } - public func Login_TermsOfService_ProceedBot(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1370]!, self._r[1370]!, [_0]) - } - public var Group_ErrorSendRestrictedMedia: String { return self._s[1371]! } - public func UserInfo_NotificationsDefaultSound(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1372]!, self._r[1372]!, [_0]) - } - public var Login_UnknownError: String { return self._s[1373]! } - public var Passport_Identity_TypeDriversLicense: String { return self._s[1376]! } - public var ChatList_AutoarchiveSuggestion_Title: String { return self._s[1377]! } - public var Watch_PhotoView_Title: String { return self._s[1378]! } - public var Appearance_ThemePreview_ChatList_3_Text: String { return self._s[1379]! } - public var Checkout_TotalAmount: String { return self._s[1380]! } - public var ChatList_RemoveFolderAction: String { return self._s[1381]! } - public var GroupInfo_SetGroupPhoto: String { return self._s[1382]! } - public var Watch_AppName: String { return self._s[1383]! } - public func PUSH_PINNED_GAME_SCORE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1384]!, self._r[1384]!, [_1]) - } - public var Channel_Username_CheckingUsername: String { return self._s[1385]! } - public var ContactList_Context_Call: String { return self._s[1386]! } - public var ChatList_ReorderTabs: String { return self._s[1387]! } - public var Watch_ChatList_Compose: String { return self._s[1388]! } - public func Conversation_LiveLocationYouAnd(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1389]!, self._r[1389]!, [_0]) - } - public var Channel_AdminLog_EmptyFilterTitle: String { return self._s[1390]! } - public var ArchivedChats_IntroTitle1: String { return self._s[1391]! } - public func PUSH_ENCRYPTION_ACCEPT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1392]!, self._r[1392]!, [_1]) - } - public var Call_StatusRequesting: String { return self._s[1394]! } - public var Checkout_TotalPaidAmount: String { return self._s[1395]! } - public var Weekday_Friday: String { return self._s[1397]! } - public var CreateGroup_ChannelsTooMuch: String { return self._s[1398]! } - public var Watch_ChatList_NoConversationsText: String { return self._s[1399]! } - public func Channel_AdminLog_MessageChangedGroupStickerPack(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1400]!, self._r[1400]!, [_0]) - } - public var SecretVideo_Title: String { return self._s[1401]! } - public func Notification_PinnedStickerMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1404]!, self._r[1404]!, [_0]) - } - public var Undo_Undo: String { return self._s[1405]! } - public var Watch_Microphone_Access: String { return self._s[1406]! } - public func PUSH_CHAT_MESSAGE_PHOTO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1407]!, self._r[1407]!, [_1, _2]) - } - public func ChatList_Search_NoResultsQueryDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1408]!, self._r[1408]!, [_0]) - } - public var Wallet_Configuration_SourceURL: String { return self._s[1409]! } - public var Wallet_Intro_CreateErrorTitle: String { return self._s[1410]! } - public var Checkout_NewCard_PostcodeTitle: String { return self._s[1411]! } - public var TwoFactorSetup_Intro_Action: String { return self._s[1412]! } - public var Passport_Language_ne: String { return self._s[1414]! } - public var TwoStepAuth_EmailHelp: String { return self._s[1416]! } - public var Profile_MessageLifetime2s: String { return self._s[1417]! } - public func Conversation_MessageDialogRetryAll(_ _1: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1418]!, self._r[1418]!, ["\(_1)"]) - } - public func Items_NOfM(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1419]!, self._r[1419]!, [_1, _2]) - } - public var GroupPermission_NoPinMessages: String { return self._s[1420]! } - public func PUSH_CHAT_TITLE_EDITED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1421]!, self._r[1421]!, [_1, _2]) - } - public var Wallet_Month_GenJuly: String { return self._s[1422]! } - public func Notification_CreatedChat(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1423]!, self._r[1423]!, [_0]) - } - public var FastTwoStepSetup_HintHelp: String { return self._s[1424]! } - public var WallpaperSearch_ColorRed: String { return self._s[1425]! } - public var Watch_ConnectionDescription: String { return self._s[1426]! } - public var Notification_Exceptions_AddException: String { return self._s[1427]! } - public var LocalGroup_IrrelevantWarning: String { return self._s[1428]! } - public var VoiceOver_MessageContextDelete: String { return self._s[1429]! } - public var LogoutOptions_AlternativeOptionsSection: String { return self._s[1430]! } - public var Passport_PasswordPlaceholder: String { return self._s[1431]! } - public var TwoStepAuth_RecoveryEmailAddDescription: String { return self._s[1432]! } - public var Stats_MessageInteractionsTitle: String { return self._s[1433]! } - public var Appearance_ThemeCarouselClassic: String { return self._s[1434]! } - public var TwoFactorSetup_Email_SkipConfirmationText: String { return self._s[1436]! } - public var Channel_AdminLog_PinMessages: String { return self._s[1437]! } - public var Passport_Address_AddRentalAgreement: String { return self._s[1438]! } - public var Watch_Message_Game: String { return self._s[1439]! } - public var PrivacyLastSeenSettings_NeverShareWith: String { return self._s[1440]! } - public var PrivacyPolicy_DeclineLastWarning: String { return self._s[1441]! } - public var EditTheme_FileReadError: String { return self._s[1442]! } - public var Group_ErrorAddBlocked: String { return self._s[1443]! } - public var CallSettings_UseLessDataLongDescription: String { return self._s[1444]! } - public func PUSH_MESSAGE_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1446]!, self._r[1446]!, [_1]) - } - public func UserInfo_BlockConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1447]!, self._r[1447]!, [_0]) - } - public var CheckoutInfo_ShippingInfoAddress2Placeholder: String { return self._s[1448]! } - public var TwoFactorSetup_EmailVerification_Action: String { return self._s[1449]! } - public func Username_LinkHint(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1450]!, self._r[1450]!, [_0]) - } - public var ConversationProfile_ErrorCreatingConversation: String { return self._s[1451]! } - public var Bot_GroupStatusReadsHistory: String { return self._s[1452]! } - public var PhotoEditor_CurvesRed: String { return self._s[1453]! } - public var InstantPage_TapToOpenLink: String { return self._s[1454]! } - public var FastTwoStepSetup_PasswordHelp: String { return self._s[1455]! } - public var Notification_CallMissedShort: String { return self._s[1456]! } - public func Notification_JoinedGroupByLink(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1457]!, self._r[1457]!, [_0]) - } - public var Conversation_DeleteMessagesForEveryone: String { return self._s[1458]! } - public var Wallet_Words_NotDoneTitle: String { return self._s[1459]! } - public var Permissions_SiriTitle_v0: String { return self._s[1460]! } - public var GroupInfo_AddUserLeftError: String { return self._s[1461]! } - public var Conversation_SendMessage_SendSilently: String { return self._s[1462]! } - public var Paint_Duplicate: String { return self._s[1463]! } - public var AttachmentMenu_WebSearch: String { return self._s[1464]! } - public var Bot_Stop: String { return self._s[1466]! } - public var Conversation_PrivateChannelTimeLimitedAlertTitle: String { return self._s[1467]! } - public var Wallet_TransactionInfo_SendGrams: String { return self._s[1468]! } - public var ReportGroupLocation_Report: String { return self._s[1469]! } - public var Compose_Create: String { return self._s[1470]! } - public var Stats_GroupViewers: String { return self._s[1471]! } - public var AutoDownloadSettings_Channels: String { return self._s[1472]! } - public var PhotoEditor_QualityHigh: String { return self._s[1473]! } - public var Call_Speaker: String { return self._s[1474]! } - public func ChatList_LeaveGroupConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1475]!, self._r[1475]!, [_0]) - } - public var Conversation_CloudStorage_ChatStatus: String { return self._s[1476]! } - public var Chat_AttachmentMultipleFilesDisabled: String { return self._s[1477]! } - public var ChatList_Context_AddToFolder: String { return self._s[1478]! } - public func Wallet_SecureStorageReset_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1479]!, self._r[1479]!, [_0]) - } - public var Conversation_Unblock: String { return self._s[1480]! } - public var SettingsSearch_Synonyms_Proxy_UseForCalls: String { return self._s[1481]! } - public func Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1482]!, self._r[1482]!, [_1, _2, _3]) - } - public var Conversation_ContextMenuReply: String { return self._s[1483]! } - public var Contacts_SearchLabel: String { return self._s[1484]! } - public var Forward_ErrorPublicQuizDisabledInChannels: String { return self._s[1485]! } - public var Stats_GroupMessagesTitle: String { return self._s[1487]! } - public var Wallet_Send_UninitializedTitle: String { return self._s[1488]! } - public var Notification_CallCanceled: String { return self._s[1489]! } - public var VoiceOver_Chat_Selected: String { return self._s[1490]! } - public var NotificationsSound_Tremolo: String { return self._s[1492]! } - public var ChatList_Search_NoResultsDescription: String { return self._s[1493]! } - public var AccessDenied_PhotosAndVideos: String { return self._s[1494]! } - public var AppWallet_Intro_Text: String { return self._s[1495]! } - public var LogoutOptions_ClearCacheText: String { return self._s[1497]! } - public var ChatListFolder_NameUnread: String { return self._s[1498]! } - public var PeerInfo_ButtonMessage: String { return self._s[1500]! } - public var InfoPlist_NSPhotoLibraryAddUsageDescription: String { return self._s[1501]! } - public var BlockedUsers_SelectUserTitle: String { return self._s[1502]! } - public var ChatSettings_Other: String { return self._s[1503]! } - public var UserInfo_NotificationsEnabled: String { return self._s[1504]! } - public var CreatePoll_OptionsHeader: String { return self._s[1505]! } - public var Wallet_Created_Title: String { return self._s[1508]! } - public var Appearance_RemoveThemeColorConfirmation: String { return self._s[1509]! } - public var Channel_Moderator_Title: String { return self._s[1510]! } - public var Channel_AdminLog_MessageRestrictedForever: String { return self._s[1511]! } - public var WallpaperColors_Title: String { return self._s[1512]! } - public var PrivacyPolicy_DeclineMessage: String { return self._s[1514]! } - public var AutoDownloadSettings_VoiceMessagesTitle: String { return self._s[1515]! } - public var Your_card_was_declined: String { return self._s[1516]! } - public var SettingsSearch_FAQ: String { return self._s[1518]! } - public var EditTheme_Expand_Preview_IncomingReplyName: String { return self._s[1519]! } - public var Conversation_ReportSpamConfirmation: String { return self._s[1520]! } - public var OwnershipTransfer_SecurityCheck: String { return self._s[1522]! } - public var PrivacySettings_DataSettingsHelp: String { return self._s[1523]! } - public var Settings_About_Help: String { return self._s[1524]! } - public func Channel_DiscussionGroup_HeaderGroupSet(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1525]!, self._r[1525]!, [_0]) - } - public var Wallet_Settings_Title: String { return self._s[1526]! } - public var Settings_Proxy: String { return self._s[1527]! } - public var TwoStepAuth_ResetAccountConfirmation: String { return self._s[1528]! } - public var Passport_Identity_TypePassportUploadScan: String { return self._s[1530]! } - public var NotificationsSound_Bell: String { return self._s[1531]! } - public var PrivacySettings_Title: String { return self._s[1532]! } - public var PrivacySettings_DataSettings: String { return self._s[1533]! } - public var ConversationMedia_Title: String { return self._s[1534]! } - public func Conversation_EncryptedPlaceholderTitleIncoming(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1535]!, self._r[1535]!, [_0]) - } - public var PrivacySettings_BlockedPeersEmpty: String { return self._s[1536]! } - public var ReportPeer_ReasonPornography: String { return self._s[1538]! } - public var Privacy_Calls: String { return self._s[1539]! } - public var TwoFactorSetup_Email_Text: String { return self._s[1540]! } - public var Conversation_EncryptedDescriptionTitle: String { return self._s[1541]! } - public func VoiceOver_Chat_MusicTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1542]!, self._r[1542]!, [_1, _2]) - } - public var Passport_Identity_FrontSideHelp: String { return self._s[1543]! } - public var GroupInfo_Permissions_SlowmodeHeader: String { return self._s[1545]! } - public var ContactList_Context_VideoCall: String { return self._s[1546]! } - public var Settings_SaveIncomingPhotos: String { return self._s[1547]! } - public var Passport_Identity_MiddleName: String { return self._s[1548]! } - public var MessagePoll_QuizNoUsers: String { return self._s[1549]! } - public var OldChannels_ChannelFormat: String { return self._s[1550]! } - public var Watch_Message_Call: String { return self._s[1551]! } - public var Wallpaper_Title: String { return self._s[1552]! } - public var PasscodeSettings_TurnPasscodeOff: String { return self._s[1553]! } - public var IntentsSettings_SuggestedChatsSavedMessages: String { return self._s[1554]! } - public var ReportGroupLocation_Text: String { return self._s[1555]! } - public var InviteText_URL: String { return self._s[1556]! } - public var ClearCache_StorageServiceFiles: String { return self._s[1557]! } - public var MessageTimer_Custom: String { return self._s[1558]! } - public var Message_PinnedLocationMessage: String { return self._s[1559]! } - public func VoiceOver_Chat_ContactOrganization(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1560]!, self._r[1560]!, [_0]) - } - public var EditTheme_UploadNewTheme: String { return self._s[1561]! } - public func AutoDownloadSettings_UpToForAll(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1563]!, self._r[1563]!, [_0]) - } - public var Login_CodeSentCall: String { return self._s[1565]! } - public var Conversation_Report: String { return self._s[1566]! } - public var NotificationSettings_ContactJoined: String { return self._s[1567]! } - public func PUSH_MESSAGE_SCREENSHOT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1568]!, self._r[1568]!, [_1]) - } - public var StickerPacksSettings_ShowStickersButtonHelp: String { return self._s[1569]! } - public var IntentsSettings_SuggestByAll: String { return self._s[1570]! } - public var StickerPacksSettings_ShowStickersButton: String { return self._s[1571]! } - public var AuthSessions_Title: String { return self._s[1572]! } - public var Channel_AdminLog_TitleAllEvents: String { return self._s[1573]! } - public var Wallet_Completed_ViewWallet: String { return self._s[1574]! } - public var KeyCommand_JumpToNextUnreadChat: String { return self._s[1575]! } - public var Passport_Address_AddPassportRegistration: String { return self._s[1579]! } - public var AutoDownloadSettings_MaxVideoSize: String { return self._s[1580]! } - public var ExplicitContent_AlertTitle: String { return self._s[1581]! } - public var Channel_UpdatePhotoItem: String { return self._s[1582]! } - public var ChatList_AutoarchiveSuggestion_Text: String { return self._s[1584]! } - public var Channel_DiscussionGroup_LinkGroup: String { return self._s[1585]! } - public func Call_BatteryLow(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1586]!, self._r[1586]!, [_0]) - } - public var Login_HaveNotReceivedCodeInternal: String { return self._s[1587]! } - public var WallpaperPreview_PatternPaternApply: String { return self._s[1588]! } - public var Notifications_MessageNotificationsSound: String { return self._s[1589]! } - public var Appearance_AccentColor: String { return self._s[1591]! } - public var GroupInfo_SharedMedia: String { return self._s[1592]! } - public var Login_PhonePlaceholder: String { return self._s[1593]! } - public var Appearance_TextSize_Automatic: String { return self._s[1594]! } - public var EmptyGroupInfo_Line2: String { return self._s[1595]! } - public func PUSH_CHAT_CREATED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1596]!, self._r[1596]!, [_1, _2]) - } - public var Conversation_WalletRequiredNotNow: String { return self._s[1598]! } - public var Appearance_AppIconDefaultX: String { return self._s[1599]! } - public var EditProfile_NameAndPhotoOrVideoHelp: String { return self._s[1600]! } - public var CheckoutInfo_ShippingInfoPostcodePlaceholder: String { return self._s[1601]! } - public var Notifications_GroupNotificationsHelp: String { return self._s[1602]! } - public func PUSH_CHAT_MESSAGE_NOTEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1603]!, self._r[1603]!, [_1, _2]) - } - public var ChatList_EmptyChatListEditFilter: String { return self._s[1604]! } - public var ChatSettings_ConnectionType_UseProxy: String { return self._s[1607]! } - public var UserInfo_NotificationsEnable: String { return self._s[1608]! } - public var Checkout_PayWithTouchId: String { return self._s[1609]! } - public var SharedMedia_ViewInChat: String { return self._s[1610]! } - public func Notification_CreatedChatWithTitle(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1611]!, self._r[1611]!, [_0, _1]) - } - public var ChatSettings_AutoDownloadSettings_OffForAll: String { return self._s[1612]! } - public func Channel_DiscussionGroup_PublicChannelLink(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1613]!, self._r[1613]!, [_1, _2]) - } - public func Cache_Clear(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1615]!, self._r[1615]!, [_0]) - } - public var Conversation_PeerNearbyText: String { return self._s[1617]! } - public var Conversation_StopPollConfirmationTitle: String { return self._s[1618]! } - public var PhotoEditor_Skip: String { return self._s[1619]! } - public var SettingsSearch_Synonyms_Appearance_ChatBackground_SetColor: String { return self._s[1620]! } - public var ChatList_EmptyChatList: String { return self._s[1621]! } - public var Channel_BanUser_Unban: String { return self._s[1622]! } - public func Message_GenericForwardedPsa(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1623]!, self._r[1623]!, [_0]) - } - public var Appearance_TextSize_Apply: String { return self._s[1624]! } - public var Wallet_Send_SyncInProgress: String { return self._s[1625]! } - public var Login_InfoFirstNamePlaceholder: String { return self._s[1626]! } - public var Wallet_Configuration_SourceHeader: String { return self._s[1627]! } - public var TwoStepAuth_HintPlaceholder: String { return self._s[1628]! } - public var TwoStepAuth_EmailSkip: String { return self._s[1630]! } - public var ChatList_UndoArchiveMultipleTitle: String { return self._s[1631]! } - public var TwoFactorSetup_Email_SkipConfirmationTitle: String { return self._s[1632]! } - public func PUSH_MESSAGE_QUIZ(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1633]!, self._r[1633]!, [_1]) - } - public var State_WaitingForNetwork: String { return self._s[1635]! } - public var AccessDenied_CameraRestricted: String { return self._s[1636]! } - public var ChatSettings_Appearance: String { return self._s[1637]! } - public var ScheduledMessages_BotActionUnavailable: String { return self._s[1638]! } - public func Wallet_Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1639]!, self._r[1639]!, [_1, _2, _3]) - } - public var GroupInfo_InviteLink_CopyAlert_Success: String { return self._s[1640]! } - public var Channel_DiscussionGroupAdd: String { return self._s[1641]! } - public var Map_NoPlacesNearby: String { return self._s[1643]! } - public var AuthSessions_IncompleteAttemptsInfo: String { return self._s[1644]! } - public var GroupRemoved_Title: String { return self._s[1645]! } - public var TwoStepAuth_EnterPasswordHelp: String { return self._s[1647]! } - public var Paint_Marker: String { return self._s[1648]! } - public func AddContact_ContactWillBeSharedAfterMutual(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1649]!, self._r[1649]!, [_1]) - } - public var SocksProxySetup_ShareProxyList: String { return self._s[1650]! } - public var GroupInfo_InvitationLinkDoesNotExist: String { return self._s[1651]! } - public func VoiceOver_Chat_Size(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1652]!, self._r[1652]!, [_0]) - } - public var EditTheme_ErrorInvalidCharacters: String { return self._s[1653]! } - public var Appearance_ThemePreview_ChatList_7_Name: String { return self._s[1654]! } - public var Notifications_GroupNotificationsAlert: String { return self._s[1655]! } - public var SocksProxySetup_ShareQRCode: String { return self._s[1656]! } - public var Compose_NewGroup: String { return self._s[1657]! } - public func Passport_Address_UploadOneOfScan(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1658]!, self._r[1658]!, [_0]) - } - public var Conversation_ClearGroupHistory: String { return self._s[1660]! } - public var GroupInfo_InviteLink_Help: String { return self._s[1663]! } - public var Channel_BanUser_BlockFor: String { return self._s[1664]! } - public var Bot_Start: String { return self._s[1665]! } - public var Your_card_has_expired: String { return self._s[1666]! } - public var Channel_About_Title: String { return self._s[1667]! } - public var Passport_Identity_ExpiryDatePlaceholder: String { return self._s[1668]! } - public var SettingsSearch_Synonyms_Notifications_MessageNotificationsExceptions: String { return self._s[1670]! } - public var Wallet_Info_Updating: String { return self._s[1671]! } - public var Conversation_FileDropbox: String { return self._s[1672]! } - public var Conversation_WalletRequiredTitle: String { return self._s[1673]! } - public var ChatList_Search_NoResultsFitlerMusic: String { return self._s[1674]! } - public var Month_GenNovember: String { return self._s[1675]! } - public var IntentsSettings_SuggestByShare: String { return self._s[1676]! } - public func Call_PrivacyErrorMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1677]!, self._r[1677]!, [_0]) - } - public var StickerPack_Add: String { return self._s[1678]! } - public var Theme_ErrorNotFound: String { return self._s[1679]! } - public var Wallpaper_SearchShort: String { return self._s[1681]! } - public var Channel_BanUser_PermissionsHeader: String { return self._s[1682]! } - public var ConversationProfile_UsersTooMuchError: String { return self._s[1683]! } - public var ChatList_FolderAllChats: String { return self._s[1684]! } - public var Passport_Authorize: String { return self._s[1685]! } - public func Channel_AdminLog_MessageChangedLinkedChannel(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1686]!, self._r[1686]!, [_1, _2]) - } - public var GroupInfo_GroupHistoryVisible: String { return self._s[1687]! } - public func PUSH_MESSAGE_VIDEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1688]!, self._r[1688]!, [_1]) - } - public var LocalGroup_ButtonTitle: String { return self._s[1689]! } - public var UserInfo_GroupsInCommon: String { return self._s[1690]! } - public var Wallpaper_Set: String { return self._s[1691]! } - public var LoginPassword_Title: String { return self._s[1692]! } - public var Stats_InteractionsTitle: String { return self._s[1693]! } - public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[1695]! } - public func SecretGIF_NotViewedYet(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1696]!, self._r[1696]!, [_0]) - } - public var Conversation_MessageDialogEdit: String { return self._s[1697]! } - public var Paint_Outlined: String { return self._s[1698]! } - public func Login_ResetAccountProtected_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1699]!, self._r[1699]!, [_0]) - } - public func Conversation_SetReminder_RemindTomorrow(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1700]!, self._r[1700]!, [_0]) - } - public var Invite_LargeRecipientsCountWarning: String { return self._s[1701]! } - public var Passport_Address_Street1Placeholder: String { return self._s[1702]! } - public var Appearance_ColorThemeNight: String { return self._s[1703]! } - public var ChannelInfo_Stats: String { return self._s[1704]! } - public var TwoStepAuth_RecoveryTitle: String { return self._s[1705]! } - public var MediaPicker_TimerTooltip: String { return self._s[1706]! } - public var Common_ChoosePhoto: String { return self._s[1707]! } - public var ChatSettings_AutoDownloadVideos: String { return self._s[1708]! } - public var PeerInfo_PaneGroups: String { return self._s[1709]! } - public var Wallet_Month_ShortMarch: String { return self._s[1711]! } - public var ChangePhoneNumberNumber_Title: String { return self._s[1712]! } - public var SocksProxySetup_UsernamePlaceholder: String { return self._s[1713]! } - public var ContactInfo_PhoneLabelMobile: String { return self._s[1714]! } - public var OldChannels_ChannelsHeader: String { return self._s[1715]! } - public var MuteFor_Forever: String { return self._s[1716]! } - public var Passport_Address_PostcodePlaceholder: String { return self._s[1717]! } - public var SettingsSearch_Synonyms_Appearance_ChatBackground: String { return self._s[1718]! } - public var MessagePoll_LabelAnonymous: String { return self._s[1719]! } - public var ContactInfo_Job: String { return self._s[1720]! } - public var Passport_Language_mk: String { return self._s[1721]! } - public var EditTheme_ShortLink: String { return self._s[1722]! } - public var AutoDownloadSettings_PhotosTitle: String { return self._s[1724]! } - public var Wallet_Send_Send: String { return self._s[1725]! } - public var Month_GenApril: String { return self._s[1727]! } - public var Channel_DiscussionGroup_HeaderLabel: String { return self._s[1729]! } - public var NetworkUsageSettings_TotalSection: String { return self._s[1730]! } - public var EditTheme_Create_Preview_OutgoingText: String { return self._s[1731]! } - public var EditTheme_Title: String { return self._s[1732]! } - public var Conversation_LinkDialogCopy: String { return self._s[1733]! } - public func Channel_AdminLog_MessageInvitedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1734]!, self._r[1734]!, [_1, _2]) - } - public var Passport_ForgottenPassword: String { return self._s[1735]! } - public var WallpaperSearch_Recent: String { return self._s[1736]! } - public var ChatSettings_Title: String { return self._s[1741]! } - public var Appearance_ReduceMotionInfo: String { return self._s[1742]! } - public func StickerPackActionInfo_AddedText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1743]!, self._r[1743]!, [_0]) - } - public var SocksProxySetup_UseForCallsHelp: String { return self._s[1744]! } - public var LastSeen_WithinAMonth: String { return self._s[1745]! } - public var PeerInfo_ButtonCall: String { return self._s[1746]! } - public var SettingsSearch_Synonyms_Appearance_Title: String { return self._s[1747]! } - public var Group_Username_InvalidStartsWithNumber: String { return self._s[1748]! } - public var Call_AudioRouteHide: String { return self._s[1749]! } - public var DialogList_SavedMessages: String { return self._s[1750]! } - public var ChatList_Context_Mute: String { return self._s[1751]! } - public var Conversation_StatusKickedFromChannel: String { return self._s[1752]! } - public func Notification_Exceptions_MutedUntil(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1753]!, self._r[1753]!, [_0]) - } - public var Passport_Language_et: String { return self._s[1754]! } - public var PhotoEditor_CropReset: String { return self._s[1755]! } - public var Wallet_Send_TransactionInProgress: String { return self._s[1756]! } - public var Privacy_GroupsAndChannels_AlwaysAllow: String { return self._s[1757]! } - public var SocksProxySetup_HostnamePlaceholder: String { return self._s[1758]! } - public var CreateGroup_ErrorLocatedGroupsTooMuch: String { return self._s[1759]! } - public var WallpaperSearch_ColorWhite: String { return self._s[1762]! } - public var Channel_AdminLog_CanEditMessages: String { return self._s[1764]! } - public var Privacy_PaymentsClearInfoDoneHelp: String { return self._s[1765]! } - public var Channel_Username_InvalidStartsWithNumber: String { return self._s[1767]! } - public var CheckoutInfo_ReceiverInfoName: String { return self._s[1769]! } - public var Map_YouAreHere: String { return self._s[1771]! } - public var Core_ServiceUserStatus: String { return self._s[1772]! } - public var Channel_Setup_TypePrivateHelp: String { return self._s[1775]! } - public var SettingsSearch_Synonyms_Notifications_BadgeCountUnreadMessages: String { return self._s[1776]! } - public var MediaPicker_Videos: String { return self._s[1778]! } - public var Map_LiveLocationFor15Minutes: String { return self._s[1780]! } - public var Passport_Identity_TranslationsHelp: String { return self._s[1781]! } - public var SharedMedia_CategoryMedia: String { return self._s[1782]! } - public var Wallet_Month_ShortJanuary: String { return self._s[1783]! } - public func MediaPicker_Nof(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1784]!, self._r[1784]!, [_0]) - } - public var ChatSettings_AutoPlayGifs: String { return self._s[1785]! } - public var Passport_Identity_CountryPlaceholder: String { return self._s[1786]! } - public var Bot_GroupStatusDoesNotReadHistory: String { return self._s[1787]! } - public var Notification_Exceptions_RemoveFromExceptions: String { return self._s[1788]! } - public func Chat_SlowmodeTooltip(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1789]!, self._r[1789]!, [_0]) - } - public var Web_Error: String { return self._s[1790]! } - public var PhotoEditor_SkinTool: String { return self._s[1791]! } - public var ApplyLanguage_UnsufficientDataTitle: String { return self._s[1792]! } - public var ChatSettings_ConnectionType_UseSocks5: String { return self._s[1793]! } - public var PasscodeSettings_Help: String { return self._s[1794]! } - public var Appearance_ColorTheme: String { return self._s[1795]! } - public func Channel_AdminLog_MessageRestrictedNewSetting(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1796]!, self._r[1796]!, [_0]) - } - public func PUSH_PINNED_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1797]!, self._r[1797]!, [_1]) - } - public var GroupInfo_LeftStatus: String { return self._s[1798]! } - public var EditTheme_Preview: String { return self._s[1799]! } - public var Watch_Suggestion_WhatsUp: String { return self._s[1800]! } - public func AutoDownloadSettings_PreloadVideoInfo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1801]!, self._r[1801]!, [_0]) - } - public var NotificationsSound_Keys: String { return self._s[1802]! } - public var PasscodeSettings_UnlockWithTouchId: String { return self._s[1803]! } - public var ChatList_Context_MarkAsUnread: String { return self._s[1804]! } - public var DialogList_AdNoticeAlert: String { return self._s[1805]! } - public var UserInfo_Invite: String { return self._s[1806]! } - public var Checkout_Email: String { return self._s[1807]! } - public var Stats_GroupActionsTitle: String { return self._s[1808]! } - public var Wallet_Navigation_Done: String { return self._s[1809]! } - public var Coub_TapForSound: String { return self._s[1810]! } - public var Theme_ThemeChangedText: String { return self._s[1811]! } - public var Call_ExternalCallInProgressMessage: String { return self._s[1812]! } - public var Settings_ApplyProxyAlertEnable: String { return self._s[1813]! } - public var ScheduledMessages_ScheduledToday: String { return self._s[1814]! } - public var Channel_AdminLog_DefaultRestrictionsUpdated: String { return self._s[1815]! } - public var Call_ReportIncludeLogDescription: String { return self._s[1816]! } - public var Settings_FrequentlyAskedQuestions: String { return self._s[1818]! } - public var Wallet_Words_NotDoneText: String { return self._s[1819]! } - public var Channel_MessagePhotoRemoved: String { return self._s[1820]! } - public var Passport_Email_Delete: String { return self._s[1821]! } - public func PUSH_PINNED_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1822]!, self._r[1822]!, [_1]) - } - public var NotificationSettings_ShowNotificationsAllAccountsInfoOn: String { return self._s[1823]! } - public var Channel_AdminLog_CanAddAdmins: String { return self._s[1824]! } - public var SocksProxySetup_FailedToConnect: String { return self._s[1826]! } - public var SettingsSearch_Synonyms_Data_NetworkUsage: String { return self._s[1827]! } - public var Wallet_Month_GenMay: String { return self._s[1828]! } - public var Common_of: String { return self._s[1829]! } - public var PeerInfo_ButtonUnmute: String { return self._s[1831]! } - public func ChatSettings_AutoDownloadSettings_TypeFile(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1832]!, self._r[1832]!, [_0]) - } - public var ChatList_AddChatsToFolder: String { return self._s[1833]! } - public var Login_ResetAccountProtected_LimitExceeded: String { return self._s[1834]! } - public var Settings_Title: String { return self._s[1836]! } - public var AutoDownloadSettings_Contacts: String { return self._s[1838]! } - public var Appearance_BubbleCornersSetting: String { return self._s[1839]! } - public var Privacy_Calls_AlwaysAllow: String { return self._s[1840]! } - public var Privacy_Forwards_AlwaysAllow_Title: String { return self._s[1842]! } - public var WallpaperPreview_CropBottomText: String { return self._s[1843]! } - public var SecretTimer_VideoDescription: String { return self._s[1844]! } - public var WallpaperPreview_Blurred: String { return self._s[1845]! } - public var SettingsSearch_Synonyms_Notifications_GroupNotificationsExceptions: String { return self._s[1846]! } - public var ChatListFolder_ExcludedSectionHeader: String { return self._s[1848]! } - public var DialogList_PasscodeLockHelp: String { return self._s[1849]! } - public var SocksProxySetup_SecretPlaceholder: String { return self._s[1850]! } - public var NetworkUsageSettings_CallDataSection: String { return self._s[1851]! } - public var SettingsSearch_Synonyms_Wallet: String { return self._s[1852]! } - public var TwoStepAuth_PasswordRemovePassportConfirmation: String { return self._s[1853]! } - public var Passport_FieldAddressTranslationHelp: String { return self._s[1854]! } - public var SocksProxySetup_Connection: String { return self._s[1855]! } - public var Passport_Address_TypePassportRegistration: String { return self._s[1856]! } - public var Contacts_PermissionsAllowInSettings: String { return self._s[1857]! } - public var Conversation_Unpin: String { return self._s[1858]! } - public var Notifications_MessageNotificationsExceptionsHelp: String { return self._s[1859]! } - public var TwoFactorSetup_Hint_Placeholder: String { return self._s[1860]! } - public var Call_ReportSkip: String { return self._s[1861]! } - public func VoiceOver_Chat_PhotoFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1862]!, self._r[1862]!, [_0]) - } - public func VoiceOver_Chat_Caption(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1864]!, self._r[1864]!, [_0]) - } - public var AutoNightTheme_Automatic: String { return self._s[1865]! } - public var Wallet_TransactionInfo_AddressCopied: String { return self._s[1866]! } - public var Wallet_Month_GenMarch: String { return self._s[1867]! } - public var Passport_Language_az: String { return self._s[1868]! } - public var SettingsSearch_Synonyms_Data_Storage_ClearCache: String { return self._s[1869]! } - public var Watch_UserInfo_Unmute: String { return self._s[1870]! } - public var Channel_Stickers_YourStickers: String { return self._s[1871]! } - public var Channel_DiscussionGroup_UnlinkChannel: String { return self._s[1872]! } - public var Tour_Text1: String { return self._s[1873]! } - public var Common_Delete: String { return self._s[1874]! } - public var Settings_EditPhoto: String { return self._s[1875]! } - public var Common_Edit: String { return self._s[1876]! } - public var ShareMenu_ShareTo: String { return self._s[1878]! } - public var Passport_Identity_ExpiryDate: String { return self._s[1879]! } - public var Preview_DeleteGif: String { return self._s[1880]! } - public var WallpaperPreview_PatternPaternDiscard: String { return self._s[1881]! } - public var ChatSettings_AutoDownloadUsingCellular: String { return self._s[1882]! } - public var Stats_LoadingText: String { return self._s[1883]! } - public var Channel_EditAdmin_PermissinAddAdminOn: String { return self._s[1884]! } - public var CheckoutInfo_ReceiverInfoEmailPlaceholder: String { return self._s[1885]! } - public var Channel_AdminLog_CanChangeInfo: String { return self._s[1886]! } - public func Passport_Phone_UseTelegramNumber(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1887]!, self._r[1887]!, [_0]) - } - public func Time_MonthOfYear_m2(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1888]!, self._r[1888]!, [_0]) - } - public func VoiceOver_Chat_VideoMessageFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1890]!, self._r[1890]!, [_0]) - } - public var Passport_Address_OneOfTypeRentalAgreement: String { return self._s[1891]! } - public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[1894]! } - public var IntentsSettings_MainAccount: String { return self._s[1895]! } - public var Group_MessagePhotoRemoved: String { return self._s[1898]! } - public var GroupInfo_Permissions_Exceptions: String { return self._s[1900]! } - public var GroupRemoved_UsersSectionTitle: String { return self._s[1901]! } - public var Contacts_PermissionsEnable: String { return self._s[1902]! } - public var Channel_EditAdmin_PermissionDeleteMessagesOfOthers: String { return self._s[1903]! } - public var Common_NotNow: String { return self._s[1904]! } - public var Notification_CreatedChannel: String { return self._s[1905]! } - public var Stats_ViewsBySourceTitle: String { return self._s[1907]! } - public var Appearance_AppIconClassic: String { return self._s[1908]! } - public var PhotoEditor_QualityTool: String { return self._s[1909]! } - public var ClearCache_ClearCache: String { return self._s[1910]! } - public var TwoFactorSetup_Password_PlaceholderConfirmPassword: String { return self._s[1911]! } - public var AutoDownloadSettings_Videos: String { return self._s[1912]! } - public var GroupPermission_Duration: String { return self._s[1913]! } - public var ChatList_Read: String { return self._s[1914]! } - public func Group_OwnershipTransfer_DescriptionInfo(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1915]!, self._r[1915]!, [_1, _2]) - } - public var CallFeedback_Send: String { return self._s[1916]! } - public var Channel_Stickers_Searching: String { return self._s[1917]! } - public var ScheduledMessages_ReminderNotification: String { return self._s[1918]! } - public var FastTwoStepSetup_HintSection: String { return self._s[1919]! } - public var ChatSettings_AutoDownloadVideoMessages: String { return self._s[1920]! } - public var EditTheme_CreateTitle: String { return self._s[1921]! } - public var Application_Name: String { return self._s[1922]! } - public var Paint_Stickers: String { return self._s[1923]! } - public var Appearance_ThemePreview_Chat_1_Text: String { return self._s[1924]! } - public var Call_StatusFailed: String { return self._s[1925]! } - public var Stickers_FavoriteStickers: String { return self._s[1926]! } - public var ClearCache_Clear: String { return self._s[1927]! } - public var Passport_Language_mn: String { return self._s[1928]! } - public var WallpaperPreview_PreviewTopText: String { return self._s[1929]! } - public var LogoutOptions_ClearCacheTitle: String { return self._s[1930]! } - public var TwoFactorSetup_Hint_Text: String { return self._s[1933]! } - public var WallpaperPreview_PatternIntensity: String { return self._s[1934]! } - public var CheckoutInfo_ErrorShippingNotAvailable: String { return self._s[1935]! } - public var Wallet_RestoreFailed_CreateWallet: String { return self._s[1936]! } - public var Passport_Address_AddBankStatement: String { return self._s[1937]! } - public var ChatListFolderSettings_RecommendedNewFolder: String { return self._s[1939]! } - public var UserInfo_ShareContact: String { return self._s[1940]! } - public var Passport_Identity_NamePlaceholder: String { return self._s[1941]! } - public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[1943]! } - public var Call_RateCall: String { return self._s[1944]! } - public var Contacts_AccessDeniedError: String { return self._s[1945]! } - public var Invite_ChannelsTooMuch: String { return self._s[1946]! } - public var CheckoutInfo_ShippingInfoPostcode: String { return self._s[1947]! } - public var Channel_BanUser_PermissionReadMessages: String { return self._s[1948]! } - public var Cache_NoLimit: String { return self._s[1950]! } - public var Conversation_EmptyPlaceholder: String { return self._s[1951]! } - public var Privacy_GroupsAndChannels_AlwaysAllow_Placeholder: String { return self._s[1955]! } - public var GroupRemoved_RemoveInfo: String { return self._s[1956]! } - public var Privacy_Calls_IntegrationHelp: String { return self._s[1957]! } - public func PUSH_VIDEO_CALL_MISSED(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1958]!, self._r[1958]!, [_1]) - } - public var VoiceOver_Media_PlaybackRateFast: String { return self._s[1959]! } - public var Theme_ThemeChanged: String { return self._s[1960]! } - public var Privacy_GroupsAndChannels_NeverAllow: String { return self._s[1962]! } - public var AutoDownloadSettings_MediaTypes: String { return self._s[1963]! } - public func Notification_PinnedDocumentMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1964]!, self._r[1964]!, [_0]) - } - public var Channel_AdminLog_InfoPanelTitle: String { return self._s[1965]! } - public var Passport_Language_da: String { return self._s[1967]! } - public var Wallet_Receive_AmountText: String { return self._s[1968]! } - public var Chat_SlowmodeSendError: String { return self._s[1969]! } - public var Application_Update: String { return self._s[1971]! } - public var SocksProxySetup_SaveProxy: String { return self._s[1972]! } - public func PUSH_AUTH_REGION(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1973]!, self._r[1973]!, [_1, _2]) - } - public var Wallet_Receive_ShareAddress: String { return self._s[1975]! } - public var Privacy_AddNewPeer: String { return self._s[1976]! } - public var Channel_DiscussionGroup_MakeHistoryPublicProceed: String { return self._s[1978]! } - public var Wallet_Receive_CommentInfo: String { return self._s[1979]! } - public var Channel_Members_Title: String { return self._s[1980]! } - public var Settings_LogoutConfirmationText: String { return self._s[1981]! } - public var Chat_UnsendMyMessages: String { return self._s[1982]! } - public var Conversation_EditingMessageMediaEditCurrentVideo: String { return self._s[1983]! } - public var ChatListFilter_AddChatsTitle: String { return self._s[1984]! } - public var Passport_FloodError: String { return self._s[1985]! } - public var NotificationSettings_ContactJoinedInfo: String { return self._s[1986]! } - public var SettingsSearch_Synonyms_Privacy_Data_SecretChatLinkPreview: String { return self._s[1987]! } - public var CallSettings_TabIconDescription: String { return self._s[1988]! } - public var Wallet_Intro_Text: String { return self._s[1989]! } - public var Group_Setup_HistoryHeader: String { return self._s[1991]! } - public var TwoStepAuth_EmailTitle: String { return self._s[1992]! } - public var GroupInfo_Permissions_Removed: String { return self._s[1993]! } - public var DialogList_ClearHistoryConfirmation: String { return self._s[1994]! } - public var Contacts_Title: String { return self._s[1996]! } - public func Notification_Invited(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1997]!, self._r[1997]!, [_0, _1]) - } - public var ChatList_PeerTypeBot: String { return self._s[2000]! } - public func Channel_AdminLog_SetSlowmode(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2001]!, self._r[2001]!, [_1, _2]) - } - public var Appearance_ThemePreview_Chat_6_Text: String { return self._s[2002]! } - public func Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2003]!, self._r[2003]!, [_1, _2, _3]) - } - public var Camera_PhotoMode: String { return self._s[2005]! } - public func PUSH_MESSAGE_GAME_SCORE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2006]!, self._r[2006]!, [_1, _2, _3]) - } - public var ContactInfo_PhoneLabelPager: String { return self._s[2007]! } - public var SettingsSearch_Synonyms_FAQ: String { return self._s[2008]! } - public var Call_CallAgain: String { return self._s[2009]! } - public var TwoStepAuth_PasswordSet: String { return self._s[2010]! } - public func Channel_Management_RestrictedBy(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2011]!, self._r[2011]!, [_0]) - } - public var GroupInfo_InviteLink_RevokeAlert_Success: String { return self._s[2012]! } - public var ClearCache_FreeSpaceDescription: String { return self._s[2013]! } - public var Permissions_ContactsAllowInSettings_v0: String { return self._s[2014]! } - public var Group_LeaveGroup: String { return self._s[2015]! } - public var Wallet_WordImport_IncorrectText: String { return self._s[2018]! } - public var GroupInfo_LabelAdmin: String { return self._s[2019]! } - public var CheckoutInfo_ErrorStateInvalid: String { return self._s[2021]! } - public var Notification_PassportValuePersonalDetails: String { return self._s[2022]! } - public func WebSearch_SearchNoResultsDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2023]!, self._r[2023]!, [_0]) - } - public var Stats_GroupNewMembersBySourceTitle: String { return self._s[2024]! } - public var Appearance_Preview: String { return self._s[2025]! } - public var VoiceOver_Chat_Contact: String { return self._s[2026]! } - public var Passport_Language_th: String { return self._s[2027]! } - public var PhotoEditor_CropAspectRatioOriginal: String { return self._s[2029]! } - public var LastSeen_Offline: String { return self._s[2032]! } - public var Map_OpenInHereMaps: String { return self._s[2033]! } - public var SettingsSearch_Synonyms_Data_AutoplayVideos: String { return self._s[2034]! } - public var AutoDownloadSettings_Reset: String { return self._s[2036]! } - public var Wallet_Month_GenFebruary: String { return self._s[2037]! } - public var Conversation_SendMessage_SetReminder: String { return self._s[2038]! } - public var Channel_AdminLog_EmptyMessageText: String { return self._s[2039]! } - public func AddContact_StatusSuccess(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2040]!, self._r[2040]!, [_0]) - } - public func AuthCode_Alert(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2041]!, self._r[2041]!, [_0]) - } - public var Passport_Identity_EditDriversLicense: String { return self._s[2042]! } - public var ChatListFolder_NameNonMuted: String { return self._s[2043]! } - public var Username_Placeholder: String { return self._s[2044]! } - public func PUSH_ALBUM(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2045]!, self._r[2045]!, [_1]) - } - public var Wallet_Send_NetworkErrorText: String { return self._s[2046]! } - public var Checkout_NewCard_SaveInfo: String { return self._s[2047]! } - public var Passport_Language_it: String { return self._s[2048]! } - public func Channel_OwnershipTransfer_DescriptionInfo(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2049]!, self._r[2049]!, [_1, _2]) - } - public var NotificationsSound_Pulse: String { return self._s[2050]! } - public var MessagePoll_NoVotes: String { return self._s[2054]! } - public var Message_Wallpaper: String { return self._s[2055]! } - public var Wallet_Created_Proceed: String { return self._s[2056]! } - public var Appearance_Other: String { return self._s[2057]! } - public var Passport_Identity_NativeNameHelp: String { return self._s[2059]! } - public var Group_PublicLink_Placeholder: String { return self._s[2062]! } - public var Appearance_ThemePreview_ChatList_2_Text: String { return self._s[2063]! } - public var VoiceOver_Recording_StopAndPreview: String { return self._s[2064]! } - public var ChatListFolder_NameBots: String { return self._s[2065]! } - public var Conversation_StopPollConfirmation: String { return self._s[2066]! } - public var UserInfo_DeleteContact: String { return self._s[2067]! } - public func Time_MonthOfYear_m11(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2068]!, self._r[2068]!, [_0]) - } - public var Wallpaper_Wallpaper: String { return self._s[2070]! } - public func PUSH_MESSAGE_NOTEXT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2071]!, self._r[2071]!, [_1]) - } - public var LoginPassword_ForgotPassword: String { return self._s[2072]! } - public var FeaturedStickerPacks_Title: String { return self._s[2073]! } - public var Paint_Pen: String { return self._s[2074]! } - public var Channel_AdminLogFilter_EventsInfo: String { return self._s[2075]! } - public var ChatListFolderSettings_Info: String { return self._s[2076]! } - public var FastTwoStepSetup_HintPlaceholder: String { return self._s[2077]! } - public var PhotoEditor_CurvesAll: String { return self._s[2079]! } - public var Wallet_Info_UnknownTransaction: String { return self._s[2080]! } - public func Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2082]!, self._r[2082]!, [_1, _2, _3]) - } - public var Passport_Address_TypeRentalAgreement: String { return self._s[2084]! } - public var Message_ImageExpired: String { return self._s[2085]! } - public var Call_ConnectionErrorMessage: String { return self._s[2086]! } - public var SearchImages_NoImagesFound: String { return self._s[2088]! } - public var PeerInfo_PaneGifs: String { return self._s[2089]! } - public var Passport_DeletePersonalDetailsConfirmation: String { return self._s[2090]! } - public var EnterPasscode_RepeatNewPasscode: String { return self._s[2091]! } - public var PhotoEditor_VignetteTool: String { return self._s[2092]! } - public var Passport_Language_dz: String { return self._s[2093]! } - public var Notifications_ChannelNotificationsHelp: String { return self._s[2094]! } - public var Conversation_BlockUser: String { return self._s[2095]! } - public func Wallet_Send_Balance(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2097]!, self._r[2097]!, [_0]) - } - public var GroupPermission_PermissionDisabledByDefault: String { return self._s[2098]! } - public var Group_OwnershipTransfer_ErrorAdminsTooMuch: String { return self._s[2099]! } - public func Time_MonthOfYear_m8(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2100]!, self._r[2100]!, [_0]) - } - public var KeyCommand_NewMessage: String { return self._s[2101]! } - public var EditTheme_Edit_Preview_IncomingReplyText: String { return self._s[2103]! } - public func PUSH_CHAT_MESSAGE_GEO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2105]!, self._r[2105]!, [_1, _2]) - } - public var ContactList_Context_StartSecretChat: String { return self._s[2106]! } - public var VoiceOver_Chat_File: String { return self._s[2107]! } - public var ChatList_EditFolder: String { return self._s[2109]! } - public var Appearance_BubbleCorners_Title: String { return self._s[2110]! } - public var PeerInfo_PaneAudio: String { return self._s[2111]! } - public var Wallet_SecureStorageReset_Title: String { return self._s[2112]! } - public var ChatListFolder_CategoryContacts: String { return self._s[2114]! } - public func Login_InvalidPhoneEmailBody(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2115]!, self._r[2115]!, [_1, _2, _3, _4, _5]) - } - public var ChatList_PeerTypeChannel: String { return self._s[2116]! } - public var VoiceOver_Navigation_Search: String { return self._s[2117]! } - public var Settings_Search: String { return self._s[2118]! } - public var WallpaperSearch_ColorYellow: String { return self._s[2119]! } - public var Login_PhoneBannedError: String { return self._s[2120]! } - public var KeyCommand_JumpToNextChat: String { return self._s[2121]! } - public var Passport_Language_fa: String { return self._s[2122]! } - public var Settings_About: String { return self._s[2123]! } - public var Wallet_Configuration_Title: String { return self._s[2124]! } - public var AutoDownloadSettings_MaxFileSize: String { return self._s[2125]! } - public var Channel_AdminLog_InfoPanelChannelAlertText: String { return self._s[2126]! } - public var AutoDownloadSettings_DataUsageHigh: String { return self._s[2127]! } - public func PUSH_CHAT_MESSAGE_TEXT(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2128]!, self._r[2128]!, [_1, _2, _3]) - } - public var Common_OK: String { return self._s[2129]! } - public var Contacts_SortBy: String { return self._s[2130]! } - public var AutoNightTheme_PreferredTheme: String { return self._s[2131]! } - public func AutoDownloadSettings_OnFor(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2133]!, self._r[2133]!, [_0]) - } - public var CallFeedback_IncludeLogs: String { return self._s[2136]! } - public func External_OpenIn(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2137]!, self._r[2137]!, [_0]) - } - public var Passcode_AppLockedAlert: String { return self._s[2138]! } - public var TwoStepAuth_SetupPasswordTitle: String { return self._s[2139]! } - public var Wallet_Send_ErrorDecryptionFailed: String { return self._s[2140]! } - public var Channel_NotificationLoading: String { return self._s[2142]! } - public var Passport_Identity_DocumentNumber: String { return self._s[2143]! } - public var VoiceOver_Chat_PagePreview: String { return self._s[2144]! } - public var VoiceOver_Chat_OpenHint: String { return self._s[2145]! } - public var Weekday_ShortFriday: String { return self._s[2146]! } - public var Wallet_CreateInvoice_Title: String { return self._s[2147]! } - public var Conversation_TitleMute: String { return self._s[2148]! } - public var SettingsSearch_Synonyms_Notifications_GroupNotificationsSound: String { return self._s[2149]! } - public var ScheduledMessages_PollUnavailable: String { return self._s[2150]! } - public var DialogList_LanguageTooltip: String { return self._s[2151]! } - public var Channel_AdminLogFilter_EventsPinned: String { return self._s[2152]! } - public func DialogList_SingleUploadingVideoSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2153]!, self._r[2153]!, [_0]) - } - public var TwoStepAuth_SetupResendEmailCodeAlert: String { return self._s[2155]! } - public var Privacy_Calls_AlwaysAllow_Title: String { return self._s[2156]! } - public var Settings_EditVideo: String { return self._s[2157]! } - public var Stickers_FrequentlyUsed: String { return self._s[2158]! } - public var GroupPermission_Title: String { return self._s[2159]! } - public var AccessDenied_VideoMessageCamera: String { return self._s[2160]! } - public var Appearance_ThemeCarouselDay: String { return self._s[2161]! } - public func PUSH_CHAT_MESSAGE_AUDIO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2162]!, self._r[2162]!, [_1, _2]) - } - public var Passport_Identity_DocumentNumberPlaceholder: String { return self._s[2163]! } - public var Tour_Title6: String { return self._s[2164]! } - public var EmptyGroupInfo_Title: String { return self._s[2165]! } - public func Channel_AdminLog_MessageToggleSignaturesOn(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2166]!, self._r[2166]!, [_0]) - } - public var Passport_Language_sk: String { return self._s[2167]! } - public var VoiceOver_Chat_YourAnonymousPoll: String { return self._s[2168]! } - public var Preview_SaveToCameraRoll: String { return self._s[2169]! } - public var LogoutOptions_SetPasscodeTitle: String { return self._s[2170]! } - public var Passport_Address_TypeUtilityBillUploadScan: String { return self._s[2171]! } - public var Conversation_ContextMenuMore: String { return self._s[2172]! } - public var Conversation_ForwardAuthorHiddenTooltip: String { return self._s[2173]! } - public var Channel_AdminLog_CanBeAnonymous: String { return self._s[2174]! } - public var CallFeedback_ReasonSilentLocal: String { return self._s[2176]! } - public var UserInfo_NotificationsDisable: String { return self._s[2177]! } - public func Channel_AdminLog_EmptyFilterQueryText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2179]!, self._r[2179]!, [_0]) - } - public var SettingsSearch_Synonyms_EditProfile_Bio: String { return self._s[2180]! } - public func Date_ChatDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2182]!, self._r[2182]!, [_1, _2]) - } - public var WallpaperSearch_ColorPrefix: String { return self._s[2183]! } - public func Message_ForwardedPsa_covid(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2184]!, self._r[2184]!, [_0]) - } - public var Conversation_RestrictedMedia: String { return self._s[2186]! } - public var Group_MessageVideoUpdated: String { return self._s[2187]! } - public var NetworkUsageSettings_ResetStatsConfirmation: String { return self._s[2188]! } - public var GroupInfo_DeleteAndExit: String { return self._s[2189]! } - public var TwoFactorSetup_Email_Action: String { return self._s[2190]! } - public var Media_ShareThisVideo: String { return self._s[2192]! } - public var DialogList_Replies: String { return self._s[2193]! } - public func Conversation_Moderate_DeleteAllMessages(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2194]!, self._r[2194]!, [_0]) - } - public var CheckoutInfo_ShippingInfoAddress1: String { return self._s[2195]! } - public var Watch_Suggestion_OnMyWay: String { return self._s[2196]! } - public var CheckoutInfo_ShippingInfoAddress2: String { return self._s[2197]! } - public func PUSH_PINNED_POLL(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2198]!, self._r[2198]!, [_1, _2]) - } - public func GroupInfo_InvitationLinkAcceptChannel(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2199]!, self._r[2199]!, [_0]) - } - public var Channel_EditAdmin_PermissinAddAdminOff: String { return self._s[2200]! } - public var Conversation_WalletRequiredSetup: String { return self._s[2201]! } - public var ChatAdmins_AllMembersAreAdminsOnHelp: String { return self._s[2202]! } - public var ChatList_Search_NoResultsFitlerMedia: String { return self._s[2203]! } - public var Channel_Members_InviteLink: String { return self._s[2204]! } - public var Conversation_TapAndHoldToRecord: String { return self._s[2205]! } - public var Wallet_Info_Receive: String { return self._s[2206]! } - public var WatchRemote_AlertText: String { return self._s[2207]! } - public func Channel_DiscussionGroup_PrivateChannelLink(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2208]!, self._r[2208]!, [_1, _2]) - } - public var Conversation_Pin: String { return self._s[2209]! } - public var InfoPlist_NSMicrophoneUsageDescription: String { return self._s[2210]! } - public var Stickers_RemoveFromFavorites: String { return self._s[2211]! } - public func Notification_PinnedPollMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2212]!, self._r[2212]!, [_0]) - } - public var Appearance_AppIconFilled: String { return self._s[2213]! } - public var StickerPack_ErrorNotFound: String { return self._s[2214]! } - public func Channel_AdminLog_MessageRestrictedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2215]!, self._r[2215]!, [_1]) - } - public var Passport_Identity_AddIdentityCard: String { return self._s[2216]! } - public func PUSH_CHANNEL_MESSAGE_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2217]!, self._r[2217]!, [_1]) - } - public var Call_Camera: String { return self._s[2218]! } - public var GroupInfo_InviteLink_RevokeAlert_Text: String { return self._s[2219]! } - public var Group_Location_Info: String { return self._s[2220]! } - public var Watch_LastSeen_WithinAMonth: String { return self._s[2221]! } - public var UserInfo_NotificationsDefaultEnabled: String { return self._s[2222]! } - public func DialogList_PinLimitError(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2223]!, self._r[2223]!, [_0]) - } - public var Weekday_Yesterday: String { return self._s[2224]! } - public var TwoStepAuth_SetupPasswordEnterPasswordNew: String { return self._s[2225]! } - public var ArchivedPacksAlert_Title: String { return self._s[2226]! } - public var PeerInfo_PaneMembers: String { return self._s[2227]! } - public var PhotoEditor_SelectCoverFrame: String { return self._s[2228]! } - public var ContactInfo_PhoneLabelMain: String { return self._s[2229]! } - public func Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2230]!, self._r[2230]!, [_1, _2, _3]) - } - public var TwoFactorSetup_EmailVerification_ChangeAction: String { return self._s[2231]! } - public var Channel_DiscussionGroup: String { return self._s[2232]! } - public var EditTheme_Edit_Preview_IncomingReplyName: String { return self._s[2233]! } - public var Channel_EditAdmin_PermissionsHeader: String { return self._s[2235]! } - public var VoiceOver_MessageContextForward: String { return self._s[2236]! } - public var SocksProxySetup_TypeNone: String { return self._s[2237]! } - public var CreatePoll_MultipleChoiceQuizAlert: String { return self._s[2239]! } - public var ProfilePhoto_OpenInEditor: String { return self._s[2241]! } - public var WallpaperSearch_ColorPurple: String { return self._s[2242]! } - public var ChatListFolder_IncludeChatsTitle: String { return self._s[2243]! } - public var Group_Username_InvalidTooShort: String { return self._s[2244]! } - public func Login_EmailPhoneBody(_ _0: String, _ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2245]!, self._r[2245]!, [_0, _1, _2]) - } - public var Passport_Language_tk: String { return self._s[2246]! } - public var ConvertToSupergroup_Title: String { return self._s[2247]! } - public var Channel_BanUser_PermissionEmbedLinks: String { return self._s[2248]! } - public var Cache_KeepMediaHelp: String { return self._s[2249]! } - public var Channel_Management_Title: String { return self._s[2250]! } - public func PUSH_MESSAGE_PHOTO_SECRET(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2251]!, self._r[2251]!, [_1]) - } - public var Conversation_ForwardChats: String { return self._s[2252]! } - public var Passport_Language_bg: String { return self._s[2253]! } - public var SocksProxySetup_TypeSocks: String { return self._s[2254]! } - public var Permissions_PrivacyPolicy: String { return self._s[2255]! } - public var VoiceOver_Chat_YourMusic: String { return self._s[2256]! } - public var SettingsSearch_Synonyms_Notifications_ResetAllNotifications: String { return self._s[2257]! } - public var Conversation_EmptyGifPanelPlaceholder: String { return self._s[2258]! } - public var Conversation_ContextMenuOpenChannel: String { return self._s[2259]! } - public var Activity_UploadingVideo: String { return self._s[2260]! } - public var PrivacyPolicy_AgeVerificationAgree: String { return self._s[2262]! } - public var Wallet_Sending_Text: String { return self._s[2263]! } - public var SocksProxySetup_Credentials: String { return self._s[2265]! } - public var Preview_SaveGif: String { return self._s[2266]! } - public var Cache_Photos: String { return self._s[2267]! } - public var Conversation_ContextMenuCancelEditing: String { return self._s[2268]! } - public var Wallet_Intro_NotNow: String { return self._s[2269]! } - public var Contacts_FailedToSendInvitesMessage: String { return self._s[2270]! } - public var Passport_Language_lt: String { return self._s[2271]! } - public var Passport_DeleteDocument: String { return self._s[2272]! } - public var GroupInfo_SetGroupPhotoStop: String { return self._s[2273]! } - public var AccessDenied_VideoMessageMicrophone: String { return self._s[2274]! } - public func PeopleNearby_VisibleUntil(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2275]!, self._r[2275]!, [_0]) - } - public var AccessDenied_VideoCallCamera: String { return self._s[2276]! } - public func Channel_AdminLog_MessageDeleted(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2277]!, self._r[2277]!, [_0]) - } - public var PhotoEditor_SharpenTool: String { return self._s[2278]! } - public func PUSH_CHANNEL_MESSAGE_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2279]!, self._r[2279]!, [_1]) - } - public var DialogList_Unpin: String { return self._s[2280]! } - public var Stickers_NoStickersFound: String { return self._s[2281]! } - public var UserInfo_AddContact: String { return self._s[2283]! } - public func AddContact_SharedContactExceptionInfo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2285]!, self._r[2285]!, [_0]) - } - public func Notification_PinnedLocationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2286]!, self._r[2286]!, [_0]) - } - public var CallFeedback_VideoReasonDistorted: String { return self._s[2287]! } - public var Tour_Text2: String { return self._s[2288]! } - public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[2290]! } - public var Paint_Delete: String { return self._s[2292]! } - public var SettingsSearch_Synonyms_Notifications_InAppNotificationsVibrate: String { return self._s[2293]! } - public func PrivacySettings_LastSeenEverybodyMinus(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2295]!, self._r[2295]!, [_0]) - } - public var Privacy_Calls_NeverAllow_Title: String { return self._s[2296]! } - public var Notification_CallOutgoingShort: String { return self._s[2297]! } - public var Checkout_PasswordEntry_Title: String { return self._s[2298]! } - public var Channel_AdminLogFilter_AdminsAll: String { return self._s[2299]! } - public var Notification_MessageLifetime1m: String { return self._s[2300]! } - public var Wallet_TransactionInfo_CommentHeader: String { return self._s[2302]! } - public var BlockedUsers_AddNew: String { return self._s[2303]! } - public var Wallet_Intro_CreateErrorText: String { return self._s[2304]! } - public var FastTwoStepSetup_EmailSection: String { return self._s[2305]! } - public var Settings_SaveEditedPhotos: String { return self._s[2306]! } - public var GroupInfo_GroupNamePlaceholder: String { return self._s[2307]! } - public var Channel_AboutItem: String { return self._s[2308]! } - public var GroupInfo_InviteLink_RevokeLink: String { return self._s[2309]! } - public var Privacy_Calls_P2PNever: String { return self._s[2311]! } - public var Wallet_Weekday_Yesterday: String { return self._s[2312]! } - public var Passport_Language_uk: String { return self._s[2313]! } - public var NetworkUsageSettings_Wifi: String { return self._s[2314]! } - public var Conversation_Moderate_Report: String { return self._s[2315]! } - public var Wallpaper_ResetWallpapersConfirmation: String { return self._s[2316]! } - public var VoiceOver_Chat_SeenByRecipients: String { return self._s[2317]! } - public var Permissions_SiriText_v0: String { return self._s[2318]! } - public var Theme_Colors_Background: String { return self._s[2319]! } - public var Notification_CallMissed: String { return self._s[2320]! } - public var Stats_ZoomOut: String { return self._s[2321]! } - public var Profile_AddToExisting: String { return self._s[2322]! } - public var Passport_FieldAddressUploadHelp: String { return self._s[2325]! } - public var Undo_DeletedChannel: String { return self._s[2326]! } - public func Channel_AdminLog_MessagePinned(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2327]!, self._r[2327]!, [_0]) - } - public var Login_ResetAccountProtected_TimerTitle: String { return self._s[2328]! } - public var Map_LiveLocationGroupDescription: String { return self._s[2329]! } - public var Passport_InfoFAQ_URL: String { return self._s[2330]! } - public var IntentsSettings_SuggestedChats: String { return self._s[2332]! } - public func PUSH_MESSAGE_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2333]!, self._r[2333]!, [_1]) - } - public var State_connecting: String { return self._s[2334]! } - public var Passport_Identity_Country: String { return self._s[2335]! } - public var Passport_PasswordDescription: String { return self._s[2336]! } - public var ChatList_PsaLabel_covid: String { return self._s[2337]! } - public func PUSH_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2338]!, self._r[2338]!, [_1]) - } - public var Contacts_AddPeopleNearby: String { return self._s[2339]! } - public var OwnershipTransfer_SetupTwoStepAuth: String { return self._s[2340]! } - public var ClearCache_Description: String { return self._s[2341]! } - public var Localization_LanguageName: String { return self._s[2342]! } - public func UserInfo_UnblockConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2343]!, self._r[2343]!, [_0]) - } - public var ChatList_TabIconFoldersTooltipEmptyFolders: String { return self._s[2344]! } - public var UserInfo_CreateNewContact: String { return self._s[2345]! } - public var Channel_Stickers_NotFound: String { return self._s[2346]! } - public var Watch_Message_Poll: String { return self._s[2347]! } - public var Privacy_Forwards_WhoCanForward: String { return self._s[2348]! } - public func Notification_Kicked(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2349]!, self._r[2349]!, [_0, _1]) - } - public var Login_InfoDeletePhoto: String { return self._s[2350]! } - public var Appearance_ThemePreview_ChatList_6_Name: String { return self._s[2351]! } - public var InstantPage_FeedbackButton: String { return self._s[2352]! } - public var Appearance_PreviewReplyText: String { return self._s[2353]! } - public var Passport_FieldPhoneHelp: String { return self._s[2354]! } - public var Group_ErrorAddTooMuchBots: String { return self._s[2355]! } - public var Media_SendingOptionsTooltip: String { return self._s[2356]! } - public var ScheduledMessages_ScheduledOnline: String { return self._s[2357]! } - public var Notifications_Badge: String { return self._s[2358]! } - public var VoiceOver_Chat_VideoMessage: String { return self._s[2359]! } - public var TwoStepAuth_RecoveryCodeExpired: String { return self._s[2360]! } - public var Wallet_Configuration_ApplyErrorTitle: String { return self._s[2361]! } - public func Notification_PinnedPhotoMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2363]!, self._r[2363]!, [_0]) - } - public var Wallet_Info_Send: String { return self._s[2364]! } - public var Passport_InfoLearnMore: String { return self._s[2365]! } - public var EnterPasscode_EnterTitle: String { return self._s[2366]! } - public var Appearance_EditTheme: String { return self._s[2367]! } - public var EditTheme_Expand_BottomInfo: String { return self._s[2368]! } - public var Stats_FollowersTitle: String { return self._s[2369]! } - public var Passport_Identity_SurnamePlaceholder: String { return self._s[2370]! } - public var Channel_Subscribers_Title: String { return self._s[2371]! } - public var Group_ErrorSupergroupConversionNotPossible: String { return self._s[2372]! } - public func Wallet_Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2373]!, self._r[2373]!, [_1, _2, _3]) - } - public var EditTheme_ThemeTemplateAlertTitle: String { return self._s[2374]! } - public var Wallet_Intro_CreateWallet: String { return self._s[2375]! } - public var Conversation_AddToReadingList: String { return self._s[2376]! } - public var EditTheme_Create_Preview_IncomingText: String { return self._s[2377]! } - public func Notifications_ExceptionsChangeSound(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2378]!, self._r[2378]!, [_0]) - } - public var Group_AdminLog_EmptyText: String { return self._s[2379]! } - public var Passport_Identity_EditInternalPassport: String { return self._s[2380]! } - public var Wallet_Sending_Title: String { return self._s[2381]! } - public var Watch_Location_Current: String { return self._s[2382]! } - public var PrivacyPolicy_Title: String { return self._s[2383]! } - public var Privacy_GroupsAndChannels_CustomHelp: String { return self._s[2390]! } - public var Channel_TypeSetup_Title: String { return self._s[2393]! } - public var Appearance_PreviewReplyAuthor: String { return self._s[2394]! } - public var Passport_Language_ja: String { return self._s[2395]! } - public var ReportPeer_ReasonSpam: String { return self._s[2396]! } - public var Privacy_PaymentsClearInfoHelp: String { return self._s[2397]! } - public var Conversation_EditingMessageMediaEditCurrentPhoto: String { return self._s[2399]! } - public var Channel_AdminLog_ChangeInfo: String { return self._s[2400]! } - public var ChatListFolder_NameNonContacts: String { return self._s[2401]! } - public var Call_Audio: String { return self._s[2402]! } - public var PhotoEditor_CurvesGreen: String { return self._s[2403]! } - public var Wallet_Updated_JustNow: String { return self._s[2404]! } - public var ChatList_Search_NoResultsFitlerFiles: String { return self._s[2405]! } - public var Settings_PrivacySettings: String { return self._s[2406]! } - public var Stats_Followers: String { return self._s[2407]! } - public var Notifications_AddExceptionTitle: String { return self._s[2408]! } - public var TwoFactorSetup_Password_Title: String { return self._s[2409]! } - public var ChannelMembers_WhoCanAddMembersAllHelp: String { return self._s[2410]! } - public var OldChannels_NoticeText: String { return self._s[2411]! } - public var Conversation_SavedMessages: String { return self._s[2412]! } - public func Conversation_PeerNearbyTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2414]!, self._r[2414]!, [_1, _2]) - } - public var Passport_Address_TypeResidentialAddress: String { return self._s[2415]! } - public func Wallet_Info_TransactionBlockchainFee(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2416]!, self._r[2416]!, [_0]) - } - public var Appearance_ThemeNightBlue: String { return self._s[2417]! } - public var Notification_ChannelInviterSelf: String { return self._s[2418]! } - public var Watch_UserInfo_Service: String { return self._s[2420]! } - public var ChatList_Context_Back: String { return self._s[2421]! } - public var Passport_Email_Title: String { return self._s[2422]! } - public var Wallet_Month_ShortDecember: String { return self._s[2423]! } - public var Stats_GroupTopAdmin_Promote: String { return self._s[2424]! } - public func PUSH_PINNED_INVOICE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2425]!, self._r[2425]!, [_1]) - } - public var Conversation_UnsupportedMedia: String { return self._s[2426]! } - public var Passport_Address_OneOfTypePassportRegistration: String { return self._s[2427]! } - public var Privacy_TopPeersHelp: String { return self._s[2429]! } - public var Privacy_Forwards_AlwaysLink: String { return self._s[2430]! } - public var Notifications_Badge_CountUnreadMessages_InfoOn: String { return self._s[2431]! } - public var Permissions_NotificationsTitle_v0: String { return self._s[2432]! } - public var Notification_PassportValueProofOfAddress: String { return self._s[2433]! } - public var Map_Map: String { return self._s[2434]! } - public var WallpaperSearch_ColorBlue: String { return self._s[2435]! } - public var Privacy_Calls_CustomShareHelp: String { return self._s[2436]! } - public var PhotoEditor_BlurToolRadial: String { return self._s[2437]! } - public var ChatList_Search_FilterMusic: String { return self._s[2438]! } - public var SettingsSearch_Synonyms_Data_AutoplayGifs: String { return self._s[2439]! } - public var Privacy_PaymentsClear_ShippingInfo: String { return self._s[2440]! } - public var Settings_LogoutConfirmationTitle: String { return self._s[2442]! } - public func PUSH_CHANNEL_MESSAGE_VIDEOS(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2443]!, self._r[2443]!, [_1, _2]) - } - public func Notification_ChangedGroupPhoto(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2444]!, self._r[2444]!, [_0]) - } - public var Channel_Username_RevokeExistingUsernamesInfo: String { return self._s[2445]! } - public var Group_Username_CreatePublicLinkHelp: String { return self._s[2446]! } - public var GroupInfo_Location: String { return self._s[2448]! } - public var Passport_Language_ka: String { return self._s[2449]! } - public func TwoStepAuth_SetupPendingEmail(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2450]!, self._r[2450]!, [_0]) - } - public var Conversation_ContextMenuOpenChannelProfile: String { return self._s[2451]! } - public var ScheduledMessages_ClearAllConfirmation: String { return self._s[2454]! } - public var DialogList_SearchSectionRecent: String { return self._s[2455]! } - public var Passport_Address_OneOfTypeTemporaryRegistration: String { return self._s[2456]! } - public var Conversation_Timer_Send: String { return self._s[2457]! } - public var ChatState_Updating: String { return self._s[2459]! } - public var ChannelMembers_WhoCanAddMembers: String { return self._s[2460]! } - public var ChannelInfo_DeleteGroup: String { return self._s[2461]! } - public var TwoStepAuth_RecoveryFailed: String { return self._s[2462]! } - public var Channel_OwnershipTransfer_EnterPassword: String { return self._s[2463]! } - public var ChatList_Search_NoResults: String { return self._s[2464]! } - public var ChatListFolderSettings_AddRecommended: String { return self._s[2466]! } - public var ChangePhoneNumberCode_Called: String { return self._s[2467]! } - public var PeerInfo_GroupAboutItem: String { return self._s[2468]! } - public var Wallet_Info_YourBalance: String { return self._s[2470]! } - public func LiveLocationUpdated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2471]!, self._r[2471]!, [_0]) - } - public var PrivacySettings_AuthSessions: String { return self._s[2472]! } - public var Passport_Address_Postcode: String { return self._s[2473]! } - public var VoiceOver_Chat_YourVideoMessage: String { return self._s[2474]! } - public var Passport_Address_Street2Placeholder: String { return self._s[2475]! } - public var Group_Location_Title: String { return self._s[2476]! } - public var SettingsSearch_Synonyms_Data_AutoDownloadReset: String { return self._s[2477]! } - public var PeopleNearby_UsersEmpty: String { return self._s[2478]! } - public var SettingsSearch_Synonyms_Data_Title: String { return self._s[2480]! } - public func Checkout_PasswordEntry_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2482]!, self._r[2482]!, [_0]) - } - public var Proxy_TooltipUnavailable: String { return self._s[2483]! } - public var Map_Search: String { return self._s[2484]! } - public var AutoDownloadSettings_TypeContacts: String { return self._s[2485]! } - public var Conversation_SearchByName_Prefix: String { return self._s[2486]! } - public func Channel_AdminLog_MessageToggleSignaturesOff(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2487]!, self._r[2487]!, [_0]) - } - public var TwoStepAuth_EmailAddSuccess: String { return self._s[2488]! } - public var ProfilePhoto_MainPhoto: String { return self._s[2489]! } - public var SettingsSearch_Synonyms_Notifications_InAppNotificationsSound: String { return self._s[2490]! } - public var SharedMedia_EmptyMusicText: String { return self._s[2491]! } - public var ChatSettings_AutoDownloadPhotos: String { return self._s[2492]! } - public var NetworkUsageSettings_BytesReceived: String { return self._s[2493]! } - public var Channel_AdminLog_EmptyText: String { return self._s[2494]! } - public var Channel_BanUser_PermissionSendMessages: String { return self._s[2495]! } - public var Undo_ChatDeletedForBothSides: String { return self._s[2496]! } - public var Notifications_GroupNotifications: String { return self._s[2497]! } - public var Wallet_Configuration_BlockchainNameChangedTitle: String { return self._s[2498]! } - public var Wallet_AccessDenied_Title: String { return self._s[2499]! } - public var AccessDenied_SaveMedia: String { return self._s[2500]! } - public var GroupInfo_LabelOwner: String { return self._s[2501]! } - public var Passport_Language_id: String { return self._s[2502]! } - public var ChatSettings_AutoDownloadTitle: String { return self._s[2503]! } - public var Conversation_UnpinMessageAlert: String { return self._s[2504]! } - public func LiveLocationUpdated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2505]!, self._r[2505]!, [_0]) - } - public func Call_RemoteVideoPaused(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2506]!, self._r[2506]!, [_0]) - } - public var TwoFactorSetup_Done_Text: String { return self._s[2507]! } - public func LastSeen_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2508]!, self._r[2508]!, [_0]) - } - public var Wallet_Words_Title: String { return self._s[2509]! } - public var NetworkUsageSettings_BytesSent: String { return self._s[2510]! } - public var OwnershipTransfer_Transfer: String { return self._s[2511]! } - public func Notification_Exceptions_Sound(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2512]!, self._r[2512]!, [_0]) - } - public var Passport_Language_pt: String { return self._s[2513]! } - public var PrivacySettings_WebSessions: String { return self._s[2514]! } - public var PrivacyPolicy_DeclineDeleteNow: String { return self._s[2516]! } - public var TwoFactorSetup_Hint_Title: String { return self._s[2517]! } - public func Notification_Joined(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2518]!, self._r[2518]!, [_0]) - } - public var Group_Username_RemoveExistingUsernamesInfo: String { return self._s[2519]! } - public var PrivacyLastSeenSettings_CustomShareSettings_Delete: String { return self._s[2520]! } - public var AutoNightTheme_Scheduled: String { return self._s[2521]! } - public var CreatePoll_ExplanationHeader: String { return self._s[2522]! } - public var Calls_TabTitle: String { return self._s[2523]! } - public var ChatList_UndoArchiveHiddenText: String { return self._s[2524]! } - public var Notification_VideoCallCanceled: String { return self._s[2525]! } - public var Login_CodeSentInternal: String { return self._s[2526]! } - public var SettingsSearch_Synonyms_Proxy_AddProxy: String { return self._s[2527]! } - public var Call_RecordingDisabledMessage: String { return self._s[2529]! } - public var AutoDownloadSettings_TypeChannels: String { return self._s[2531]! } - public var Wallet_Configuration_BlockchainNameChangedProceed: String { return self._s[2532]! } - public var Channel_Info_Stickers: String { return self._s[2533]! } - public var Passport_DeleteAddressConfirmation: String { return self._s[2534]! } - public func Conversation_PeerNearbyDistance(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2535]!, self._r[2535]!, [_1, _2]) - } - public var ChannelMembers_WhoCanAddMembers_Admins: String { return self._s[2536]! } - public func Call_StatusOngoing(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2537]!, self._r[2537]!, [_0]) - } - public var Passport_DiscardMessageTitle: String { return self._s[2538]! } - public var Localization_LanguageOther: String { return self._s[2539]! } - public var Conversation_EncryptionCanceled: String { return self._s[2540]! } - public var ChatSettings_AutomaticPhotoDownload: String { return self._s[2541]! } - public func Notification_SecretChatMessageScreenshot(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2543]!, self._r[2543]!, [_0]) - } - public var Target_InviteToGroupErrorAlreadyInvited: String { return self._s[2545]! } - public var SocksProxySetup_SavedProxies: String { return self._s[2546]! } - public func ApplyLanguage_ChangeLanguageAlreadyActive(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2547]!, self._r[2547]!, [_1]) - } - public var Conversation_ScamWarning: String { return self._s[2548]! } - public var Channel_AdminLog_InfoPanelAlertTitle: String { return self._s[2549]! } - public var LocalGroup_Title: String { return self._s[2550]! } - public var SettingsSearch_Synonyms_Notifications_MessageNotificationsAlert: String { return self._s[2551]! } - public var SettingsSearch_Synonyms_Privacy_PasscodeAndFaceId: String { return self._s[2552]! } - public var Login_PhoneFloodError: String { return self._s[2553]! } - public var Username_InvalidTaken: String { return self._s[2555]! } - public var SocksProxySetup_AddProxy: String { return self._s[2557]! } - public var PrivacyLastSeenSettings_WhoCanSeeMyTimestamp: String { return self._s[2558]! } - public var MediaPicker_UngroupDescription: String { return self._s[2559]! } - public var Login_CodeExpired: String { return self._s[2560]! } - public var Localization_ChooseLanguage: String { return self._s[2561]! } - public var Checkout_NewCard_PostcodePlaceholder: String { return self._s[2562]! } - public func ChangePhone_ErrorOccupied(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2563]!, self._r[2563]!, [_0]) - } - public func Channel_DiscussionGroup_HeaderSet(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2564]!, self._r[2564]!, [_0]) - } - public var ReportPeer_ReasonOther_Title: String { return self._s[2566]! } - public var Conversation_ScheduleMessage_Title: String { return self._s[2567]! } - public var PeerInfo_ButtonDiscuss: String { return self._s[2568]! } - public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedPublicGroups: String { return self._s[2569]! } - public var Call_StatusNoAnswer: String { return self._s[2570]! } - public var ScheduledMessages_DeleteMany: String { return self._s[2572]! } - public var Channel_DiscussionGroupInfo: String { return self._s[2573]! } - public var Conversation_UnarchiveDone: String { return self._s[2574]! } - public var LogoutOptions_AddAccountText: String { return self._s[2575]! } - public var Message_PinnedContactMessage: String { return self._s[2576]! } - public func FileSize_GB(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2578]!, self._r[2578]!, [_0]) - } - public var Stats_GroupLanguagesTitle: String { return self._s[2579]! } - public var Passport_FieldAddressHelp: String { return self._s[2580]! } - public func Passport_FieldOneOf_Or(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2581]!, self._r[2581]!, [_1, _2]) - } - public var ChatSettings_OpenLinksIn: String { return self._s[2583]! } - public var TwoFactorSetup_Hint_SkipAction: String { return self._s[2584]! } - public var Message_Photo: String { return self._s[2585]! } - public var MediaPicker_AddCaption: String { return self._s[2587]! } - public var LogoutOptions_Title: String { return self._s[2588]! } - public func PUSH_PINNED_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2589]!, self._r[2589]!, [_1]) - } - public var Conversation_StatusKickedFromGroup: String { return self._s[2590]! } - public var Channel_AdminLogFilter_AdminsTitle: String { return self._s[2591]! } - public var ChatList_DeleteSavedMessagesConfirmationTitle: String { return self._s[2592]! } - public var Channel_AdminLogFilter_Title: String { return self._s[2593]! } - public var Passport_Address_TypeRentalAgreementUploadScan: String { return self._s[2594]! } - public func Wallet_Info_TransactionDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2595]!, self._r[2595]!, [_1, _2]) - } - public var Compose_GroupTokenListPlaceholder: String { return self._s[2596]! } - public var Wallet_Words_NotDoneResponse: String { return self._s[2597]! } - public var Notifications_MessageNotificationsExceptions: String { return self._s[2598]! } - public var ChannelIntro_Title: String { return self._s[2599]! } - public var Stickers_Install: String { return self._s[2600]! } - public func VoiceOver_Chat_FileFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2601]!, self._r[2601]!, [_0]) - } - public var EditTheme_Create_Preview_IncomingReplyText: String { return self._s[2602]! } - public var Conversation_SwipeToReplyHintTitle: String { return self._s[2604]! } - public var Settings_Username: String { return self._s[2607]! } - public var FastTwoStepSetup_Title: String { return self._s[2608]! } - public var Notifications_Badge_CountUnreadMessages_InfoOff: String { return self._s[2609]! } - public var SettingsSearch_Synonyms_Privacy_Title: String { return self._s[2610]! } - public var Passport_Identity_IssueDatePlaceholder: String { return self._s[2611]! } - public var CallFeedback_ReasonEcho: String { return self._s[2612]! } - public func Time_MonthOfYear_m1(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2613]!, self._r[2613]!, [_0]) - } - public var Conversation_OpenBotLinkTitle: String { return self._s[2614]! } - public var SocksProxySetup_Title: String { return self._s[2615]! } - public var CallFeedback_Success: String { return self._s[2616]! } - public var WallpaperPreview_SwipeTopText: String { return self._s[2618]! } - public var InstantPage_AutoNightTheme: String { return self._s[2620]! } - public var Watch_Conversation_Reply: String { return self._s[2621]! } - public var WallpaperPreview_Pattern: String { return self._s[2622]! } - public var CheckoutInfo_ReceiverInfoEmail: String { return self._s[2623]! } - public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[2624]! } - public func Conversation_DeleteMessagesFor(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2625]!, self._r[2625]!, [_0]) - } - public var AutoDownloadSettings_TypeGroupChats: String { return self._s[2626]! } - public var DialogList_SavedMessagesTooltip: String { return self._s[2628]! } - public var Update_Title: String { return self._s[2629]! } - public var Conversation_ShareMyPhoneNumber: String { return self._s[2630]! } - public func Wallet_WordCheck_Text(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2631]!, self._r[2631]!, [_1, _2, _3]) - } - public var Wallet_SecureStorageReset_BiometryTouchId: String { return self._s[2632]! } - public var WallpaperPreview_CropTopText: String { return self._s[2634]! } - public var Channel_EditMessageErrorGeneric: String { return self._s[2635]! } - public var AccessDenied_LocationAlwaysDenied: String { return self._s[2636]! } - public var ChatListFolder_DiscardCancel: String { return self._s[2637]! } - public var Message_PinnedPhotoMessage: String { return self._s[2638]! } - public var Appearance_ThemeDayClassic: String { return self._s[2639]! } - public var SocksProxySetup_ProxySocks5: String { return self._s[2640]! } - public var AccessDenied_Wallpapers: String { return self._s[2645]! } - public func Channel_AdminLog_MessageChangedGroupAbout(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2646]!, self._r[2646]!, [_0]) - } - public var Weekday_Sunday: String { return self._s[2647]! } - public var SettingsSearch_Synonyms_Privacy_GroupsAndChannels: String { return self._s[2649]! } - public var PeopleNearby_MakeVisibleDescription: String { return self._s[2650]! } - public var AccessDenied_LocationDisabled: String { return self._s[2651]! } - public var Tour_Text3: String { return self._s[2652]! } - public var AuthSessions_AddDevice_ScanTitle: String { return self._s[2653]! } - public func Time_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2654]!, self._r[2654]!, [_0]) - } - public var Privacy_SecretChatsLinkPreviewsHelp: String { return self._s[2655]! } - public var Conversation_ClearCache: String { return self._s[2656]! } - public var StickerPacksSettings_ArchivedMasks_Info: String { return self._s[2657]! } - public var ChatList_Tabs_AllChats: String { return self._s[2658]! } - public var DialogList_RecentTitlePeople: String { return self._s[2659]! } - public var Stickers_AddToFavorites: String { return self._s[2660]! } - public var ChatList_Context_RemoveFromFolder: String { return self._s[2661]! } - public var Settings_RemoveVideo: String { return self._s[2662]! } - public var PhotoEditor_CropAspectRatioSquare: String { return self._s[2663]! } - public var ConversationProfile_LeaveDeleteAndExit: String { return self._s[2664]! } - public var VoiceOver_Chat_YourFile: String { return self._s[2665]! } - public var SettingsSearch_Synonyms_Privacy_Forwards: String { return self._s[2666]! } - public var Group_OwnershipTransfer_ErrorPrivacyRestricted: String { return self._s[2667]! } - public var Channel_AdminLog_AddMembers: String { return self._s[2668]! } - public var Map_SendThisLocation: String { return self._s[2670]! } - public var TwoStepAuth_EmailSkipAlert: String { return self._s[2672]! } - public var IntentsSettings_SuggestedChatsPrivateChats: String { return self._s[2673]! } - public var CloudStorage_Title: String { return self._s[2674]! } - public var TwoFactorSetup_Password_Action: String { return self._s[2675]! } - public var TwoStepAuth_ConfirmationText: String { return self._s[2676]! } - public var Passport_Address_EditTemporaryRegistration: String { return self._s[2678]! } - public var Undo_LeftGroup: String { return self._s[2679]! } - public var Conversation_StopLiveLocation: String { return self._s[2681]! } - public var NotificationSettings_ShowNotificationsFromAccountsSection: String { return self._s[2682]! } - public var Message_PinnedInvoice: String { return self._s[2683]! } - public var ApplyLanguage_LanguageNotSupportedError: String { return self._s[2684]! } - public func PUSH_CHAT_MESSAGE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2685]!, self._r[2685]!, [_1, _2]) - } - public func Notification_PinnedAudioMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2686]!, self._r[2686]!, [_0]) - } - public var Weekday_Tuesday: String { return self._s[2687]! } - public var ChangePhoneNumberCode_Code: String { return self._s[2688]! } - public var VoiceOver_Chat_YourMessage: String { return self._s[2689]! } - public var Calls_CallTabDescription: String { return self._s[2690]! } - public var SocksProxySetup_UseProxy: String { return self._s[2692]! } - public var SettingsSearch_Synonyms_Stickers_Title: String { return self._s[2693]! } - public var PasscodeSettings_AlphanumericCode: String { return self._s[2694]! } - public var VoiceOver_Chat_YourVideo: String { return self._s[2695]! } - public var ChannelMembers_WhoCanAddMembersAdminsHelp: String { return self._s[2697]! } - public var SettingsSearch_Synonyms_Privacy_DeleteAccountIfAwayFor: String { return self._s[2698]! } - public var Exceptions_AddToExceptions: String { return self._s[2699]! } - public var UserInfo_Title: String { return self._s[2700]! } - public var Passport_DeleteDocumentConfirmation: String { return self._s[2702]! } - public var ChatList_Unmute: String { return self._s[2704]! } - public var SettingsSearch_Synonyms_Privacy_Data_ContactsSync: String { return self._s[2705]! } - public var Stats_GroupTopPostersTitle: String { return self._s[2706]! } - public var Username_CheckingUsername: String { return self._s[2707]! } - public var WallpaperColors_SetCustomColor: String { return self._s[2708]! } - public var AuthSessions_AddedDeviceTerminate: String { return self._s[2712]! } - public var Privacy_ProfilePhoto_CustomHelp: String { return self._s[2713]! } - public var Settings_ChangePhoneNumber: String { return self._s[2714]! } - public var PeerInfo_PaneLinks: String { return self._s[2715]! } - public var Appearance_ThemePreview_ChatList_1_Text: String { return self._s[2718]! } - public var Channel_EditAdmin_PermissionInviteSubscribers: String { return self._s[2720]! } - public var LogoutOptions_ChangePhoneNumberText: String { return self._s[2721]! } - public var VoiceOver_Media_PlaybackPause: String { return self._s[2722]! } - public var Wallet_RestoreFailed_Title: String { return self._s[2723]! } - public var Stats_FollowersBySourceTitle: String { return self._s[2725]! } - public func Conversation_ScheduleMessage_SendOn(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2726]!, self._r[2726]!, [_0, _1]) - } - public var Compose_NewEncryptedChatTitle: String { return self._s[2727]! } - public func ShareFileTip_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2732]!, self._r[2732]!, [_0]) - } - public func PUSH_MESSAGE_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2733]!, self._r[2733]!, [_1]) - } - public var Group_Setup_BasicHistoryHiddenHelp: String { return self._s[2734]! } - public func TwoStepAuth_RecoveryEmailUnavailable(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2735]!, self._r[2735]!, [_0]) - } - public var Conversation_OpenBotLinkOpen: String { return self._s[2736]! } - public var VoiceOver_Chat_RecordModeVoiceMessage: String { return self._s[2737]! } - public var PrivacySettings_LastSeen: String { return self._s[2739]! } - public var SettingsSearch_Synonyms_Privacy_Passcode: String { return self._s[2740]! } - public var Theme_Colors_Proceed: String { return self._s[2741]! } - public var UserInfo_ScamBotWarning: String { return self._s[2742]! } - public var LogoutOptions_LogOut: String { return self._s[2743]! } - public var Conversation_SendMessage: String { return self._s[2744]! } - public var Passport_Address_Region: String { return self._s[2746]! } - public var MediaPicker_CameraRoll: String { return self._s[2748]! } - public func VoiceOver_Chat_ForwardedFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2749]!, self._r[2749]!, [_0]) - } - public var Call_ReportSend: String { return self._s[2751]! } - public var Month_ShortJune: String { return self._s[2752]! } - public var AutoDownloadSettings_GroupChats: String { return self._s[2753]! } - public func Channel_AdminLog_CaptionEdited(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2755]!, self._r[2755]!, [_0]) - } - public var TwoStepAuth_DisableSuccess: String { return self._s[2756]! } - public var Cache_KeepMedia: String { return self._s[2757]! } - public func Date_ChatDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2758]!, self._r[2758]!, [_1, _2, _3]) - } - public var Wallet_Alert_OK: String { return self._s[2759]! } - public var Appearance_LargeEmoji: String { return self._s[2760]! } - public func Notification_NewAuthDetected(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String, _ _6: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2761]!, self._r[2761]!, [_1, _2, _3, _4, _5, _6]) - } - public var Chat_AttachmentMultipleForwardDisabled: String { return self._s[2762]! } - public var Wallet_Navigation_Close: String { return self._s[2763]! } - public var Call_CameraConfirmationText: String { return self._s[2764]! } - public func AuthSessions_AppUnofficial(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2766]!, self._r[2766]!, [_0]) - } - public var VoiceOver_MessageContextReport: String { return self._s[2768]! } - public var ChatListFolder_ExcludeChatsTitle: String { return self._s[2769]! } - public var NotificationsSound_Tritone: String { return self._s[2771]! } - public var Wallet_Configuration_BlockchainIdHeader: String { return self._s[2772]! } - public var Notifications_InAppNotificationsPreview: String { return self._s[2775]! } - public var Stats_GroupTopAdmin_Actions: String { return self._s[2776]! } - public var PeerInfo_AddToContacts: String { return self._s[2777]! } - public var AccessDenied_Title: String { return self._s[2778]! } - public var Tour_Title1: String { return self._s[2779]! } - public var VoiceOver_AttachMedia: String { return self._s[2780]! } - public func SharedMedia_SearchNoResultsDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2782]!, self._r[2782]!, [_0]) - } - public var Chat_Gifs_SavedSectionHeader: String { return self._s[2783]! } - public var LogoutOptions_ChangePhoneNumberTitle: String { return self._s[2784]! } - public func Passport_Scans_ScanIndex(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2785]!, self._r[2785]!, [_0]) - } - public var Channel_AdminLog_MessagePreviousLink: String { return self._s[2786]! } - public var Wallet_Send_AddressText: String { return self._s[2787]! } - public var OldChannels_Title: String { return self._s[2788]! } - public var LoginPassword_FloodError: String { return self._s[2789]! } - public var Checkout_ErrorPaymentFailed: String { return self._s[2791]! } - public func Time_MonthOfYear_m7(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2792]!, self._r[2792]!, [_0]) - } - public var VoiceOver_Media_PlaybackPlay: String { return self._s[2795]! } - public var Passport_CorrectErrors: String { return self._s[2797]! } - public func PUSH_CHAT_PHOTO_EDITED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2798]!, self._r[2798]!, [_1, _2]) - } - public var ChatListFolderSettings_Title: String { return self._s[2799]! } - public func AutoDownloadSettings_UpToFor(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2800]!, self._r[2800]!, [_1, _2]) - } - public var PhotoEditor_HighlightsTool: String { return self._s[2801]! } - public var Contacts_NotRegisteredSection: String { return self._s[2804]! } - public func PUSH_PINNED_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2805]!, self._r[2805]!, [_1]) - } - public var User_DeletedAccount: String { return self._s[2806]! } - public var Conversation_ViewContactDetails: String { return self._s[2807]! } - public var WebSearch_GIFs: String { return self._s[2808]! } - public var ChatList_DeleteSavedMessagesConfirmationAction: String { return self._s[2809]! } - public var Appearance_PreviewOutgoingText: String { return self._s[2810]! } - public var Calls_CallTabTitle: String { return self._s[2811]! } - public func LastSeen_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2812]!, self._r[2812]!, [_0]) - } - public var Channel_Status: String { return self._s[2813]! } - public var Conversation_SendMessageErrorGroupRestricted: String { return self._s[2815]! } - public var VoiceOver_Chat_OptionSelected: String { return self._s[2816]! } - public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsAlert: String { return self._s[2817]! } - public func ClearCache_Success(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2818]!, self._r[2818]!, [_0, _1]) - } - public var Passport_Identity_ExpiryDateNone: String { return self._s[2820]! } - public var Your_cards_expiration_month_is_invalid: String { return self._s[2822]! } - public var Month_ShortDecember: String { return self._s[2823]! } - public var Username_Help: String { return self._s[2824]! } - public var Login_InfoAvatarAdd: String { return self._s[2825]! } - public var Month_ShortMay: String { return self._s[2826]! } - public var DialogList_UnknownPinLimitError: String { return self._s[2827]! } - public var PasscodeSettings_AutoLock_IfAwayFor_5hours: String { return self._s[2828]! } - public var TwoStepAuth_EnabledSuccess: String { return self._s[2829]! } - public var Weekday_ShortSunday: String { return self._s[2830]! } - public var Channel_Username_InvalidTooShort: String { return self._s[2831]! } - public var AuthSessions_TerminateSession: String { return self._s[2832]! } - public var Passport_Identity_FilesTitle: String { return self._s[2833]! } - public func Notification_PinnedRoundMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2834]!, self._r[2834]!, [_0]) - } - public var PeopleNearby_MakeVisible: String { return self._s[2836]! } - public func Conversation_RestrictedMediaTimed(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2837]!, self._r[2837]!, [_0]) - } - public func Notification_MessageLifetimeChanged(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2838]!, self._r[2838]!, [_1, _2]) - } - public func GroupInfo_AddParticipantConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2839]!, self._r[2839]!, [_0]) - } - public var PrivacyPolicy_DeclineDeclineAndDelete: String { return self._s[2840]! } - public var Conversation_ContextMenuForward: String { return self._s[2841]! } - public func PUSH_CHAT_MESSAGE_QUIZ(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2843]!, self._r[2843]!, [_1, _2, _3]) - } - public var Notification_GroupInviterSelf: String { return self._s[2844]! } - public var Privacy_Forwards_NeverLink: String { return self._s[2845]! } - public var AuthSessions_CurrentSession: String { return self._s[2846]! } - public var Passport_Address_EditPassportRegistration: String { return self._s[2847]! } - public var ChannelInfo_DeleteChannelConfirmation: String { return self._s[2848]! } - public var ChatSearch_ResultsTooltip: String { return self._s[2850]! } - public var CheckoutInfo_Pay: String { return self._s[2851]! } - public func Channel_AdminLog_MessageChangedChannelUsername(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2853]!, self._r[2853]!, [_0]) - } - public var GroupInfo_AddParticipant: String { return self._s[2854]! } - public var GroupPermission_ApplyAlertAction: String { return self._s[2855]! } - public var ChatList_UndoArchiveText1: String { return self._s[2856]! } - public var Localization_LanguageCustom: String { return self._s[2857]! } - public var SettingsSearch_Synonyms_Passport: String { return self._s[2858]! } - public var Settings_UsernameEmpty: String { return self._s[2859]! } - public var Settings_FAQ_URL: String { return self._s[2860]! } - public var Common_Select: String { return self._s[2862]! } - public var Notification_MessageLifetimeRemovedOutgoing: String { return self._s[2863]! } - public var Notification_PassportValueAddress: String { return self._s[2864]! } - public var Conversation_MessageDialogDelete: String { return self._s[2865]! } - public var Map_OpenInYandexNavigator: String { return self._s[2867]! } - public var DialogList_SearchSectionDialogs: String { return self._s[2868]! } - public var AccessDenied_Contacts: String { return self._s[2869]! } - public var SettingsSearch_Synonyms_Privacy_Data_DeleteDrafts: String { return self._s[2871]! } - public var Passport_ScanPassportHelp: String { return self._s[2872]! } - public var ChatListFolder_NameChannels: String { return self._s[2873]! } - public var Appearance_ThemePreview_Chat_5_Text: String { return self._s[2874]! } - public func Channel_OwnershipTransfer_TransferCompleted(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2875]!, self._r[2875]!, [_1, _2]) - } - public var Checkout_ErrorInvoiceAlreadyPaid: String { return self._s[2876]! } - public var Conversation_GifTooltip: String { return self._s[2877]! } - public var Passport_Identity_TypeDriversLicenseUploadScan: String { return self._s[2879]! } - public var AutoDownloadSettings_OffForAll: String { return self._s[2880]! } - public var Privacy_GroupsAndChannels_InviteToChannelMultipleError: String { return self._s[2881]! } - public var AutoDownloadSettings_PreloadVideo: String { return self._s[2882]! } - public var CreatePoll_Quiz: String { return self._s[2883]! } - public var TwoFactorSetup_Email_Placeholder: String { return self._s[2884]! } - public var Watch_Message_Invoice: String { return self._s[2885]! } - public var Settings_AddAnotherAccount_Help: String { return self._s[2886]! } - public var Watch_Message_Unsupported: String { return self._s[2887]! } - public func Call_CameraOff(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2889]!, self._r[2889]!, [_0]) - } - public var AuthSessions_TerminateOtherSessions: String { return self._s[2890]! } - public var CreatePoll_AllOptionsAdded: String { return self._s[2892]! } - public var TwoStepAuth_RecoveryEmailTitle: String { return self._s[2893]! } - public var Call_IncomingVoiceCall: String { return self._s[2894]! } - public func Channel_AdminLog_MessageTransferedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2895]!, self._r[2895]!, [_1, _2]) - } - public var PrivacySettings_DeleteAccountHelp: String { return self._s[2896]! } - public var Passport_Address_TypePassportRegistrationUploadScan: String { return self._s[2897]! } - public var Group_EditAdmin_RankOwnerPlaceholder: String { return self._s[2898]! } - public var Group_ErrorAccessDenied: String { return self._s[2899]! } - public var PasscodeSettings_HelpTop: String { return self._s[2900]! } - public var Watch_ChatList_NoConversationsTitle: String { return self._s[2901]! } - public var AddContact_SharedContactException: String { return self._s[2902]! } - public var AccessDenied_MicrophoneRestricted: String { return self._s[2903]! } - public var Privacy_TopPeers: String { return self._s[2904]! } - public var Web_OpenExternal: String { return self._s[2905]! } - public var Group_ErrorSendRestrictedStickers: String { return self._s[2906]! } - public var Channel_Management_LabelAdministrator: String { return self._s[2907]! } - public func ChangePhoneNumberCode_CallTimer(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2908]!, self._r[2908]!, [_0]) - } - public var Permissions_Skip: String { return self._s[2909]! } - public var Notifications_GroupNotificationsExceptions: String { return self._s[2910]! } - public var PeopleNearby_Title: String { return self._s[2911]! } - public var GroupInfo_SharedMediaNone: String { return self._s[2912]! } - public func PUSH_MESSAGE_GEOLIVE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2914]!, self._r[2914]!, [_1]) - } - public var Profile_MessageLifetime1w: String { return self._s[2915]! } - public func Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2916]!, self._r[2916]!, [_1, _2, _3]) - } - public var WebBrowser_DefaultBrowser: String { return self._s[2917]! } - public var EditTheme_Edit_BottomInfo: String { return self._s[2919]! } - public var Privacy_Forwards_Preview: String { return self._s[2920]! } - public var Settings_EditAccount: String { return self._s[2921]! } - public func Conversation_RestrictedInlineTimed(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2922]!, self._r[2922]!, [_0]) - } - public var TwoFactorSetup_Intro_Title: String { return self._s[2923]! } - public func Channel_AdminLog_MessagePromotedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2925]!, self._r[2925]!, [_1]) - } - public var PeerInfo_ButtonVideoCall: String { return self._s[2926]! } - public func DialogList_SingleUploadingPhotoSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2927]!, self._r[2927]!, [_0]) - } - public var Login_InfoHelp: String { return self._s[2928]! } - public var Notification_SecretChatMessageScreenshotSelf: String { return self._s[2929]! } - public var Profile_MessageLifetime1d: String { return self._s[2930]! } - public var Group_UpgradeConfirmation: String { return self._s[2931]! } - public func PUSH_PINNED_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2932]!, self._r[2932]!, [_1, _2]) - } - public var Appearance_RemoveThemeColor: String { return self._s[2933]! } - public var Channel_AdminLog_TitleSelectedEvents: String { return self._s[2934]! } - public var Wallet_Configuration_BlockchainIdInfo: String { return self._s[2935]! } - public func Call_AnsweringWithAccount(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2936]!, self._r[2936]!, [_0]) - } - public var UserInfo_BotSettings: String { return self._s[2937]! } - public func Notification_ChannelInviter(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2939]!, self._r[2939]!, [_0]) - } - public var Permissions_ContactsText_v0: String { return self._s[2940]! } - public var SettingsSearch_Synonyms_Privacy_TwoStepAuth: String { return self._s[2942]! } - public var SharedMedia_SearchNoResults: String { return self._s[2944]! } - public func Login_EmailPhoneSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2946]!, self._r[2946]!, [_0]) - } - public func Conversation_ShareMyPhoneNumber_StatusSuccess(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2948]!, self._r[2948]!, [_0]) - } - public var ReportPeer_ReasonOther_Placeholder: String { return self._s[2949]! } - public var ContactInfo_PhoneLabelHomeFax: String { return self._s[2950]! } - public var Call_AudioRouteHeadphones: String { return self._s[2951]! } - public func PUSH_AUTH_UNKNOWN(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2952]!, self._r[2952]!, [_1]) - } - public var Passport_Identity_FilesView: String { return self._s[2953]! } - public var TwoStepAuth_SetupEmail: String { return self._s[2954]! } - public var Widget_ApplicationStartRequired: String { return self._s[2955]! } - public var PhotoEditor_Original: String { return self._s[2956]! } - public var Call_YourMicrophoneOff: String { return self._s[2957]! } - public var Permissions_ContactsAllow_v0: String { return self._s[2958]! } - public var Notification_Exceptions_PreviewAlwaysOn: String { return self._s[2959]! } - public var PrivacyPolicy_Decline: String { return self._s[2960]! } - public var SettingsSearch_Synonyms_ChatFolders: String { return self._s[2961]! } - public var TwoStepAuth_PasswordRemoveConfirmation: String { return self._s[2962]! } - public var ChatListFolder_IncludeSectionInfo: String { return self._s[2963]! } - public func Map_DirectionsDriveEta(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2964]!, self._r[2964]!, [_0]) - } - public var Passport_Identity_Name: String { return self._s[2965]! } - public var WallpaperPreview_PatternTitle: String { return self._s[2967]! } - public var VoiceOver_Chat_RecordModeVideoMessage: String { return self._s[2968]! } - public var WallpaperSearch_ColorOrange: String { return self._s[2970]! } - public var Appearance_ThemePreview_ChatList_5_Name: String { return self._s[2971]! } - public var GroupInfo_Permissions_SlowmodeInfo: String { return self._s[2972]! } - public var Your_cards_security_code_is_invalid: String { return self._s[2973]! } - public var IntentsSettings_ResetAll: String { return self._s[2974]! } - public var SettingsSearch_Synonyms_Calls_CallTab: String { return self._s[2976]! } - public var Group_EditAdmin_TransferOwnership: String { return self._s[2977]! } - public var Notification_Exceptions_Add: String { return self._s[2978]! } - public var Cache_Help: String { return self._s[2979]! } - public var Call_AudioRouteMute: String { return self._s[2980]! } - public var VoiceOver_Chat_YourVoiceMessage: String { return self._s[2981]! } - public var SocksProxySetup_ProxyEnabled: String { return self._s[2982]! } - public func ApplyLanguage_UnsufficientDataText(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2983]!, self._r[2983]!, [_1]) - } - public func Call_CallInProgressMessage(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2984]!, self._r[2984]!, [_1, _2]) - } - public var AutoDownloadSettings_VideoMessagesTitle: String { return self._s[2985]! } - public var Channel_BanUser_PermissionAddMembers: String { return self._s[2986]! } - public var Contacts_MemberSearchSectionTitleGroup: String { return self._s[2987]! } - public func Wallet_Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2988]!, self._r[2988]!, [_1, _2, _3]) - } - public var TwoStepAuth_RecoveryCodeHelp: String { return self._s[2989]! } - public var ClearCache_StorageFree: String { return self._s[2990]! } - public func DialogList_SingleRecordingVideoMessageSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2991]!, self._r[2991]!, [_0]) - } - public var Privacy_Forwards_CustomHelp: String { return self._s[2992]! } - public var Group_ErrorAddTooMuchAdmins: String { return self._s[2994]! } - public var DialogList_Typing: String { return self._s[2995]! } - public func Login_EmailCodeSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2996]!, self._r[2996]!, [_0]) - } - public var Target_SelectGroup: String { return self._s[2997]! } - public var AuthSessions_IncompleteAttempts: String { return self._s[2998]! } - public var TwoStepAuth_EmailChangeSuccess: String { return self._s[2999]! } - public func Settings_CheckPhoneNumberTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3000]!, self._r[3000]!, [_0]) - } - public var Channel_AdminLog_CanSendMessages: String { return self._s[3001]! } - public var TwoFactorSetup_EmailVerification_Title: String { return self._s[3002]! } - public var ChatSettings_TextSize: String { return self._s[3003]! } - public var Channel_AdminLogFilter_EventsEditedMessages: String { return self._s[3005]! } - public var Map_SendThisPlace: String { return self._s[3006]! } - public var Login_PhoneNumberAlreadyAuthorized: String { return self._s[3007]! } - public var ContactInfo_BirthdayLabel: String { return self._s[3008]! } - public var Call_ShareStats: String { return self._s[3009]! } - public var ChatList_UndoArchiveRevealedText: String { return self._s[3011]! } - public var Notifications_GroupNotificationsPreview: String { return self._s[3012]! } - public var Settings_Support: String { return self._s[3013]! } - public var GroupInfo_ChannelListNamePlaceholder: String { return self._s[3014]! } - public func EmptyGroupInfo_Line1(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3016]!, self._r[3016]!, [_0]) - } - public var Watch_Conversation_GroupInfo: String { return self._s[3017]! } - public var Tour_Text4: String { return self._s[3018]! } - public var PasscodeSettings_AutoLock: String { return self._s[3020]! } - public var Channel_BanList_BlockedTitle: String { return self._s[3021]! } - public var Bot_DescriptionTitle: String { return self._s[3022]! } - public var Map_LocationTitle: String { return self._s[3023]! } - public var ChatListFolder_ExcludeSectionInfo: String { return self._s[3024]! } - public func Notification_MessageLifetimeChangedOutgoing(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3025]!, self._r[3025]!, [_1]) - } - public var Login_EmailNotConfiguredError: String { return self._s[3026]! } - public var AutoDownloadSettings_LimitBySize: String { return self._s[3027]! } - public var PrivacySettings_LastSeenNobody: String { return self._s[3028]! } - public var Permissions_CellularDataText_v0: String { return self._s[3029]! } - public var Conversation_EncryptionProcessing: String { return self._s[3030]! } - public var GroupPermission_Delete: String { return self._s[3031]! } - public var Contacts_SortByName: String { return self._s[3032]! } - public var TwoStepAuth_RecoveryUnavailable: String { return self._s[3033]! } - public var Compose_ChannelTokenListPlaceholder: String { return self._s[3034]! } - public var Group_Management_AddModeratorHelp: String { return self._s[3036]! } - public var SettingsSearch_Synonyms_EditProfile_Logout: String { return self._s[3037]! } - public var Forward_ErrorPublicPollDisabledInChannels: String { return self._s[3038]! } - public func Wallet_Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3040]!, self._r[3040]!, [_1, _2, _3]) - } - public var CallFeedback_IncludeLogsInfo: String { return self._s[3041]! } - public func PUSH_CHANNEL_MESSAGE_QUIZ(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3042]!, self._r[3042]!, [_1]) - } - public func SecretVideo_NotViewedYet(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3043]!, self._r[3043]!, [_0]) - } - public var ChatList_Context_Delete: String { return self._s[3044]! } - public var PrivacyPhoneNumberSettings_CustomDisabledHelp: String { return self._s[3045]! } - public var Conversation_Processing: String { return self._s[3046]! } - public var TwoStepAuth_EmailCodeExpired: String { return self._s[3047]! } - public var ChatSettings_Stickers: String { return self._s[3048]! } - public var AppleWatch_ReplyPresetsHelp: String { return self._s[3049]! } - public var Passport_Language_cs: String { return self._s[3050]! } - public var GroupInfo_InvitationLinkGroupFull: String { return self._s[3052]! } - public var Conversation_Contact: String { return self._s[3053]! } - public var Passport_Identity_ReverseSideHelp: String { return self._s[3054]! } - public var SocksProxySetup_PasteFromClipboard: String { return self._s[3055]! } - public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[3056]! } - public var Theme_Unsupported: String { return self._s[3057]! } - public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[3058]! } - public var Privacy_TopPeersWarning: String { return self._s[3059]! } - public func UserInfo_BlockConfirmationTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3060]!, self._r[3060]!, [_0]) - } - public var Conversation_SilentBroadcastTooltipOn: String { return self._s[3061]! } - public var TwoStepAuth_RemovePassword: String { return self._s[3062]! } - public var Settings_CheckPhoneNumberText: String { return self._s[3063]! } - public var PeopleNearby_Users: String { return self._s[3064]! } - public var Appearance_TextSize_UseSystem: String { return self._s[3065]! } - public var Settings_SetProfilePhoto: String { return self._s[3066]! } - public var Conversation_ContextMenuBan: String { return self._s[3067]! } - public var KeyCommand_ScrollUp: String { return self._s[3068]! } - public var Settings_ChatSettings: String { return self._s[3070]! } - public func PUSH_CHAT_MESSAGE_VIDEO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3071]!, self._r[3071]!, [_1, _2]) - } - public var Stats_GroupTopInvitersTitle: String { return self._s[3072]! } - public var Passport_Phone_EnterOtherNumber: String { return self._s[3073]! } - public var Passport_Identity_MiddleNamePlaceholder: String { return self._s[3075]! } - public var Passport_Address_OneOfTypeBankStatement: String { return self._s[3076]! } - public var Stats_GroupTopPoster_Promote: String { return self._s[3077]! } - public var Cache_Title: String { return self._s[3078]! } - public var Clipboard_SendPhoto: String { return self._s[3079]! } - public var Notifications_ExceptionsMessagePlaceholder: String { return self._s[3081]! } - public var TwoStepAuth_EnterPasswordForgot: String { return self._s[3082]! } - public var WatchRemote_AlertTitle: String { return self._s[3083]! } - public var Appearance_ReduceMotion: String { return self._s[3084]! } - public func PUSH_CHAT_MESSAGE_ROUND(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3087]!, self._r[3087]!, [_1, _2]) - } - public var Notifications_PermissionsSuppressWarningText: String { return self._s[3088]! } - public var ChatList_UndoArchiveHiddenTitle: String { return self._s[3089]! } - public var Passport_Identity_TypePersonalDetails: String { return self._s[3090]! } - public var Wallet_TransactionInfo_CopyAddress: String { return self._s[3092]! } - public func Passport_Identity_UploadOneOfScan(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3093]!, self._r[3093]!, [_0]) - } - public var ChatListFolder_DiscardConfirmation: String { return self._s[3094]! } - public func Conversation_RestrictedStickersTimed(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3095]!, self._r[3095]!, [_0]) - } - public var ChatState_WaitingForNetwork: String { return self._s[3096]! } - public var GroupInfo_Sound: String { return self._s[3097]! } - public var NotificationsSound_Telegraph: String { return self._s[3098]! } - public var NotificationsSound_Hello: String { return self._s[3099]! } - public var Passport_FieldIdentityDetailsHelp: String { return self._s[3100]! } - public var Wallet_Settings_BackupWallet: String { return self._s[3101]! } - public var Group_Members_AddMemberBotErrorNotAllowed: String { return self._s[3102]! } - public var Conversation_HoldForVideo: String { return self._s[3103]! } - public var Wallet_Configuration_ApplyErrorTextURLInvalidData: String { return self._s[3104]! } - public var Wallet_RestoreFailed_EnterWords: String { return self._s[3105]! } - public var Appearance_ShareTheme: String { return self._s[3106]! } - public var TwoStepAuth_SetupHint: String { return self._s[3107]! } - public var Wallet_Created_Text: String { return self._s[3110]! } - public var Stats_GrowthTitle: String { return self._s[3111]! } - public var GroupInfo_InviteLink_ShareLink: String { return self._s[3112]! } - public var Conversation_DefaultRestrictedMedia: String { return self._s[3113]! } - public var Channel_EditAdmin_PermissionPostMessages: String { return self._s[3114]! } - public var GroupPermission_NoSendMessages: String { return self._s[3116]! } - public var Conversation_SetReminder_Title: String { return self._s[3117]! } - public var Privacy_Calls_CustomHelp: String { return self._s[3118]! } - public var CheckoutInfo_ErrorPostcodeInvalid: String { return self._s[3119]! } - public func ClearCache_StorageTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3120]!, self._r[3120]!, [_0]) - } - public var Undo_SecretChatDeleted: String { return self._s[3122]! } - public var PhotoEditor_ContrastTool: String { return self._s[3123]! } - public var Privacy_Forwards: String { return self._s[3124]! } - public var AuthSessions_LoggedInWithTelegram: String { return self._s[3125]! } - public var KeyCommand_SendMessage: String { return self._s[3127]! } - public func InstantPage_RelatedArticleAuthorAndDateTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3128]!, self._r[3128]!, [_1, _2]) - } - public var GroupPermission_NoSendGifs: String { return self._s[3129]! } - public var Wallet_Month_ShortJune: String { return self._s[3130]! } - public var Notification_MessageLifetime2s: String { return self._s[3131]! } - public var Message_Theme: String { return self._s[3132]! } - public var Conversation_Dice_u1F3AF: String { return self._s[3135]! } - public func DialogList_SinglePlayingGameSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3136]!, self._r[3136]!, [_0]) - } - public var Group_UpgradeNoticeHeader: String { return self._s[3138]! } - public var PeerInfo_BioExpand: String { return self._s[3139]! } - public var Passport_DeletePersonalDetails: String { return self._s[3140]! } - public var Widget_NoUsers: String { return self._s[3141]! } - public var TwoStepAuth_AddHintTitle: String { return self._s[3142]! } - public var Login_TermsOfServiceDecline: String { return self._s[3143]! } - public var CreatePoll_QuizTip: String { return self._s[3145]! } - public var Watch_LastSeen_WithinAWeek: String { return self._s[3146]! } - public var MessagePoll_SubmitVote: String { return self._s[3148]! } - public var ChatSettings_AutoDownloadEnabled: String { return self._s[3149]! } - public var Passport_Address_EditRentalAgreement: String { return self._s[3150]! } - public var Conversation_SearchByName_Placeholder: String { return self._s[3151]! } - public var Conversation_UpdateTelegram: String { return self._s[3152]! } - public func FileSize_KB(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3153]!, self._r[3153]!, [_0]) - } - public var UserInfo_About_Placeholder: String { return self._s[3154]! } - public var CallSettings_Always: String { return self._s[3155]! } - public var ChannelInfo_ScamChannelWarning: String { return self._s[3156]! } - public var Login_TermsOfServiceHeader: String { return self._s[3157]! } - public var KeyCommand_ChatInfo: String { return self._s[3158]! } - public var MessagePoll_LabelPoll: String { return self._s[3159]! } - public var Paint_Clear: String { return self._s[3160]! } - public var PeerInfo_ButtonMute: String { return self._s[3161]! } - public var LastSeen_WithinAWeek: String { return self._s[3162]! } - public var Passport_Identity_FrontSide: String { return self._s[3163]! } - public var Stickers_GroupStickers: String { return self._s[3164]! } - public var ChangePhoneNumberNumber_NumberPlaceholder: String { return self._s[3165]! } - public func Map_SearchNoResultsDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3166]!, self._r[3166]!, [_0]) - } - public func PUSH_MESSAGE_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3169]!, self._r[3169]!, [_1]) - } - public var SocksProxySetup_ProxyStatusConnected: String { return self._s[3170]! } - public var Chat_MultipleTextMessagesDisabled: String { return self._s[3171]! } - public func Notification_LeftChat(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3172]!, self._r[3172]!, [_0]) - } - public var Wallet_Send_AmountText: String { return self._s[3173]! } - public var WebSearch_SearchNoResults: String { return self._s[3175]! } - public var Channel_DiscussionGroup_Create: String { return self._s[3176]! } - public var Passport_Language_es: String { return self._s[3177]! } - public var EnterPasscode_EnterCurrentPasscode: String { return self._s[3178]! } - public var Wallet_Intro_Title: String { return self._s[3179]! } - public var Map_LiveLocationShowAll: String { return self._s[3180]! } - public var Cache_MaximumCacheSizeHelp: String { return self._s[3182]! } - public var Map_OpenInGoogleMaps: String { return self._s[3183]! } - public var CheckoutInfo_ErrorNameInvalid: String { return self._s[3185]! } - public var EditTheme_Create_BottomInfo: String { return self._s[3186]! } - public var PhotoEditor_BlurToolLinear: String { return self._s[3187]! } - public func Channel_AdminLog_MessageEdited(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3188]!, self._r[3188]!, [_0]) - } - public var Passport_Phone_Delete: String { return self._s[3189]! } - public var Channel_Username_CreatePrivateLinkHelp: String { return self._s[3190]! } - public var PrivacySettings_PrivacyTitle: String { return self._s[3191]! } - public var CheckoutInfo_ReceiverInfoNamePlaceholder: String { return self._s[3192]! } - public func EncryptionKey_Description(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3193]!, self._r[3193]!, [_1, _2]) - } - public var LogoutOptions_LogOutInfo: String { return self._s[3194]! } - public var Wallet_Month_GenAugust: String { return self._s[3195]! } - public var Cache_ByPeerHeader: String { return self._s[3196]! } - public var Username_InvalidCharacters: String { return self._s[3197]! } - public var Wallet_Qr_Title: String { return self._s[3199]! } - public var Checkout_ShippingAddress: String { return self._s[3200]! } - public func PUSH_CHAT_MESSAGE_GAME_SCORE(_ _1: String, _ _2: String, _ _3: String, _ _4: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3201]!, self._r[3201]!, [_1, _2, _3, _4]) - } - public var Conversation_AddContact: String { return self._s[3203]! } - public var Passport_Address_EditUtilityBill: String { return self._s[3204]! } - public var Message_Video: String { return self._s[3205]! } - public func Watch_Time_ShortYesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3206]!, self._r[3206]!, [_0]) - } - public func Conversation_Megabytes(_ _0: Float) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3207]!, self._r[3207]!, ["\(_0)"]) - } - public var Passport_Language_km: String { return self._s[3208]! } - public func PUSH_MESSAGE_CHANNEL_MESSAGE_GAME_SCORE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3209]!, self._r[3209]!, [_1, _2, _3]) - } - public var EmptyGroupInfo_Line4: String { return self._s[3210]! } - public var Conversation_SendMessageErrorTooMuchScheduled: String { return self._s[3212]! } - public var Notification_CallCanceledShort: String { return self._s[3213]! } - public var PhotoEditor_FadeTool: String { return self._s[3214]! } - public var Group_PublicLink_Info: String { return self._s[3215]! } - public var Contacts_DeselectAll: String { return self._s[3216]! } - public var Conversation_Moderate_Delete: String { return self._s[3217]! } - public var TwoStepAuth_RecoveryCodeInvalid: String { return self._s[3218]! } - public var NotificationsSound_Note: String { return self._s[3221]! } - public func Message_PaymentSent(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3222]!, self._r[3222]!, [_0]) - } - public var Appearance_ThemePreview_ChatList_7_Text: String { return self._s[3223]! } - public var Channel_EditAdmin_PermissionInviteViaLink: String { return self._s[3224]! } - public var DialogList_SearchSectionGlobal: String { return self._s[3225]! } - public var AccessDenied_Settings: String { return self._s[3226]! } - public var Passport_Identity_TypeIdentityCardUploadScan: String { return self._s[3227]! } - public var AuthSessions_EmptyTitle: String { return self._s[3228]! } - public var TwoStepAuth_PasswordChangeSuccess: String { return self._s[3229]! } - public var GroupInfo_GroupType: String { return self._s[3230]! } - public var Calls_Missed: String { return self._s[3231]! } - public var UserInfo_GenericPhoneLabel: String { return self._s[3232]! } - public var Passport_Language_uz: String { return self._s[3233]! } - public var Conversation_StopQuizConfirmationTitle: String { return self._s[3234]! } - public var PhotoEditor_BlurToolPortrait: String { return self._s[3235]! } - public var Map_ChooseLocationTitle: String { return self._s[3236]! } - public var Checkout_EnterPassword: String { return self._s[3237]! } - public var GroupInfo_ConvertToSupergroup: String { return self._s[3238]! } - public var AutoNightTheme_UpdateLocation: String { return self._s[3239]! } - public var NetworkUsageSettings_Title: String { return self._s[3240]! } - public var SettingsSearch_Synonyms_ChatSettings_IntentsSettings: String { return self._s[3241]! } - public var Message_PinnedLiveLocationMessage: String { return self._s[3242]! } - public var Compose_NewChannel: String { return self._s[3243]! } - public var Privacy_PaymentsClearInfo: String { return self._s[3245]! } - public func PUSH_MESSAGE_POLL(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3246]!, self._r[3246]!, [_1]) - } - public var Notification_Exceptions_AlwaysOn: String { return self._s[3247]! } - public var Privacy_GroupsAndChannels_WhoCanAddMe: String { return self._s[3248]! } - public var AutoNightTheme_AutomaticSection: String { return self._s[3251]! } - public var WallpaperSearch_ColorBrown: String { return self._s[3252]! } - public var Appearance_AppIconDefault: String { return self._s[3253]! } - public var Wallet_Month_GenJune: String { return self._s[3255]! } - public var StickerSettings_ContextInfo: String { return self._s[3256]! } - public var Channel_AddBotErrorNoRights: String { return self._s[3257]! } - public var Passport_FieldPhone: String { return self._s[3259]! } - public var Contacts_PermissionsTitle: String { return self._s[3260]! } - public var TwoFactorSetup_Email_SkipConfirmationSkip: String { return self._s[3261]! } - public func Notification_JoinedChat(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3262]!, self._r[3262]!, [_0]) - } - public var Bot_Unblock: String { return self._s[3263]! } - public var PasscodeSettings_SimplePasscode: String { return self._s[3264]! } - public var Passport_PasswordHelp: String { return self._s[3265]! } - public var Watch_Conversation_UserInfo: String { return self._s[3266]! } - public func Channel_AdminLog_MessageChangedGroupGeoLocation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3270]!, self._r[3270]!, [_0]) - } - public var State_Connecting: String { return self._s[3272]! } - public var Passport_Address_TypeTemporaryRegistration: String { return self._s[3273]! } - public var TextFormat_AddLinkPlaceholder: String { return self._s[3274]! } - public var Conversation_Dice_u1F3B2: String { return self._s[3275]! } - public func Call_StatusBar(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3276]!, self._r[3276]!, [_0]) - } - public var Conversation_SendingOptionsTooltip: String { return self._s[3277]! } - public var ChatList_UndoArchiveTitle: String { return self._s[3278]! } - public var ChatList_EmptyChatListNewMessage: String { return self._s[3279]! } - public var WallpaperSearch_ColorGreen: String { return self._s[3281]! } - public var PhotoEditor_BlurToolOff: String { return self._s[3282]! } - public var SocksProxySetup_PortPlaceholder: String { return self._s[3283]! } - public var Weekday_Saturday: String { return self._s[3284]! } - public var DialogList_Unread: String { return self._s[3285]! } - public var Watch_LastSeen_ALongTimeAgo: String { return self._s[3286]! } - public var Stats_GroupPosters: String { return self._s[3287]! } - public func PUSH_ENCRYPTION_REQUEST(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3288]!, self._r[3288]!, [_1]) - } - public func Target_ShareGameConfirmationGroup(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3291]!, self._r[3291]!, [_0]) - } - public var ReportPeer_ReasonChildAbuse: String { return self._s[3292]! } - public func Channel_AdminLog_MessageUnkickedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3293]!, self._r[3293]!, [_1, _2]) - } - public var InfoPlist_NSContactsUsageDescription: String { return self._s[3294]! } - public var AutoNightTheme_UseSunsetSunrise: String { return self._s[3296]! } - public var Channel_OwnershipTransfer_ChangeOwner: String { return self._s[3297]! } - public var Passport_Language_dv: String { return self._s[3298]! } - public var GroupPermission_AddSuccess: String { return self._s[3301]! } - public var Passport_Email_Help: String { return self._s[3302]! } - public var Call_ReportPlaceholder: String { return self._s[3303]! } - public var CreatePoll_AddOption: String { return self._s[3304]! } - public var MessagePoll_LabelAnonymousQuiz: String { return self._s[3305]! } - public var PeerInfo_ButtonLeave: String { return self._s[3306]! } - public var PhotoEditor_TiltShift: String { return self._s[3309]! } - public var SecretGif_Title: String { return self._s[3311]! } - public var PhotoEditor_QualityVeryLow: String { return self._s[3312]! } - public var SocksProxySetup_Connecting: String { return self._s[3313]! } - public var PrivacySettings_PasscodeAndFaceId: String { return self._s[3314]! } - public var ContactInfo_PhoneLabelWork: String { return self._s[3315]! } - public var Stats_GroupTopHoursTitle: String { return self._s[3316]! } - public var Compose_NewMessage: String { return self._s[3317]! } - public var NotificationsSound_Synth: String { return self._s[3318]! } - public var Conversation_FileOpenIn: String { return self._s[3319]! } - public var AutoDownloadSettings_WifiTitle: String { return self._s[3320]! } - public var UserInfo_SendMessage: String { return self._s[3321]! } - public var Checkout_PayWithFaceId: String { return self._s[3322]! } - public func Map_LiveLocationShortHour(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3323]!, self._r[3323]!, [_0]) - } - public var TextFormat_Strikethrough: String { return self._s[3324]! } - public var SettingsSearch_Synonyms_Notifications_DisplayNamesOnLockScreen: String { return self._s[3325]! } - public var Conversation_ViewChannel: String { return self._s[3326]! } - public func Message_ForwardedMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3327]!, self._r[3327]!, [_0]) - } - public var Channel_Stickers_Placeholder: String { return self._s[3328]! } - public var Channel_OwnershipTransfer_PasswordPlaceholder: String { return self._s[3329]! } - public var Camera_FlashAuto: String { return self._s[3330]! } - public var Conversation_EncryptedDescription1: String { return self._s[3331]! } - public var LocalGroup_Text: String { return self._s[3332]! } - public var SettingsSearch_Synonyms_Data_Storage_KeepMedia: String { return self._s[3333]! } - public var UserInfo_FirstNamePlaceholder: String { return self._s[3334]! } - public var Conversation_SendMessageErrorFlood: String { return self._s[3335]! } - public var Conversation_EncryptedDescription2: String { return self._s[3336]! } - public var Notification_GroupActivated: String { return self._s[3337]! } - public var LastSeen_Lately: String { return self._s[3338]! } - public var Conversation_EncryptedDescription3: String { return self._s[3339]! } - public var SettingsSearch_Synonyms_Privacy_ProfilePhoto: String { return self._s[3340]! } - public var Conversation_SwipeToReplyHintText: String { return self._s[3341]! } - public var Conversation_EncryptedDescription4: String { return self._s[3342]! } - public var SharedMedia_EmptyTitle: String { return self._s[3343]! } - public var Wallet_Configuration_Apply: String { return self._s[3344]! } - public var Appearance_CreateTheme: String { return self._s[3345]! } - public var Stats_SharesPerPost: String { return self._s[3346]! } - public var Contacts_TabTitle: String { return self._s[3347]! } - public var Weekday_ShortThursday: String { return self._s[3348]! } - public var MessageTimer_Forever: String { return self._s[3349]! } - public var ChatListFolder_CategoryArchived: String { return self._s[3350]! } - public var Channel_EditAdmin_PermissionDeleteMessages: String { return self._s[3351]! } - public var EditTheme_Create_TopInfo: String { return self._s[3353]! } - public var Month_GenDecember: String { return self._s[3354]! } - public var EnterPasscode_EnterPasscode: String { return self._s[3355]! } - public var SettingsSearch_Synonyms_Appearance_LargeEmoji: String { return self._s[3356]! } - public var PeopleNearby_CreateGroup: String { return self._s[3358]! } - public var Group_EditAdmin_PermissionChangeInfo: String { return self._s[3359]! } - public var Paint_ClearConfirm: String { return self._s[3360]! } - public var ChatList_ReadAll: String { return self._s[3361]! } - public var ChatSettings_IntentsSettings: String { return self._s[3362]! } - public var Passport_PassportInformation: String { return self._s[3364]! } - public var Login_CheckOtherSessionMessages: String { return self._s[3366]! } - public var PhotoEditor_ExposureTool: String { return self._s[3369]! } - public var Group_Username_CreatePrivateLinkHelp: String { return self._s[3370]! } - public var SettingsSearch_Synonyms_Watch: String { return self._s[3371]! } - public var Stats_GroupTopPoster_History: String { return self._s[3372]! } - public var UserInfo_AddPhone: String { return self._s[3373]! } - public var Media_SendWithTimer: String { return self._s[3375]! } - public var SettingsSearch_Synonyms_Notifications_Title: String { return self._s[3376]! } - public var Channel_EditAdmin_PermissionEnabledByDefault: String { return self._s[3377]! } - public var PasscodeSettings_AutoLock_Disabled: String { return self._s[3378]! } - public var ChatList_Context_Unarchive: String { return self._s[3380]! } - public func DialogList_LiveLocationSharingTo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3381]!, self._r[3381]!, [_0]) - } - public var BlockedUsers_Title: String { return self._s[3383]! } - public var TwoStepAuth_EmailPlaceholder: String { return self._s[3384]! } - public var Media_ShareThisPhoto: String { return self._s[3385]! } - public var Notifications_DisplayNamesOnLockScreen: String { return self._s[3386]! } - public var Conversation_FilePhotoOrVideo: String { return self._s[3387]! } - public var Appearance_ThemePreview_Chat_2_ReplyName: String { return self._s[3391]! } - public var CallFeedback_ReasonNoise: String { return self._s[3393]! } - public var WebBrowser_Title: String { return self._s[3394]! } - public func Checkout_SavePasswordTimeoutAndTouchId(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3395]!, self._r[3395]!, [_0]) - } - public var Notification_MessageLifetime5s: String { return self._s[3396]! } - public var Passport_Address_AddResidentialAddress: String { return self._s[3397]! } - public var Profile_MessageLifetime1m: String { return self._s[3398]! } - public var Stats_LoadingTitle: String { return self._s[3400]! } - public var Passport_ScanPassport: String { return self._s[3401]! } - public var Passport_Address_AddTemporaryRegistration: String { return self._s[3403]! } - public var Permissions_NotificationsAllow_v0: String { return self._s[3404]! } - public var Login_InvalidFirstNameError: String { return self._s[3405]! } - public var Undo_ChatCleared: String { return self._s[3407]! } - public func ApplyLanguage_ChangeLanguageUnofficialText(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3409]!, self._r[3409]!, [_1, _2]) - } - public func Login_PhoneBannedEmailBody(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3410]!, self._r[3410]!, [_1, _2, _3, _4, _5]) - } - public func PUSH_MESSAGE_FWD(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3411]!, self._r[3411]!, [_1]) - } - public var Share_MultipleMessagesDisabled: String { return self._s[3412]! } - public var TwoStepAuth_EmailInvalid: String { return self._s[3413]! } - public var EnterPasscode_ChangeTitle: String { return self._s[3415]! } - public func Wallet_Send_ConfirmationText(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3416]!, self._r[3416]!, [_1, _2, _3]) - } - public var CallSettings_RecentCalls: String { return self._s[3417]! } - public var GroupInfo_DeactivatedStatus: String { return self._s[3418]! } - public var AuthSessions_OtherSessions: String { return self._s[3419]! } - public var PrivacyLastSeenSettings_CustomHelp: String { return self._s[3420]! } - public var Tour_Text5: String { return self._s[3421]! } - public var Login_PadPhoneHelp: String { return self._s[3422]! } - public var Wallpaper_PhotoLibrary: String { return self._s[3424]! } - public var Conversation_ViewGroup: String { return self._s[3425]! } - public var PeopleNearby_MakeVisibleTitle: String { return self._s[3427]! } - public var VoiceOver_Chat_YourContact: String { return self._s[3428]! } - public var Watch_AuthRequired: String { return self._s[3429]! } - public var VoiceOver_Chat_ForwardedFromYou: String { return self._s[3430]! } - public var Conversation_ForwardContacts: String { return self._s[3431]! } - public var Conversation_InputTextPlaceholder: String { return self._s[3432]! } - public func PUSH_CHANNEL_MESSAGE_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3433]!, self._r[3433]!, [_1]) - } - public func Conversation_MessageViaUser(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3434]!, self._r[3434]!, [_0]) - } - public var Channel_Setup_TypePrivate: String { return self._s[3435]! } - public func Conversation_NoticeInvitedByInChannel(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3436]!, self._r[3436]!, [_0]) - } - public var InfoPlist_NSSiriUsageDescription: String { return self._s[3437]! } - public var Wallet_ContextMenuCopy: String { return self._s[3438]! } - public var EmptyGroupInfo_Subtitle: String { return self._s[3439]! } - public var AutoDownloadSettings_Delimeter: String { return self._s[3440]! } - public var UserInfo_StartSecretChatStart: String { return self._s[3441]! } - public func GroupPermission_AddedInfo(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3442]!, self._r[3442]!, [_1, _2]) - } - public func Channel_AdminLog_MessageRestricted(_ _0: String, _ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3443]!, self._r[3443]!, [_0, _1, _2]) - } - public var PrivacySettings_AutoArchiveTitle: String { return self._s[3444]! } - public var GroupInfo_InviteLink_LinkSection: String { return self._s[3445]! } - public var FastTwoStepSetup_EmailPlaceholder: String { return self._s[3446]! } - public var Wallet_SecureStorageReset_BiometryFaceId: String { return self._s[3447]! } - public var StickerPacksSettings_ArchivedMasks: String { return self._s[3449]! } - public var NewContact_Title: String { return self._s[3452]! } - public var Appearance_ThemeCarouselTintedNight: String { return self._s[3453]! } - public var Notifications_PermissionsKeepDisabled: String { return self._s[3454]! } - public func Time_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3455]!, self._r[3455]!, [_0]) - } - public func AutoNightTheme_LocationHelp(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3456]!, self._r[3456]!, [_0, _1]) - } - public var Chat_SlowmodeTooltipPending: String { return self._s[3457]! } - public var Wallet_WordCheck_TryAgain: String { return self._s[3458]! } - public var CallFeedback_ReasonInterruption: String { return self._s[3460]! } - public var ContactInfo_PhoneLabelHome: String { return self._s[3461]! } - public var Passport_Identity_OneOfTypeDriversLicense: String { return self._s[3462]! } - public var Conversation_MessageEditedLabel: String { return self._s[3464]! } - public var Wallet_Settings_DeleteWalletInfo: String { return self._s[3465]! } - public var SocksProxySetup_PasswordPlaceholder: String { return self._s[3466]! } - public var ChatList_Context_AddToContacts: String { return self._s[3467]! } - public var Passport_Language_is: String { return self._s[3468]! } - public var Notification_PassportValueProofOfIdentity: String { return self._s[3469]! } - public var Wallet_Month_ShortOctober: String { return self._s[3470]! } - public var PhotoEditor_CurvesBlue: String { return self._s[3471]! } - public func FileSize_MB(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3472]!, self._r[3472]!, [_0]) - } - public var SocksProxySetup_Username: String { return self._s[3473]! } - public var Login_SmsRequestState3: String { return self._s[3474]! } - public var Message_PinnedVideoMessage: String { return self._s[3475]! } - public var SharedMedia_TitleLink: String { return self._s[3476]! } - public var Passport_FieldIdentity: String { return self._s[3477]! } - public var Wallet_Configuration_SourceInfo: String { return self._s[3478]! } - public func Conversation_EncryptedPlaceholderTitleOutgoing(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3482]!, self._r[3482]!, [_0]) - } - public var DialogList_ProxyConnectionIssuesTooltip: String { return self._s[3485]! } - public var ReportSpam_DeleteThisChat: String { return self._s[3486]! } - public var Checkout_NewCard_CardholderNamePlaceholder: String { return self._s[3487]! } - public var Passport_Identity_DateOfBirth: String { return self._s[3488]! } - public var Call_StatusIncoming: String { return self._s[3489]! } - public var Wallet_TransactionInfo_NoAddress: String { return self._s[3490]! } - public var ChatAdmins_AdminLabel: String { return self._s[3491]! } - public var Wallet_WordCheck_IncorrectHeader: String { return self._s[3492]! } - public func Time_MonthOfYear_m10(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3494]!, self._r[3494]!, [_0]) - } - public var Message_PinnedAnimationMessage: String { return self._s[3495]! } - public var Conversation_ReportSpamAndLeave: String { return self._s[3496]! } - public var Preview_CopyAddress: String { return self._s[3497]! } - public var MediaPlayer_UnknownTrack: String { return self._s[3498]! } - public var Login_CancelSignUpConfirmation: String { return self._s[3499]! } - public var Map_OpenInYandexMaps: String { return self._s[3501]! } - public func Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3504]!, self._r[3504]!, [_1, _2, _3]) - } - public var GroupRemoved_Remove: String { return self._s[3505]! } - public var ChatListFolder_TitleCreate: String { return self._s[3506]! } - public func InstantPage_AuthorAndDateTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3508]!, self._r[3508]!, [_1, _2]) - } - public var Watch_UserInfo_MuteTitle: String { return self._s[3509]! } - public var Group_UpgradeNoticeText2: String { return self._s[3511]! } - public var Stats_GroupGrowthTitle: String { return self._s[3512]! } - public var CreatePoll_CancelConfirmation: String { return self._s[3515]! } - public var Month_GenOctober: String { return self._s[3516]! } - public var Settings_Appearance: String { return self._s[3517]! } - public func Time_MonthOfYear_m6(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3518]!, self._r[3518]!, [_0]) - } - public var Wallet_Completed_Title: String { return self._s[3519]! } - public var UserInfo_AddToExisting: String { return self._s[3520]! } - public var Call_PhoneCallInProgressMessage: String { return self._s[3521]! } - public var Map_HomeAndWorkInfo: String { return self._s[3522]! } - public var Paint_Arrow: String { return self._s[3523]! } - public var CancelResetAccount_Title: String { return self._s[3524]! } - public var NotificationsSound_Circles: String { return self._s[3525]! } - public var Notifications_GroupNotificationsExceptionsHelp: String { return self._s[3526]! } - public var ChatState_Connecting: String { return self._s[3528]! } - public var Profile_MessageLifetime5s: String { return self._s[3529]! } - public func DialogList_AwaitingEncryption(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3530]!, self._r[3530]!, [_0]) - } - public var PrivacyPolicy_AgeVerificationTitle: String { return self._s[3531]! } - public var Channel_Username_CreatePublicLinkHelp: String { return self._s[3532]! } - public var AutoNightTheme_ScheduledTo: String { return self._s[3533]! } - public var Conversation_DefaultRestrictedStickers: String { return self._s[3534]! } - public var TwoStepAuth_ConfirmationTitle: String { return self._s[3535]! } - public func Chat_UnsendMyMessagesAlertTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3536]!, self._r[3536]!, [_0]) - } - public var Passport_Phone_Help: String { return self._s[3537]! } - public var Privacy_ContactsSync: String { return self._s[3538]! } - public var CheckoutInfo_ReceiverInfoPhone: String { return self._s[3539]! } - public var Channel_AdminLogFilter_EventsLeavingSubscribers: String { return self._s[3540]! } - public var Map_SendMyCurrentLocation: String { return self._s[3541]! } - public var Map_AddressOnMap: String { return self._s[3542]! } - public func Wallet_Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3543]!, self._r[3543]!, [_1, _2, _3]) - } - public var DialogList_SearchLabel: String { return self._s[3545]! } - public var Notification_Exceptions_NewException_NotificationHeader: String { return self._s[3546]! } - public var ConversationProfile_UnknownAddMemberError: String { return self._s[3547]! } - public var ChatList_Search_ShowMore: String { return self._s[3548]! } - public var DialogList_EncryptionRejected: String { return self._s[3549]! } - public var Wallet_WordImport_Text: String { return self._s[3550]! } - public var DialogList_DeleteBotConfirmation: String { return self._s[3551]! } - public var Privacy_TopPeersDelete: String { return self._s[3552]! } - public var AttachmentMenu_SendAsFile: String { return self._s[3553]! } - public var ChatList_GenericPsaAlert: String { return self._s[3555]! } - public var SecretTimer_ImageDescription: String { return self._s[3557]! } - public func Conversation_SetReminder_RemindOn(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3558]!, self._r[3558]!, [_0, _1]) - } - public var ChatSettings_TextSizeUnits: String { return self._s[3559]! } - public var Notification_RenamedGroup: String { return self._s[3560]! } - public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[3561]! } - public var Tour_Title2: String { return self._s[3562]! } - public var Settings_CopyUsername: String { return self._s[3563]! } - public var Compose_NewEncryptedChat: String { return self._s[3564]! } - public var Conversation_CloudStorageInfo_Title: String { return self._s[3565]! } - public var Month_ShortSeptember: String { return self._s[3566]! } - public var AutoDownloadSettings_OnForAll: String { return self._s[3567]! } - public var ChatList_DeleteForEveryoneConfirmationText: String { return self._s[3568]! } - public var Settings_Wallet: String { return self._s[3569]! } - public var Call_StatusConnecting: String { return self._s[3571]! } - public var Privacy_GroupsAndChannels_NeverAllow_Placeholder: String { return self._s[3572]! } - public var Map_ShareLiveLocationHelp: String { return self._s[3573]! } - public var Cache_Files: String { return self._s[3574]! } - public var Notifications_Reset: String { return self._s[3575]! } - public func Settings_KeepPhoneNumber(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3576]!, self._r[3576]!, [_0]) - } - public var Privacy_GroupsAndChannels_AlwaysAllow_Title: String { return self._s[3577]! } - public func Conversation_OpenBotLinkLogin(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3578]!, self._r[3578]!, [_1, _2]) - } - public var Notification_CallIncomingShort: String { return self._s[3579]! } - public var UserInfo_BotPrivacy: String { return self._s[3581]! } - public var Appearance_BubbleCorners_Apply: String { return self._s[3582]! } - public var WebSearch_RecentClearConfirmation: String { return self._s[3583]! } - public var Conversation_ContextMenuLookUp: String { return self._s[3584]! } - public var Calls_RatingTitle: String { return self._s[3585]! } - public var SecretImage_Title: String { return self._s[3586]! } - public var Weekday_Monday: String { return self._s[3587]! } - public func Passport_PrivacyPolicy(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3589]!, self._r[3589]!, [_1, _2]) - } - public var KeyCommand_JumpToPreviousChat: String { return self._s[3590]! } - public func Wallet_Updated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3591]!, self._r[3591]!, [_0]) - } - public func DialogList_SearchSubtitleFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3592]!, self._r[3592]!, [_1, _2]) - } - public var Stats_GroupMembers: String { return self._s[3593]! } - public var Camera_Retake: String { return self._s[3594]! } - public var Conversation_SearchPlaceholder: String { return self._s[3596]! } - public func Passport_Identity_NativeNameGenericHelp(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3597]!, self._r[3597]!, [_0]) - } - public var Channel_DiscussionGroup_Info: String { return self._s[3598]! } - public var SocksProxySetup_Hostname: String { return self._s[3599]! } - public var Wallet_Send_OwnAddressAlertProceed: String { return self._s[3600]! } - public var PrivacyLastSeenSettings_EmpryUsersPlaceholder: String { return self._s[3601]! } - public var Privacy_DeleteDrafts: String { return self._s[3602]! } - public func Checkout_LiabilityAlert(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3603]!, self._r[3603]!, [_1, _1, _1, _2]) - } - public var Wallet_RestoreFailed_Text: String { return self._s[3604]! } - public var Wallet_Settings_DeleteWallet: String { return self._s[3605]! } - public var Login_CancelPhoneVerification: String { return self._s[3606]! } - public var TwoStepAuth_ResetAccountHelp: String { return self._s[3608]! } - public func SocksProxySetup_ProxyStatusPing(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3609]!, self._r[3609]!, [_0]) - } - public var TwoStepAuth_EmailSent: String { return self._s[3610]! } - public var Cache_Indexing: String { return self._s[3611]! } - public var Notifications_ExceptionsNone: String { return self._s[3612]! } - public var MessagePoll_LabelQuiz: String { return self._s[3613]! } - public var Call_EncryptionKey_Title: String { return self._s[3614]! } - public var Common_Yes: String { return self._s[3615]! } - public var Channel_ErrorAddBlocked: String { return self._s[3616]! } - public var Month_GenJanuary: String { return self._s[3617]! } - public var Checkout_NewCard_Title: String { return self._s[3618]! } - public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[3619]! } - public func TwoStepAuth_EnterPasswordHint(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3620]!, self._r[3620]!, [_0]) - } - public var PasscodeSettings_AutoLock_IfAwayFor_1hour: String { return self._s[3622]! } - public var Conversation_SendDice: String { return self._s[3623]! } - public func ChatSettings_AutoDownloadSettings_TypeVideo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3624]!, self._r[3624]!, [_0]) - } - public func VoiceOver_Chat_VideoFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3625]!, self._r[3625]!, [_0]) - } - public var Weekday_Wednesday: String { return self._s[3626]! } - public var ReportPeer_ReasonOther_Send: String { return self._s[3627]! } - public var PasscodeSettings_EncryptDataHelp: String { return self._s[3628]! } - public var PrivacyLastSeenSettings_CustomShareSettingsHelp: String { return self._s[3629]! } - public var OldChannels_NoticeTitle: String { return self._s[3630]! } - public var TwoStepAuth_ChangeEmail: String { return self._s[3631]! } - public var PasscodeSettings_PasscodeOptions: String { return self._s[3632]! } - public var InfoPlist_NSPhotoLibraryUsageDescription: String { return self._s[3633]! } - public var Passport_Address_AddUtilityBill: String { return self._s[3634]! } - public func Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3636]!, self._r[3636]!, [_1, _2, _3]) - } - public var TwoFactorSetup_EmailVerification_ResendAction: String { return self._s[3638]! } - public var Stats_GroupTopAdminsTitle: String { return self._s[3639]! } - public var Paint_Regular: String { return self._s[3640]! } - public var Message_Contact: String { return self._s[3641]! } - public var NetworkUsageSettings_MediaVideoDataSection: String { return self._s[3642]! } - public var VoiceOver_Chat_YourPhoto: String { return self._s[3643]! } - public var Notification_Mute1hMin: String { return self._s[3644]! } - public func Login_BannedPhoneSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3645]!, self._r[3645]!, [_0]) - } - public var Profile_MessageLifetime1h: String { return self._s[3646]! } - public var TwoStepAuth_GenericHelp: String { return self._s[3647]! } - public var TextFormat_Monospace: String { return self._s[3648]! } - public var VoiceOver_Media_PlaybackRateChange: String { return self._s[3650]! } - public var Conversation_DeleteMessagesForMe: String { return self._s[3651]! } - public var ChatList_DeleteChat: String { return self._s[3652]! } - public var Channel_OwnershipTransfer_EnterPasswordText: String { return self._s[3655]! } - public func Settings_ApplyProxyAlertCredentials(_ _1: String, _ _2: String, _ _3: String, _ _4: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3656]!, self._r[3656]!, [_1, _2, _3, _4]) - } - public var Login_CancelPhoneVerificationStop: String { return self._s[3657]! } - public var Appearance_ThemePreview_ChatList_4_Name: String { return self._s[3658]! } - public var MediaPicker_MomentsDateRangeSameMonthYearFormat: String { return self._s[3659]! } - public var Wallet_Settings_Configuration: String { return self._s[3660]! } - public var Notifications_Badge_IncludeChannels: String { return self._s[3661]! } - public func Channel_AdminLog_MessageToggleInvitesOn(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3662]!, self._r[3662]!, [_0]) - } - public var Wallet_Sent_ViewWallet: String { return self._s[3663]! } - public var StickerPack_ViewPack: String { return self._s[3666]! } - public var FastTwoStepSetup_PasswordConfirmationPlaceholder: String { return self._s[3668]! } - public var EditTheme_Expand_Preview_IncomingText: String { return self._s[3669]! } - public var Notifications_Title: String { return self._s[3670]! } - public var Wallet_WordImport_Continue: String { return self._s[3671]! } - public var GroupInfo_PublicLink: String { return self._s[3672]! } - public var VoiceOver_DiscardPreparedContent: String { return self._s[3673]! } - public var Conversation_Moderate_Ban: String { return self._s[3677]! } - public func Activity_RemindAboutGroup(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3678]!, self._r[3678]!, [_0]) - } - public var TextFormat_Underline: String { return self._s[3679]! } - public func DownloadingStatus(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3680]!, self._r[3680]!, [_0, _1]) - } - public func PUSH_PINNED_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3681]!, self._r[3681]!, [_1]) - } - public var PollResults_Collapse: String { return self._s[3683]! } - public var Contacts_GlobalSearch: String { return self._s[3684]! } - public func Conversation_EncryptionWaiting(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3685]!, self._r[3685]!, [_0]) - } - public var Channel_Management_LabelEditor: String { return self._s[3686]! } - public var SettingsSearch_Synonyms_Stickers_FeaturedPacks: String { return self._s[3688]! } - public var Conversation_Theme: String { return self._s[3689]! } - public var Conversation_LinkDialogSave: String { return self._s[3690]! } - public var EnterPasscode_TouchId: String { return self._s[3691]! } - public var Stats_MessageOverview: String { return self._s[3692]! } - public var Privacy_Calls_P2PAlways: String { return self._s[3694]! } - public var Message_Sticker: String { return self._s[3695]! } - public var Conversation_Mute: String { return self._s[3697]! } - public var ContactInfo_Title: String { return self._s[3698]! } - public func PUSH_CHANNEL_MESSAGE_CONTACT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3699]!, self._r[3699]!, [_1]) - } - public var Channel_Setup_TypeHeader: String { return self._s[3700]! } - public var AuthSessions_LogOut: String { return self._s[3701]! } - public var Wallet_WordCheck_ViewWords: String { return self._s[3702]! } - public var ChatSettings_AutoDownloadReset: String { return self._s[3703]! } - public var ChatListFolderSettings_NewFolder: String { return self._s[3705]! } - public var Appearance_ThemePreview_ChatList_3_AuthorName: String { return self._s[3706]! } - public var CreatePoll_Title: String { return self._s[3707]! } - public var EditTheme_EditTitle: String { return self._s[3708]! } - public var ChatListFolderSettings_RecommendedFoldersSection: String { return self._s[3709]! } - public var TwoStepAuth_SetPassword: String { return self._s[3710]! } - public var Wallet_Words_Done: String { return self._s[3711]! } - public func Login_InvalidPhoneEmailSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3712]!, self._r[3712]!, [_0]) - } - public var BlockedUsers_Info: String { return self._s[3713]! } - public var AuthSessions_Sessions: String { return self._s[3714]! } - public var Group_EditAdmin_RankTitle: String { return self._s[3715]! } - public func Wallet_Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3716]!, self._r[3716]!, [_1, _2, _3]) - } - public var Common_ActionNotAllowedError: String { return self._s[3717]! } - public var WebPreview_GettingLinkInfo: String { return self._s[3718]! } - public var Appearance_AppIconFilledX: String { return self._s[3719]! } - public var Wallet_TransactionInfo_StorageFeeInfo: String { return self._s[3720]! } - public var Passport_Email_EmailPlaceholder: String { return self._s[3721]! } - public var FeaturedStickers_OtherSection: String { return self._s[3722]! } - public var EditTheme_Edit_Preview_OutgoingText: String { return self._s[3723]! } - public var Profile_Username: String { return self._s[3724]! } - public var Appearance_RemoveTheme: String { return self._s[3725]! } - public var TwoStepAuth_SetupPasswordConfirmPassword: String { return self._s[3726]! } - public var Message_PinnedStickerMessage: String { return self._s[3727]! } - public var AccessDenied_VideoMicrophone: String { return self._s[3728]! } - public var WallpaperPreview_CustomColorBottomText: String { return self._s[3729]! } - public var Passport_Address_RegionPlaceholder: String { return self._s[3730]! } - public var SettingsSearch_Synonyms_Data_Storage_Title: String { return self._s[3731]! } - public var TwoStepAuth_Title: String { return self._s[3732]! } - public var Checkout_WebConfirmation_Title: String { return self._s[3733]! } - public var AutoDownloadSettings_VoiceMessagesInfo: String { return self._s[3734]! } - public var ChatListFolder_CategoryGroups: String { return self._s[3736]! } - public var Stats_GroupTopInviter_Promote: String { return self._s[3737]! } - public var Month_GenJuly: String { return self._s[3738]! } - public var Passport_Identity_Gender: String { return self._s[3739]! } - public var Channel_DiscussionGroup_UnlinkGroup: String { return self._s[3740]! } - public var Notification_Exceptions_DeleteAll: String { return self._s[3741]! } - public func Conversation_FileHowToText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3742]!, self._r[3742]!, [_0]) - } - public func Channel_AdminLog_MessageAdmin(_ _0: String, _ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3743]!, self._r[3743]!, [_0, _1, _2]) - } - public var Login_CodeSentSms: String { return self._s[3744]! } - public func VoiceOver_Chat_ReplyFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3746]!, self._r[3746]!, [_0]) - } - public var Login_CallRequestState2: String { return self._s[3747]! } - public var Channel_DiscussionGroup_Header: String { return self._s[3748]! } - public func Channel_AdminLog_MessageToggleInvitesOff(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3749]!, self._r[3749]!, [_0]) - } - public var Passport_Language_ms: String { return self._s[3750]! } - public var PeopleNearby_MakeInvisible: String { return self._s[3752]! } - public var ChatList_Search_FilterVoice: String { return self._s[3754]! } - public var Camera_TapAndHoldForVideo: String { return self._s[3756]! } - public var Permissions_NotificationsAllowInSettings_v0: String { return self._s[3757]! } - public func Notification_LeftChannel(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3758]!, self._r[3758]!, [_0]) - } - public func Wallet_Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3759]!, self._r[3759]!, [_1, _2, _3]) - } - public var Wallet_Info_TransactionTo: String { return self._s[3760]! } - public var Map_Locating: String { return self._s[3761]! } - public func Checkout_SavePasswordTimeout(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3763]!, self._r[3763]!, [_0]) - } - public var Passport_Identity_TypeInternalPassport: String { return self._s[3765]! } - public var Appearance_ThemePreview_Chat_4_Text: String { return self._s[3766]! } - public var SettingsSearch_Synonyms_EditProfile_Username: String { return self._s[3767]! } - public var Stickers_Installed: String { return self._s[3768]! } - public var Notifications_PermissionsAllowInSettings: String { return self._s[3769]! } - public var StickerPackActionInfo_RemovedTitle: String { return self._s[3770]! } - public var CallSettings_Never: String { return self._s[3772]! } - public var Wallet_AccessDenied_Camera: String { return self._s[3773]! } - public var Channel_Setup_TypePublicHelp: String { return self._s[3774]! } - public func ChatList_DeleteForEveryone(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3776]!, self._r[3776]!, [_0]) - } - public var Message_Game: String { return self._s[3777]! } - public var Call_Message: String { return self._s[3778]! } - public func PUSH_CHANNEL_MESSAGE_VIDEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3779]!, self._r[3779]!, [_1]) - } - public var ChannelIntro_Text: String { return self._s[3780]! } - public var StickerPack_Send: String { return self._s[3781]! } - public var Share_AuthDescription: String { return self._s[3782]! } - public var PasscodeSettings_AutoLock_IfAwayFor_5minutes: String { return self._s[3783]! } - public var CallFeedback_WhatWentWrong: String { return self._s[3784]! } - public var Common_Create: String { return self._s[3787]! } - public var Passport_Language_hy: String { return self._s[3788]! } - public var CreatePoll_Explanation: String { return self._s[3789]! } - public var GroupPermission_AddMembersNotAvailable: String { return self._s[3790]! } - public var Undo_ChatClearedForBothSides: String { return self._s[3791]! } - public var DialogList_NoMessagesTitle: String { return self._s[3792]! } - public var GroupInfo_Title: String { return self._s[3794]! } - public var Channel_AdminLog_CanBanUsers: String { return self._s[3795]! } - public var PhoneNumberHelp_Help: String { return self._s[3796]! } - public var TwoStepAuth_AdditionalPassword: String { return self._s[3797]! } - public var Settings_Logout: String { return self._s[3798]! } - public var Privacy_PaymentsTitle: String { return self._s[3799]! } - public var StickerPacksSettings_StickerPacksSection: String { return self._s[3800]! } - public var Tour_Text6: String { return self._s[3801]! } - public var Channel_Username_Help: String { return self._s[3803]! } - public var Wallet_Info_RefreshErrorTitle: String { return self._s[3804]! } - public var VoiceOver_Chat_RecordModeVoiceMessageInfo: String { return self._s[3805]! } - public var AttachmentMenu_Poll: String { return self._s[3806]! } - public var EditTheme_Create_Preview_IncomingReplyName: String { return self._s[3807]! } - public var Conversation_ReportSpamChannelConfirmation: String { return self._s[3808]! } - public var Passport_DeletePassport: String { return self._s[3809]! } - public var Login_Code: String { return self._s[3810]! } - public var Notification_SecretChatScreenshot: String { return self._s[3811]! } - public var Login_CodeFloodError: String { return self._s[3812]! } - public func Notification_PinnedAnimationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3813]!, self._r[3813]!, [_0]) - } - public func Channel_Username_UsernameIsAvailable(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3814]!, self._r[3814]!, [_0]) - } - public var Watch_Stickers_Recents: String { return self._s[3815]! } - public var Generic_ErrorMoreInfo: String { return self._s[3816]! } - public func Call_AccountIsLoggedOnCurrentDevice(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3817]!, self._r[3817]!, [_0]) - } - public var AutoDownloadSettings_DataUsage: String { return self._s[3818]! } - public var Conversation_ViewTheme: String { return self._s[3819]! } - public var Contacts_InviteSearchLabel: String { return self._s[3820]! } - public var Settings_CancelUpload: String { return self._s[3822]! } - public var Settings_AppLanguage_Unofficial: String { return self._s[3823]! } - public func ChatList_ClearChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3824]!, self._r[3824]!, [_0]) - } - public var ChatList_AddFolder: String { return self._s[3825]! } - public var Conversation_Location: String { return self._s[3827]! } - public var Appearance_BubbleCorners_AdjustAdjacent: String { return self._s[3828]! } - public var DialogList_AdLabel: String { return self._s[3829]! } - public func Time_TomorrowAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3831]!, self._r[3831]!, [_0]) - } - public var Message_InvoiceLabel: String { return self._s[3832]! } - public var Channel_TooMuchBots: String { return self._s[3833]! } - public func Channel_AdminLog_MessageRemovedChannelUsername(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3834]!, self._r[3834]!, [_0]) - } - public var Wallet_Month_ShortAugust: String { return self._s[3835]! } - public var Call_IncomingVideoCall: String { return self._s[3836]! } - public var Conversation_LiveLocation: String { return self._s[3837]! } - public var TwoStepAuth_SetupPasswordEnterPasswordChange: String { return self._s[3838]! } - public var Passport_Identity_EditPassport: String { return self._s[3839]! } - public var Permissions_CellularDataTitle_v0: String { return self._s[3841]! } - public var ChatList_Search_NoResultsFitlerVoice: String { return self._s[3842]! } - public var GroupInfo_Permissions_AddException: String { return self._s[3843]! } - public var Channel_AdminLog_CanInviteUsers: String { return self._s[3845]! } - public var Channel_MessageVideoUpdated: String { return self._s[3846]! } - public var GroupInfo_Permissions_EditingDisabled: String { return self._s[3847]! } - public var AccessDenied_Camera: String { return self._s[3850]! } - public func Target_InviteToGroupConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3851]!, self._r[3851]!, [_0]) - } - public var Theme_Context_ChangeColors: String { return self._s[3852]! } - public var PrivacySettings_TwoStepAuth: String { return self._s[3853]! } - public var Privacy_Forwards_PreviewMessageText: String { return self._s[3854]! } - public var Login_CodeExpiredError: String { return self._s[3855]! } - public var State_ConnectingToProxy: String { return self._s[3856]! } - public var TextFormat_Link: String { return self._s[3857]! } - public var Passport_Language_lv: String { return self._s[3858]! } - public var AccessDenied_VoiceMicrophone: String { return self._s[3859]! } - public var WallpaperPreview_SwipeBottomText: String { return self._s[3860]! } - public var ProfilePhoto_SetMainVideo: String { return self._s[3861]! } - public var AutoDownloadSettings_Cellular: String { return self._s[3863]! } - public var ChatSettings_AutoDownloadVoiceMessages: String { return self._s[3864]! } - public func Channel_AdminLog_MessageKickedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3865]!, self._r[3865]!, [_1, _2]) - } - public var ChatList_EmptyChatListFilterTitle: String { return self._s[3866]! } - public var Checkout_PayNone: String { return self._s[3867]! } - public var NotificationsSound_Complete: String { return self._s[3869]! } - public var TwoStepAuth_ConfirmEmailCodePlaceholder: String { return self._s[3870]! } - public var AuthSessions_DevicesTitle: String { return self._s[3871]! } - public func DialogList_MultipleTyping(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3872]!, self._r[3872]!, [_0, _1]) - } - public var Message_LiveLocation: String { return self._s[3873]! } - public var Watch_Suggestion_BRB: String { return self._s[3874]! } - public var Channel_BanUser_Title: String { return self._s[3875]! } - public var SettingsSearch_Synonyms_Privacy_Data_Title: String { return self._s[3876]! } - public var Conversation_Dice_u1F3C0: String { return self._s[3877]! } - public var Conversation_ClearSelfHistory: String { return self._s[3878]! } - public var ProfilePhoto_OpenGallery: String { return self._s[3879]! } - public var PrivacySettings_LastSeenTitle: String { return self._s[3880]! } - public var Weekday_Thursday: String { return self._s[3881]! } - public var BroadcastListInfo_AddRecipient: String { return self._s[3882]! } - public var Privacy_ProfilePhoto: String { return self._s[3884]! } - public var StickerPacksSettings_ArchivedPacks_Info: String { return self._s[3885]! } - public func Channel_AdminLog_MessageChangedUnlinkedGroup(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3886]!, self._r[3886]!, [_1, _2]) - } - public var Message_Audio: String { return self._s[3887]! } - public var Conversation_Info: String { return self._s[3888]! } - public var Cache_Videos: String { return self._s[3889]! } - public var Appearance_ThemePreview_ChatList_6_Text: String { return self._s[3890]! } - public var Channel_ErrorAddTooMuch: String { return self._s[3891]! } - public func ChatList_DeleteSecretChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3892]!, self._r[3892]!, [_0]) - } - public var ChannelMembers_ChannelAdminsTitle: String { return self._s[3894]! } - public var ScheduledMessages_Title: String { return self._s[3896]! } - public var ShareFileTip_Title: String { return self._s[3899]! } - public var Chat_Gifs_TrendingSectionHeader: String { return self._s[3900]! } - public var ChatList_RemoveFolderConfirmation: String { return self._s[3901]! } - public func PUSH_CHAT_MESSAGE_GEOLIVE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3902]!, self._r[3902]!, [_1, _2]) - } - public var Channel_DiscussionGroup_SearchPlaceholder: String { return self._s[3904]! } - public var PasscodeSettings_Title: String { return self._s[3905]! } - public var Channel_AdminLog_SendPolls: String { return self._s[3906]! } - public var LastSeen_ALongTimeAgo: String { return self._s[3907]! } - public func PUSH_CHANNEL_MESSAGE_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3908]!, self._r[3908]!, [_1]) - } - public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedChannels: String { return self._s[3909]! } - public var CallFeedback_VideoReasonLowQuality: String { return self._s[3910]! } - public var SocksProxySetup_AddProxyTitle: String { return self._s[3911]! } - public var Passport_Identity_AddInternalPassport: String { return self._s[3912]! } - public func ChatList_RemovedFromFolderTooltip(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3913]!, self._r[3913]!, [_1, _2]) - } - public func Conversation_SetReminder_RemindToday(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3914]!, self._r[3914]!, [_0]) - } - public var Passport_Identity_GenderFemale: String { return self._s[3915]! } - public var ConvertToSupergroup_HelpTitle: String { return self._s[3918]! } - public var SharedMedia_TitleAll: String { return self._s[3919]! } - public var Settings_Context_Logout: String { return self._s[3920]! } - public var GroupInfo_SetGroupPhotoDelete: String { return self._s[3922]! } - public var Settings_About_Title: String { return self._s[3923]! } - public var StickerSettings_ContextHide: String { return self._s[3924]! } + public var Passport_Identity_MainPage: String { return self._s[750]! } + public var TwoStepAuth_ConfirmEmailResendCode: String { return self._s[751]! } + public var Passport_Language_de: String { return self._s[752]! } + public var Update_Title: String { return self._s[753]! } + public var ContactInfo_PhoneLabelWorkFax: String { return self._s[754]! } + public var Channel_AdminLog_BanEmbedLinks: String { return self._s[755]! } + public var Passport_Email_UseTelegramEmailHelp: String { return self._s[756]! } + public var Notifications_ChannelNotificationsPreview: String { return self._s[757]! } + public var NotificationsSound_Telegraph: String { return self._s[758]! } + public var Watch_LastSeen_ALongTimeAgo: String { return self._s[759]! } + public var ChannelMembers_WhoCanAddMembers: String { return self._s[760]! } public func AutoDownloadSettings_UpTo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3925]!, self._r[3925]!, [_0]) + return formatWithArgumentRanges(self._s[761]!, self._r[761]!, [_0]) } + public var ClearCache_Description: String { return self._s[762]! } + public var Stickers_SuggestAll: String { return self._s[763]! } + public var Conversation_ForwardTitle: String { return self._s[764]! } + public var Appearance_ThemePreview_ChatList_7_Name: String { return self._s[765]! } + public func Notification_JoinedChannel(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[766]!, self._r[766]!, [_0]) + } + public var Calls_NewCall: String { return self._s[767]! } + public var Call_StatusEnded: String { return self._s[768]! } + public var AutoDownloadSettings_DataUsageLow: String { return self._s[770]! } + public var Settings_ProxyConnected: String { return self._s[771]! } + public var Channel_AdminLogFilter_EventsPinned: String { return self._s[772]! } + public var PhotoEditor_QualityVeryLow: String { return self._s[773]! } + public var Channel_AdminLogFilter_EventsDeletedMessages: String { return self._s[774]! } + public var Passport_PasswordPlaceholder: String { return self._s[775]! } + public var Message_PinnedInvoice: String { return self._s[776]! } + public var Passport_Identity_IssueDate: String { return self._s[777]! } + public var Stats_GroupTopHoursTitle: String { return self._s[778]! } + public func ChannelInfo_ChannelForbidden(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[779]!, self._r[779]!, [_0]) + } + public var Passport_Language_pl: String { return self._s[780]! } + public var Call_StatusConnecting: String { return self._s[781]! } + public var SocksProxySetup_PasteFromClipboard: String { return self._s[782]! } + public func Username_UsernameIsAvailable(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[783]!, self._r[783]!, [_0]) + } + public var ChatSettings_ConnectionType_UseProxy: String { return self._s[785]! } + public var Common_Edit: String { return self._s[786]! } + public var PrivacySettings_LastSeenNobody: String { return self._s[787]! } + public func Notification_LeftChat(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[788]!, self._r[788]!, [_0]) + } + public var GroupInfo_ChatAdmins: String { return self._s[789]! } + public var PrivateDataSettings_Title: String { return self._s[790]! } + public var Login_CancelPhoneVerificationStop: String { return self._s[791]! } + public var ChatList_Read: String { return self._s[792]! } + public var Wallet_WordImport_Text: String { return self._s[793]! } + public var Undo_ChatClearedForBothSides: String { return self._s[794]! } + public var ChatListFolder_AddChats: String { return self._s[795]! } + public var GroupPermission_SectionTitle: String { return self._s[796]! } + public var Settings_ViewVideo: String { return self._s[797]! } + public var TwoFactorSetup_Intro_Title: String { return self._s[799]! } + public func PUSH_CHAT_LEFT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[800]!, self._r[800]!, [_1, _2]) + } + public var Checkout_ErrorPaymentFailed: String { return self._s[801]! } + public var Update_UpdateApp: String { return self._s[803]! } + public var Group_Username_RevokeExistingUsernamesInfo: String { return self._s[804]! } + public var Settings_Appearance: String { return self._s[805]! } + public var SettingsSearch_Synonyms_Stickers_SuggestStickers: String { return self._s[809]! } + public var Watch_Location_Access: String { return self._s[810]! } + public var ShareMenu_CopyShareLink: String { return self._s[812]! } + public var TwoStepAuth_SetupHintTitle: String { return self._s[813]! } + public var Conversation_Theme: String { return self._s[815]! } + public func DialogList_SingleRecordingVideoMessageSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[816]!, self._r[816]!, [_0]) + } + public var Notifications_ClassicTones: String { return self._s[817]! } + public var Weekday_ShortWednesday: String { return self._s[818]! } + public var WallpaperPreview_SwipeColorsBottomText: String { return self._s[819]! } + public var Undo_LeftGroup: String { return self._s[822]! } + public var ChatListFolder_DiscardCancel: String { return self._s[823]! } + public var Wallet_RestoreFailed_Text: String { return self._s[824]! } + public var Conversation_LinkDialogCopy: String { return self._s[825]! } + public var Wallet_TransactionInfo_NoAddress: String { return self._s[827]! } + public var Wallet_Navigation_Back: String { return self._s[828]! } + public var KeyCommand_FocusOnInputField: String { return self._s[829]! } + public var Contacts_SelectAll: String { return self._s[830]! } + public var Preview_SaveToCameraRoll: String { return self._s[831]! } + public var PrivacySettings_PasscodeOff: String { return self._s[832]! } + public var Appearance_ThemePreview_ChatList_6_Name: String { return self._s[833]! } + public func PUSH_CHANNEL_MESSAGE_QUIZ(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[834]!, self._r[834]!, [_1]) + } + public var Wallpaper_Title: String { return self._s[835]! } + public var Conversation_FilePhotoOrVideo: String { return self._s[836]! } + public var AccessDenied_Camera: String { return self._s[837]! } + public var Watch_Compose_CurrentLocation: String { return self._s[838]! } + public var PeerInfo_ButtonMessage: String { return self._s[840]! } + public var Channel_DiscussionGroup_MakeHistoryPublicProceed: String { return self._s[841]! } + public func SecretImage_NotViewedYet(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[842]!, self._r[842]!, [_0]) + } + public var GroupInfo_InvitationLinkDoesNotExist: String { return self._s[843]! } + public var Passport_Language_ro: String { return self._s[844]! } + public var EditTheme_UploadNewTheme: String { return self._s[845]! } + public var CheckoutInfo_SaveInfoHelp: String { return self._s[846]! } + public var Wallet_Intro_Terms: String { return self._s[847]! } + public func Notification_SecretChatMessageScreenshot(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[848]!, self._r[848]!, [_0]) + } + public var Login_CancelPhoneVerification: String { return self._s[849]! } + public var State_ConnectingToProxy: String { return self._s[850]! } + public var Calls_RatingTitle: String { return self._s[851]! } + public var Generic_ErrorMoreInfo: String { return self._s[852]! } + public var ChatList_Search_ShowMore: String { return self._s[853]! } + public var Appearance_PreviewReplyText: String { return self._s[854]! } + public var CheckoutInfo_ShippingInfoPostcodePlaceholder: String { return self._s[855]! } + public func Wallet_Send_Balance(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[856]!, self._r[856]!, [_0]) + } + public var IntentsSettings_SuggestedChatsContacts: String { return self._s[857]! } + public var SharedMedia_CategoryLinks: String { return self._s[858]! } + public var Calls_Missed: String { return self._s[859]! } + public var Settings_AddAnotherAccount_Help: String { return self._s[863]! } + public var Cache_Photos: String { return self._s[864]! } + public var GroupPermission_NoAddMembers: String { return self._s[865]! } + public var ScheduledMessages_Title: String { return self._s[866]! } + public func Channel_AdminLog_MessageUnpinned(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[867]!, self._r[867]!, [_0]) + } + public var Conversation_ShareBotLocationConfirmationTitle: String { return self._s[868]! } + public var Settings_ProxyDisabled: String { return self._s[869]! } + public func Settings_ApplyProxyAlertCredentials(_ _1: String, _ _2: String, _ _3: String, _ _4: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[870]!, self._r[870]!, [_1, _2, _3, _4]) + } + public func Conversation_RestrictedMediaTimed(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[871]!, self._r[871]!, [_0]) + } + public var Stats_ViewsPerPost: String { return self._s[873]! } + public var ChatList_AutoarchiveSuggestion_OpenSettings: String { return self._s[874]! } + public var ChatList_Context_RemoveFromRecents: String { return self._s[875]! } + public var Appearance_Title: String { return self._s[876]! } + public func Time_MonthOfYear_m2(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[878]!, self._r[878]!, [_0]) + } + public func Call_CameraOff(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[879]!, self._r[879]!, [_0]) + } + public var Conversation_WalletRequiredText: String { return self._s[880]! } + public var StickerPacksSettings_ShowStickersButtonHelp: String { return self._s[881]! } + public var OldChannels_NoticeCreateText: String { return self._s[882]! } + public var Channel_EditMessageErrorGeneric: String { return self._s[883]! } + public var Privacy_Calls_IntegrationHelp: String { return self._s[884]! } + public var Preview_DeletePhoto: String { return self._s[885]! } + public var Appearance_AppIconFilledX: String { return self._s[886]! } + public var PrivacySettings_PrivacyTitle: String { return self._s[887]! } + public func Conversation_BotInteractiveUrlAlert(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[888]!, self._r[888]!, [_0]) + } + public var ChatListFolder_TitleEdit: String { return self._s[891]! } + public var MuteFor_Forever: String { return self._s[892]! } + public var Coub_TapForSound: String { return self._s[893]! } + public var Map_LocatingError: String { return self._s[894]! } + public var TwoStepAuth_EmailChangeSuccess: String { return self._s[896]! } + public var Conversation_SendMessage_SendSilently: String { return self._s[897]! } + public var VoiceOver_MessageContextOpenMessageMenu: String { return self._s[898]! } + public func Wallet_Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[899]!, self._r[899]!, [_1, _2, _3]) + } + public var Passport_ForgottenPassword: String { return self._s[900]! } + public var GroupInfo_InviteLink_RevokeLink: String { return self._s[901]! } + public var StickerPacksSettings_ArchivedPacks: String { return self._s[902]! } + public var Login_TermsOfServiceSignupDecline: String { return self._s[904]! } + public var Channel_Moderator_AccessLevelRevoke: String { return self._s[905]! } + public var Message_Location: String { return self._s[906]! } + public var Passport_Identity_NamePlaceholder: String { return self._s[907]! } + public var Channel_Management_Title: String { return self._s[908]! } + public var DialogList_SearchSectionDialogs: String { return self._s[910]! } + public var Compose_NewChannel_Members: String { return self._s[911]! } + public func DialogList_SingleUploadingFileSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[912]!, self._r[912]!, [_0]) + } + public var GroupInfo_Location: String { return self._s[913]! } + public var Appearance_ThemePreview_ChatList_5_Name: String { return self._s[914]! } + public var ClearCache_Clear: String { return self._s[915]! } + public var InstantPage_FeedbackButtonShort: String { return self._s[916]! } + public var AutoNightTheme_ScheduledFrom: String { return self._s[917]! } + public var PhotoEditor_WarmthTool: String { return self._s[918]! } + public func PUSH_MESSAGE_GAME_SCORE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[919]!, self._r[919]!, [_1, _2, _3]) + } + public var Passport_Language_tr: String { return self._s[920]! } + public var OldChannels_NoticeUpgradeText: String { return self._s[921]! } + public var Login_ResetAccountProtected_Reset: String { return self._s[923]! } + public var Watch_PhotoView_Title: String { return self._s[924]! } + public var Passport_Phone_Delete: String { return self._s[925]! } + public var Undo_ChatDeletedForBothSides: String { return self._s[926]! } + public var Conversation_EditingMessageMediaEditCurrentPhoto: String { return self._s[927]! } + public var GroupInfo_Permissions: String { return self._s[928]! } + public var PasscodeSettings_TurnPasscodeOff: String { return self._s[929]! } + public var Profile_ShareContactButton: String { return self._s[930]! } + public var ChatSettings_Other: String { return self._s[931]! } + public var UserInfo_NotificationsDisabled: String { return self._s[932]! } + public var CheckoutInfo_ShippingInfoCity: String { return self._s[933]! } + public var LastSeen_WithinAMonth: String { return self._s[934]! } + public var VoiceOver_Chat_PlayHint: String { return self._s[935]! } + public var Conversation_ReportGroupLocation: String { return self._s[936]! } + public var Conversation_EncryptionCanceled: String { return self._s[937]! } + public var MediaPicker_GroupDescription: String { return self._s[938]! } + public var WebSearch_Images: String { return self._s[939]! } + public func Channel_Management_PromotedBy(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[940]!, self._r[940]!, [_0]) + } + public var Message_Photo: String { return self._s[941]! } + public var PasscodeSettings_HelpBottom: String { return self._s[942]! } + public var AutoDownloadSettings_VideosTitle: String { return self._s[943]! } + public var Conversation_ContextMenuSendMessage: String { return self._s[944]! } + public var VoiceOver_Media_PlaybackRateChange: String { return self._s[945]! } + public var Passport_Identity_AddDriversLicense: String { return self._s[946]! } + public var TwoStepAuth_EnterPasswordPassword: String { return self._s[947]! } + public var NotificationsSound_Calypso: String { return self._s[948]! } + public var Map_Map: String { return self._s[949]! } public func Conversation_LiveLocationYouAndOther(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3926]!, self._r[3926]!, [_0]) + return formatWithArgumentRanges(self._s[950]!, self._r[950]!, [_0]) } - public var Common_Cancel: String { return self._s[3928]! } - public var CallFeedback_Title: String { return self._s[3930]! } - public func Notification_PinnedContactMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3931]!, self._r[3931]!, [_0]) + public var CheckoutInfo_ReceiverInfoTitle: String { return self._s[953]! } + public var ChatSettings_TextSizeUnits: String { return self._s[954]! } + public func VoiceOver_Chat_FileFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[955]!, self._r[955]!, [_0]) } - public var Activity_UploadingVideoMessage: String { return self._s[3932]! } - public var Wallet_TransactionInfo_OtherFeeInfo: String { return self._s[3933]! } - public var MediaPicker_Send: String { return self._s[3934]! } - public var PasscodeSettings_AutoLock_IfAwayFor_1minute: String { return self._s[3935]! } - public var Conversation_LiveLocationYou: String { return self._s[3936]! } - public var Notifications_ExceptionsUnmuted: String { return self._s[3937]! } - public func Channel_AdminLog_MessageGroupPreHistoryHidden(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3938]!, self._r[3938]!, [_0]) + public var Common_of: String { return self._s[956]! } + public var Conversation_ForwardContacts: String { return self._s[959]! } + public var IntentsSettings_SuggestByAll: String { return self._s[961]! } + public func Call_AnsweringWithAccount(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[962]!, self._r[962]!, [_0]) } - public func PUSH_CHAT_ADD_YOU(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3939]!, self._r[3939]!, [_1, _2]) + public var Call_CameraConfirmationText: String { return self._s[963]! } + public var Passport_Language_hy: String { return self._s[964]! } + public var Notifications_MessageNotificationsHelp: String { return self._s[965]! } + public var AutoDownloadSettings_Reset: String { return self._s[966]! } + public var Wallet_TransactionInfo_AddressCopied: String { return self._s[967]! } + public var Paint_ClearConfirm: String { return self._s[968]! } + public var Camera_VideoMode: String { return self._s[969]! } + public func Conversation_RestrictedStickersTimed(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[970]!, self._r[970]!, [_0]) } - public var Conversation_ViewBackground: String { return self._s[3940]! } - public var ChatSettings_PrivateChats: String { return self._s[3943]! } - public var Conversation_ErrorInaccessibleMessage: String { return self._s[3944]! } - public var Wallet_Receive_AmountInfo: String { return self._s[3945]! } - public var Appearance_ThemeNight: String { return self._s[3946]! } - public var Common_Search: String { return self._s[3947]! } - public var TwoStepAuth_ReEnterPasswordTitle: String { return self._s[3948]! } - public var ChangePhoneNumberNumber_Help: String { return self._s[3950]! } - public var Stickers_SuggestAdded: String { return self._s[3951]! } - public var Conversation_DiscardVoiceMessageDescription: String { return self._s[3954]! } - public var NetworkUsageSettings_Cellular: String { return self._s[3955]! } - public var CheckoutInfo_Title: String { return self._s[3956]! } - public var Conversation_ShareBotLocationConfirmationTitle: String { return self._s[3957]! } - public var Channel_BotDoesntSupportGroups: String { return self._s[3958]! } - public func DialogList_SingleRecordingAudioSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3959]!, self._r[3959]!, [_0]) + public var Privacy_Calls_AlwaysAllow_Placeholder: String { return self._s[971]! } + public var Conversation_ViewBackground: String { return self._s[972]! } + public func Wallet_Info_TransactionDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[973]!, self._r[973]!, [_1, _2, _3]) } - public var MaskStickerSettings_Info: String { return self._s[3960]! } - public var GroupRemoved_DeleteUser: String { return self._s[3961]! } - public var Contacts_ShareTelegram: String { return self._s[3962]! } - public var Group_UpgradeNoticeText1: String { return self._s[3963]! } - public func PUSH_PHONE_CALL_REQUEST(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3964]!, self._r[3964]!, [_1]) + public var Passport_Language_el: String { return self._s[974]! } + public var PhotoEditor_Original: String { return self._s[975]! } + public var Settings_FAQ_Button: String { return self._s[978]! } + public var Channel_Setup_PublicNoLink: String { return self._s[980]! } + public var Conversation_UnsupportedMedia: String { return self._s[981]! } + public var Conversation_SlideToCancel: String { return self._s[982]! } + public var Appearance_ThemePreview_ChatList_4_Name: String { return self._s[983]! } + public var Passport_Identity_OneOfTypeInternalPassport: String { return self._s[984]! } + public var CheckoutInfo_ShippingInfoPostcode: String { return self._s[985]! } + public var Conversation_ReportSpamChannelConfirmation: String { return self._s[986]! } + public var Stats_GroupViewers: String { return self._s[987]! } + public var AutoNightTheme_NotAvailable: String { return self._s[988]! } + public var Conversation_Owner: String { return self._s[989]! } + public var Common_Create: String { return self._s[990]! } + public var Settings_ApplyProxyAlertEnable: String { return self._s[991]! } + public var ContactList_Context_Call: String { return self._s[992]! } + public var Localization_ChooseLanguage: String { return self._s[994]! } + public var ChatList_Context_AddToContacts: String { return self._s[996]! } + public var OldChannels_NoticeTitle: String { return self._s[997]! } + public var Settings_Proxy: String { return self._s[999]! } + public var Privacy_TopPeersHelp: String { return self._s[1000]! } + public var CheckoutInfo_ShippingInfoCountryPlaceholder: String { return self._s[1001]! } + public var Chat_UnsendMyMessages: String { return self._s[1002]! } + public func VoiceOver_Chat_Duration(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1003]!, self._r[1003]!, [_0]) } - public var PrivacyLastSeenSettings_Title: String { return self._s[3965]! } - public var SettingsSearch_Synonyms_Support: String { return self._s[3969]! } - public var PhotoEditor_TintTool: String { return self._s[3970]! } - public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[3972]! } - public var GroupPermission_NoSendPolls: String { return self._s[3973]! } - public var NotificationsSound_None: String { return self._s[3974]! } - public func LOCAL_CHANNEL_MESSAGE_FWDS(_ _1: String, _ _2: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3975]!, self._r[3975]!, [_1, "\(_2)"]) + public var TwoStepAuth_ConfirmationAbort: String { return self._s[1004]! } + public func Contacts_AccessDeniedHelpPortrait(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1006]!, self._r[1006]!, [_0]) } - public var CheckoutInfo_ShippingInfoCityPlaceholder: String { return self._s[3977]! } - public var ExplicitContent_AlertChannel: String { return self._s[3979]! } - public var Conversation_ClousStorageInfo_Description1: String { return self._s[3980]! } - public var Contacts_SortedByPresence: String { return self._s[3981]! } - public var WallpaperSearch_ColorGray: String { return self._s[3982]! } - public var Channel_AdminLogFilter_EventsNewSubscribers: String { return self._s[3983]! } - public var Conversation_ReportSpam: String { return self._s[3984]! } - public var ChatList_Search_NoResultsFilter: String { return self._s[3987]! } - public var WallpaperSearch_ColorBlack: String { return self._s[3988]! } - public var ArchivedChats_IntroTitle3: String { return self._s[3989]! } - public var Conversation_DefaultRestrictedText: String { return self._s[3990]! } - public var Settings_Devices: String { return self._s[3991]! } - public var Call_AudioRouteSpeaker: String { return self._s[3992]! } - public var GroupInfo_InviteLink_CopyLink: String { return self._s[3993]! } - public var Passport_Address_Country: String { return self._s[3995]! } - public var Cache_MaximumCacheSize: String { return self._s[3996]! } - public var Notifications_Badge_IncludePublicGroups: String { return self._s[3997]! } - public var Wallet_Receive_CreateInvoice: String { return self._s[3999]! } - public var ChatSettings_AutoDownloadUsingWiFi: String { return self._s[4000]! } - public var Login_TermsOfServiceLabel: String { return self._s[4001]! } - public var Calls_NoMissedCallsPlacehoder: String { return self._s[4002]! } - public var SocksProxySetup_RequiredCredentials: String { return self._s[4003]! } - public var VoiceOver_MessageContextOpenMessageMenu: String { return self._s[4004]! } - public var AutoNightTheme_ScheduledFrom: String { return self._s[4005]! } - public var ChatSettings_AutoDownloadDocuments: String { return self._s[4006]! } - public var ConvertToSupergroup_Note: String { return self._s[4008]! } - public var Settings_SetNewProfilePhotoOrVideo: String { return self._s[4009]! } - public var PrivacySettings_PasscodeAndTouchId: String { return self._s[4010]! } - public var Common_More: String { return self._s[4011]! } - public var ShareMenu_SelectChats: String { return self._s[4013]! } - public func Conversation_ScheduleMessage_SendToday(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4015]!, self._r[4015]!, [_0]) + public var Contacts_SortedByPresence: String { return self._s[1007]! } + public var Passport_Identity_SurnamePlaceholder: String { return self._s[1008]! } + public var Cache_Title: String { return self._s[1009]! } + public func Login_PhoneBannedEmailSubject(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1010]!, self._r[1010]!, [_0]) + } + public var TwoStepAuth_EmailCodeExpired: String { return self._s[1011]! } + public var Channel_Moderator_Title: String { return self._s[1012]! } + public var InstantPage_AutoNightTheme: String { return self._s[1014]! } + public func PUSH_MESSAGE_POLL(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1017]!, self._r[1017]!, [_1]) + } + public var Passport_Scans_Upload: String { return self._s[1018]! } + public var Undo_Undo: String { return self._s[1020]! } + public var Contacts_AccessDeniedHelpON: String { return self._s[1021]! } + public var TwoStepAuth_RemovePassword: String { return self._s[1022]! } + public var Common_Delete: String { return self._s[1023]! } + public var Contacts_AddPeopleNearby: String { return self._s[1025]! } + public var Conversation_ContextMenuDelete: String { return self._s[1026]! } + public var SocksProxySetup_Credentials: String { return self._s[1027]! } + public var Appearance_EditTheme: String { return self._s[1029]! } + public var ClearCache_StorageOtherApps: String { return self._s[1030]! } + public func Conversation_PeerNearbyTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1032]!, self._r[1032]!, [_1, _2]) + } + public var Settings_EditPhoto: String { return self._s[1033]! } + public var PasscodeSettings_AutoLock_Disabled: String { return self._s[1034]! } + public var Wallet_Send_NetworkErrorText: String { return self._s[1035]! } + public var AuthSessions_DevicesTitle: String { return self._s[1037]! } + public var Passport_Address_OneOfTypeRentalAgreement: String { return self._s[1039]! } + public var Conversation_ShareBotContactConfirmationTitle: String { return self._s[1040]! } + public var Passport_Language_id: String { return self._s[1042]! } + public var Chat_Gifs_TrendingSectionHeader: String { return self._s[1043]! } + public var WallpaperSearch_ColorTeal: String { return self._s[1044]! } + public var ChannelIntro_Title: String { return self._s[1045]! } + public func Channel_AdminLog_MessageToggleSignaturesOff(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1046]!, self._r[1046]!, [_0]) + } + public var VoiceOver_Chat_OpenLinkHint: String { return self._s[1048]! } + public var VoiceOver_Chat_Reply: String { return self._s[1049]! } + public var ScheduledMessages_BotActionUnavailable: String { return self._s[1050]! } + public var Channel_Info_Description: String { return self._s[1051]! } + public var Stickers_FavoriteStickers: String { return self._s[1052]! } + public var Channel_BanUser_PermissionAddMembers: String { return self._s[1053]! } + public var Notifications_DisplayNamesOnLockScreen: String { return self._s[1054]! } + public var ChatSearch_ResultsTooltip: String { return self._s[1055]! } + public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[1056]! } + public var Calls_NoMissedCallsPlacehoder: String { return self._s[1057]! } + public var Group_PublicLink_Placeholder: String { return self._s[1058]! } + public var Notifications_ExceptionsDefaultSound: String { return self._s[1059]! } + public func PUSH_CHANNEL_MESSAGE_POLL(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1060]!, self._r[1060]!, [_1]) + } + public var TextFormat_Underline: String { return self._s[1061]! } + public func DialogList_SearchSubtitleFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1063]!, self._r[1063]!, [_1, _2]) } public func Channel_AdminLog_MessageRemovedGroupStickerPack(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4016]!, self._r[4016]!, [_0]) + return formatWithArgumentRanges(self._s[1064]!, self._r[1064]!, [_0]) } - public var Contacts_PermissionsKeepDisabled: String { return self._s[4018]! } - public func Call_ParticipantVersionOutdatedError(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4019]!, self._r[4019]!, [_0]) + public var Appearance_ThemePreview_ChatList_3_Name: String { return self._s[1065]! } + public func Channel_OwnershipTransfer_TransferCompleted(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1066]!, self._r[1066]!, [_1, _2]) } - public var WatchRemote_AlertOpen: String { return self._s[4020]! } - public func PUSH_CHAT_ADD_MEMBER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4021]!, self._r[4021]!, [_1, _2, _3]) + public var Wallet_Intro_ImportExisting: String { return self._s[1067]! } + public var GroupPermission_Delete: String { return self._s[1068]! } + public var Passport_Language_uk: String { return self._s[1069]! } + public var StickerPack_HideStickers: String { return self._s[1071]! } + public var ChangePhoneNumberNumber_NumberPlaceholder: String { return self._s[1072]! } + public func PUSH_CHAT_MESSAGE_PHOTO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1073]!, self._r[1073]!, [_1, _2]) } - public var Channel_Members_AddMembersHelp: String { return self._s[4022]! } - public var Shortcut_SwitchAccount: String { return self._s[4023]! } - public var Map_LiveLocationFor8Hours: String { return self._s[4024]! } - public func AutoNightTheme_AutomaticHelp(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4025]!, self._r[4025]!, [_0]) + public var Activity_UploadingVideoMessage: String { return self._s[1074]! } + public func GroupPermission_ApplyAlertText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1075]!, self._r[1075]!, [_0]) } - public var Compose_NewGroupTitle: String { return self._s[4026]! } - public var DialogList_You: String { return self._s[4027]! } - public var ReportPeer_ReasonViolence: String { return self._s[4028]! } - public func PUSH_CHANNEL_MESSAGE_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4029]!, self._r[4029]!, [_1, _2]) + public var Channel_TitleInfo: String { return self._s[1076]! } + public var StickerPacksSettings_ArchivedPacks_Info: String { return self._s[1077]! } + public var Settings_CallSettings: String { return self._s[1078]! } + public var Camera_SquareMode: String { return self._s[1079]! } + public var Conversation_SendMessage_ScheduleMessage: String { return self._s[1080]! } + public var GroupInfo_SharedMediaNone: String { return self._s[1081]! } + public func PUSH_MESSAGE_VIDEO_SECRET(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1082]!, self._r[1082]!, [_1]) } - public var KeyCommand_ScrollDown: String { return self._s[4033]! } - public var ChatSettings_DownloadInBackground: String { return self._s[4034]! } - public var Wallpaper_ResetWallpapers: String { return self._s[4035]! } - public var Channel_BanList_RestrictedTitle: String { return self._s[4036]! } - public var ArchivedChats_IntroText3: String { return self._s[4037]! } - public var HashtagSearch_AllChats: String { return self._s[4039]! } - public var Channel_Info_BlackList: String { return self._s[4041]! } - public var Contacts_SearchUsersAndGroupsLabel: String { return self._s[4042]! } - public var PrivacyPhoneNumberSettings_DiscoveryHeader: String { return self._s[4043]! } - public var Paint_Neon: String { return self._s[4045]! } - public var SettingsSearch_Synonyms_AppLanguage: String { return self._s[4046]! } - public var AutoDownloadSettings_AutoDownload: String { return self._s[4047]! } - public func Notification_PinnedVideoMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4049]!, self._r[4049]!, [_0]) + public var Bot_GenericBotStatus: String { return self._s[1083]! } + public var Application_Update: String { return self._s[1085]! } + public var Month_ShortJanuary: String { return self._s[1086]! } + public var Contacts_PermissionsKeepDisabled: String { return self._s[1087]! } + public var Channel_AdminLog_BanReadMessages: String { return self._s[1088]! } + public var Settings_AppLanguage_Unofficial: String { return self._s[1089]! } + public var Passport_Address_Street2Placeholder: String { return self._s[1090]! } + public func Map_LiveLocationShortHour(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1091]!, self._r[1091]!, [_0]) } - public var Map_StopLiveLocation: String { return self._s[4050]! } - public var SettingsSearch_Synonyms_Data_SaveEditedPhotos: String { return self._s[4051]! } - public var Channel_Username_InvalidCharacters: String { return self._s[4052]! } - public var InstantPage_Reference: String { return self._s[4053]! } - public var ChatList_HideAction: String { return self._s[4055]! } - public var Conversation_FileICloudDrive: String { return self._s[4057]! } - public func PUSH_PINNED_GEOLIVE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4058]!, self._r[4058]!, [_1]) - } - public var Passport_PasswordReset: String { return self._s[4060]! } - public var ChatList_Context_UnhideArchive: String { return self._s[4062]! } - public var ConvertToSupergroup_HelpText: String { return self._s[4063]! } - public var Calls_AddTab: String { return self._s[4064]! } - public var TwoStepAuth_ConfirmEmailResendCode: String { return self._s[4065]! } - public var SettingsSearch_Synonyms_Stickers_SuggestStickers: String { return self._s[4066]! } - public var Privacy_GroupsAndChannels: String { return self._s[4068]! } - public var AutoNightTheme_Disabled: String { return self._s[4069]! } - public var CreatePoll_MultipleChoice: String { return self._s[4070]! } - public func PINNED_INVOICE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4071]!, self._r[4071]!, [_1]) - } - public var Watch_Bot_Restart: String { return self._s[4073]! } - public func Conversation_Kilobytes(_ _0: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4074]!, self._r[4074]!, ["\(_0)"]) - } - public var GroupInfo_ScamGroupWarning: String { return self._s[4075]! } - public var Conversation_EditingMessagePanelMedia: String { return self._s[4076]! } - public var Appearance_PreviewIncomingText: String { return self._s[4077]! } - public var Notifications_ChannelNotificationsExceptionsHelp: String { return self._s[4078]! } - public var ChatList_UndoArchiveRevealedTitle: String { return self._s[4080]! } - public var Stats_GroupOverview: String { return self._s[4082]! } - public var ScheduledMessages_EditTime: String { return self._s[4084]! } - public var Month_GenFebruary: String { return self._s[4085]! } - public var ChatList_AutoarchiveSuggestion_OpenSettings: String { return self._s[4086]! } - public var Stickers_ClearRecent: String { return self._s[4087]! } - public var TwoStepAuth_EnterPasswordPassword: String { return self._s[4088]! } - public func Checkout_PayPrice(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4089]!, self._r[4089]!, [_0]) - } - public var Login_TermsOfServiceSignupDecline: String { return self._s[4090]! } - public var CheckoutInfo_ErrorCityInvalid: String { return self._s[4091]! } - public var VoiceOver_Chat_PlayHint: String { return self._s[4092]! } - public var ChatAdmins_AllMembersAreAdminsOffHelp: String { return self._s[4093]! } - public var CheckoutInfo_ShippingInfoTitle: String { return self._s[4095]! } - public var CreatePoll_Create: String { return self._s[4096]! } - public var ChatList_Search_FilterLinks: String { return self._s[4097]! } - public var Your_cards_number_is_invalid: String { return self._s[4098]! } - public var Month_ShortApril: String { return self._s[4099]! } - public var SocksProxySetup_UseForCalls: String { return self._s[4100]! } - public var Conversation_EditingCaptionPanelTitle: String { return self._s[4101]! } - public var SocksProxySetup_Status: String { return self._s[4102]! } - public var ChannelInfo_DeleteGroupConfirmation: String { return self._s[4103]! } - public var ChatListFolder_CategoryBots: String { return self._s[4104]! } - public var Passport_FieldIdentitySelfieHelp: String { return self._s[4106]! } - public var GroupInfo_BroadcastListNamePlaceholder: String { return self._s[4107]! } - public var Wallpaper_ResetWallpapersInfo: String { return self._s[4108]! } - public var Conversation_TitleUnmute: String { return self._s[4109]! } - public var Group_Setup_TypeHeader: String { return self._s[4110]! } - public var Stats_ViewsPerPost: String { return self._s[4111]! } - public var CheckoutInfo_ShippingInfoCountry: String { return self._s[4112]! } - public var Passport_Identity_TranslationHelp: String { return self._s[4113]! } - public func PUSH_CHANNEL_MESSAGE_FWD(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4114]!, self._r[4114]!, [_1]) - } - public var GroupInfo_Administrators_Title: String { return self._s[4115]! } - public func Channel_AdminLog_MessageRankName(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4116]!, self._r[4116]!, [_1, _2]) - } - public func PUSH_CHAT_MESSAGE_POLL(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4117]!, self._r[4117]!, [_1, _2, _3]) - } - public var Wallet_Receive_Title: String { return self._s[4118]! } - public var CheckoutInfo_ShippingInfoState: String { return self._s[4119]! } - public var Passport_Language_my: String { return self._s[4121]! } - public var PrivacyLastSeenSettings_AlwaysShareWith_Title: String { return self._s[4122]! } - public var Map_PlacesNearby: String { return self._s[4123]! } - public var Channel_About_Help: String { return self._s[4124]! } - public var LogoutOptions_AddAccountTitle: String { return self._s[4125]! } - public var ChatSettings_AutomaticAudioDownload: String { return self._s[4126]! } - public var Channel_Username_Title: String { return self._s[4127]! } - public var Activity_RecordingVideoMessage: String { return self._s[4128]! } + public var NetworkUsageSettings_Cellular: String { return self._s[1092]! } + public var Appearance_PreviewOutgoingText: String { return self._s[1093]! } public func StickerPackActionInfo_RemovedText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4129]!, self._r[4129]!, [_0]) + return formatWithArgumentRanges(self._s[1094]!, self._r[1094]!, [_0]) } - public var CheckoutInfo_ShippingInfoCity: String { return self._s[4130]! } - public var Passport_DiscardMessageDescription: String { return self._s[4131]! } - public var Conversation_LinkDialogOpen: String { return self._s[4132]! } - public var ChatList_Context_HideArchive: String { return self._s[4133]! } - public func Message_AuthorPinnedGame(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4134]!, self._r[4134]!, [_0]) + public var Notifications_PermissionsAllowInSettings: String { return self._s[1095]! } + public var AutoDownloadSettings_OnForAll: String { return self._s[1098]! } + public var Map_Directions: String { return self._s[1099]! } + public var Passport_FieldIdentityTranslationHelp: String { return self._s[1101]! } + public var Appearance_ThemeDay: String { return self._s[1102]! } + public var LogoutOptions_LogOut: String { return self._s[1103]! } + public var Group_PublicLink_Title: String { return self._s[1105]! } + public var Channel_AddBotErrorNoRights: String { return self._s[1106]! } + public var ChatList_Search_ShowLess: String { return self._s[1109]! } + public var Passport_Identity_AddPassport: String { return self._s[1110]! } + public var LocalGroup_ButtonTitle: String { return self._s[1111]! } + public var Stats_InteractionsTitle: String { return self._s[1112]! } + public var Stats_GroupActionsTitle: String { return self._s[1113]! } + public var Call_Message: String { return self._s[1114]! } + public var PhotoEditor_ExposureTool: String { return self._s[1115]! } + public var Wallet_Receive_CommentInfo: String { return self._s[1117]! } + public var Passport_FieldOneOf_Delimeter: String { return self._s[1118]! } + public var Channel_AdminLog_CanBanUsers: String { return self._s[1120]! } + public var Appearance_ThemePreview_ChatList_2_Name: String { return self._s[1121]! } + public var Appearance_Preview: String { return self._s[1122]! } + public var Compose_ChannelMembers: String { return self._s[1123]! } + public var Conversation_DeleteManyMessages: String { return self._s[1124]! } + public var ReportPeer_ReasonOther_Title: String { return self._s[1125]! } + public var Checkout_ErrorProviderAccountTimeout: String { return self._s[1126]! } + public var TwoStepAuth_ResetAccountConfirmation: String { return self._s[1127]! } + public var Channel_Stickers_CreateYourOwn: String { return self._s[1130]! } + public var Conversation_UpdateTelegram: String { return self._s[1131]! } + public var EditTheme_Create_TopInfo: String { return self._s[1132]! } + public func Notification_PinnedPhotoMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1133]!, self._r[1133]!, [_0]) } - public var Privacy_GroupsAndChannels_CustomShareHelp: String { return self._s[4135]! } - public var Conversation_Admin: String { return self._s[4136]! } - public var DialogList_TabTitle: String { return self._s[4137]! } - public func PUSH_CHAT_ALBUM(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4138]!, self._r[4138]!, [_1, _2]) + public var Wallet_WordCheck_Continue: String { return self._s[1134]! } + public var TwoFactorSetup_Hint_Action: String { return self._s[1135]! } + public var IntentsSettings_ResetAll: String { return self._s[1136]! } + public func PUSH_PINNED_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1137]!, self._r[1137]!, [_1]) } - public var Notifications_PermissionsUnreachableText: String { return self._s[4139]! } - public var Passport_Identity_GenderMale: String { return self._s[4141]! } - public var SettingsSearch_Synonyms_Privacy_BlockedUsers: String { return self._s[4143]! } - public var PhoneNumberHelp_Alert: String { return self._s[4144]! } - public var EnterPasscode_EnterNewPasscodeChange: String { return self._s[4145]! } - public var Notifications_InAppNotifications: String { return self._s[4146]! } - public func Update_AppVersion(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4147]!, self._r[4147]!, [_0]) + public var ChatList_RemoveFolder: String { return self._s[1138]! } + public var GroupInfo_Administrators_Title: String { return self._s[1139]! } + public var Stats_GroupPosters: String { return self._s[1140]! } + public var Stats_MessageTitle: String { return self._s[1141]! } + public var Privacy_Forwards_PreviewMessageText: String { return self._s[1142]! } + public func PrivacySettings_LastSeenNobodyPlus(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1143]!, self._r[1143]!, [_0]) } - public var Notification_VideoCallOutgoing: String { return self._s[4148]! } - public var Login_InvalidCodeError: String { return self._s[4149]! } - public var Conversation_PrivateChannelTimeLimitedAlertJoin: String { return self._s[4150]! } - public func LastSeen_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4151]!, self._r[4151]!, [_0]) + public var Tour_Title3: String { return self._s[1144]! } + public var Channel_EditAdmin_PermissionInviteSubscribers: String { return self._s[1145]! } + public var Settings_RemoveVideo: String { return self._s[1148]! } + public var Clipboard_SendPhoto: String { return self._s[1150]! } + public var MediaPicker_Videos: String { return self._s[1151]! } + public var Passport_Email_Title: String { return self._s[1152]! } + public func PrivacySettings_LastSeenEverybodyMinus(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1153]!, self._r[1153]!, [_0]) } - public var Conversation_InputTextCaptionPlaceholder: String { return self._s[4152]! } - public var ReportPeer_Report: String { return self._s[4153]! } - public var Camera_FlashOff: String { return self._s[4155]! } - public var Conversation_InputTextBroadcastPlaceholder: String { return self._s[4158]! } - public var PrivacyPolicy_DeclineTitle: String { return self._s[4160]! } - public var SettingsSearch_Synonyms_Privacy_PasscodeAndTouchId: String { return self._s[4161]! } - public var Passport_FieldEmail: String { return self._s[4162]! } - public func Channel_AdminLog_MessageKickedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4163]!, self._r[4163]!, [_1]) + public var StickerPacksSettings_Title: String { return self._s[1154]! } + public var Conversation_MessageDialogDelete: String { return self._s[1155]! } + public var Privacy_Calls_CustomHelp: String { return self._s[1157]! } + public var Message_Wallpaper: String { return self._s[1158]! } + public var MemberSearch_BotSection: String { return self._s[1159]! } + public var GroupInfo_SetSound: String { return self._s[1160]! } + public var Wallet_Send_EncryptComment: String { return self._s[1161]! } + public func Time_TomorrowAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1162]!, self._r[1162]!, [_0]) } - public var Notifications_ExceptionsResetToDefaults: String { return self._s[4164]! } - public var PeerInfo_PaneVoiceAndVideo: String { return self._s[4165]! } - public var Group_OwnershipTransfer_Title: String { return self._s[4166]! } - public var Conversation_DefaultRestrictedInline: String { return self._s[4167]! } - public var Login_PhoneNumberHelp: String { return self._s[4169]! } - public var Channel_AdminLogFilter_EventsNewMembers: String { return self._s[4170]! } - public var Conversation_PinnedQuiz: String { return self._s[4171]! } - public var CreateGroup_SoftUserLimitAlert: String { return self._s[4172]! } - public var Login_PhoneNumberAlreadyAuthorizedSwitch: String { return self._s[4173]! } - public var Group_MessagePhotoUpdated: String { return self._s[4174]! } - public var LoginPassword_PasswordPlaceholder: String { return self._s[4175]! } - public var Passport_Identity_Translations: String { return self._s[4177]! } - public var ChatAdmins_AllMembersAreAdmins: String { return self._s[4178]! } - public var ChannelInfo_DeleteChannel: String { return self._s[4180]! } - public var PasscodeSettings_HelpBottom: String { return self._s[4181]! } - public var Channel_Members_AddMembers: String { return self._s[4182]! } - public var AutoDownloadSettings_LastDelimeter: String { return self._s[4183]! } - public var Notification_Exceptions_DeleteAllConfirmation: String { return self._s[4185]! } - public var Conversation_HoldForAudio: String { return self._s[4186]! } - public var Watch_LastSeen_Lately: String { return self._s[4188]! } - public var ChatList_Context_MarkAsRead: String { return self._s[4189]! } - public var Conversation_PinnedMessage: String { return self._s[4190]! } - public var SettingsSearch_Synonyms_Appearance_ColorTheme: String { return self._s[4191]! } - public var Passport_UpdateRequiredError: String { return self._s[4193]! } - public var PrivacySettings_Passcode: String { return self._s[4194]! } - public func Call_EmojiDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4195]!, self._r[4195]!, [_0]) + public var Core_ServiceUserStatus: String { return self._s[1163]! } + public var LiveLocationUpdated_JustNow: String { return self._s[1164]! } + public var Call_StatusFailed: String { return self._s[1165]! } + public var TwoFactorSetup_Email_Placeholder: String { return self._s[1166]! } + public var TwoStepAuth_SetupPasswordDescription: String { return self._s[1167]! } + public var TwoStepAuth_SetPassword: String { return self._s[1168]! } + public var Permissions_PeopleNearbyText_v0: String { return self._s[1169]! } + public func SocksProxySetup_ProxyStatusPing(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1171]!, self._r[1171]!, [_0]) } - public var AutoNightTheme_NotAvailable: String { return self._s[4196]! } - public var Conversation_PressVolumeButtonForSound: String { return self._s[4197]! } - public var LoginPassword_InvalidPasswordError: String { return self._s[4198]! } - public var ChatListFolder_IncludedSectionHeader: String { return self._s[4199]! } - public var Channel_SignMessages_Help: String { return self._s[4200]! } - public var ChatList_DeleteForEveryoneConfirmationTitle: String { return self._s[4201]! } - public var MediaPicker_LivePhotoDescription: String { return self._s[4202]! } - public var GroupInfo_Permissions: String { return self._s[4203]! } - public var GroupPermission_NoSendLinks: String { return self._s[4206]! } - public var Passport_Identity_ResidenceCountry: String { return self._s[4207]! } - public var Appearance_ThemeCarouselNightBlue: String { return self._s[4209]! } - public var ChatList_ArchiveAction: String { return self._s[4210]! } - public func Channel_AdminLog_DisabledSlowmode(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4211]!, self._r[4211]!, [_0]) + public var Calls_SubmitRating: String { return self._s[1172]! } + public var Map_NoPlacesNearby: String { return self._s[1173]! } + public var Profile_Username: String { return self._s[1174]! } + public var Bot_DescriptionTitle: String { return self._s[1175]! } + public var MaskStickerSettings_Title: String { return self._s[1176]! } + public var SharedMedia_CategoryOther: String { return self._s[1177]! } + public var GroupInfo_SetGroupPhoto: String { return self._s[1178]! } + public var Common_NotNow: String { return self._s[1179]! } + public var CallFeedback_IncludeLogsInfo: String { return self._s[1180]! } + public var Conversation_ShareMyPhoneNumber: String { return self._s[1181]! } + public var Map_Location: String { return self._s[1182]! } + public var Invitation_JoinGroup: String { return self._s[1183]! } + public var AutoDownloadSettings_Title: String { return self._s[1185]! } + public var Conversation_DiscardVoiceMessageDescription: String { return self._s[1186]! } + public var Channel_ErrorAddBlocked: String { return self._s[1187]! } + public var ChatList_AddChatsToFolder: String { return self._s[1188]! } + public var Conversation_UnblockUser: String { return self._s[1189]! } + public var EditTheme_Edit_TopInfo: String { return self._s[1190]! } + public var Watch_Bot_Restart: String { return self._s[1191]! } + public var TwoStepAuth_Title: String { return self._s[1192]! } + public var Channel_AdminLog_BanSendMessages: String { return self._s[1193]! } + public var Checkout_ShippingMethod: String { return self._s[1194]! } + public var Passport_Identity_OneOfTypeIdentityCard: String { return self._s[1195]! } + public func PUSH_CHAT_MESSAGE_STICKER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1196]!, self._r[1196]!, [_1, _2, _3]) } - public var GroupInfo_GroupHistory: String { return self._s[4212]! } + public var PeerInfo_ButtonDiscuss: String { return self._s[1197]! } + public var EditTheme_ChangeColors: String { return self._s[1199]! } + public func Chat_UnsendMyMessagesAlertTitle(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1200]!, self._r[1200]!, [_0]) + } + public func Channel_Username_LinkHint(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1201]!, self._r[1201]!, [_0]) + } + public var Appearance_ThemePreview_ChatList_1_Name: String { return self._s[1202]! } + public var Notification_VideoCallMissed: String { return self._s[1204]! } + public var SettingsSearch_Synonyms_Data_AutoplayGifs: String { return self._s[1205]! } + public var AuthSessions_TerminateOtherSessions: String { return self._s[1207]! } + public var Contacts_FailedToSendInvitesMessage: String { return self._s[1208]! } + public var PrivacySettings_TwoStepAuth: String { return self._s[1209]! } + public var Notification_Exceptions_PreviewAlwaysOn: String { return self._s[1210]! } + public var SettingsSearch_Synonyms_Privacy_Passcode: String { return self._s[1211]! } + public var Conversation_EditingMessagePanelMedia: String { return self._s[1212]! } + public var Checkout_PaymentMethod_Title: String { return self._s[1213]! } + public var SocksProxySetup_Connection: String { return self._s[1214]! } + public var Group_MessagePhotoRemoved: String { return self._s[1215]! } + public var PeopleNearby_MakeInvisible: String { return self._s[1217]! } + public var Channel_Stickers_NotFound: String { return self._s[1219]! } + public var Group_About_Help: String { return self._s[1220]! } + public var Notification_PassportValueProofOfIdentity: String { return self._s[1221]! } + public var PeopleNearby_Title: String { return self._s[1223]! } + public func ApplyLanguage_ChangeLanguageOfficialText(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1224]!, self._r[1224]!, [_1]) + } + public var Map_Home: String { return self._s[1225]! } + public var Stats_ZoomOut: String { return self._s[1226]! } + public var CheckoutInfo_ShippingInfoStatePlaceholder: String { return self._s[1228]! } + public var Notifications_GroupNotificationsExceptionsHelp: String { return self._s[1229]! } + public var SocksProxySetup_Password: String { return self._s[1230]! } + public var Notifications_PermissionsEnable: String { return self._s[1231]! } + public var TwoStepAuth_ChangeEmail: String { return self._s[1233]! } + public func Channel_AdminLog_MessageInvitedName(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1234]!, self._r[1234]!, [_1]) + } + public func Time_MonthOfYear_m10(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1236]!, self._r[1236]!, [_0]) + } + public var Passport_Identity_TypeDriversLicense: String { return self._s[1237]! } + public var ArchivedPacksAlert_Title: String { return self._s[1238]! } + public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[1239]! } + public var Map_PlacesNearby: String { return self._s[1240]! } + public func Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1241]!, self._r[1241]!, [_1, _2, _3]) + } + public var PrivacyLastSeenSettings_GroupsAndChannelsHelp: String { return self._s[1242]! } + public var Privacy_Calls_NeverAllow_Placeholder: String { return self._s[1245]! } + public var Conversation_StatusTyping: String { return self._s[1246]! } + public var Widget_ApplicationStartRequired: String { return self._s[1247]! } + public var Broadcast_AdminLog_EmptyText: String { return self._s[1248]! } + public var Notification_PassportValueProofOfAddress: String { return self._s[1249]! } + public var UserInfo_CreateNewContact: String { return self._s[1250]! } + public var Passport_Identity_FrontSide: String { return self._s[1251]! } + public var Login_PhoneNumberAlreadyAuthorizedSwitch: String { return self._s[1252]! } + public var Calls_CallTabTitle: String { return self._s[1253]! } + public var Channel_AdminLog_ChannelEmptyText: String { return self._s[1254]! } + public func Login_BannedPhoneBody(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1256]!, self._r[1256]!, [_0]) + } + public var Watch_UserInfo_MuteTitle: String { return self._s[1257]! } + public var Group_EditAdmin_RankAdminPlaceholder: String { return self._s[1258]! } + public var SharedMedia_EmptyMusicText: String { return self._s[1259]! } + public var Wallet_Completed_Text: String { return self._s[1260]! } + public var PasscodeSettings_AutoLock_IfAwayFor_1minute: String { return self._s[1261]! } + public var Paint_Stickers: String { return self._s[1262]! } + public var Privacy_GroupsAndChannels: String { return self._s[1263]! } + public var ChatList_Context_Delete: String { return self._s[1265]! } + public var UserInfo_AddContact: String { return self._s[1266]! } + public func Conversation_MessageViaUser(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1267]!, self._r[1267]!, [_0]) + } + public var PhoneNumberHelp_ChangeNumber: String { return self._s[1269]! } + public func ChatList_ClearChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1271]!, self._r[1271]!, [_0]) + } + public var DialogList_NoMessagesTitle: String { return self._s[1272]! } + public var EditProfile_NameAndPhotoHelp: String { return self._s[1273]! } + public var BlockedUsers_BlockUser: String { return self._s[1274]! } + public var Notifications_PermissionsOpenSettings: String { return self._s[1275]! } + public var MediaPicker_UngroupDescription: String { return self._s[1278]! } + public var Watch_NoConnection: String { return self._s[1279]! } + public var Month_GenSeptember: String { return self._s[1280]! } + public var Conversation_ViewGroup: String { return self._s[1282]! } + public var Channel_AdminLogFilter_EventsLeavingSubscribers: String { return self._s[1285]! } + public var Privacy_Forwards_AlwaysLink: String { return self._s[1286]! } + public var Channel_OwnershipTransfer_ErrorAdminsTooMuch: String { return self._s[1287]! } + public var Passport_FieldOneOf_FinalDelimeter: String { return self._s[1288]! } + public var Wallet_WordCheck_IncorrectHeader: String { return self._s[1289]! } + public var MediaPicker_CameraRoll: String { return self._s[1291]! } + public var Month_GenAugust: String { return self._s[1292]! } + public var Wallet_Configuration_SourceHeader: String { return self._s[1293]! } + public var AccessDenied_VideoMessageMicrophone: String { return self._s[1294]! } + public var SharedMedia_EmptyText: String { return self._s[1295]! } + public var Map_ShareLiveLocation: String { return self._s[1296]! } + public var Calls_All: String { return self._s[1297]! } + public var Map_SendThisPlace: String { return self._s[1299]! } + public var Appearance_ThemeNight: String { return self._s[1301]! } + public var Conversation_HoldForAudio: String { return self._s[1302]! } + public var SettingsSearch_Synonyms_Support: String { return self._s[1305]! } + public var GroupInfo_GroupHistoryHidden: String { return self._s[1306]! } + public var SocksProxySetup_Secret: String { return self._s[1307]! } + public func Activity_RemindAboutChannel(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1308]!, self._r[1308]!, [_0]) + } + public var Channel_BanList_RestrictedTitle: String { return self._s[1310]! } + public var Conversation_Location: String { return self._s[1311]! } + public func AutoDownloadSettings_UpToFor(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1312]!, self._r[1312]!, [_1, _2]) + } + public var ChatSettings_AutoDownloadPhotos: String { return self._s[1314]! } + public var SettingsSearch_Synonyms_Privacy_Title: String { return self._s[1315]! } + public var Notifications_PermissionsText: String { return self._s[1316]! } + public var SettingsSearch_Synonyms_Data_SaveIncomingPhotos: String { return self._s[1317]! } + public var Call_Flip: String { return self._s[1318]! } + public var Channel_AdminLog_CanDeleteMessagesOfOthers: String { return self._s[1320]! } + public var SocksProxySetup_ProxyStatusConnecting: String { return self._s[1321]! } + public var Wallet_TransactionInfo_StorageFeeInfoUrl: String { return self._s[1322]! } + public var PrivacyPhoneNumberSettings_DiscoveryHeader: String { return self._s[1323]! } + public var Stats_GroupTopAdmin_Promote: String { return self._s[1325]! } + public var Channel_EditAdmin_PermissionPinMessages: String { return self._s[1326]! } + public var TwoStepAuth_ReEnterPasswordDescription: String { return self._s[1328]! } + public var ChatList_EditFolders: String { return self._s[1330]! } + public var Channel_TooMuchBots: String { return self._s[1331]! } + public var Passport_DeletePassportConfirmation: String { return self._s[1332]! } + public var Login_InvalidCodeError: String { return self._s[1333]! } + public var StickerPacksSettings_FeaturedPacks: String { return self._s[1334]! } + public func ChatList_DeleteSecretChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1335]!, self._r[1335]!, [_0]) + } + public func GroupInfo_InvitationLinkAcceptChannel(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1336]!, self._r[1336]!, [_0]) + } + public var VoiceOver_Navigation_ProxySettings: String { return self._s[1337]! } + public var Call_CallInProgressTitle: String { return self._s[1338]! } + public var Month_ShortSeptember: String { return self._s[1339]! } + public var Watch_ChannelInfo_Title: String { return self._s[1340]! } + public var ChatList_DeleteSavedMessagesConfirmation: String { return self._s[1343]! } + public var DialogList_PasscodeLockHelp: String { return self._s[1344]! } + public var Chat_MultipleTextMessagesDisabled: String { return self._s[1345]! } + public var Wallet_Receive_Title: String { return self._s[1346]! } + public var Notifications_Badge_IncludePublicGroups: String { return self._s[1347]! } + public var EditProfile_NameAndPhotoOrVideoHelp: String { return self._s[1348]! } + public var Channel_AdminLogFilter_EventsTitle: String { return self._s[1349]! } + public var PhotoEditor_CropReset: String { return self._s[1350]! } + public var Group_Username_CreatePrivateLinkHelp: String { return self._s[1352]! } + public var Channel_Management_LabelEditor: String { return self._s[1353]! } + public var Passport_Identity_LatinNameHelp: String { return self._s[1355]! } + public var PhotoEditor_HighlightsTool: String { return self._s[1356]! } + public var Wallet_Info_WalletCreated: String { return self._s[1357]! } + public var UserInfo_Title: String { return self._s[1358]! } + public var ChatList_HideAction: String { return self._s[1359]! } + public var AccessDenied_Title: String { return self._s[1360]! } + public var DialogList_SearchLabel: String { return self._s[1361]! } + public var Group_Setup_HistoryHidden: String { return self._s[1362]! } + public var TwoStepAuth_PasswordChangeSuccess: String { return self._s[1363]! } + public var State_Updating: String { return self._s[1365]! } + public var Contacts_TabTitle: String { return self._s[1366]! } + public var Notifications_Badge_CountUnreadMessages: String { return self._s[1368]! } + public var GroupInfo_GroupHistory: String { return self._s[1369]! } + public var Conversation_UnsupportedMediaPlaceholder: String { return self._s[1370]! } + public var Wallpaper_SetColor: String { return self._s[1371]! } + public var CheckoutInfo_ShippingInfoCountry: String { return self._s[1372]! } + public var SettingsSearch_Synonyms_SavedMessages: String { return self._s[1373]! } + public var ChatList_ReorderTabs: String { return self._s[1374]! } + public var ChatListFolder_IncludeChatsTitle: String { return self._s[1375]! } + public var Chat_AttachmentLimitReached: String { return self._s[1376]! } + public var Passport_Identity_OneOfTypeDriversLicense: String { return self._s[1377]! } + public var Contacts_NotRegisteredSection: String { return self._s[1378]! } + public func Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1379]!, self._r[1379]!, [_1, _2, _3]) + } + public var Paint_Clear: String { return self._s[1380]! } + public var Call_Audio: String { return self._s[1381]! } + public var StickerPacksSettings_ArchivedMasks: String { return self._s[1382]! } + public var SocksProxySetup_Connecting: String { return self._s[1383]! } + public var ExplicitContent_AlertChannel: String { return self._s[1384]! } + public var CreatePoll_AllOptionsAdded: String { return self._s[1385]! } + public var Conversation_Contact: String { return self._s[1386]! } + public var Login_CodeExpired: String { return self._s[1387]! } + public var Passport_DiscardMessageAction: String { return self._s[1388]! } + public var ChatList_Context_Unpin: String { return self._s[1389]! } + public var Channel_AdminLog_MessagePreviousDescription: String { return self._s[1390]! } + public func VoiceOver_Chat_MusicFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1391]!, self._r[1391]!, [_0]) + } + public var Channel_AdminLog_EmptyMessageText: String { return self._s[1392]! } + public var SettingsSearch_Synonyms_Data_NetworkUsage: String { return self._s[1393]! } + public func Group_EditAdmin_RankInfo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1394]!, self._r[1394]!, [_0]) + } + public var Month_ShortApril: String { return self._s[1395]! } + public var AuthSessions_CurrentSession: String { return self._s[1396]! } + public var Chat_AttachmentMultipleFilesDisabled: String { return self._s[1399]! } + public var Wallet_Navigation_Cancel: String { return self._s[1401]! } + public var WallpaperPreview_CropTopText: String { return self._s[1402]! } + public var PrivacySettings_DeleteAccountIfAwayFor: String { return self._s[1403]! } + public var CheckoutInfo_ShippingInfoTitle: String { return self._s[1405]! } + public func Conversation_ScheduleMessage_SendOn(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1406]!, self._r[1406]!, [_0, _1]) + } + public var Appearance_ThemePreview_Chat_2_Text: String { return self._s[1407]! } + public var Channel_Setup_TypePrivate: String { return self._s[1409]! } + public var Forward_ChannelReadOnly: String { return self._s[1412]! } + public var PhotoEditor_CurvesBlue: String { return self._s[1413]! } + public var AddContact_SharedContactException: String { return self._s[1414]! } + public var UserInfo_BotPrivacy: String { return self._s[1416]! } + public var Wallet_CreateInvoice_Title: String { return self._s[1417]! } + public var Notification_PassportValueEmail: String { return self._s[1418]! } + public var EmptyGroupInfo_Subtitle: String { return self._s[1419]! } + public var GroupPermission_NewTitle: String { return self._s[1420]! } + public var CallFeedback_ReasonDropped: String { return self._s[1421]! } + public var GroupInfo_Permissions_AddException: String { return self._s[1422]! } + public var Channel_SignMessages_Help: String { return self._s[1425]! } + public var Undo_ChatDeleted: String { return self._s[1427]! } + public var Conversation_ChatBackground: String { return self._s[1428]! } + public func Wallet_WordCheck_Text(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1429]!, self._r[1429]!, [_1, _2, _3]) + } + public func PUSH_CHAT_MESSAGE_QUIZ(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1432]!, self._r[1432]!, [_1, _2, _3]) + } + public var ChannelMembers_WhoCanAddMembers_Admins: String { return self._s[1433]! } + public var FastTwoStepSetup_EmailPlaceholder: String { return self._s[1434]! } + public var Passport_Language_pt: String { return self._s[1435]! } + public var VoiceOver_Chat_YourVoiceMessage: String { return self._s[1436]! } + public var NotificationsSound_Popcorn: String { return self._s[1439]! } + public var AutoNightTheme_Disabled: String { return self._s[1440]! } + public var BlockedUsers_LeavePrefix: String { return self._s[1441]! } + public var WallpaperPreview_CustomColorTopText: String { return self._s[1442]! } + public var Contacts_PermissionsSuppressWarningText: String { return self._s[1443]! } + public var WallpaperSearch_ColorBlue: String { return self._s[1444]! } + public func CancelResetAccount_TextSMS(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1445]!, self._r[1445]!, [_0]) + } + public var ChatListFolder_TitleCreate: String { return self._s[1446]! } + public var CheckoutInfo_ErrorNameInvalid: String { return self._s[1447]! } + public var SocksProxySetup_UseForCalls: String { return self._s[1448]! } + public var Passport_DeleteDocumentConfirmation: String { return self._s[1450]! } + public var PeerInfo_PaneGroups: String { return self._s[1451]! } + public func Conversation_Megabytes(_ _0: Float) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1452]!, self._r[1452]!, ["\(_0)"]) + } + public var SocksProxySetup_Hostname: String { return self._s[1455]! } + public var ChatSettings_AutoDownloadSettings_OffForAll: String { return self._s[1456]! } + public var Compose_NewEncryptedChat: String { return self._s[1457]! } + public var Login_CodeFloodError: String { return self._s[1458]! } + public var Calls_TabTitle: String { return self._s[1459]! } + public var Privacy_ProfilePhoto: String { return self._s[1460]! } + public var Passport_Language_he: String { return self._s[1461]! } + public func Conversation_SetReminder_RemindToday(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1462]!, self._r[1462]!, [_0]) + } + public var ChatList_TabIconFoldersTooltipNonEmptyFolders: String { return self._s[1463]! } + public var GroupPermission_Title: String { return self._s[1464]! } + public func Channel_AdminLog_MessageGroupPreHistoryHidden(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1465]!, self._r[1465]!, [_0]) + } + public var Wallet_TransactionInfo_SenderHeader: String { return self._s[1466]! } + public var GroupPermission_NoChangeInfo: String { return self._s[1467]! } + public var ChatList_DeleteForCurrentUser: String { return self._s[1468]! } + public var Tour_Text1: String { return self._s[1469]! } + public var Channel_EditAdmin_TransferOwnership: String { return self._s[1470]! } + public var Month_ShortFebruary: String { return self._s[1471]! } + public var Call_ExternalCallInProgressMessage: String { return self._s[1472]! } + public var TwoStepAuth_EmailSkip: String { return self._s[1473]! } + public var ContactList_Context_VideoCall: String { return self._s[1474]! } + public func Wallet_Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1475]!, self._r[1475]!, [_1, _2, _3]) + } + public var NotificationsSound_Glass: String { return self._s[1476]! } + public var Appearance_ThemeNightBlue: String { return self._s[1477]! } + public var CheckoutInfo_Pay: String { return self._s[1478]! } + public var Stats_LanguagesTitle: String { return self._s[1480]! } + public var PeerInfo_ButtonLeave: String { return self._s[1481]! } + public var SettingsSearch_Synonyms_ChatFolders: String { return self._s[1482]! } + public var Invite_LargeRecipientsCountWarning: String { return self._s[1483]! } + public var Call_CallAgain: String { return self._s[1485]! } + public var AttachmentMenu_SendAsFile: String { return self._s[1486]! } + public var AccessDenied_MicrophoneRestricted: String { return self._s[1487]! } + public var Passport_InvalidPasswordError: String { return self._s[1488]! } + public var Watch_Message_Game: String { return self._s[1489]! } + public var Stickers_Install: String { return self._s[1490]! } + public var VoiceOver_Chat_Message: String { return self._s[1491]! } + public var PrivacyLastSeenSettings_NeverShareWith: String { return self._s[1492]! } + public var Passport_Identity_ResidenceCountry: String { return self._s[1494]! } + public var Notifications_GroupNotificationsHelp: String { return self._s[1495]! } + public var AuthSessions_OtherSessions: String { return self._s[1496]! } + public var Channel_Username_Help: String { return self._s[1497]! } + public var Camera_Title: String { return self._s[1498]! } + public var IntentsSettings_Title: String { return self._s[1500]! } + public var GroupInfo_SetGroupPhotoDelete: String { return self._s[1502]! } + public var Privacy_ProfilePhoto_NeverShareWith_Title: String { return self._s[1503]! } + public var Channel_AdminLog_SendPolls: String { return self._s[1504]! } + public var Channel_AdminLog_TitleAllEvents: String { return self._s[1505]! } + public var Channel_EditAdmin_PermissionInviteMembers: String { return self._s[1506]! } + public var Contacts_MemberSearchSectionTitleGroup: String { return self._s[1507]! } + public var ScheduledMessages_DeleteMany: String { return self._s[1508]! } + public var Conversation_RestrictedStickers: String { return self._s[1509]! } + public var Notifications_ExceptionsResetToDefaults: String { return self._s[1511]! } + public var UserInfo_TelegramCall: String { return self._s[1513]! } + public var TwoStepAuth_SetupResendEmailCode: String { return self._s[1514]! } + public var CreatePoll_OptionsHeader: String { return self._s[1515]! } + public var SettingsSearch_Synonyms_Data_CallsUseLessData: String { return self._s[1516]! } + public var ArchivedChats_IntroTitle1: String { return self._s[1517]! } + public var Privacy_GroupsAndChannels_AlwaysAllow_Title: String { return self._s[1518]! } + public var Theme_Colors_Proceed: String { return self._s[1519]! } + public var Passport_Identity_EditPersonalDetails: String { return self._s[1520]! } + public func Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1521]!, self._r[1521]!, [_1, _2, _3]) + } + public var Wallet_Month_GenAugust: String { return self._s[1522]! } + public var Settings_SaveEditedPhotos: String { return self._s[1523]! } + public var Stats_FollowersBySourceTitle: String { return self._s[1524]! } + public var TwoStepAuth_ConfirmationTitle: String { return self._s[1525]! } + public var Privacy_GroupsAndChannels_NeverAllow_Title: String { return self._s[1526]! } + public var Conversation_MessageDialogRetry: String { return self._s[1527]! } + public var ChatList_Context_MarkAsUnread: String { return self._s[1528]! } + public var MessagePoll_SubmitVote: String { return self._s[1529]! } + public var Conversation_DiscardVoiceMessageAction: String { return self._s[1530]! } + public var Permissions_PeopleNearbyTitle_v0: String { return self._s[1531]! } + public var ChatList_Context_Back: String { return self._s[1532]! } + public var Group_Setup_TypeHeader: String { return self._s[1533]! } + public var Paint_RecentStickers: String { return self._s[1534]! } + public var PhotoEditor_GrainTool: String { return self._s[1535]! } + public var CheckoutInfo_ShippingInfoState: String { return self._s[1536]! } + public var EmptyGroupInfo_Line4: String { return self._s[1537]! } + public var Watch_AuthRequired: String { return self._s[1539]! } + public func Passport_Email_UseTelegramEmail(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1540]!, self._r[1540]!, [_0]) + } + public var Conversation_EncryptedDescriptionTitle: String { return self._s[1541]! } + public var ChannelIntro_Text: String { return self._s[1542]! } + public var DialogList_DeleteBotConfirmation: String { return self._s[1543]! } + public var GroupPermission_NoSendMedia: String { return self._s[1544]! } + public var Calls_AddTab: String { return self._s[1545]! } + public var Message_ReplyActionButtonShowReceipt: String { return self._s[1546]! } + public var Channel_AdminLog_EmptyFilterText: String { return self._s[1547]! } + public var Conversation_WalletRequiredSetup: String { return self._s[1548]! } + public var Notification_MessageLifetime1d: String { return self._s[1549]! } + public var Notifications_ChannelNotificationsExceptionsHelp: String { return self._s[1550]! } + public var Channel_BanUser_PermissionsHeader: String { return self._s[1551]! } + public var Passport_Identity_GenderFemale: String { return self._s[1552]! } + public var BlockedUsers_BlockTitle: String { return self._s[1553]! } + public func PUSH_CHANNEL_MESSAGE_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1554]!, self._r[1554]!, [_1]) + } + public var Weekday_Yesterday: String { return self._s[1555]! } + public var WallpaperSearch_ColorBlack: String { return self._s[1556]! } + public var Settings_Context_Logout: String { return self._s[1557]! } + public var Wallet_Info_UnknownTransaction: String { return self._s[1558]! } + public var ChatList_ArchiveAction: String { return self._s[1559]! } + public var AutoNightTheme_Scheduled: String { return self._s[1560]! } + public var TwoFactorSetup_Email_SkipAction: String { return self._s[1561]! } + public var Settings_Devices: String { return self._s[1562]! } + public var ContactInfo_Note: String { return self._s[1563]! } + public func Login_PhoneGenericEmailBody(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String, _ _6: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1564]!, self._r[1564]!, [_1, _2, _3, _4, _5, _6]) + } + public var EditTheme_ThemeTemplateAlertTitle: String { return self._s[1565]! } + public var Wallet_Receive_CreateInvoice: String { return self._s[1566]! } + public var PrivacyPolicy_DeclineDeleteNow: String { return self._s[1567]! } + public var Theme_Colors_ColorWallpaperWarningProceed: String { return self._s[1568]! } + public func PUSH_CHAT_JOINED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1569]!, self._r[1569]!, [_1, _2]) + } + public var CreatePoll_Create: String { return self._s[1570]! } + public var Channel_Members_AddBannedErrorAdmin: String { return self._s[1571]! } + public func Notification_CallFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1572]!, self._r[1572]!, [_1, _2]) + } + public var ScheduledMessages_ClearAllConfirmation: String { return self._s[1573]! } + public var Checkout_ErrorProviderAccountInvalid: String { return self._s[1574]! } + public var Notifications_InAppNotificationsSounds: String { return self._s[1576]! } + public func PUSH_PINNED_GAME_SCORE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1577]!, self._r[1577]!, [_1]) + } + public var Preview_OpenInInstagram: String { return self._s[1578]! } + public var Notification_MessageLifetimeRemovedOutgoing: String { return self._s[1579]! } + public func PUSH_CHAT_ADD_MEMBER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1580]!, self._r[1580]!, [_1, _2, _3]) + } + public func Passport_PrivacyPolicy(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1581]!, self._r[1581]!, [_1, _2]) + } + public var Channel_AdminLog_InfoPanelAlertTitle: String { return self._s[1582]! } + public var ArchivedChats_IntroText3: String { return self._s[1583]! } + public var ChatList_UndoArchiveHiddenText: String { return self._s[1584]! } + public var NetworkUsageSettings_TotalSection: String { return self._s[1585]! } + public var Wallet_Month_GenSeptember: String { return self._s[1586]! } + public var Channel_Setup_TypePrivateHelp: String { return self._s[1587]! } + public func PUSH_CHAT_MESSAGE_POLL(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1588]!, self._r[1588]!, [_1, _2, _3]) + } + public var Privacy_GroupsAndChannels_NeverAllow_Placeholder: String { return self._s[1590]! } + public var FastTwoStepSetup_HintSection: String { return self._s[1591]! } + public var Wallpaper_PhotoLibrary: String { return self._s[1592]! } + public var TwoStepAuth_SetupResendEmailCodeAlert: String { return self._s[1593]! } + public var Gif_NoGifsFound: String { return self._s[1594]! } + public var Watch_LastSeen_WithinAMonth: String { return self._s[1595]! } + public var VoiceOver_MessageContextDelete: String { return self._s[1596]! } + public var EditTheme_Preview: String { return self._s[1597]! } + public func ClearCache_StorageTitle(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1598]!, self._r[1598]!, [_0]) + } + public var GroupInfo_ActionPromote: String { return self._s[1599]! } + public var PasscodeSettings_SimplePasscode: String { return self._s[1600]! } + public var GroupInfo_Permissions_Title: String { return self._s[1601]! } + public var Permissions_ContactsText_v0: String { return self._s[1602]! } + public var PrivacyPhoneNumberSettings_CustomDisabledHelp: String { return self._s[1603]! } + public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedPublicGroups: String { return self._s[1604]! } + public var PrivacySettings_DataSettingsHelp: String { return self._s[1607]! } + public var Passport_FieldEmailHelp: String { return self._s[1608]! } + public func Activity_RemindAboutUser(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1609]!, self._r[1609]!, [_0]) + } + public var Passport_Identity_GenderPlaceholder: String { return self._s[1610]! } + public var Weekday_ShortSaturday: String { return self._s[1611]! } + public var ContactInfo_PhoneLabelMain: String { return self._s[1612]! } + public var Watch_Conversation_UserInfo: String { return self._s[1613]! } + public var CheckoutInfo_ShippingInfoCityPlaceholder: String { return self._s[1614]! } + public var GroupPermission_PermissionDisabledByDefault: String { return self._s[1615]! } + public var PrivacyLastSeenSettings_Title: String { return self._s[1616]! } + public var Conversation_ShareBotLocationConfirmation: String { return self._s[1618]! } + public var PhotoEditor_VignetteTool: String { return self._s[1619]! } + public var Conversation_ContextMenuDiscuss: String { return self._s[1620]! } + public var Passport_Address_Street1Placeholder: String { return self._s[1621]! } + public var Passport_Language_et: String { return self._s[1622]! } + public var AppUpgrade_Running: String { return self._s[1623]! } + public var Channel_DiscussionGroup_Info: String { return self._s[1625]! } + public var EditTheme_Create_Preview_IncomingReplyName: String { return self._s[1626]! } + public var Passport_Language_bg: String { return self._s[1627]! } + public var Stickers_NoStickersFound: String { return self._s[1629]! } + public func PUSH_CHANNEL_MESSAGE_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1631]!, self._r[1631]!, [_1, _2]) + } + public func VoiceOver_Chat_ContactFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1632]!, self._r[1632]!, [_0]) + } + public var Wallet_Month_GenJuly: String { return self._s[1633]! } + public var Wallet_Receive_AddressHeader: String { return self._s[1635]! } + public var Wallet_Send_AmountText: String { return self._s[1636]! } + public var Settings_About: String { return self._s[1637]! } + public func Channel_AdminLog_MessageRestricted(_ _0: String, _ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1638]!, self._r[1638]!, [_0, _1, _2]) + } + public var ChatList_Context_MarkAsRead: String { return self._s[1640]! } + public var KeyCommand_NewMessage: String { return self._s[1641]! } + public var Group_ErrorAddBlocked: String { return self._s[1642]! } + public func Message_PaymentSent(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1643]!, self._r[1643]!, [_0]) + } + public var Map_LocationTitle: String { return self._s[1644]! } + public var ReportGroupLocation_Title: String { return self._s[1645]! } + public var CallSettings_UseLessDataLongDescription: String { return self._s[1646]! } + public var Cache_ClearProgress: String { return self._s[1647]! } public func Channel_Management_ErrorNotMember(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4214]!, self._r[4214]!, [_0]) + return formatWithArgumentRanges(self._s[1648]!, self._r[1648]!, [_0]) } - public var Privacy_Forwards_LinkIfAllowed: String { return self._s[4216]! } - public var Channel_Info_Banned: String { return self._s[4217]! } - public var Paint_RecentStickers: String { return self._s[4218]! } - public var VoiceOver_MessageContextSend: String { return self._s[4219]! } - public var Group_ErrorNotMutualContact: String { return self._s[4220]! } - public var ReportPeer_ReasonOther: String { return self._s[4222]! } - public var Channel_BanUser_PermissionChangeGroupInfo: String { return self._s[4223]! } - public var SocksProxySetup_ShareQRCodeInfo: String { return self._s[4225]! } - public var KeyCommand_Find: String { return self._s[4226]! } + public var GroupRemoved_AddToGroup: String { return self._s[1649]! } + public func External_OpenIn(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1650]!, self._r[1650]!, [_0]) + } + public var Passport_UpdateRequiredError: String { return self._s[1651]! } + public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[1652]! } + public func PUSH_MESSAGE_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1653]!, self._r[1653]!, [_1]) + } + public var Notifications_PermissionsSuppressWarningText: String { return self._s[1655]! } + public var Passport_Identity_MainPageHelp: String { return self._s[1656]! } + public var PeerInfo_ButtonSearch: String { return self._s[1657]! } + public var Conversation_StatusKickedFromGroup: String { return self._s[1658]! } + public var Passport_Language_ka: String { return self._s[1659]! } + public func Wallet_Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1660]!, self._r[1660]!, [_1, _2, _3]) + } + public var Call_Decline: String { return self._s[1661]! } + public var SocksProxySetup_ProxyEnabled: String { return self._s[1662]! } + public var TwoFactorSetup_Email_SkipConfirmationText: String { return self._s[1665]! } + public func AuthCode_Alert(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1666]!, self._r[1666]!, [_0]) + } + public var CallFeedback_Send: String { return self._s[1667]! } + public var EditTheme_EditTitle: String { return self._s[1668]! } + public func Channel_AdminLog_MessagePromotedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1669]!, self._r[1669]!, [_1, _2]) + } + public var Passport_Phone_UseTelegramNumberHelp: String { return self._s[1670]! } + public func Wallet_Updated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1672]!, self._r[1672]!, [_0]) + } + public var Media_SendingOptionsTooltip: String { return self._s[1673]! } + public var Call_YourMicrophoneOff: String { return self._s[1674]! } + public var SettingsSearch_Synonyms_Data_Title: String { return self._s[1675]! } + public var Passport_DeletePassport: String { return self._s[1676]! } + public var Appearance_AppIconFilled: String { return self._s[1677]! } + public var Privacy_Calls_P2PAlways: String { return self._s[1678]! } + public var Month_ShortDecember: String { return self._s[1679]! } + public var Channel_AdminLog_CanEditMessages: String { return self._s[1681]! } + public func Contacts_AccessDeniedHelpLandscape(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1682]!, self._r[1682]!, [_0]) + } + public var Channel_Stickers_Searching: String { return self._s[1683]! } + public var Conversation_EncryptedDescription1: String { return self._s[1684]! } + public var Conversation_EncryptedDescription2: String { return self._s[1685]! } + public var PasscodeSettings_PasscodeOptions: String { return self._s[1686]! } + public var ChatListFolder_NameUnread: String { return self._s[1688]! } + public var Conversation_EncryptedDescription3: String { return self._s[1689]! } + public var PhotoEditor_SharpenTool: String { return self._s[1690]! } + public var Wallet_Configuration_Title: String { return self._s[1691]! } + public func Conversation_AddNameToContacts(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1692]!, self._r[1692]!, [_0]) + } + public var Conversation_EncryptedDescription4: String { return self._s[1695]! } + public var Channel_Members_AddMembers: String { return self._s[1696]! } + public var Wallpaper_Search: String { return self._s[1697]! } + public func Message_GenericForwardedPsa(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1699]!, self._r[1699]!, [_0]) + } + public var Weekday_Friday: String { return self._s[1700]! } + public var Privacy_ContactsSync: String { return self._s[1701]! } + public var SettingsSearch_Synonyms_Privacy_Data_ContactsReset: String { return self._s[1702]! } + public var ApplyLanguage_ChangeLanguageAction: String { return self._s[1703]! } + public func Channel_Management_RestrictedBy(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1704]!, self._r[1704]!, [_0]) + } + public var Wallet_Configuration_BlockchainIdHeader: String { return self._s[1705]! } + public var GroupInfo_Permissions_Removed: String { return self._s[1706]! } + public var ScheduledMessages_ScheduledOnline: String { return self._s[1707]! } + public var Passport_Identity_GenderMale: String { return self._s[1708]! } + public func Call_StatusBar(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1709]!, self._r[1709]!, [_0]) + } + public var Notifications_PermissionsKeepDisabled: String { return self._s[1710]! } + public var Conversation_JumpToDate: String { return self._s[1711]! } + public var Contacts_GlobalSearch: String { return self._s[1712]! } + public var AutoDownloadSettings_ResetHelp: String { return self._s[1713]! } + public var SettingsSearch_Synonyms_FAQ: String { return self._s[1714]! } + public var ChatListFolderSettings_NewFolder: String { return self._s[1715]! } + public var Profile_MessageLifetime1d: String { return self._s[1716]! } + public func MESSAGE_INVOICE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1717]!, self._r[1717]!, [_1, _2]) + } + public var StickerPack_BuiltinPackName: String { return self._s[1720]! } + public func PUSH_CHAT_MESSAGE_AUDIO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1721]!, self._r[1721]!, [_1, _2]) + } + public var VoiceOver_Chat_RecordModeVoiceMessageInfo: String { return self._s[1722]! } + public var Passport_InfoTitle: String { return self._s[1724]! } + public var Notifications_PermissionsUnreachableText: String { return self._s[1725]! } + public func NetworkUsageSettings_CellularUsageSince(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1729]!, self._r[1729]!, [_0]) + } + public func PUSH_CHAT_MESSAGE_GEO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1730]!, self._r[1730]!, [_1, _2]) + } + public var Passport_Address_TypePassportRegistrationUploadScan: String { return self._s[1731]! } + public var Profile_BotInfo: String { return self._s[1732]! } + public var Watch_Compose_CreateMessage: String { return self._s[1733]! } + public var AutoDownloadSettings_VoiceMessagesInfo: String { return self._s[1734]! } + public var Month_ShortNovember: String { return self._s[1735]! } + public var Conversation_ScamWarning: String { return self._s[1736]! } + public var Wallpaper_SetCustomBackground: String { return self._s[1737]! } + public var Appearance_TextSize_Title: String { return self._s[1738]! } + public var Conversation_ContextMenuOpenProfile: String { return self._s[1739]! } + public var ChatList_EmptyChatListFilterTitle: String { return self._s[1740]! } + public func Call_BatteryLow(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1741]!, self._r[1741]!, [_0]) + } + public var Passport_Identity_TranslationsHelp: String { return self._s[1742]! } + public var NotificationsSound_Chime: String { return self._s[1743]! } + public var Passport_Language_ko: String { return self._s[1745]! } + public var InviteText_URL: String { return self._s[1746]! } + public var TextFormat_Monospace: String { return self._s[1747]! } + public func Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1748]!, self._r[1748]!, [_1, _2, _3]) + } + public var EditTheme_Edit_BottomInfo: String { return self._s[1749]! } + public func Login_WillSendSms(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1750]!, self._r[1750]!, [_0]) + } + public func Watch_Time_ShortWeekdayAt(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1751]!, self._r[1751]!, [_1, _2]) + } + public var Wallet_Words_Title: String { return self._s[1752]! } + public var Wallet_Month_ShortMay: String { return self._s[1753]! } + public var EditTheme_CreateTitle: String { return self._s[1755]! } + public var Passport_InfoLearnMore: String { return self._s[1756]! } + public var TwoStepAuth_EmailPlaceholder: String { return self._s[1757]! } + public var Passport_Identity_AddIdentityCard: String { return self._s[1758]! } + public var Your_card_has_expired: String { return self._s[1759]! } + public var StickerPacksSettings_StickerPacksSection: String { return self._s[1760]! } + public var Call_AudioRouteMute: String { return self._s[1761]! } + public var GroupInfo_InviteLink_Help: String { return self._s[1762]! } + public var TwoFactorSetup_EmailVerification_ResendAction: String { return self._s[1766]! } + public var Conversation_Report: String { return self._s[1768]! } + public var Notifications_MessageNotificationsSound: String { return self._s[1769]! } + public var Notification_MessageLifetime1m: String { return self._s[1770]! } + public var Privacy_ContactsTitle: String { return self._s[1771]! } + public var Conversation_ShareMyContactInfo: String { return self._s[1772]! } + public var Wallet_WordCheck_Title: String { return self._s[1773]! } + public var ChannelMembers_WhoCanAddMembersAdminsHelp: String { return self._s[1774]! } + public var Channel_Members_Title: String { return self._s[1775]! } + public var Map_OpenInWaze: String { return self._s[1776]! } + public var Appearance_RemoveThemeColorConfirmation: String { return self._s[1777]! } + public var Stats_GroupTopWeekdaysTitle: String { return self._s[1778]! } + public var Login_PhoneBannedError: String { return self._s[1779]! } + public var PeerInfo_GroupAboutItem: String { return self._s[1780]! } + public func LiveLocationUpdated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1781]!, self._r[1781]!, [_0]) + } + public var IntentsSettings_MainAccount: String { return self._s[1782]! } + public var Group_Management_AddModeratorHelp: String { return self._s[1783]! } + public var AutoDownloadSettings_WifiTitle: String { return self._s[1784]! } + public var Common_OK: String { return self._s[1785]! } + public var Passport_Address_TypeBankStatementUploadScan: String { return self._s[1786]! } + public var Wallet_Words_NotDoneResponse: String { return self._s[1787]! } + public var Cache_Music: String { return self._s[1788]! } + public var Wallet_Configuration_SourceURL: String { return self._s[1789]! } + public var SettingsSearch_Synonyms_EditProfile_PhoneNumber: String { return self._s[1790]! } + public var PasscodeSettings_UnlockWithTouchId: String { return self._s[1793]! } + public var ChatList_EmptyChatListEditFilter: String { return self._s[1794]! } + public var TwoStepAuth_HintPlaceholder: String { return self._s[1795]! } + public func PUSH_PINNED_INVOICE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1796]!, self._r[1796]!, [_1]) + } + public func Passport_RequestHeader(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1797]!, self._r[1797]!, [_0]) + } + public var TwoFactorSetup_Done_Action: String { return self._s[1798]! } + public func VoiceOver_Chat_ContactOrganization(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1799]!, self._r[1799]!, [_0]) + } + public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[1800]! } + public var Watch_MessageView_ViewOnPhone: String { return self._s[1802]! } + public var Privacy_Calls_CustomShareHelp: String { return self._s[1803]! } + public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[1805]! } + public var ChangePhoneNumberNumber_Title: String { return self._s[1806]! } + public var State_ConnectingToProxyInfo: String { return self._s[1807]! } + public var Conversation_SwipeToReplyHintTitle: String { return self._s[1808]! } + public var Message_VideoMessage: String { return self._s[1810]! } + public var ChannelInfo_DeleteChannel: String { return self._s[1811]! } + public var ContactInfo_PhoneLabelOther: String { return self._s[1812]! } + public var Channel_EditAdmin_CannotEdit: String { return self._s[1813]! } + public var Passport_DeleteAddressConfirmation: String { return self._s[1814]! } + public func Wallet_Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1815]!, self._r[1815]!, [_1, _2, _3]) + } + public var WallpaperPreview_SwipeBottomText: String { return self._s[1816]! } + public var Activity_RecordingAudio: String { return self._s[1817]! } + public var SettingsSearch_Synonyms_Watch: String { return self._s[1818]! } + public var PasscodeSettings_TryAgainIn1Minute: String { return self._s[1819]! } + public var Wallet_Info_Address: String { return self._s[1820]! } + public var Notification_VideoCallCanceled: String { return self._s[1821]! } + public func Notification_ChangedGroupName(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1823]!, self._r[1823]!, [_0, _1]) + } + public func EmptyGroupInfo_Line1(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1827]!, self._r[1827]!, [_0]) + } + public var ChatList_RemoveFolderConfirmation: String { return self._s[1828]! } + public var Conversation_ApplyLocalization: String { return self._s[1829]! } + public func Conversation_PeerNearbyDistance(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1830]!, self._r[1830]!, [_1, _2]) + } + public var TwoFactorSetup_Intro_Action: String { return self._s[1831]! } + public var UserInfo_AddPhone: String { return self._s[1833]! } + public var Map_ShareLiveLocationHelp: String { return self._s[1834]! } + public func Passport_Identity_NativeNameGenericHelp(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1835]!, self._r[1835]!, [_0]) + } + public var ChatListFolder_CategoryArchived: String { return self._s[1837]! } + public var Call_IncomingVideoCall: String { return self._s[1838]! } + public var Passport_Scans: String { return self._s[1839]! } + public var BlockedUsers_Unblock: String { return self._s[1840]! } + public func PUSH_ENCRYPTION_REQUEST(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1841]!, self._r[1841]!, [_1]) + } + public var Channel_Management_LabelCreator: String { return self._s[1842]! } + public var Conversation_ReportSpamAndLeave: String { return self._s[1843]! } + public var SettingsSearch_Synonyms_EditProfile_Bio: String { return self._s[1844]! } + public var ChatList_UndoArchiveMultipleTitle: String { return self._s[1845]! } + public var Passport_Identity_NativeNameGenericTitle: String { return self._s[1846]! } + public func Login_EmailPhoneBody(_ _0: String, _ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1847]!, self._r[1847]!, [_0, _1, _2]) + } + public var Login_PhoneNumberHelp: String { return self._s[1848]! } + public var LastSeen_ALongTimeAgo: String { return self._s[1849]! } + public var Channel_AdminLog_CanPinMessages: String { return self._s[1850]! } + public var ChannelIntro_CreateChannel: String { return self._s[1851]! } + public var Conversation_UnreadMessages: String { return self._s[1852]! } + public var SettingsSearch_Synonyms_Stickers_ArchivedPacks: String { return self._s[1853]! } + public var Channel_AdminLog_EmptyText: String { return self._s[1854]! } + public var Theme_Context_Apply: String { return self._s[1855]! } + public var Notification_GroupActivated: String { return self._s[1856]! } + public var NotificationSettings_ContactJoinedInfo: String { return self._s[1857]! } + public var Wallet_Intro_CreateWallet: String { return self._s[1858]! } + public func Call_MicrophoneOff(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1859]!, self._r[1859]!, [_0]) + } + public func Notification_PinnedContactMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1860]!, self._r[1860]!, [_0]) + } + public func DownloadingStatus(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1861]!, self._r[1861]!, [_0, _1]) + } + public var GroupInfo_ConvertToSupergroup: String { return self._s[1863]! } + public func PrivacyPolicy_AgeVerificationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1864]!, self._r[1864]!, [_0]) + } + public var Undo_DeletedChannel: String { return self._s[1865]! } + public var CallFeedback_AddComment: String { return self._s[1866]! } + public func Conversation_OpenBotLinkAllowMessages(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1867]!, self._r[1867]!, [_0]) + } + public var Document_TargetConfirmationFormat: String { return self._s[1868]! } + public func Call_StatusOngoing(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1869]!, self._r[1869]!, [_0]) + } + public var LogoutOptions_SetPasscodeTitle: String { return self._s[1870]! } + public func PUSH_CHAT_MESSAGE_GAME_SCORE(_ _1: String, _ _2: String, _ _3: String, _ _4: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1871]!, self._r[1871]!, [_1, _2, _3, _4]) + } + public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[1872]! } + public var Theme_ErrorNotFound: String { return self._s[1873]! } + public var Contacts_SortByName: String { return self._s[1874]! } + public var SettingsSearch_Synonyms_Privacy_Forwards: String { return self._s[1875]! } + public func CHAT_MESSAGE_INVOICE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1878]!, self._r[1878]!, [_1, _2, _3]) + } + public var Notification_Exceptions_RemoveFromExceptions: String { return self._s[1879]! } + public var ScheduledMessages_EditTime: String { return self._s[1880]! } + public var Conversation_ClearSelfHistory: String { return self._s[1881]! } + public var Checkout_NewCard_PostcodePlaceholder: String { return self._s[1882]! } + public var PasscodeSettings_DoNotMatch: String { return self._s[1883]! } + public var Stickers_SuggestNone: String { return self._s[1884]! } + public var ChatSettings_Cache: String { return self._s[1885]! } + public var Settings_SaveIncomingPhotos: String { return self._s[1886]! } + public var Media_ShareThisPhoto: String { return self._s[1887]! } + public var Chat_SlowmodeTooltipPending: String { return self._s[1888]! } + public var InfoPlist_NSContactsUsageDescription: String { return self._s[1889]! } + public var Conversation_ContextMenuCopyLink: String { return self._s[1890]! } + public var PrivacyPolicy_AgeVerificationTitle: String { return self._s[1891]! } + public var SettingsSearch_Synonyms_Stickers_Masks: String { return self._s[1892]! } + public var TwoStepAuth_SetupPasswordEnterPasswordNew: String { return self._s[1893]! } + public var Appearance_ThemePreview_Chat_6_Text: String { return self._s[1894]! } + public func Wallet_SecureStorageReset_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1895]!, self._r[1895]!, [_0]) + } + public var PhotoEditor_BlurToolPortrait: String { return self._s[1896]! } + public var Permissions_CellularDataTitle_v0: String { return self._s[1897]! } + public var WallpaperSearch_ColorWhite: String { return self._s[1899]! } + public var Channel_AdminLog_DefaultRestrictionsUpdated: String { return self._s[1900]! } + public var Conversation_ErrorInaccessibleMessage: String { return self._s[1901]! } + public var Map_OpenIn: String { return self._s[1902]! } + public var PeerInfo_ButtonCall: String { return self._s[1903]! } + public func PUSH_PHONE_CALL_MISSED(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1907]!, self._r[1907]!, [_1]) + } + public func ChannelInfo_AddParticipantConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1908]!, self._r[1908]!, [_0]) + } + public var GroupInfo_Permissions_SlowmodeHeader: String { return self._s[1909]! } + public var MessagePoll_LabelClosed: String { return self._s[1910]! } + public var GroupPermission_PermissionGloballyDisabled: String { return self._s[1912]! } + public var Wallet_Send_SendAnyway: String { return self._s[1913]! } + public var Passport_Identity_MiddleNamePlaceholder: String { return self._s[1914]! } + public var UserInfo_FirstNamePlaceholder: String { return self._s[1915]! } + public var PrivacyLastSeenSettings_WhoCanSeeMyTimestamp: String { return self._s[1916]! } + public var Map_SetThisPlace: String { return self._s[1917]! } + public var Stats_GroupTopAdmin_Actions: String { return self._s[1918]! } + public var Login_SelectCountry_Title: String { return self._s[1919]! } + public var Channel_EditAdmin_PermissionBanUsers: String { return self._s[1920]! } + public func Conversation_OpenBotLinkLogin(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1921]!, self._r[1921]!, [_1, _2]) + } + public var Channel_AdminLog_ChangeInfo: String { return self._s[1922]! } + public var Watch_Suggestion_BRB: String { return self._s[1923]! } + public var Passport_Identity_EditIdentityCard: String { return self._s[1924]! } + public var Contacts_PermissionsTitle: String { return self._s[1925]! } + public var Conversation_RestrictedInline: String { return self._s[1926]! } + public var Appearance_RemoveThemeColor: String { return self._s[1928]! } + public var StickerPack_ViewPack: String { return self._s[1929]! } + public var Wallet_UnknownError: String { return self._s[1930]! } + public func Update_AppVersion(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1931]!, self._r[1931]!, [_0]) + } + public var Compose_NewChannel: String { return self._s[1933]! } + public var ChatSettings_AutoDownloadSettings_TypePhoto: String { return self._s[1937]! } + public var MessagePoll_LabelQuiz: String { return self._s[1939]! } + public var Conversation_ReportSpamGroupConfirmation: String { return self._s[1940]! } + public var Channel_Info_Stickers: String { return self._s[1941]! } + public var AutoNightTheme_PreferredTheme: String { return self._s[1942]! } + public var PrivacyPolicy_AgeVerificationAgree: String { return self._s[1943]! } + public var Passport_DeletePersonalDetails: String { return self._s[1944]! } + public var LogoutOptions_AddAccountTitle: String { return self._s[1945]! } + public var Channel_DiscussionGroupInfo: String { return self._s[1946]! } + public var Group_EditAdmin_RankOwnerPlaceholder: String { return self._s[1947]! } + public var Stats_LoadingText: String { return self._s[1950]! } + public var Conversation_SearchNoResults: String { return self._s[1951]! } + public var ChatList_AddFolder: String { return self._s[1952]! } + public var Wallet_Configuration_ApplyErrorTextURLInvalid: String { return self._s[1953]! } + public var ChatListFolder_NameNonContacts: String { return self._s[1954]! } + public var MessagePoll_LabelAnonymous: String { return self._s[1955]! } + public var Channel_Members_AddAdminErrorNotAMember: String { return self._s[1956]! } + public var Login_Code: String { return self._s[1957]! } + public var EditTheme_Create_BottomInfo: String { return self._s[1958]! } + public var Watch_Suggestion_WhatsUp: String { return self._s[1959]! } + public var Weekday_ShortThursday: String { return self._s[1960]! } + public var Notification_VideoCallOutgoing: String { return self._s[1961]! } + public var Resolve_ErrorNotFound: String { return self._s[1962]! } + public var LastSeen_Offline: String { return self._s[1964]! } + public var PeopleNearby_NoMembers: String { return self._s[1965]! } + public var GroupPermission_AddMembersNotAvailable: String { return self._s[1966]! } + public var Privacy_Calls_AlwaysAllow_Title: String { return self._s[1967]! } + public var Conversation_Dice_u1F3AF: String { return self._s[1969]! } + public var GroupInfo_Title: String { return self._s[1970]! } + public var NotificationsSound_Note: String { return self._s[1971]! } + public var Conversation_EditingMessagePanelTitle: String { return self._s[1972]! } + public var Watch_Message_Poll: String { return self._s[1973]! } + public var Privacy_Calls: String { return self._s[1974]! } + public func Channel_AdminLog_MessageRankUsername(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1975]!, self._r[1975]!, [_1, _2, _3]) + } + public var Month_ShortAugust: String { return self._s[1976]! } + public var TwoStepAuth_SetPasswordHelp: String { return self._s[1977]! } + public var Notifications_Reset: String { return self._s[1978]! } + public var Conversation_Pin: String { return self._s[1979]! } + public var Passport_Language_lv: String { return self._s[1980]! } + public var Permissions_PeopleNearbyAllowInSettings_v0: String { return self._s[1981]! } + public var BlockedUsers_Info: String { return self._s[1982]! } + public var SettingsSearch_Synonyms_Data_AutoplayVideos: String { return self._s[1984]! } + public var Watch_Conversation_Unblock: String { return self._s[1986]! } + public func Time_MonthOfYear_m9(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1987]!, self._r[1987]!, [_0]) + } + public var CloudStorage_Title: String { return self._s[1988]! } + public var GroupInfo_DeleteAndExitConfirmation: String { return self._s[1989]! } + public func NetworkUsageSettings_WifiUsageSince(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1990]!, self._r[1990]!, [_0]) + } + public var Channel_AdminLogFilter_AdminsTitle: String { return self._s[1991]! } + public var Watch_Suggestion_OnMyWay: String { return self._s[1992]! } + public var TwoStepAuth_RecoveryEmailTitle: String { return self._s[1993]! } + public var Passport_Address_EditBankStatement: String { return self._s[1994]! } + public func Channel_AdminLog_MessageChangedUnlinkedGroup(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1995]!, self._r[1995]!, [_1, _2]) + } + public var ChatSettings_DownloadInBackgroundInfo: String { return self._s[1996]! } + public var ShareMenu_Comment: String { return self._s[1997]! } + public var Permissions_ContactsTitle_v0: String { return self._s[1998]! } + public var Notifications_PermissionsTitle: String { return self._s[1999]! } + public var GroupPermission_NoSendLinks: String { return self._s[2000]! } + public var Privacy_Forwards_NeverAllow_Title: String { return self._s[2001]! } + public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[2002]! } + public var PeerInfo_PaneLinks: String { return self._s[2003]! } + public var Settings_Support: String { return self._s[2004]! } + public var Notifications_ChannelNotificationsSound: String { return self._s[2005]! } + public var SettingsSearch_Synonyms_Data_AutoDownloadReset: String { return self._s[2006]! } + public var Settings_SetNewProfilePhotoOrVideo: String { return self._s[2007]! } + public var Privacy_Forwards_Preview: String { return self._s[2008]! } + public var GroupPermission_ApplyAlertAction: String { return self._s[2009]! } + public var Watch_Stickers_StickerPacks: String { return self._s[2010]! } + public var Common_Select: String { return self._s[2012]! } + public var CheckoutInfo_ErrorEmailInvalid: String { return self._s[2013]! } + public var WallpaperSearch_ColorGray: String { return self._s[2016]! } + public var TwoFactorSetup_Password_PlaceholderPassword: String { return self._s[2017]! } + public var TwoFactorSetup_Hint_SkipAction: String { return self._s[2018]! } + public var ChatAdmins_AllMembersAreAdminsOffHelp: String { return self._s[2019]! } + public var PollResults_Title: String { return self._s[2020]! } + public var PasscodeSettings_AutoLock_IfAwayFor_5hours: String { return self._s[2021]! } + public var Appearance_PreviewReplyAuthor: String { return self._s[2022]! } + public var TwoStepAuth_RecoveryTitle: String { return self._s[2023]! } + public var Widget_AuthRequired: String { return self._s[2024]! } + public var ProfilePhoto_OpenInEditor: String { return self._s[2025]! } + public var Camera_FlashOn: String { return self._s[2026]! } + public var Conversation_ContextMenuLookUp: String { return self._s[2027]! } + public var Channel_Stickers_NotFoundHelp: String { return self._s[2028]! } + public var Watch_Suggestion_OK: String { return self._s[2029]! } + public func Username_LinkHint(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2031]!, self._r[2031]!, [_0]) + } + public func Notification_PinnedLiveLocationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2033]!, self._r[2033]!, [_0]) + } + public var TextFormat_Strikethrough: String { return self._s[2034]! } + public var DialogList_AdLabel: String { return self._s[2035]! } + public var WatchRemote_NotificationText: String { return self._s[2036]! } + public var IntentsSettings_SuggestedChatsSavedMessages: String { return self._s[2037]! } + public var SettingsSearch_Synonyms_Notifications_MessageNotificationsAlert: String { return self._s[2038]! } + public var Conversation_ReportSpam: String { return self._s[2039]! } + public var SettingsSearch_Synonyms_Privacy_Data_TopPeers: String { return self._s[2040]! } + public var Settings_LogoutConfirmationTitle: String { return self._s[2042]! } + public var PhoneLabel_Title: String { return self._s[2043]! } + public var Passport_Address_EditRentalAgreement: String { return self._s[2044]! } + public var Settings_ChangePhoneNumber: String { return self._s[2045]! } + public var Notifications_ExceptionsTitle: String { return self._s[2046]! } + public var Notifications_AlertTones: String { return self._s[2047]! } + public var Call_ReportIncludeLogDescription: String { return self._s[2048]! } + public var SettingsSearch_Synonyms_Notifications_ResetAllNotifications: String { return self._s[2049]! } + public var AutoDownloadSettings_PrivateChats: String { return self._s[2050]! } + public var VoiceOver_Chat_Photo: String { return self._s[2052]! } + public var TwoStepAuth_AddHintTitle: String { return self._s[2053]! } + public var Stats_PostsTitle: String { return self._s[2054]! } + public var ReportPeer_ReasonOther: String { return self._s[2055]! } + public var ChatList_Context_JoinChannel: String { return self._s[2056]! } + public var PhotoEditor_SkinTool: String { return self._s[2057]! } + public var KeyCommand_ScrollDown: String { return self._s[2059]! } + public var Conversation_ScheduleMessage_Title: String { return self._s[2060]! } + public func Login_BannedPhoneSubject(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2061]!, self._r[2061]!, [_0]) + } + public var NetworkUsageSettings_MediaVideoDataSection: String { return self._s[2063]! } + public var ChannelInfo_DeleteGroupConfirmation: String { return self._s[2064]! } + public var AuthSessions_LogOut: String { return self._s[2065]! } + public func PUSH_VIDEO_CALL_MISSED(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2066]!, self._r[2066]!, [_1]) + } + public var Passport_Identity_TypeInternalPassport: String { return self._s[2067]! } + public var ChatSettings_AutoDownloadVoiceMessages: String { return self._s[2068]! } + public var Passport_Phone_Title: String { return self._s[2069]! } + public var ContactList_Context_StartSecretChat: String { return self._s[2070]! } + public var Settings_PhoneNumber: String { return self._s[2071]! } + public func Conversation_ScheduleMessage_SendToday(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2072]!, self._r[2072]!, [_0]) + } + public var NotificationsSound_Alert: String { return self._s[2074]! } + public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[2075]! } + public var WebSearch_SearchNoResults: String { return self._s[2076]! } + public var Privacy_ProfilePhoto_AlwaysShareWith_Title: String { return self._s[2078]! } + public var Wallet_Configuration_SourceInfo: String { return self._s[2079]! } + public var LogoutOptions_AlternativeOptionsSection: String { return self._s[2080]! } + public var SettingsSearch_Synonyms_Passport: String { return self._s[2081]! } + public var PhotoEditor_CurvesTool: String { return self._s[2082]! } + public var Checkout_PaymentMethod: String { return self._s[2084]! } + public func PUSH_CHAT_ADD_YOU(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2085]!, self._r[2085]!, [_1, _2]) + } + public var Contacts_AccessDeniedError: String { return self._s[2086]! } + public var Camera_PhotoMode: String { return self._s[2089]! } + public var EditTheme_Expand_Preview_IncomingText: String { return self._s[2090]! } + public var Appearance_TextSize_Apply: String { return self._s[2091]! } + public var Passport_Address_AddUtilityBill: String { return self._s[2093]! } + public var ChatListFolderSettings_RecommendedNewFolder: String { return self._s[2094]! } + public var CallSettings_OnMobile: String { return self._s[2095]! } + public var Tour_Text2: String { return self._s[2096]! } + public func PUSH_CHAT_MESSAGE_ROUND(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2097]!, self._r[2097]!, [_1, _2]) + } + public var DialogList_EncryptionProcessing: String { return self._s[2099]! } + public var Permissions_Skip: String { return self._s[2100]! } + public var Wallet_Words_NotDoneOk: String { return self._s[2101]! } + public var SecretImage_Title: String { return self._s[2102]! } + public var Watch_MessageView_Title: String { return self._s[2103]! } + public var Channel_DiscussionGroupAdd: String { return self._s[2104]! } + public var AttachmentMenu_Poll: String { return self._s[2105]! } + public func Notification_GroupInviter(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2106]!, self._r[2106]!, [_0]) + } + public func Channel_DiscussionGroup_PrivateChannelLink(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2107]!, self._r[2107]!, [_1, _2]) + } + public var Notification_CallCanceled: String { return self._s[2108]! } + public var WallpaperPreview_Title: String { return self._s[2109]! } + public var Privacy_PaymentsClear_PaymentInfo: String { return self._s[2110]! } + public var Settings_ProxyConnecting: String { return self._s[2111]! } + public var Settings_CheckPhoneNumberText: String { return self._s[2113]! } + public var VoiceOver_Chat_YourVideo: String { return self._s[2114]! } + public var Wallet_Intro_Title: String { return self._s[2115]! } + public var TwoFactorSetup_Password_Action: String { return self._s[2116]! } + public var Profile_MessageLifetime5s: String { return self._s[2117]! } + public var Username_InvalidCharacters: String { return self._s[2118]! } + public var VoiceOver_Media_PlaybackRateFast: String { return self._s[2119]! } + public var ScheduledMessages_ClearAll: String { return self._s[2120]! } + public var Group_MessageVideoUpdated: String { return self._s[2121]! } + public var WallpaperPreview_CropBottomText: String { return self._s[2122]! } + public var AutoDownloadSettings_LimitBySize: String { return self._s[2123]! } + public var Settings_AddAccount: String { return self._s[2124]! } + public var Notification_CreatedChannel: String { return self._s[2127]! } + public func PUSH_CHAT_DELETE_MEMBER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2128]!, self._r[2128]!, [_1, _2, _3]) + } + public var Passcode_AppLockedAlert: String { return self._s[2130]! } + public var StickerPacksSettings_AnimatedStickersInfo: String { return self._s[2131]! } + public var VoiceOver_Media_PlaybackStop: String { return self._s[2132]! } + public var Contacts_TopSection: String { return self._s[2133]! } + public var ChatList_DeleteForEveryoneConfirmationAction: String { return self._s[2134]! } + public func Conversation_SetReminder_RemindOn(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2135]!, self._r[2135]!, [_0, _1]) + } + public var Wallet_Info_Receive: String { return self._s[2136]! } + public var Wallet_Completed_ViewWallet: String { return self._s[2138]! } + public func Time_MonthOfYear_m6(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2139]!, self._r[2139]!, [_0]) + } + public var ReportPeer_ReasonSpam: String { return self._s[2140]! } + public var UserInfo_TapToCall: String { return self._s[2141]! } + public var Conversation_ForwardAuthorHiddenTooltip: String { return self._s[2143]! } + public var AutoDownloadSettings_DataUsageCustom: String { return self._s[2144]! } + public var Common_Search: String { return self._s[2145]! } + public var ScheduledMessages_EmptyPlaceholder: String { return self._s[2146]! } + public func Channel_AdminLog_MessageChangedGroupGeoLocation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2147]!, self._r[2147]!, [_0]) + } + public var Wallet_Month_ShortJuly: String { return self._s[2148]! } + public var AuthSessions_IncompleteAttemptsInfo: String { return self._s[2150]! } + public var Message_InvoiceLabel: String { return self._s[2151]! } + public var Conversation_InputTextPlaceholder: String { return self._s[2152]! } + public var NetworkUsageSettings_MediaImageDataSection: String { return self._s[2153]! } + public func Passport_Address_UploadOneOfScan(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2154]!, self._r[2154]!, [_0]) + } + public var IntentsSettings_Reset: String { return self._s[2155]! } + public var Conversation_Info: String { return self._s[2156]! } + public var Login_InfoDeletePhoto: String { return self._s[2157]! } + public var ChatListFolder_DiscardDiscard: String { return self._s[2159]! } + public var Passport_Language_vi: String { return self._s[2160]! } + public var UserInfo_ScamUserWarning: String { return self._s[2161]! } + public var Conversation_Search: String { return self._s[2162]! } + public var DialogList_DeleteBotConversationConfirmation: String { return self._s[2164]! } + public var ChatListFolder_NameGroups: String { return self._s[2165]! } + public var ReportPeer_ReasonPornography: String { return self._s[2166]! } + public var AutoDownloadSettings_PhotosTitle: String { return self._s[2167]! } + public var Conversation_SendMessageErrorGroupRestricted: String { return self._s[2168]! } + public var Map_LiveLocationGroupDescription: String { return self._s[2169]! } + public var Channel_Setup_TypeHeader: String { return self._s[2170]! } + public var AuthSessions_LoggedIn: String { return self._s[2171]! } + public var Privacy_Forwards_AlwaysAllow_Title: String { return self._s[2172]! } + public var Login_SmsRequestState3: String { return self._s[2173]! } + public var Passport_Address_EditUtilityBill: String { return self._s[2174]! } + public var Appearance_ReduceMotionInfo: String { return self._s[2175]! } + public var Join_ChannelsTooMuch: String { return self._s[2176]! } + public var Channel_Edit_LinkItem: String { return self._s[2177]! } + public var Privacy_Calls_P2PNever: String { return self._s[2178]! } + public var Conversation_AddToReadingList: String { return self._s[2180]! } + public var Share_MultipleMessagesDisabled: String { return self._s[2181]! } + public var Message_Animation: String { return self._s[2182]! } + public var Conversation_DefaultRestrictedMedia: String { return self._s[2183]! } + public var Map_Unknown: String { return self._s[2184]! } + public var AutoDownloadSettings_LastDelimeter: String { return self._s[2185]! } + public func PUSH_PINNED_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2186]!, self._r[2186]!, [_1, _2]) + } + public func Passport_FieldOneOf_Or(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2187]!, self._r[2187]!, [_1, _2]) + } + public var Call_StatusRequesting: String { return self._s[2188]! } + public var Conversation_SecretChatContextBotAlert: String { return self._s[2189]! } + public var SocksProxySetup_ProxyStatusChecking: String { return self._s[2190]! } + public var Stats_MessageInteractionsTitle: String { return self._s[2191]! } + public func PUSH_CHAT_MESSAGE_DOC(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2192]!, self._r[2192]!, [_1, _2]) + } + public func Notification_PinnedLocationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2193]!, self._r[2193]!, [_0]) + } + public var Update_Skip: String { return self._s[2194]! } + public var Group_Username_RemoveExistingUsernamesInfo: String { return self._s[2195]! } + public var BlockedUsers_Title: String { return self._s[2196]! } + public var Weekday_Monday: String { return self._s[2197]! } + public func PUSH_CHANNEL_MESSAGE_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2198]!, self._r[2198]!, [_1]) + } + public var Username_CheckingUsername: String { return self._s[2199]! } + public var NotificationsSound_Bell: String { return self._s[2200]! } + public var Conversation_SendMessageErrorFlood: String { return self._s[2201]! } + public var SettingsSearch_Synonyms_Notifications_DisplayNamesOnLockScreen: String { return self._s[2202]! } + public var ChannelMembers_ChannelAdminsTitle: String { return self._s[2203]! } + public func Notification_ChangedGroupVideo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2204]!, self._r[2204]!, [_0]) + } + public var ChatSettings_Groups: String { return self._s[2205]! } + public var WallpaperPreview_PatternPaternDiscard: String { return self._s[2206]! } + public var ChatList_PeerTypeContact: String { return self._s[2207]! } + public func Conversation_SetReminder_RemindTomorrow(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2208]!, self._r[2208]!, [_0]) + } + public var Your_card_was_declined: String { return self._s[2209]! } + public var TwoStepAuth_EnterPasswordHelp: String { return self._s[2211]! } + public var Wallet_Month_ShortApril: String { return self._s[2212]! } + public var ChatList_Unmute: String { return self._s[2213]! } + public var AuthSessions_AddDevice_ScanTitle: String { return self._s[2214]! } + public var PhotoEditor_CurvesAll: String { return self._s[2215]! } + public var Weekday_ShortTuesday: String { return self._s[2216]! } + public var DialogList_Read: String { return self._s[2217]! } + public var Appearance_AppIconClassic: String { return self._s[2218]! } + public var Conversation_Dice_u1F3B2: String { return self._s[2219]! } + public var ChannelMembers_WhoCanAddMembers_AllMembers: String { return self._s[2220]! } + public var Passport_Identity_Gender: String { return self._s[2221]! } + public func Target_ShareGameConfirmationPrivate(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2222]!, self._r[2222]!, [_0]) + } + public var Target_SelectGroup: String { return self._s[2223]! } + public var Map_HomeAndWorkInfo: String { return self._s[2225]! } + public func DialogList_EncryptedChatStartedIncoming(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2226]!, self._r[2226]!, [_0]) + } + public var Passport_Language_en: String { return self._s[2227]! } + public var AutoDownloadSettings_AutodownloadPhotos: String { return self._s[2228]! } + public var Channel_Username_CreatePublicLinkHelp: String { return self._s[2229]! } + public var Login_CancelPhoneVerificationContinue: String { return self._s[2230]! } + public var ScheduledMessages_SendNow: String { return self._s[2231]! } + public var Checkout_NewCard_PaymentCard: String { return self._s[2233]! } + public var Login_InfoHelp: String { return self._s[2234]! } + public var Appearance_BubbleCorners_AdjustAdjacent: String { return self._s[2235]! } + public var ProfilePhoto_SetMainPhoto: String { return self._s[2236]! } + public var Contacts_PermissionsSuppressWarningTitle: String { return self._s[2237]! } + public var SettingsSearch_Synonyms_Stickers_FeaturedPacks: String { return self._s[2238]! } + public func Channel_AdminLog_MessageChangedLinkedChannel(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2239]!, self._r[2239]!, [_1, _2]) + } + public var SocksProxySetup_AddProxy: String { return self._s[2242]! } + public var CreatePoll_Title: String { return self._s[2243]! } + public var MessagePoll_QuizNoUsers: String { return self._s[2244]! } + public var Conversation_ViewTheme: String { return self._s[2245]! } + public var SettingsSearch_Synonyms_Privacy_Data_SecretChatLinkPreview: String { return self._s[2246]! } + public var PasscodeSettings_SimplePasscodeHelp: String { return self._s[2247]! } + public var TwoFactorSetup_Intro_Text: String { return self._s[2248]! } + public var UserInfo_GroupsInCommon: String { return self._s[2249]! } + public var TelegramWallet_Intro_TermsUrl: String { return self._s[2250]! } + public var Stats_ViewsByHoursTitle: String { return self._s[2251]! } + public var Conversation_PrivateChannelTimeLimitedAlertTitle: String { return self._s[2252]! } + public var Call_AudioRouteHide: String { return self._s[2253]! } + public func Wallet_Info_TransactionDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2255]!, self._r[2255]!, [_1, _2]) + } + public var ContactInfo_PhoneLabelMobile: String { return self._s[2256]! } + public var IntentsSettings_SuggestedChatsInfo: String { return self._s[2257]! } + public var CreatePoll_QuizOptionsHeader: String { return self._s[2258]! } + public func ChatList_LeaveGroupConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2259]!, self._r[2259]!, [_0]) + } + public var TextFormat_Bold: String { return self._s[2260]! } + public var CreatePoll_ExplanationInfo: String { return self._s[2261]! } + public var FastTwoStepSetup_EmailSection: String { return self._s[2262]! } + public var StickerPackActionInfo_AddedTitle: String { return self._s[2263]! } + public var Notifications_Title: String { return self._s[2264]! } + public var Group_Username_InvalidTooShort: String { return self._s[2265]! } + public var Channel_ErrorAddTooMuch: String { return self._s[2266]! } + public func DialogList_MultipleTypingSuffix(_ _0: Int) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2267]!, self._r[2267]!, ["\(_0)"]) + } + public var VoiceOver_DiscardPreparedContent: String { return self._s[2269]! } + public var Stickers_SuggestAdded: String { return self._s[2270]! } + public var Login_CountryCode: String { return self._s[2271]! } + public var ChatSettings_AutoPlayVideos: String { return self._s[2272]! } + public var Map_GetDirections: String { return self._s[2273]! } + public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[2274]! } + public var Stats_GroupNewMembersBySourceTitle: String { return self._s[2275]! } + public var Login_PhoneFloodError: String { return self._s[2276]! } + public func Time_MonthOfYear_m3(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2277]!, self._r[2277]!, [_0]) + } + public func Wallet_Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2278]!, self._r[2278]!, [_1, _2, _3]) + } + public var IntentsSettings_SuggestedChatsPrivateChats: String { return self._s[2279]! } + public var Settings_SetUsername: String { return self._s[2281]! } + public var Group_Location_ChangeLocation: String { return self._s[2282]! } + public var Notification_GroupInviterSelf: String { return self._s[2283]! } + public var InstantPage_TapToOpenLink: String { return self._s[2284]! } + public func Notification_ChannelInviter(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2285]!, self._r[2285]!, [_0]) + } + public var PrivacySettings_AutoArchiveInfo: String { return self._s[2286]! } + public var Watch_Suggestion_TalkLater: String { return self._s[2287]! } + public var SecretChat_Title: String { return self._s[2288]! } + public var Group_UpgradeNoticeText1: String { return self._s[2289]! } + public var AuthSessions_Title: String { return self._s[2290]! } + public func TextFormat_AddLinkText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2291]!, self._r[2291]!, [_0]) + } + public var PhotoEditor_CropAuto: String { return self._s[2292]! } + public var Channel_About_Title: String { return self._s[2294]! } + public var Theme_ThemeChanged: String { return self._s[2295]! } + public var FastTwoStepSetup_EmailHelp: String { return self._s[2296]! } + public func Conversation_Bytes(_ _0: Int) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2299]!, self._r[2299]!, ["\(_0)"]) + } + public var VoiceOver_MessageContextReport: String { return self._s[2300]! } + public var Conversation_PinMessageAlert_OnlyPin: String { return self._s[2302]! } + public var Group_Setup_HistoryVisibleHelp: String { return self._s[2303]! } + public func PUSH_MESSAGE_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2304]!, self._r[2304]!, [_1]) + } + public func SharedMedia_SearchNoResultsDescription(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2306]!, self._r[2306]!, [_0]) + } + public func TwoStepAuth_RecoveryEmailUnavailable(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2307]!, self._r[2307]!, [_0]) + } + public var Privacy_PaymentsClearInfoHelp: String { return self._s[2308]! } + public var PeopleNearby_DiscoverDescription: String { return self._s[2310]! } + public var Presence_online: String { return self._s[2312]! } + public var PasscodeSettings_Title: String { return self._s[2313]! } + public var Passport_Identity_ExpiryDatePlaceholder: String { return self._s[2314]! } + public var Web_OpenExternal: String { return self._s[2315]! } + public var AutoDownloadSettings_AutoDownload: String { return self._s[2317]! } + public var Channel_OwnershipTransfer_EnterPasswordText: String { return self._s[2318]! } + public var LocalGroup_Title: String { return self._s[2319]! } + public func AutoNightTheme_AutomaticHelp(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2320]!, self._r[2320]!, [_0]) + } + public var FastTwoStepSetup_PasswordConfirmationPlaceholder: String { return self._s[2321]! } + public var Conversation_StopQuizConfirmation: String { return self._s[2322]! } + public var Map_YouAreHere: String { return self._s[2323]! } + public func AuthSessions_Message(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2324]!, self._r[2324]!, [_0]) + } + public func ChatList_DeleteChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2325]!, self._r[2325]!, [_0]) + } + public var Theme_Context_ChangeColors: String { return self._s[2326]! } + public var PrivacyLastSeenSettings_AlwaysShareWith: String { return self._s[2327]! } + public var Target_InviteToGroupErrorAlreadyInvited: String { return self._s[2328]! } + public func AuthSessions_AppUnofficial(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2329]!, self._r[2329]!, [_0]) + } + public func DialogList_LiveLocationSharingTo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2330]!, self._r[2330]!, [_0]) + } + public var SocksProxySetup_Username: String { return self._s[2331]! } + public var Bot_Start: String { return self._s[2332]! } + public func Channel_AdminLog_EmptyFilterQueryText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2333]!, self._r[2333]!, [_0]) + } + public func Channel_AdminLog_MessagePinned(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2334]!, self._r[2334]!, [_0]) + } + public var Contacts_SortByPresence: String { return self._s[2335]! } + public var AccentColor_Title: String { return self._s[2338]! } + public var Conversation_DiscardVoiceMessageTitle: String { return self._s[2339]! } + public func PUSH_CHAT_CREATED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2340]!, self._r[2340]!, [_1, _2]) + } + public func PrivacySettings_LastSeenContactsMinus(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2341]!, self._r[2341]!, [_0]) + } + public func Channel_AdminLog_MessageChangedLinkedGroup(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2342]!, self._r[2342]!, [_1, _2]) + } + public var Stats_GroupOverview: String { return self._s[2343]! } + public var Passport_Email_EnterOtherEmail: String { return self._s[2344]! } + public var Login_InfoAvatarPhoto: String { return self._s[2345]! } + public func ChatList_RemovedFromFolderTooltip(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2346]!, self._r[2346]!, [_1, _2]) + } + public var Privacy_PaymentsClear_ShippingInfo: String { return self._s[2347]! } + public var Tour_Title4: String { return self._s[2348]! } + public var Passport_Identity_Translation: String { return self._s[2349]! } + public var SettingsSearch_Synonyms_Notifications_ContactJoined: String { return self._s[2350]! } + public var Login_TermsOfServiceLabel: String { return self._s[2352]! } + public var CallFeedback_VideoReasonLowQuality: String { return self._s[2353]! } + public var Passport_Language_it: String { return self._s[2354]! } + public var KeyCommand_JumpToNextUnreadChat: String { return self._s[2355]! } + public var Passport_Identity_SelfieHelp: String { return self._s[2356]! } + public var Conversation_ClearAll: String { return self._s[2358]! } + public var Wallet_Send_UninitializedText: String { return self._s[2360]! } + public var Channel_OwnershipTransfer_Title: String { return self._s[2361]! } + public var TwoStepAuth_FloodError: String { return self._s[2362]! } + public func PUSH_CHANNEL_MESSAGE_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2363]!, self._r[2363]!, [_1]) + } + public var Paint_Delete: String { return self._s[2364]! } + public func Wallet_Sent_Text(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2365]!, self._r[2365]!, [_0]) + } + public var Privacy_AddNewPeer: String { return self._s[2366]! } + public func Channel_AdminLog_MessageRank(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2367]!, self._r[2367]!, [_1]) + } + public var LogoutOptions_SetPasscodeText: String { return self._s[2368]! } + public func Passport_AcceptHelp(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2369]!, self._r[2369]!, [_1, _2]) + } + public var Message_PinnedAudioMessage: String { return self._s[2370]! } + public func Watch_Time_ShortTodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2371]!, self._r[2371]!, [_0]) + } + public var Notification_Mute1hMin: String { return self._s[2372]! } + public var Notifications_GroupNotificationsSound: String { return self._s[2373]! } + public var Wallet_Month_GenNovember: String { return self._s[2374]! } + public var SocksProxySetup_ShareProxyList: String { return self._s[2376]! } + public var Conversation_MessageEditedLabel: String { return self._s[2377]! } + public func ClearCache_Success(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2378]!, self._r[2378]!, [_0, _1]) + } + public var Notification_Exceptions_AlwaysOff: String { return self._s[2379]! } + public var Conversation_ContextMenuOpenChannelProfile: String { return self._s[2380]! } + public var Notification_Exceptions_NewException_MessagePreviewHeader: String { return self._s[2381]! } + public func Channel_AdminLog_MessageAdmin(_ _0: String, _ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2382]!, self._r[2382]!, [_0, _1, _2]) + } + public var NetworkUsageSettings_ResetStats: String { return self._s[2383]! } + public func PUSH_MESSAGE_GEOLIVE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2384]!, self._r[2384]!, [_1]) + } + public var AccessDenied_LocationTracking: String { return self._s[2385]! } + public var Month_GenOctober: String { return self._s[2386]! } + public var GroupInfo_InviteLink_RevokeAlert_Revoke: String { return self._s[2387]! } + public var EnterPasscode_EnterPasscode: String { return self._s[2388]! } + public var Call_CameraConfirmationConfirm: String { return self._s[2390]! } + public var MediaPicker_TimerTooltip: String { return self._s[2391]! } + public var SharedMedia_TitleAll: String { return self._s[2392]! } + public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsExceptions: String { return self._s[2395]! } + public var Conversation_RestrictedMedia: String { return self._s[2396]! } + public var AccessDenied_PhotosRestricted: String { return self._s[2397]! } + public var Privacy_Forwards_WhoCanForward: String { return self._s[2399]! } + public var ChangePhoneNumberCode_Called: String { return self._s[2400]! } + public func Notification_PinnedDocumentMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2401]!, self._r[2401]!, [_0]) + } + public var Conversation_SavedMessages: String { return self._s[2404]! } + public var Your_cards_expiration_month_is_invalid: String { return self._s[2406]! } + public var FastTwoStepSetup_PasswordPlaceholder: String { return self._s[2407]! } + public func Target_ShareGameConfirmationGroup(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2409]!, self._r[2409]!, [_0]) + } + public var VoiceOver_Chat_YourMessage: String { return self._s[2410]! } + public func VoiceOver_Chat_Title(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2411]!, self._r[2411]!, [_0]) + } + public var ReportPeer_AlertSuccess: String { return self._s[2412]! } + public var PhotoEditor_CropAspectRatioOriginal: String { return self._s[2413]! } + public func InstantPage_RelatedArticleAuthorAndDateTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2414]!, self._r[2414]!, [_1, _2]) + } + public var Checkout_PasswordEntry_Title: String { return self._s[2415]! } + public var PhotoEditor_FadeTool: String { return self._s[2416]! } + public var Privacy_ContactsReset: String { return self._s[2417]! } + public var Conversation_PrivateChannelTimeLimitedAlertText: String { return self._s[2418]! } + public func Channel_AdminLog_MessageRestrictedUntil(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2420]!, self._r[2420]!, [_0]) + } + public var Message_PinnedVideoMessage: String { return self._s[2421]! } + public var ChatList_Mute: String { return self._s[2422]! } + public func Wallet_Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2423]!, self._r[2423]!, [_1, _2, _3]) + } + public var Permissions_CellularDataText_v0: String { return self._s[2424]! } + public var Conversation_PinnedQuiz: String { return self._s[2426]! } + public var ShareMenu_SelectChats: String { return self._s[2428]! } + public var ChatList_Context_Unarchive: String { return self._s[2429]! } + public var MusicPlayer_VoiceNote: String { return self._s[2430]! } + public var Conversation_RestrictedText: String { return self._s[2431]! } + public var SettingsSearch_Synonyms_Privacy_Data_DeleteDrafts: String { return self._s[2432]! } + public var Wallet_Month_GenApril: String { return self._s[2433]! } + public var Wallet_Month_ShortMarch: String { return self._s[2434]! } + public var TwoStepAuth_DisableSuccess: String { return self._s[2435]! } + public var Chat_PsaTooltip_covid: String { return self._s[2436]! } + public var Cache_Videos: String { return self._s[2437]! } + public var PrivacySettings_PhoneNumber: String { return self._s[2438]! } + public var Wallet_Month_GenFebruary: String { return self._s[2439]! } + public var FeatureDisabled_Oops: String { return self._s[2441]! } + public var ChatList_RemoveFolderAction: String { return self._s[2442]! } + public var Passport_Address_PostcodePlaceholder: String { return self._s[2443]! } + public func AddContact_StatusSuccess(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2444]!, self._r[2444]!, [_0]) + } + public var Stickers_GroupStickersHelp: String { return self._s[2446]! } + public var GroupPermission_NoSendPolls: String { return self._s[2447]! } + public var Wallet_Qr_ScanCode: String { return self._s[2448]! } + public var Message_VideoExpired: String { return self._s[2450]! } + public var GroupInfo_GroupHistoryVisible: String { return self._s[2451]! } + public var Notifications_Badge: String { return self._s[2452]! } + public var Wallet_Receive_AddressCopied: String { return self._s[2453]! } + public var CreatePoll_OptionPlaceholder: String { return self._s[2454]! } + public var Username_InvalidTooShort: String { return self._s[2455]! } + public var EnterPasscode_EnterNewPasscodeChange: String { return self._s[2456]! } + public var Channel_AdminLog_PinMessages: String { return self._s[2457]! } + public var ArchivedChats_IntroTitle3: String { return self._s[2458]! } + public func Notification_MessageLifetimeRemoved(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2459]!, self._r[2459]!, [_1]) + } + public var Permissions_SiriAllowInSettings_v0: String { return self._s[2460]! } + public var Conversation_DefaultRestrictedText: String { return self._s[2461]! } + public var SharedMedia_CategoryDocs: String { return self._s[2464]! } + public func PUSH_MESSAGE_CONTACT(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2465]!, self._r[2465]!, [_1]) + } + public var Wallet_Send_UninitializedTitle: String { return self._s[2466]! } + public var CallFeedback_VideoReasonDistorted: String { return self._s[2467]! } + public var StickerPackActionInfo_ArchivedTitle: String { return self._s[2468]! } + public var Privacy_Forwards_NeverLink: String { return self._s[2470]! } + public func Notification_MessageLifetimeChangedOutgoing(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2471]!, self._r[2471]!, [_1]) + } + public var CheckoutInfo_ErrorShippingNotAvailable: String { return self._s[2472]! } + public func Time_MonthOfYear_m12(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2473]!, self._r[2473]!, [_0]) + } + public var ChatSettings_PrivateChats: String { return self._s[2474]! } + public var SettingsSearch_Synonyms_EditProfile_Logout: String { return self._s[2475]! } + public var Conversation_PrivateMessageLinkCopied: String { return self._s[2476]! } + public var Channel_UpdatePhotoItem: String { return self._s[2477]! } + public var GroupInfo_LeftStatus: String { return self._s[2478]! } + public var Watch_MessageView_Forward: String { return self._s[2480]! } + public var ReportPeer_ReasonChildAbuse: String { return self._s[2481]! } + public var Cache_ClearEmpty: String { return self._s[2483]! } + public var Localization_LanguageName: String { return self._s[2485]! } + public var Wallet_AccessDenied_Title: String { return self._s[2486]! } + public var WebSearch_GIFs: String { return self._s[2487]! } + public var Notifications_DisplayNamesOnLockScreenInfoWithLink: String { return self._s[2488]! } + public var Wallet_AccessDenied_Settings: String { return self._s[2489]! } + public var AccessDenied_VideoCallCamera: String { return self._s[2490]! } + public var Username_InvalidStartsWithNumber: String { return self._s[2491]! } + public var Common_Back: String { return self._s[2492]! } + public var GroupInfo_Permissions_EditingDisabled: String { return self._s[2493]! } + public var Passport_Identity_DateOfBirthPlaceholder: String { return self._s[2494]! } + public var Wallet_Send_Send: String { return self._s[2495]! } + public func PUSH_CHANNEL_MESSAGE_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2497]!, self._r[2497]!, [_1, _2]) + } + public var Wallet_Info_RefreshErrorTitle: String { return self._s[2498]! } + public var ChatList_Tabs_All: String { return self._s[2499]! } + public var Wallet_Month_GenJune: String { return self._s[2500]! } + public var Passport_Email_Help: String { return self._s[2501]! } + public var Watch_Conversation_Reply: String { return self._s[2503]! } + public var Stats_GroupTopInvitersTitle: String { return self._s[2504]! } + public var Conversation_EditingMessageMediaChange: String { return self._s[2507]! } + public var Passport_Identity_IssueDatePlaceholder: String { return self._s[2508]! } + public var Channel_BanUser_Unban: String { return self._s[2510]! } + public var Channel_EditAdmin_PermissionPostMessages: String { return self._s[2511]! } + public var Group_Username_CreatePublicLinkHelp: String { return self._s[2512]! } + public var TwoStepAuth_ConfirmEmailCodePlaceholder: String { return self._s[2514]! } + public var Wallet_Send_AddressHeader: String { return self._s[2515]! } + public var Passport_Identity_Name: String { return self._s[2516]! } + public func Channel_DiscussionGroup_HeaderGroupSet(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2517]!, self._r[2517]!, [_0]) + } + public var GroupRemoved_ViewUserInfo: String { return self._s[2518]! } + public var Stats_MessageOverview: String { return self._s[2519]! } + public var Conversation_BlockUser: String { return self._s[2520]! } + public var Month_GenJanuary: String { return self._s[2521]! } + public var ChatSettings_TextSize: String { return self._s[2522]! } + public var Notification_PassportValuePhone: String { return self._s[2523]! } + public var MediaPlayer_UnknownArtist: String { return self._s[2524]! } + public var Passport_Language_ne: String { return self._s[2525]! } + public var Notification_CallBack: String { return self._s[2526]! } + public var Wallet_SecureStorageReset_BiometryTouchId: String { return self._s[2527]! } + public var TwoStepAuth_EmailHelp: String { return self._s[2528]! } + public func Time_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2529]!, self._r[2529]!, [_0]) + } + public var Channel_Info_Management: String { return self._s[2530]! } + public var Passport_FieldIdentityUploadHelp: String { return self._s[2531]! } + public var Stickers_FrequentlyUsed: String { return self._s[2533]! } + public var Channel_BanUser_PermissionSendMessages: String { return self._s[2534]! } + public var Passport_Address_OneOfTypeUtilityBill: String { return self._s[2536]! } + public func LOCAL_CHANNEL_MESSAGE_FWDS(_ _1: String, _ _2: Int) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2537]!, self._r[2537]!, [_1, "\(_2)"]) + } + public var TwoFactorSetup_Password_Title: String { return self._s[2538]! } + public var Passport_Address_EditResidentialAddress: String { return self._s[2539]! } + public var PrivacyPolicy_DeclineTitle: String { return self._s[2540]! } + public var CreatePoll_TextHeader: String { return self._s[2541]! } + public func Checkout_SavePasswordTimeoutAndTouchId(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2542]!, self._r[2542]!, [_0]) + } + public var PhotoEditor_QualityMedium: String { return self._s[2543]! } + public var InfoPlist_NSMicrophoneUsageDescription: String { return self._s[2544]! } + public var Conversation_StatusKickedFromChannel: String { return self._s[2546]! } + public var CheckoutInfo_ReceiverInfoName: String { return self._s[2547]! } + public var Group_ErrorSendRestrictedStickers: String { return self._s[2548]! } + public func Conversation_RestrictedInlineTimed(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2549]!, self._r[2549]!, [_0]) + } + public func Channel_AdminLog_MessageTransferedName(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2550]!, self._r[2550]!, [_1]) + } + public var LogoutOptions_LogOutWalletInfo: String { return self._s[2551]! } + public var TwoFactorSetup_Email_SkipConfirmationTitle: String { return self._s[2552]! } + public var Conversation_LinkDialogOpen: String { return self._s[2554]! } + public var TwoFactorSetup_Hint_Title: String { return self._s[2555]! } + public var VoiceOver_Chat_PollNoVotes: String { return self._s[2556]! } + public var Settings_Username: String { return self._s[2558]! } + public var Conversation_Block: String { return self._s[2560]! } + public var Wallpaper_Wallpaper: String { return self._s[2561]! } + public var SocksProxySetup_UseProxy: String { return self._s[2563]! } + public var Wallet_Send_Confirmation: String { return self._s[2564]! } + public var EditTheme_UploadEditedTheme: String { return self._s[2565]! } + public var UserInfo_ShareMyContactInfo: String { return self._s[2566]! } + public var MessageTimer_Forever: String { return self._s[2567]! } + public var Privacy_Calls_WhoCanCallMe: String { return self._s[2568]! } + public var PhotoEditor_DiscardChanges: String { return self._s[2569]! } + public var AuthSessions_TerminateOtherSessionsHelp: String { return self._s[2570]! } + public var Passport_Language_da: String { return self._s[2571]! } + public var Conversation_PrivateChannelTimeLimitedAlertJoin: String { return self._s[2573]! } + public var SocksProxySetup_PortPlaceholder: String { return self._s[2574]! } + public func SecretGIF_NotViewedYet(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2575]!, self._r[2575]!, [_0]) + } + public var Passport_Address_EditPassportRegistration: String { return self._s[2576]! } + public func Channel_AdminLog_MessageChangedGroupAbout(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2578]!, self._r[2578]!, [_0]) + } + public var Settings_AddDevice: String { return self._s[2579]! } + public var Passport_Identity_ResidenceCountryPlaceholder: String { return self._s[2581]! } + public var AuthSessions_AddDeviceIntro_Text1: String { return self._s[2582]! } + public var Conversation_SearchByName_Prefix: String { return self._s[2583]! } + public var Conversation_PinnedPoll: String { return self._s[2584]! } + public var AuthSessions_AddDeviceIntro_Text2: String { return self._s[2585]! } + public var Conversation_EmptyGifPanelPlaceholder: String { return self._s[2586]! } + public var AuthSessions_AddDeviceIntro_Text3: String { return self._s[2587]! } + public func PUSH_ENCRYPTION_ACCEPT(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2588]!, self._r[2588]!, [_1]) + } + public var WallpaperSearch_ColorPurple: String { return self._s[2589]! } + public var Cache_ByPeerHeader: String { return self._s[2590]! } + public func Conversation_EncryptedPlaceholderTitleIncoming(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2591]!, self._r[2591]!, [_0]) + } + public var ChatSettings_AutoDownloadDocuments: String { return self._s[2592]! } + public var Appearance_ThemePreview_Chat_3_Text: String { return self._s[2595]! } + public var Wallet_Completed_Title: String { return self._s[2596]! } + public var Notification_PinnedMessage: String { return self._s[2597]! } + public var TwoFactorSetup_EmailVerification_Placeholder: String { return self._s[2598]! } + public var VoiceOver_Chat_RecordModeVideoMessage: String { return self._s[2600]! } + public var Contacts_SortBy: String { return self._s[2601]! } + public func PUSH_CHANNEL_MESSAGE_NOTEXT(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2602]!, self._r[2602]!, [_1]) + } + public var Appearance_ColorThemeNight: String { return self._s[2604]! } + public func PUSH_MESSAGE_GAME(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2605]!, self._r[2605]!, [_1, _2]) + } + public var Call_EncryptionKey_Title: String { return self._s[2606]! } + public var Watch_UserInfo_Service: String { return self._s[2607]! } + public var SettingsSearch_Synonyms_Data_SaveEditedPhotos: String { return self._s[2609]! } + public var Conversation_Unpin: String { return self._s[2611]! } + public var CancelResetAccount_Title: String { return self._s[2612]! } + public var Map_LiveLocationFor15Minutes: String { return self._s[2613]! } + public func Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2615]!, self._r[2615]!, [_1, _2, _3]) + } + public var Group_Members_AddMemberBotErrorNotAllowed: String { return self._s[2616]! } + public var Appearance_BubbleCorners_Title: String { return self._s[2617]! } + public var CallSettings_Title: String { return self._s[2618]! } + public var SettingsSearch_Synonyms_Appearance_ChatBackground: String { return self._s[2619]! } + public var PasscodeSettings_EncryptDataHelp: String { return self._s[2621]! } + public var AutoDownloadSettings_Contacts: String { return self._s[2622]! } + public func Channel_AdminLog_MessageRankName(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2623]!, self._r[2623]!, [_1, _2]) + } + public var ChatList_Tabs_AllChats: String { return self._s[2624]! } + public var Passport_Identity_DocumentDetails: String { return self._s[2625]! } + public var LoginPassword_PasswordHelp: String { return self._s[2626]! } + public var ChatListFolderSettings_Info: String { return self._s[2627]! } + public var SettingsSearch_Synonyms_Data_AutoDownloadUsingWifi: String { return self._s[2628]! } + public var PrivacyLastSeenSettings_CustomShareSettings_Delete: String { return self._s[2629]! } + public var ChatContextMenu_TextSelectionTip: String { return self._s[2630]! } + public var ChatListFolder_CategoryGroups: String { return self._s[2631]! } + public var Checkout_TotalPaidAmount: String { return self._s[2633]! } + public func FileSize_KB(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2634]!, self._r[2634]!, [_0]) + } + public var ChatState_Updating: String { return self._s[2635]! } + public var PasscodeSettings_ChangePasscode: String { return self._s[2636]! } + public var ChatListFolder_ExcludedSectionHeader: String { return self._s[2637]! } + public var Conversation_SecretLinkPreviewAlert: String { return self._s[2639]! } + public var Privacy_SecretChatsLinkPreviews: String { return self._s[2640]! } + public func PUSH_CHANNEL_MESSAGE_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2641]!, self._r[2641]!, [_1]) + } + public var VoiceOver_Chat_ReplyToYourMessage: String { return self._s[2642]! } + public var Contacts_InviteFriends: String { return self._s[2644]! } + public var Map_ChooseLocationTitle: String { return self._s[2645]! } + public var Conversation_StopPoll: String { return self._s[2647]! } + public func WebSearch_SearchNoResultsDescription(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2648]!, self._r[2648]!, [_0]) + } + public var Call_Camera: String { return self._s[2649]! } + public var LogoutOptions_ChangePhoneNumberTitle: String { return self._s[2650]! } + public var AppWallet_Intro_Text: String { return self._s[2651]! } + public var Appearance_BubbleCornersSetting: String { return self._s[2652]! } + public var Calls_RatingFeedback: String { return self._s[2653]! } + public func Conversation_NoticeInvitedByInGroup(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2655]!, self._r[2655]!, [_0]) + } + public var GroupInfo_BroadcastListNamePlaceholder: String { return self._s[2656]! } + public var Wallet_Alert_OK: String { return self._s[2657]! } + public var NotificationsSound_Pulse: String { return self._s[2658]! } + public var Watch_LastSeen_Lately: String { return self._s[2659]! } + public var ReportGroupLocation_Report: String { return self._s[2662]! } + public var Widget_NoUsers: String { return self._s[2663]! } + public var Conversation_UnvotePoll: String { return self._s[2664]! } + public var SettingsSearch_Synonyms_Privacy_ProfilePhoto: String { return self._s[2666]! } + public var Privacy_ProfilePhoto_WhoCanSeeMyPhoto: String { return self._s[2667]! } + public var NotificationsSound_Circles: String { return self._s[2668]! } + public var PrivacyLastSeenSettings_AlwaysShareWith_Title: String { return self._s[2671]! } + public var Wallet_Settings_DeleteWallet: String { return self._s[2672]! } + public var ChatListFolder_CategoryBots: String { return self._s[2673]! } + public var TwoStepAuth_RecoveryCodeExpired: String { return self._s[2674]! } + public var Proxy_TooltipUnavailable: String { return self._s[2675]! } + public var Passport_Identity_CountryPlaceholder: String { return self._s[2677]! } + public var GroupInfo_Permissions_SlowmodeInfo: String { return self._s[2679]! } + public var Conversation_FileDropbox: String { return self._s[2680]! } + public var Notifications_ExceptionsUnmuted: String { return self._s[2681]! } + public var Tour_Text3: String { return self._s[2683]! } + public var Login_ResetAccountProtected_Title: String { return self._s[2686]! } + public var ChatListFolder_NamePlaceholder: String { return self._s[2687]! } + public var Settings_FrequentlyAskedQuestions: String { return self._s[2688]! } + public var GroupPermission_NoSendMessages: String { return self._s[2689]! } + public var WallpaperSearch_ColorTitle: String { return self._s[2690]! } + public var ChatAdmins_AllMembersAreAdminsOnHelp: String { return self._s[2691]! } + public func Conversation_LiveLocationYouAnd(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2693]!, self._r[2693]!, [_0]) + } + public var GroupInfo_AddParticipantTitle: String { return self._s[2694]! } + public var Checkout_ShippingOption_Title: String { return self._s[2695]! } + public var ChatSettings_AutoDownloadTitle: String { return self._s[2696]! } + public func DialogList_SingleTypingSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2697]!, self._r[2697]!, [_0]) + } + public func ChatSettings_AutoDownloadSettings_TypeVideo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2698]!, self._r[2698]!, [_0]) + } + public var Channel_Management_LabelAdministrator: String { return self._s[2699]! } + public var EditTheme_FileReadError: String { return self._s[2700]! } + public var OwnershipTransfer_ComeBackLater: String { return self._s[2701]! } + public var PrivacyLastSeenSettings_NeverShareWith_Placeholder: String { return self._s[2702]! } + public var AutoDownloadSettings_Photos: String { return self._s[2704]! } + public var Appearance_PreviewIncomingText: String { return self._s[2705]! } + public var ChatList_Context_MarkAllAsRead: String { return self._s[2706]! } + public var ChannelInfo_ConfirmLeave: String { return self._s[2707]! } + public var ChatListFolder_ExcludeSectionInfo: String { return self._s[2708]! } + public var MediaPicker_MomentsDateRangeSameMonthYearFormat: String { return self._s[2709]! } + public var Passport_Identity_DocumentNumberPlaceholder: String { return self._s[2710]! } + public var Channel_AdminLogFilter_EventsNewMembers: String { return self._s[2711]! } + public var PasscodeSettings_AutoLock_IfAwayFor_5minutes: String { return self._s[2712]! } + public var GroupInfo_SetGroupPhotoStop: String { return self._s[2713]! } + public var Notification_SecretChatScreenshot: String { return self._s[2714]! } + public var AccessDenied_Wallpapers: String { return self._s[2715]! } + public var ChatList_Context_Mute: String { return self._s[2717]! } + public var Passport_Address_City: String { return self._s[2718]! } + public var Settings_EditVideo: String { return self._s[2719]! } + public var InfoPlist_NSPhotoLibraryAddUsageDescription: String { return self._s[2720]! } + public var Appearance_ThemeCarouselClassic: String { return self._s[2721]! } + public var SocksProxySetup_SecretPlaceholder: String { return self._s[2722]! } + public var AccessDenied_LocationDisabled: String { return self._s[2723]! } + public var Group_Location_Title: String { return self._s[2724]! } + public var SocksProxySetup_HostnamePlaceholder: String { return self._s[2726]! } + public var GroupInfo_Sound: String { return self._s[2727]! } + public var SettingsSearch_Synonyms_ChatSettings_OpenLinksIn: String { return self._s[2728]! } + public var ChannelInfo_ScamChannelWarning: String { return self._s[2729]! } + public var Stickers_RemoveFromFavorites: String { return self._s[2730]! } + public var Contacts_Title: String { return self._s[2731]! } + public var EditTheme_ThemeTemplateAlertText: String { return self._s[2732]! } + public var Passport_Language_fr: String { return self._s[2733]! } + public var TwoFactorSetup_EmailVerification_Action: String { return self._s[2734]! } + public var Notifications_ResetAllNotifications: String { return self._s[2735]! } + public var IntentsSettings_SuggestedChats: String { return self._s[2737]! } + public var PrivacySettings_SecurityTitle: String { return self._s[2739]! } + public var Checkout_NewCard_Title: String { return self._s[2740]! } + public var Login_HaveNotReceivedCodeInternal: String { return self._s[2741]! } + public var Conversation_ForwardChats: String { return self._s[2742]! } + public var Wallet_SecureStorageReset_PasscodeText: String { return self._s[2744]! } + public var PasscodeSettings_4DigitCode: String { return self._s[2746]! } + public var Settings_FAQ: String { return self._s[2748]! } + public var AutoDownloadSettings_DocumentsTitle: String { return self._s[2749]! } + public var Conversation_ContextMenuForward: String { return self._s[2750]! } + public var VoiceOver_Chat_YourPhoto: String { return self._s[2753]! } + public var PrivacyPolicy_Title: String { return self._s[2756]! } + public var Notifications_TextTone: String { return self._s[2757]! } + public var Profile_CreateNewContact: String { return self._s[2758]! } + public var PrivacyPhoneNumberSettings_WhoCanSeeMyPhoneNumber: String { return self._s[2759]! } + public var TwoFactorSetup_EmailVerification_Title: String { return self._s[2761]! } + public var Call_Speaker: String { return self._s[2762]! } + public var AutoNightTheme_AutomaticSection: String { return self._s[2763]! } + public var Channel_OwnershipTransfer_EnterPassword: String { return self._s[2765]! } + public var Channel_Username_InvalidCharacters: String { return self._s[2766]! } + public func Channel_AdminLog_MessageChangedChannelUsername(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2767]!, self._r[2767]!, [_0]) + } + public var AutoDownloadSettings_AutodownloadFiles: String { return self._s[2768]! } + public var PrivacySettings_LastSeenTitle: String { return self._s[2769]! } + public var Channel_AdminLog_CanInviteUsers: String { return self._s[2770]! } + public var SettingsSearch_Synonyms_Privacy_Data_ClearPaymentsInfo: String { return self._s[2771]! } + public var OwnershipTransfer_SecurityCheck: String { return self._s[2772]! } + public var Conversation_MessageDeliveryFailed: String { return self._s[2773]! } + public var Watch_ChatList_NoConversationsText: String { return self._s[2774]! } + public var Bot_Unblock: String { return self._s[2775]! } + public var TextFormat_Italic: String { return self._s[2776]! } + public var WallpaperSearch_ColorPink: String { return self._s[2777]! } + public var Settings_About_Help: String { return self._s[2779]! } + public var SearchImages_Title: String { return self._s[2780]! } + public var Weekday_Wednesday: String { return self._s[2781]! } + public var Conversation_ClousStorageInfo_Description1: String { return self._s[2782]! } + public var ExplicitContent_AlertTitle: String { return self._s[2783]! } + public func Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2784]!, self._r[2784]!, [_1, _2, _3]) + } + public var Channel_DiscussionGroup_Create: String { return self._s[2785]! } + public var Weekday_Thursday: String { return self._s[2786]! } + public var Channel_BanUser_PermissionChangeGroupInfo: String { return self._s[2787]! } + public var Channel_Members_AddMembersHelp: String { return self._s[2788]! } + public func Checkout_SavePasswordTimeout(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2789]!, self._r[2789]!, [_0]) + } + public var Channel_DiscussionGroup_LinkGroup: String { return self._s[2790]! } + public var SettingsSearch_Synonyms_Notifications_InAppNotificationsVibrate: String { return self._s[2791]! } + public var Passport_RequestedInformation: String { return self._s[2792]! } + public var Login_PhoneAndCountryHelp: String { return self._s[2793]! } + public var Conversation_EncryptionProcessing: String { return self._s[2795]! } + public var Notifications_PermissionsSuppressWarningTitle: String { return self._s[2796]! } + public var PhotoEditor_EnhanceTool: String { return self._s[2798]! } + public var Channel_Setup_Title: String { return self._s[2799]! } + public var PeerInfo_PaneVoiceAndVideo: String { return self._s[2800]! } + public var Conversation_SearchPlaceholder: String { return self._s[2801]! } + public var OldChannels_GroupEmptyFormat: String { return self._s[2802]! } + public var AccessDenied_LocationAlwaysDenied: String { return self._s[2803]! } + public var Checkout_ErrorGeneric: String { return self._s[2804]! } + public var Passport_Language_hu: String { return self._s[2805]! } + public var GroupPermission_EditingDisabled: String { return self._s[2806]! } + public var Wallet_Month_ShortSeptember: String { return self._s[2808]! } + public func Passport_Identity_UploadOneOfScan(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2809]!, self._r[2809]!, [_0]) + } + public func PUSH_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2812]!, self._r[2812]!, [_1]) + } + public var ChatList_DeleteSavedMessagesConfirmationTitle: String { return self._s[2813]! } + public func UserInfo_BlockConfirmationTitle(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2814]!, self._r[2814]!, [_0]) + } + public var Conversation_CloudStorageInfo_Title: String { return self._s[2815]! } + public var Group_Location_Info: String { return self._s[2816]! } + public var PhotoEditor_CropAspectRatioSquare: String { return self._s[2817]! } + public var Permissions_PeopleNearbyAllow_v0: String { return self._s[2818]! } + public func Notification_Exceptions_MutedUntil(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2820]!, self._r[2820]!, [_0]) + } + public var Conversation_ClearPrivateHistory: String { return self._s[2821]! } + public var ContactInfo_PhoneLabelHome: String { return self._s[2822]! } + public var Appearance_RemoveThemeConfirmation: String { return self._s[2823]! } + public var PrivacySettings_LastSeenContacts: String { return self._s[2824]! } + public func ChangePhone_ErrorOccupied(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2825]!, self._r[2825]!, [_0]) + } + public var Cache_MaximumCacheSizeHelp: String { return self._s[2826]! } + public func Notification_PinnedQuizMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2827]!, self._r[2827]!, [_0]) + } + public var Passport_Language_cs: String { return self._s[2828]! } + public var Message_PinnedAnimationMessage: String { return self._s[2830]! } + public var Passport_Identity_ReverseSideHelp: String { return self._s[2832]! } + public var SettingsSearch_Synonyms_Data_Storage_Title: String { return self._s[2833]! } + public var Wallet_Info_TransactionTo: String { return self._s[2835]! } + public var Stats_ViewsBySourceTitle: String { return self._s[2836]! } + public var ChatList_DeleteForEveryoneConfirmationText: String { return self._s[2837]! } + public var SettingsSearch_Synonyms_Privacy_PasscodeAndTouchId: String { return self._s[2838]! } + public var Embed_PlayingInPIP: String { return self._s[2839]! } + public var Appearance_ThemePreview_Chat_3_TextWithLink: String { return self._s[2840]! } + public var AutoNightTheme_ScheduleSection: String { return self._s[2841]! } + public var Stats_GroupMessages: String { return self._s[2842]! } + public func Call_EmojiDescription(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2843]!, self._r[2843]!, [_0]) + } + public var MediaPicker_LivePhotoDescription: String { return self._s[2844]! } + public func Channel_AdminLog_MessageRestrictedName(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2845]!, self._r[2845]!, [_1]) + } + public var Notification_PaymentSent: String { return self._s[2846]! } + public var PhotoEditor_CurvesGreen: String { return self._s[2847]! } + public var Notification_Exceptions_PreviewAlwaysOff: String { return self._s[2848]! } + public var AutoNightTheme_System: String { return self._s[2849]! } + public var SaveIncomingPhotosSettings_Title: String { return self._s[2850]! } + public var CreatePoll_QuizTitle: String { return self._s[2851]! } + public var NotificationSettings_ShowNotificationsAllAccounts: String { return self._s[2852]! } + public var VoiceOver_Chat_PagePreview: String { return self._s[2853]! } + public func PUSH_MESSAGE_SCREENSHOT(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2856]!, self._r[2856]!, [_1]) + } + public func PUSH_MESSAGE_PHOTO_SECRET(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2857]!, self._r[2857]!, [_1]) + } + public func ApplyLanguage_UnsufficientDataText(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2858]!, self._r[2858]!, [_1]) + } + public var NetworkUsageSettings_CallDataSection: String { return self._s[2860]! } + public var PasscodeSettings_HelpTop: String { return self._s[2861]! } + public var Conversation_WalletRequiredTitle: String { return self._s[2862]! } + public var PeerInfo_AddToContacts: String { return self._s[2863]! } + public var Group_OwnershipTransfer_ErrorAdminsTooMuch: String { return self._s[2864]! } + public var Passport_Address_TypeRentalAgreement: String { return self._s[2865]! } + public var FeaturedStickers_OtherSection: String { return self._s[2866]! } + public var EditTheme_ShortLink: String { return self._s[2868]! } + public var Theme_Colors_ColorWallpaperWarning: String { return self._s[2869]! } + public var ProxyServer_VoiceOver_Active: String { return self._s[2870]! } + public var ReportPeer_ReasonOther_Placeholder: String { return self._s[2871]! } + public var CheckoutInfo_ErrorPhoneInvalid: String { return self._s[2872]! } + public var Call_Accept: String { return self._s[2874]! } + public var GroupRemoved_RemoveInfo: String { return self._s[2875]! } + public var Month_GenMarch: String { return self._s[2877]! } + public var PhotoEditor_ShadowsTool: String { return self._s[2878]! } + public var LoginPassword_Title: String { return self._s[2879]! } + public var Call_End: String { return self._s[2880]! } + public var Watch_Conversation_GroupInfo: String { return self._s[2881]! } + public var VoiceOver_Chat_Contact: String { return self._s[2882]! } + public var EditTheme_Create_Preview_IncomingText: String { return self._s[2883]! } + public var CallSettings_Always: String { return self._s[2884]! } + public var CallFeedback_Success: String { return self._s[2885]! } + public var TwoStepAuth_SetupHint: String { return self._s[2886]! } + public func AddContact_ContactWillBeSharedAfterMutual(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2887]!, self._r[2887]!, [_1]) + } + public var ConversationProfile_UsersTooMuchError: String { return self._s[2888]! } + public var PeerInfo_ButtonAddMember: String { return self._s[2889]! } + public var Login_PhoneTitle: String { return self._s[2890]! } + public var Passport_FieldPhoneHelp: String { return self._s[2891]! } + public var Weekday_ShortSunday: String { return self._s[2892]! } + public var Passport_InfoFAQ_URL: String { return self._s[2893]! } + public var ContactInfo_Job: String { return self._s[2895]! } + public var UserInfo_InviteBotToGroup: String { return self._s[2896]! } + public var Appearance_ThemeCarouselNightBlue: String { return self._s[2897]! } + public var CreatePoll_QuizTip: String { return self._s[2898]! } + public var TwoFactorSetup_Email_Text: String { return self._s[2899]! } + public var TwoStepAuth_PasswordRemovePassportConfirmation: String { return self._s[2900]! } + public var Invite_ChannelsTooMuch: String { return self._s[2901]! } + public var Wallet_Send_ConfirmationConfirm: String { return self._s[2902]! } + public var Wallet_TransactionInfo_OtherFeeInfo: String { return self._s[2903]! } + public var SettingsSearch_Synonyms_Notifications_InAppNotificationsPreview: String { return self._s[2904]! } + public var Wallet_Receive_AmountText: String { return self._s[2905]! } + public var TwoStepAuth_Disable: String { return self._s[2906]! } + public var Passport_DeletePersonalDetailsConfirmation: String { return self._s[2907]! } + public var CallFeedback_ReasonNoise: String { return self._s[2908]! } + public var Appearance_AppIconDefault: String { return self._s[2910]! } + public var Passport_Identity_AddInternalPassport: String { return self._s[2911]! } + public var MediaPicker_AddCaption: String { return self._s[2912]! } + public var CallSettings_TabIconDescription: String { return self._s[2913]! } + public func VoiceOver_Chat_Caption(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2914]!, self._r[2914]!, [_0]) + } + public var IntentsSettings_SuggestedChatsGroups: String { return self._s[2915]! } + public func Map_SearchNoResultsDescription(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2916]!, self._r[2916]!, [_0]) + } + public var CreatePoll_ExplanationHeader: String { return self._s[2918]! } + public var ChatList_UndoArchiveHiddenTitle: String { return self._s[2919]! } + public var Privacy_GroupsAndChannels_AlwaysAllow: String { return self._s[2920]! } + public var Passport_Identity_TypePersonalDetails: String { return self._s[2921]! } + public var DialogList_SearchSectionRecent: String { return self._s[2922]! } + public var PrivacyPolicy_DeclineMessage: String { return self._s[2923]! } + public var CreatePoll_Anonymous: String { return self._s[2924]! } + public var LogoutOptions_ClearCacheText: String { return self._s[2927]! } + public var Stats_GroupTopInviter_Promote: String { return self._s[2928]! } + public var LastSeen_WithinAWeek: String { return self._s[2929]! } + public var ChannelMembers_GroupAdminsTitle: String { return self._s[2930]! } + public var SettingsSearch_Synonyms_ChatSettings_IntentsSettings: String { return self._s[2932]! } + public var Conversation_CloudStorage_ChatStatus: String { return self._s[2933]! } + public var VoiceOver_Media_PlaybackRateNormal: String { return self._s[2935]! } + public func AddContact_SharedContactExceptionInfo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2936]!, self._r[2936]!, [_0]) + } + public var Passport_Address_TypeResidentialAddress: String { return self._s[2937]! } + public var Conversation_StatusLeftGroup: String { return self._s[2938]! } + public var SocksProxySetup_ProxyDetailsTitle: String { return self._s[2939]! } + public var OwnershipTransfer_Transfer: String { return self._s[2941]! } + public var SettingsSearch_Synonyms_Calls_Title: String { return self._s[2942]! } + public var ProfilePhoto_MainPhoto: String { return self._s[2943]! } + public var GroupPermission_AddSuccess: String { return self._s[2945]! } + public var PhotoEditor_BlurToolRadial: String { return self._s[2947]! } + public var Conversation_ContextMenuCopy: String { return self._s[2948]! } + public var AccessDenied_CallMicrophone: String { return self._s[2949]! } + public func Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2950]!, self._r[2950]!, [_1, _2, _3]) + } + public var Login_InvalidFirstNameError: String { return self._s[2951]! } + public var Notifications_Badge_CountUnreadMessages_InfoOn: String { return self._s[2952]! } + public var Checkout_PaymentMethod_New: String { return self._s[2953]! } + public var ShareMenu_CopyShareLinkGame: String { return self._s[2954]! } + public var PhotoEditor_QualityTool: String { return self._s[2955]! } + public var Login_SendCodeViaSms: String { return self._s[2956]! } + public var SettingsSearch_Synonyms_Privacy_DeleteAccountIfAwayFor: String { return self._s[2957]! } + public var Chat_SlowmodeAttachmentLimitReached: String { return self._s[2958]! } + public var Wallet_Receive_CopyAddress: String { return self._s[2959]! } + public var Login_EmailNotConfiguredError: String { return self._s[2960]! } + public var Stats_GroupTopAdminsTitle: String { return self._s[2961]! } + public var SocksProxySetup_Status: String { return self._s[2962]! } + public var Conversation_ScheduleMessage_SendWhenOnline: String { return self._s[2963]! } + public var PrivacyPolicy_Accept: String { return self._s[2964]! } + public var Notifications_ExceptionsMessagePlaceholder: String { return self._s[2965]! } + public var Appearance_AppIconClassicX: String { return self._s[2966]! } + public func PUSH_CHAT_MESSAGE_TEXT(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2967]!, self._r[2967]!, [_1, _2, _3]) + } + public var OwnershipTransfer_SecurityRequirements: String { return self._s[2968]! } + public var InfoPlist_NSLocationAlwaysUsageDescription: String { return self._s[2970]! } + public var AutoNightTheme_Automatic: String { return self._s[2971]! } + public var Channel_Username_InvalidStartsWithNumber: String { return self._s[2972]! } + public var Privacy_ContactsSyncHelp: String { return self._s[2973]! } + public var Cache_Help: String { return self._s[2974]! } + public var Group_ErrorAccessDenied: String { return self._s[2975]! } + public var Passport_Language_fa: String { return self._s[2976]! } + public var Wallet_Intro_Text: String { return self._s[2977]! } + public var ProfilePhoto_SetMainVideo: String { return self._s[2978]! } + public var Login_ResetAccountProtected_TimerTitle: String { return self._s[2979]! } + public var VoiceOver_Chat_YourVideoMessage: String { return self._s[2980]! } + public var PrivacySettings_LastSeen: String { return self._s[2981]! } + public func DialogList_MultipleTyping(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2982]!, self._r[2982]!, [_0, _1]) + } + public var Wallet_Configuration_Apply: String { return self._s[2986]! } + public var Preview_SaveGif: String { return self._s[2987]! } + public var SettingsSearch_Synonyms_Privacy_TwoStepAuth: String { return self._s[2988]! } + public var Profile_About: String { return self._s[2989]! } + public var Channel_About_Placeholder: String { return self._s[2991]! } + public var Login_InfoTitle: String { return self._s[2992]! } + public func TwoStepAuth_SetupPendingEmail(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2993]!, self._r[2993]!, [_0]) + } + public var EditTheme_Expand_Preview_IncomingReplyText: String { return self._s[2994]! } + public var Watch_Suggestion_CantTalk: String { return self._s[2997]! } + public var ContactInfo_Title: String { return self._s[2998]! } + public var Media_ShareThisVideo: String { return self._s[2999]! } + public var Chat_GenericPsaTooltip: String { return self._s[3000]! } + public var Weekday_ShortFriday: String { return self._s[3001]! } + public var AccessDenied_Contacts: String { return self._s[3003]! } + public var Notification_CallIncomingShort: String { return self._s[3004]! } + public var Group_Setup_TypePublic: String { return self._s[3005]! } + public var Notifications_MessageNotificationsExceptions: String { return self._s[3006]! } + public var Notifications_Badge_IncludeChannels: String { return self._s[3007]! } + public var Settings_EditAccount: String { return self._s[3010]! } + public var Notifications_MessageNotificationsPreview: String { return self._s[3011]! } + public var ConversationProfile_ErrorCreatingConversation: String { return self._s[3012]! } + public var Group_ErrorAddTooMuchBots: String { return self._s[3013]! } + public var Privacy_GroupsAndChannels_CustomShareHelp: String { return self._s[3014]! } + public var Permissions_CellularDataAllowInSettings_v0: String { return self._s[3015]! } + public func Call_RemoteVideoPaused(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3016]!, self._r[3016]!, [_0]) + } + public func Wallet_SecureStorageChanged_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3017]!, self._r[3017]!, [_0]) + } + public var DialogList_Typing: String { return self._s[3018]! } + public var CallFeedback_IncludeLogs: String { return self._s[3020]! } + public var Checkout_Phone: String { return self._s[3022]! } + public var Login_InfoFirstNamePlaceholder: String { return self._s[3025]! } + public var Privacy_Calls_Integration: String { return self._s[3026]! } + public var Notifications_PermissionsAllow: String { return self._s[3027]! } + public var TwoStepAuth_AddHintDescription: String { return self._s[3033]! } + public var Settings_ChatSettings: String { return self._s[3034]! } + public var Conversation_SendingOptionsTooltip: String { return self._s[3035]! } + public func UserInfo_StartSecretChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3037]!, self._r[3037]!, [_0]) + } + public func Channel_AdminLog_MessageInvitedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3038]!, self._r[3038]!, [_1, _2]) + } + public var GroupRemoved_DeleteUser: String { return self._s[3040]! } + public func Channel_AdminLog_PollStopped(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3041]!, self._r[3041]!, [_0]) + } + public var ChatListFolder_CategoryMuted: String { return self._s[3042]! } + public func PUSH_MESSAGE_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3043]!, self._r[3043]!, [_1]) + } + public var Login_ContinueWithLocalization: String { return self._s[3044]! } + public var Watch_Message_ForwardedFrom: String { return self._s[3045]! } + public var TwoStepAuth_EnterEmailCode: String { return self._s[3047]! } + public var Notification_VideoCallIncoming: String { return self._s[3048]! } + public var Conversation_Unblock: String { return self._s[3049]! } + public var PrivacySettings_DataSettings: String { return self._s[3050]! } + public var WallpaperPreview_PatternPaternApply: String { return self._s[3051]! } + public var Group_PublicLink_Info: String { return self._s[3052]! } + public func Wallet_Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3053]!, self._r[3053]!, [_1, _2, _3]) + } + public var Notifications_InAppNotificationsVibrate: String { return self._s[3054]! } + public func Privacy_GroupsAndChannels_InviteToChannelError(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3055]!, self._r[3055]!, [_0, _1]) + } + public var ChatList_FolderAllChats: String { return self._s[3056]! } + public var OldChannels_ChannelsHeader: String { return self._s[3058]! } + public var Wallet_RestoreFailed_CreateWallet: String { return self._s[3059]! } + public var PrivacySettings_Passcode: String { return self._s[3061]! } + public var Call_Mute: String { return self._s[3062]! } + public var Call_CameraTooltip: String { return self._s[3063]! } + public var Wallet_Weekday_Yesterday: String { return self._s[3064]! } + public var Passport_Language_dz: String { return self._s[3065]! } + public var Wallet_Receive_AmountHeader: String { return self._s[3066]! } + public var Wallet_TransactionInfo_OtherFeeInfoUrl: String { return self._s[3067]! } + public var Passport_Language_tk: String { return self._s[3068]! } + public func Login_EmailCodeSubject(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3069]!, self._r[3069]!, [_0]) + } + public var Settings_Search: String { return self._s[3070]! } + public var Wallet_Month_ShortFebruary: String { return self._s[3071]! } + public var InfoPlist_NSPhotoLibraryUsageDescription: String { return self._s[3072]! } + public var Wallet_Configuration_SourceJSON: String { return self._s[3073]! } + public var Conversation_ContextMenuReply: String { return self._s[3074]! } + public var WallpaperSearch_ColorBrown: String { return self._s[3075]! } + public var Chat_AttachmentMultipleForwardDisabled: String { return self._s[3076]! } + public var Tour_Title1: String { return self._s[3077]! } + public var Wallet_Alert_Cancel: String { return self._s[3078]! } + public var Stats_Total: String { return self._s[3080]! } + public var Conversation_ClearGroupHistory: String { return self._s[3081]! } + public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[3082]! } + public var WallpaperPreview_Motion: String { return self._s[3083]! } + public func Checkout_PasswordEntry_Text(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3084]!, self._r[3084]!, [_0]) + } + public var Wallet_Configuration_ApplyErrorTextJSONInvalidData: String { return self._s[3085]! } + public var Call_RateCall: String { return self._s[3086]! } + public var Channel_AdminLog_BanSendStickersAndGifs: String { return self._s[3087]! } + public var Passport_PasswordCompleteSetup: String { return self._s[3088]! } + public var Conversation_InputTextSilentBroadcastPlaceholder: String { return self._s[3089]! } + public var UserInfo_LastNamePlaceholder: String { return self._s[3091]! } + public func Login_WillCallYou(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3093]!, self._r[3093]!, [_0]) + } + public var Compose_Create: String { return self._s[3094]! } + public var Contacts_InviteToTelegram: String { return self._s[3095]! } + public var GroupInfo_Notifications: String { return self._s[3096]! } + public var ChatList_DeleteSavedMessagesConfirmationAction: String { return self._s[3098]! } + public var Message_PinnedLiveLocationMessage: String { return self._s[3099]! } + public var Month_GenApril: String { return self._s[3100]! } + public var Appearance_AutoNightTheme: String { return self._s[3101]! } + public var ChatSettings_AutomaticAudioDownload: String { return self._s[3103]! } + public var Login_CodeSentSms: String { return self._s[3105]! } + public func UserInfo_UnblockConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3106]!, self._r[3106]!, [_0]) + } + public var EmptyGroupInfo_Line3: String { return self._s[3107]! } + public var LogoutOptions_ContactSupportText: String { return self._s[3108]! } + public var Passport_Language_hr: String { return self._s[3109]! } + public var Common_ActionNotAllowedError: String { return self._s[3110]! } + public func Channel_AdminLog_MessageRestrictedNewSetting(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3111]!, self._r[3111]!, [_0]) + } + public var GroupInfo_InviteLink_CopyLink: String { return self._s[3112]! } + public var Wallet_Info_TransactionFrom: String { return self._s[3113]! } + public var Wallet_Send_ErrorDecryptionFailed: String { return self._s[3114]! } + public var Conversation_InputTextBroadcastPlaceholder: String { return self._s[3115]! } + public var Privacy_SecretChatsTitle: String { return self._s[3116]! } + public var Notification_SecretChatMessageScreenshotSelf: String { return self._s[3118]! } + public var GroupInfo_AddUserLeftError: String { return self._s[3119]! } + public var AutoDownloadSettings_TypePrivateChats: String { return self._s[3120]! } + public var ChatListFolder_NameSectionHeader: String { return self._s[3121]! } + public var LogoutOptions_ContactSupportTitle: String { return self._s[3122]! } + public var Appearance_ThemePreview_Chat_7_Text: String { return self._s[3123]! } + public var Conversation_Unarchive: String { return self._s[3124]! } + public var Channel_AddBotErrorHaveRights: String { return self._s[3125]! } + public var Preview_DeleteGif: String { return self._s[3126]! } + public var GroupInfo_Permissions_Exceptions: String { return self._s[3127]! } + public var Group_ErrorNotMutualContact: String { return self._s[3128]! } + public var Notification_MessageLifetime5s: String { return self._s[3129]! } + public var Wallet_Send_OwnAddressAlertText: String { return self._s[3130]! } + public var OldChannels_ChannelFormat: String { return self._s[3131]! } + public func Watch_LastSeen_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3132]!, self._r[3132]!, [_0]) + } + public var VoiceOver_Chat_Video: String { return self._s[3133]! } + public var Channel_OwnershipTransfer_ErrorPublicChannelsTooMuch: String { return self._s[3135]! } + public var ReportSpam_DeleteThisChat: String { return self._s[3136]! } + public var Passport_Address_AddBankStatement: String { return self._s[3137]! } + public var Notification_CallIncoming: String { return self._s[3138]! } + public var Wallet_Words_NotDoneTitle: String { return self._s[3139]! } + public var Compose_NewGroupTitle: String { return self._s[3140]! } + public var TwoStepAuth_RecoveryCodeHelp: String { return self._s[3142]! } + public var Passport_Address_Postcode: String { return self._s[3144]! } + public func LastSeen_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3145]!, self._r[3145]!, [_0]) + } + public var Checkout_NewCard_SaveInfoHelp: String { return self._s[3146]! } + public var Wallet_Month_ShortOctober: String { return self._s[3147]! } + public var VoiceOver_Chat_YourMusic: String { return self._s[3148]! } + public var WallpaperColors_Title: String { return self._s[3149]! } + public var SocksProxySetup_ShareQRCodeInfo: String { return self._s[3150]! } + public var VoiceOver_MessageContextForward: String { return self._s[3151]! } + public var GroupPermission_Duration: String { return self._s[3152]! } + public func Cache_Clear(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3153]!, self._r[3153]!, [_0]) + } + public var Bot_GroupStatusDoesNotReadHistory: String { return self._s[3154]! } + public var Username_Placeholder: String { return self._s[3155]! } + public var CallFeedback_WhatWentWrong: String { return self._s[3156]! } + public var Passport_FieldAddressUploadHelp: String { return self._s[3157]! } + public var Permissions_NotificationsAllowInSettings_v0: String { return self._s[3158]! } + public func Channel_AdminLog_MessageChangedUnlinkedChannel(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3161]!, self._r[3161]!, [_1, _2]) + } + public var Passport_PasswordDescription: String { return self._s[3162]! } + public var Channel_MessagePhotoUpdated: String { return self._s[3163]! } + public var MediaPicker_TapToUngroupDescription: String { return self._s[3164]! } + public var SettingsSearch_Synonyms_Notifications_BadgeCountUnreadMessages: String { return self._s[3165]! } + public var AttachmentMenu_PhotoOrVideo: String { return self._s[3166]! } + public var Conversation_ContextMenuMore: String { return self._s[3167]! } + public var Privacy_PaymentsClearInfo: String { return self._s[3168]! } + public var CallSettings_TabIcon: String { return self._s[3169]! } + public var KeyCommand_Find: String { return self._s[3170]! } + public var ClearCache_FreeSpaceDescription: String { return self._s[3171]! } + public var Appearance_ThemePreview_ChatList_7_Text: String { return self._s[3172]! } + public var EditTheme_Edit_Preview_IncomingText: String { return self._s[3173]! } + public func Conversation_NoticeInvitedByInChannel(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3174]!, self._r[3174]!, [_0]) + } + public var Message_PinnedGame: String { return self._s[3175]! } + public var VoiceOver_Chat_ForwardedFromYou: String { return self._s[3176]! } + public var Notifications_Badge_CountUnreadMessages_InfoOff: String { return self._s[3178]! } + public var Login_CallRequestState2: String { return self._s[3180]! } + public var CheckoutInfo_ReceiverInfoNamePlaceholder: String { return self._s[3182]! } + public func VoiceOver_Chat_PhotoFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3183]!, self._r[3183]!, [_0]) + } + public func Checkout_PayPrice(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3185]!, self._r[3185]!, [_0]) + } + public var AuthSessions_AddDevice: String { return self._s[3186]! } + public var WallpaperPreview_Blurred: String { return self._s[3187]! } + public var Conversation_InstantPagePreview: String { return self._s[3188]! } + public var PeerInfo_ButtonUnmute: String { return self._s[3189]! } + public func DialogList_SingleUploadingVideoSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3190]!, self._r[3190]!, [_0]) + } + public var ChatList_PeerTypeChannel: String { return self._s[3191]! } + public var SecretTimer_VideoDescription: String { return self._s[3194]! } + public var WallpaperSearch_ColorRed: String { return self._s[3195]! } + public var GroupPermission_NoPinMessages: String { return self._s[3196]! } + public var Passport_Language_es: String { return self._s[3197]! } + public var Permissions_ContactsAllow_v0: String { return self._s[3199]! } + public var Conversation_EditingMessageMediaEditCurrentVideo: String { return self._s[3200]! } + public func PUSH_CHAT_MESSAGE_CONTACT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3201]!, self._r[3201]!, [_1, _2]) + } + public var Privacy_Forwards_CustomHelp: String { return self._s[3202]! } + public var WebPreview_GettingLinkInfo: String { return self._s[3204]! } + public var Watch_UserInfo_Unmute: String { return self._s[3205]! } + public var GroupInfo_ChannelListNamePlaceholder: String { return self._s[3206]! } + public var AccessDenied_CameraRestricted: String { return self._s[3208]! } + public func Conversation_Kilobytes(_ _0: Int) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3209]!, self._r[3209]!, ["\(_0)"]) + } + public var ChatList_ReadAll: String { return self._s[3211]! } + public var Settings_CopyUsername: String { return self._s[3212]! } + public var Contacts_SearchLabel: String { return self._s[3213]! } + public var Map_OpenInYandexNavigator: String { return self._s[3215]! } + public var PasscodeSettings_EncryptData: String { return self._s[3216]! } + public var Settings_Wallet: String { return self._s[3217]! } + public var Group_ErrorSupergroupConversionNotPossible: String { return self._s[3218]! } + public var ChatList_PeerTypeBot: String { return self._s[3219]! } + public var WallpaperSearch_ColorPrefix: String { return self._s[3220]! } + public var Notifications_GroupNotificationsPreview: String { return self._s[3221]! } + public var DialogList_AdNoticeAlert: String { return self._s[3222]! } + public var Wallet_Month_GenMay: String { return self._s[3224]! } + public var CheckoutInfo_ShippingInfoAddress1: String { return self._s[3225]! } + public var CheckoutInfo_ShippingInfoAddress2: String { return self._s[3226]! } + public var Localization_LanguageCustom: String { return self._s[3227]! } + public var Passport_Identity_TypeDriversLicenseUploadScan: String { return self._s[3228]! } + public var CallFeedback_Title: String { return self._s[3229]! } + public var VoiceOver_Chat_RecordPreviewVoiceMessage: String { return self._s[3232]! } + public var Passport_Address_OneOfTypePassportRegistration: String { return self._s[3233]! } + public var Wallet_Intro_CreateErrorTitle: String { return self._s[3234]! } + public var Conversation_InfoGroup: String { return self._s[3235]! } + public var Compose_NewMessage: String { return self._s[3236]! } + public var FastTwoStepSetup_HintPlaceholder: String { return self._s[3237]! } + public var ChatSettings_AutoDownloadVideoMessages: String { return self._s[3238]! } + public var Wallet_SecureStorageReset_BiometryFaceId: String { return self._s[3239]! } + public var Channel_DiscussionGroup_UnlinkChannel: String { return self._s[3240]! } + public func Passport_Scans_ScanIndex(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3241]!, self._r[3241]!, [_0]) + } + public var Channel_AdminLog_CanDeleteMessages: String { return self._s[3242]! } + public var Login_CancelSignUpConfirmation: String { return self._s[3243]! } + public var ChangePhoneNumberCode_Help: String { return self._s[3244]! } + public var PrivacySettings_DeleteAccountHelp: String { return self._s[3245]! } + public var ChatList_Context_RemoveFromFolder: String { return self._s[3246]! } + public var Channel_BlackList_Title: String { return self._s[3247]! } + public var UserInfo_PhoneCall: String { return self._s[3248]! } + public var Passport_Address_OneOfTypeBankStatement: String { return self._s[3250]! } + public var Wallet_Month_ShortJanuary: String { return self._s[3251]! } + public var State_connecting: String { return self._s[3252]! } + public var Appearance_ThemePreview_ChatList_6_Text: String { return self._s[3253]! } + public var Wallet_Month_GenMarch: String { return self._s[3254]! } + public var EditTheme_Expand_BottomInfo: String { return self._s[3255]! } + public var AuthSessions_AddedDeviceTerminate: String { return self._s[3256]! } + public func LastSeen_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3257]!, self._r[3257]!, [_0]) + } + public func DialogList_SingleRecordingAudioSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3258]!, self._r[3258]!, [_0]) + } + public var Notifications_GroupNotifications: String { return self._s[3259]! } + public var Conversation_SendMessageErrorTooMuchScheduled: String { return self._s[3260]! } + public var Passport_Identity_EditPassport: String { return self._s[3261]! } + public var EnterPasscode_RepeatNewPasscode: String { return self._s[3263]! } + public var Localization_EnglishLanguageName: String { return self._s[3264]! } + public var Share_AuthDescription: String { return self._s[3265]! } + public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsAlert: String { return self._s[3266]! } + public var Passport_Identity_Surname: String { return self._s[3267]! } + public var Compose_TokenListPlaceholder: String { return self._s[3268]! } + public var Wallet_AccessDenied_Camera: String { return self._s[3269]! } + public var Passport_Identity_OneOfTypePassport: String { return self._s[3270]! } + public var Settings_AboutEmpty: String { return self._s[3271]! } + public var Conversation_Unmute: String { return self._s[3272]! } + public var CreateGroup_ChannelsTooMuch: String { return self._s[3274]! } + public var Wallet_Sending_Text: String { return self._s[3275]! } + public func PUSH_CONTACT_JOINED(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3276]!, self._r[3276]!, [_1]) + } + public var Login_CodeSentCall: String { return self._s[3277]! } + public var ContactInfo_PhoneLabelHomeFax: String { return self._s[3279]! } + public var ChatSettings_Appearance: String { return self._s[3280]! } + public var ClearCache_StorageUsage: String { return self._s[3281]! } + public var ChatListFolder_NameContacts: String { return self._s[3282]! } + public var Appearance_PickAccentColor: String { return self._s[3284]! } + public func PUSH_CHAT_MESSAGE_NOTEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3285]!, self._r[3285]!, [_1, _2]) + } + public func PUSH_MESSAGE_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3286]!, self._r[3286]!, [_1]) + } + public var Notification_CallMissed: String { return self._s[3287]! } + public var SettingsSearch_Synonyms_Appearance_ChatBackground_Custom: String { return self._s[3288]! } + public var Channel_AdminLogFilter_EventsInfo: String { return self._s[3289]! } + public var Wallet_Month_GenOctober: String { return self._s[3291]! } + public var ChatAdmins_AdminLabel: String { return self._s[3292]! } + public var KeyCommand_JumpToNextChat: String { return self._s[3293]! } + public var Conversation_StopPollConfirmationTitle: String { return self._s[3295]! } + public var ChangePhoneNumberCode_CodePlaceholder: String { return self._s[3296]! } + public var Month_GenJune: String { return self._s[3297]! } + public var IntentsSettings_MainAccountInfo: String { return self._s[3298]! } + public var Watch_Location_Current: String { return self._s[3299]! } + public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[3300]! } + public var Conversation_TitleMute: String { return self._s[3301]! } + public var Map_PlacesInThisArea: String { return self._s[3302]! } + public func PUSH_CHANNEL_MESSAGE_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3303]!, self._r[3303]!, [_1]) + } + public var GroupInfo_DeleteAndExit: String { return self._s[3304]! } + public func Conversation_Moderate_DeleteAllMessages(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3305]!, self._r[3305]!, [_0]) + } + public var Call_ReportPlaceholder: String { return self._s[3306]! } + public var Chat_SlowmodeSendError: String { return self._s[3307]! } + public var MaskStickerSettings_Info: String { return self._s[3308]! } + public var EditTheme_Expand_TopInfo: String { return self._s[3309]! } + public func GroupInfo_AddParticipantConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3310]!, self._r[3310]!, [_0]) + } + public var Checkout_NewCard_PostcodeTitle: String { return self._s[3311]! } + public var Passport_Address_RegionPlaceholder: String { return self._s[3313]! } + public var Contacts_ShareTelegram: String { return self._s[3314]! } + public var EnterPasscode_EnterNewPasscodeNew: String { return self._s[3315]! } + public var Map_AddressOnMap: String { return self._s[3316]! } + public var Channel_ErrorAccessDenied: String { return self._s[3317]! } + public var UserInfo_ScamBotWarning: String { return self._s[3319]! } + public var Stickers_GroupChooseStickerPack: String { return self._s[3320]! } + public var Call_ConnectionErrorTitle: String { return self._s[3321]! } + public var UserInfo_NotificationsEnable: String { return self._s[3322]! } + public var ArchivedChats_IntroText1: String { return self._s[3323]! } + public var Tour_Text4: String { return self._s[3326]! } + public var WallpaperSearch_Recent: String { return self._s[3327]! } + public var GroupInfo_ScamGroupWarning: String { return self._s[3328]! } + public var PeopleNearby_MakeVisibleTitle: String { return self._s[3329]! } + public var Profile_MessageLifetime2s: String { return self._s[3331]! } + public var Appearance_ThemePreview_ChatList_5_Text: String { return self._s[3332]! } + public var Notification_MessageLifetime2s: String { return self._s[3333]! } + public func Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3334]!, self._r[3334]!, [_1, _2, _3]) + } + public var Cache_ClearCache: String { return self._s[3335]! } + public var AutoNightTheme_UpdateLocation: String { return self._s[3336]! } + public var Permissions_NotificationsUnreachableText_v0: String { return self._s[3337]! } + public func Channel_AdminLog_MessageChangedGroupUsername(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3341]!, self._r[3341]!, [_0]) + } + public func Conversation_ShareMyPhoneNumber_StatusSuccess(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3343]!, self._r[3343]!, [_0]) + } + public var LocalGroup_Text: String { return self._s[3344]! } + public var PeerInfo_PaneMembers: String { return self._s[3345]! } + public var Channel_AdminLog_EmptyFilterTitle: String { return self._s[3346]! } + public var SocksProxySetup_TypeSocks: String { return self._s[3347]! } + public var ChatList_UnarchiveAction: String { return self._s[3348]! } + public var AutoNightTheme_Title: String { return self._s[3349]! } + public var InstantPage_FeedbackButton: String { return self._s[3350]! } + public var Passport_FieldAddress: String { return self._s[3351]! } + public func Channel_AdminLog_SetSlowmode(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3352]!, self._r[3352]!, [_1, _2]) + } + public var Month_ShortMarch: String { return self._s[3353]! } + public func PUSH_MESSAGE_INVOICE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3354]!, self._r[3354]!, [_1, _2]) + } + public var SocksProxySetup_UsernamePlaceholder: String { return self._s[3355]! } + public var Conversation_ShareInlineBotLocationConfirmation: String { return self._s[3356]! } + public var Passport_FloodError: String { return self._s[3357]! } + public var SecretGif_Title: String { return self._s[3358]! } + public var NotificationSettings_ShowNotificationsAllAccountsInfoOn: String { return self._s[3359]! } + public var ChatList_Context_UnhideArchive: String { return self._s[3360]! } + public var Passport_Language_th: String { return self._s[3362]! } + public var Passport_Address_Address: String { return self._s[3363]! } + public var Login_InvalidLastNameError: String { return self._s[3364]! } + public var Notifications_InAppNotificationsPreview: String { return self._s[3365]! } + public var Notifications_PermissionsUnreachableTitle: String { return self._s[3366]! } + public var ChatList_Context_Archive: String { return self._s[3367]! } + public var SettingsSearch_FAQ: String { return self._s[3368]! } + public var ShareMenu_Send: String { return self._s[3369]! } + public var ChatState_Connecting: String { return self._s[3370]! } + public var WallpaperSearch_ColorYellow: String { return self._s[3372]! } + public var Month_GenNovember: String { return self._s[3374]! } + public var SettingsSearch_Synonyms_Appearance_LargeEmoji: String { return self._s[3376]! } + public func Conversation_ShareMyPhoneNumberConfirmation(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3377]!, self._r[3377]!, [_1, _2]) + } + public var ChatListFolder_CategoryChannels: String { return self._s[3378]! } + public var Conversation_SwipeToReplyHintText: String { return self._s[3379]! } + public var Checkout_Email: String { return self._s[3380]! } + public var NotificationsSound_Tritone: String { return self._s[3381]! } + public var Paint_Marker: String { return self._s[3383]! } + public var StickerPacksSettings_ManagingHelp: String { return self._s[3385]! } + public var Wallet_ContextMenuCopy: String { return self._s[3387]! } + public func Wallet_Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3389]!, self._r[3389]!, [_1, _2, _3]) + } + public var Appearance_TextSize_Automatic: String { return self._s[3390]! } + public var Stickers_Installed: String { return self._s[3392]! } + public func PUSH_PINNED_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3393]!, self._r[3393]!, [_1]) + } + public func StickerPackActionInfo_AddedText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3394]!, self._r[3394]!, [_0]) + } + public var ChangePhoneNumberNumber_Help: String { return self._s[3395]! } + public func Checkout_LiabilityAlert(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3396]!, self._r[3396]!, [_1, _1, _1, _2]) + } + public var ChatList_UndoArchiveTitle: String { return self._s[3397]! } + public var Notification_Exceptions_Add: String { return self._s[3398]! } + public var DialogList_You: String { return self._s[3399]! } + public var ChatList_PsaLabel_covid: String { return self._s[3401]! } + public var MediaPicker_Send: String { return self._s[3403]! } + public var SettingsSearch_Synonyms_Stickers_Title: String { return self._s[3404]! } + public var Appearance_ThemePreview_ChatList_4_Text: String { return self._s[3405]! } + public var Call_AudioRouteSpeaker: String { return self._s[3406]! } + public var Watch_UserInfo_Title: String { return self._s[3407]! } + public var VoiceOver_Chat_PollFinalResults: String { return self._s[3408]! } + public var Appearance_AccentColor: String { return self._s[3410]! } + public func Login_EmailPhoneSubject(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3411]!, self._r[3411]!, [_0]) + } + public var Permissions_ContactsAllowInSettings_v0: String { return self._s[3412]! } + public func PUSH_CHANNEL_MESSAGE_GAME(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3413]!, self._r[3413]!, [_1, _2]) + } + public var Conversation_ClousStorageInfo_Description2: String { return self._s[3414]! } + public var WebSearch_RecentClearConfirmation: String { return self._s[3415]! } + public var Notification_CallOutgoing: String { return self._s[3416]! } + public var PrivacySettings_PasscodeAndFaceId: String { return self._s[3417]! } + public var Channel_DiscussionGroup_MakeHistoryPublic: String { return self._s[3418]! } + public var Call_RecordingDisabledMessage: String { return self._s[3419]! } + public var Message_Game: String { return self._s[3420]! } + public var Conversation_PressVolumeButtonForSound: String { return self._s[3421]! } + public var PrivacyLastSeenSettings_CustomHelp: String { return self._s[3422]! } + public var Channel_DiscussionGroup_PrivateGroup: String { return self._s[3423]! } + public var Channel_EditAdmin_PermissionAddAdmins: String { return self._s[3424]! } + public var Date_DialogDateFormat: String { return self._s[3426]! } + public var WallpaperColors_SetCustomColor: String { return self._s[3427]! } + public var Notifications_InAppNotifications: String { return self._s[3428]! } + public func Channel_Management_RemovedBy(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3429]!, self._r[3429]!, [_0]) + } + public func Settings_ApplyProxyAlert(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3430]!, self._r[3430]!, [_1, _2]) + } + public var NewContact_Title: String { return self._s[3431]! } + public func AutoDownloadSettings_UpToForAll(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3432]!, self._r[3432]!, [_0]) + } + public var Stats_GroupTopPoster_Promote: String { return self._s[3433]! } + public var Conversation_ViewContactDetails: String { return self._s[3434]! } + public func PUSH_CHANNEL_MESSAGE_CONTACT(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3436]!, self._r[3436]!, [_1]) + } + public var Checkout_NewCard_CardholderNameTitle: String { return self._s[3437]! } + public var Passport_Identity_ExpiryDateNone: String { return self._s[3438]! } + public var PrivacySettings_Title: String { return self._s[3439]! } + public var Conversation_SilentBroadcastTooltipOff: String { return self._s[3442]! } + public var GroupRemoved_UsersSectionTitle: String { return self._s[3443]! } + public var VoiceOver_Chat_ContactEmail: String { return self._s[3444]! } + public var Contacts_PhoneNumber: String { return self._s[3445]! } + public var PeerInfo_ButtonMute: String { return self._s[3446]! } + public var TwoFactorSetup_Password_PlaceholderConfirmPassword: String { return self._s[3448]! } + public var Map_ShowPlaces: String { return self._s[3449]! } + public var ChatAdmins_Title: String { return self._s[3450]! } + public var InstantPage_Reference: String { return self._s[3452]! } + public var Wallet_Info_Updating: String { return self._s[3453]! } + public var ReportGroupLocation_Text: String { return self._s[3454]! } + public func PUSH_CHAT_MESSAGE_FWD(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3455]!, self._r[3455]!, [_1, _2]) + } + public var Camera_FlashOff: String { return self._s[3456]! } + public var Watch_UserInfo_Block: String { return self._s[3457]! } + public var ChatSettings_Stickers: String { return self._s[3458]! } + public var ChatSettings_DownloadInBackground: String { return self._s[3459]! } + public var Appearance_ThemeCarouselTintedNight: String { return self._s[3460]! } + public func UserInfo_BlockConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3461]!, self._r[3461]!, [_0]) + } + public var Settings_ViewPhoto: String { return self._s[3462]! } + public var Login_CheckOtherSessionMessages: String { return self._s[3463]! } + public var AutoDownloadSettings_Cellular: String { return self._s[3464]! } + public var Wallet_Created_ExportErrorTitle: String { return self._s[3465]! } + public var SettingsSearch_Synonyms_Notifications_GroupNotificationsExceptions: String { return self._s[3466]! } + public var VoiceOver_MessageContextShare: String { return self._s[3467]! } + public func Target_InviteToGroupConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3469]!, self._r[3469]!, [_0]) + } + public var Privacy_DeleteDrafts: String { return self._s[3470]! } + public var Wallpaper_SetCustomBackgroundInfo: String { return self._s[3471]! } + public func LastSeen_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3472]!, self._r[3472]!, [_0]) + } + public var DialogList_SavedMessagesHelp: String { return self._s[3473]! } + public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[3474]! } + public var DialogList_SavedMessages: String { return self._s[3475]! } + public var GroupInfo_UpgradeButton: String { return self._s[3476]! } + public var Appearance_ThemePreview_ChatList_3_Text: String { return self._s[3478]! } + public var DialogList_Pin: String { return self._s[3479]! } + public func ForwardedAuthors2(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3480]!, self._r[3480]!, [_0, _1]) + } + public func Login_PhoneGenericEmailSubject(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3481]!, self._r[3481]!, [_0]) + } + public var Notification_Exceptions_AlwaysOn: String { return self._s[3482]! } + public var UserInfo_NotificationsDisable: String { return self._s[3483]! } + public var Conversation_UnarchiveDone: String { return self._s[3484]! } + public var Conversation_ContextMenuCancelEditing: String { return self._s[3485]! } + public var Paint_Outlined: String { return self._s[3486]! } + public var Activity_PlayingGame: String { return self._s[3487]! } + public var SearchImages_NoImagesFound: String { return self._s[3488]! } + public var SocksProxySetup_ProxyType: String { return self._s[3489]! } + public var AppleWatch_ReplyPresetsHelp: String { return self._s[3491]! } + public var Conversation_ContextMenuCancelSending: String { return self._s[3492]! } + public var Settings_AppLanguage: String { return self._s[3493]! } + public var TwoStepAuth_ResetAccountHelp: String { return self._s[3494]! } + public var Common_ChoosePhoto: String { return self._s[3495]! } + public var AuthSessions_AddDevice_InvalidQRCode: String { return self._s[3496]! } + public var CallFeedback_ReasonEcho: String { return self._s[3497]! } + public func PUSH_PINNED_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3498]!, self._r[3498]!, [_1]) + } + public var Privacy_Calls_AlwaysAllow: String { return self._s[3499]! } + public var PollResults_Collapse: String { return self._s[3500]! } + public var Activity_UploadingVideo: String { return self._s[3501]! } + public var Conversation_WalletRequiredNotNow: String { return self._s[3502]! } + public var ChannelInfo_DeleteChannelConfirmation: String { return self._s[3503]! } + public var NetworkUsageSettings_Wifi: String { return self._s[3504]! } + public var VoiceOver_Editing_ClearText: String { return self._s[3505]! } + public var PUSH_SENDER_YOU: String { return self._s[3506]! } + public var Channel_BanUser_PermissionReadMessages: String { return self._s[3507]! } + public var Checkout_PayWithTouchId: String { return self._s[3508]! } + public var Wallpaper_ResetWallpapersConfirmation: String { return self._s[3509]! } + public func PUSH_LOCKED_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3511]!, self._r[3511]!, [_1]) + } + public var Notifications_ExceptionsNone: String { return self._s[3512]! } + public func Message_ForwardedMessageShort(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3513]!, self._r[3513]!, [_0]) + } + public func PUSH_PINNED_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3514]!, self._r[3514]!, [_1]) + } + public var AuthSessions_IncompleteAttempts: String { return self._s[3516]! } + public var Passport_Address_Region: String { return self._s[3519]! } + public var ChatList_DeleteChat: String { return self._s[3520]! } + public var LogoutOptions_ClearCacheTitle: String { return self._s[3521]! } + public var PhotoEditor_TiltShift: String { return self._s[3522]! } + public var Settings_FAQ_URL: String { return self._s[3523]! } + public var TwoFactorSetup_EmailVerification_ChangeAction: String { return self._s[3524]! } + public var SharedMedia_TitleLink: String { return self._s[3527]! } + public var Settings_PrivacySettings: String { return self._s[3528]! } + public var Passport_Identity_TypePassportUploadScan: String { return self._s[3529]! } + public var Passport_Language_sl: String { return self._s[3530]! } + public var Settings_SetProfilePhoto: String { return self._s[3531]! } + public var Channel_About_Help: String { return self._s[3532]! } + public var Contacts_PermissionsEnable: String { return self._s[3533]! } + public var Wallet_Sending_Title: String { return self._s[3534]! } + public var PeerInfo_PaneMedia: String { return self._s[3535]! } + public var SettingsSearch_Synonyms_Notifications_GroupNotificationsAlert: String { return self._s[3536]! } + public var AttachmentMenu_SendAsFiles: String { return self._s[3537]! } + public var CallFeedback_ReasonInterruption: String { return self._s[3539]! } + public var Passport_Address_AddTemporaryRegistration: String { return self._s[3540]! } + public var AutoDownloadSettings_AutodownloadVideos: String { return self._s[3541]! } + public var ChatSettings_AutoDownloadSettings_Delimeter: String { return self._s[3542]! } + public var OldChannels_Title: String { return self._s[3543]! } + public var PrivacySettings_DeleteAccountTitle: String { return self._s[3544]! } + public var AccessDenied_VideoMessageCamera: String { return self._s[3546]! } + public var Map_OpenInYandexMaps: String { return self._s[3548]! } + public var CreateGroup_ErrorLocatedGroupsTooMuch: String { return self._s[3549]! } + public var VoiceOver_MessageContextReply: String { return self._s[3550]! } + public var ChatListFolder_DiscardConfirmation: String { return self._s[3552]! } + public var PhotoEditor_SaturationTool: String { return self._s[3553]! } + public func PUSH_MESSAGE_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3554]!, self._r[3554]!, [_1, _2]) + } + public var PrivacyPhoneNumberSettings_CustomHelp: String { return self._s[3555]! } + public var Notification_Exceptions_NewException_NotificationHeader: String { return self._s[3556]! } + public var Group_OwnershipTransfer_ErrorLocatedGroupsTooMuch: String { return self._s[3557]! } + public func LOCAL_MESSAGE_FWDS(_ _1: String, _ _2: Int) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3558]!, self._r[3558]!, [_1, "\(_2)"]) + } + public var Appearance_ThemePreview_ChatList_2_Text: String { return self._s[3559]! } + public var Channel_Username_InvalidTooShort: String { return self._s[3561]! } + public var SettingsSearch_Synonyms_Wallet: String { return self._s[3562]! } + public func Group_OwnershipTransfer_DescriptionInfo(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3563]!, self._r[3563]!, [_1, _2]) + } + public var Forward_ErrorPublicPollDisabledInChannels: String { return self._s[3564]! } + public func PUSH_CHAT_MESSAGE_GAME(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3565]!, self._r[3565]!, [_1, _2, _3]) + } + public var WallpaperPreview_PatternTitle: String { return self._s[3566]! } + public var GroupInfo_PublicLinkAdd: String { return self._s[3567]! } + public var Passport_PassportInformation: String { return self._s[3570]! } + public var Theme_Unsupported: String { return self._s[3571]! } + public var WatchRemote_AlertTitle: String { return self._s[3572]! } + public var Privacy_GroupsAndChannels_NeverAllow: String { return self._s[3573]! } + public var ConvertToSupergroup_HelpText: String { return self._s[3575]! } + public func Time_MonthOfYear_m7(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3576]!, self._r[3576]!, [_0]) + } + public func PUSH_PHONE_CALL_REQUEST(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3577]!, self._r[3577]!, [_1]) + } + public var Privacy_GroupsAndChannels_CustomHelp: String { return self._s[3578]! } + public var Wallet_Navigation_Done: String { return self._s[3580]! } + public var TwoStepAuth_RecoveryCodeInvalid: String { return self._s[3581]! } + public var AccessDenied_CameraDisabled: String { return self._s[3582]! } + public func Channel_Username_UsernameIsAvailable(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3583]!, self._r[3583]!, [_0]) + } + public var ClearCache_Forever: String { return self._s[3584]! } + public var AuthSessions_AddDeviceIntro_Title: String { return self._s[3585]! } + public var CreatePoll_Quiz: String { return self._s[3586]! } + public var PhotoEditor_ContrastTool: String { return self._s[3589]! } + public func PUSH_PINNED_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3590]!, self._r[3590]!, [_1]) + } + public var DialogList_Draft: String { return self._s[3591]! } + public var Wallet_Configuration_BlockchainIdInfo: String { return self._s[3592]! } + public func PeopleNearby_VisibleUntil(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3593]!, self._r[3593]!, [_0]) + } + public var ChatList_PsaAlert_covid: String { return self._s[3594]! } + public var Privacy_TopPeersDelete: String { return self._s[3596]! } + public var LoginPassword_PasswordPlaceholder: String { return self._s[3597]! } + public var Passport_Identity_TypeIdentityCardUploadScan: String { return self._s[3598]! } + public var WebSearch_RecentSectionClear: String { return self._s[3599]! } + public var EditTheme_ErrorInvalidCharacters: String { return self._s[3600]! } + public var Watch_ChatList_NoConversationsTitle: String { return self._s[3602]! } + public var PeerInfo_ButtonMore: String { return self._s[3604]! } + public var Common_Done: String { return self._s[3605]! } + public var Shortcut_SwitchAccount: String { return self._s[3606]! } + public var AuthSessions_EmptyText: String { return self._s[3607]! } + public var Wallet_Configuration_BlockchainNameChangedTitle: String { return self._s[3608]! } + public var Conversation_ShareBotContactConfirmation: String { return self._s[3609]! } + public var Tour_Title5: String { return self._s[3611]! } + public var Wallet_Settings_Title: String { return self._s[3612]! } + public func Map_DirectionsDriveEta(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3613]!, self._r[3613]!, [_0]) + } + public var ApplyLanguage_UnsufficientDataTitle: String { return self._s[3614]! } + public var Conversation_LinkDialogSave: String { return self._s[3615]! } + public var GroupInfo_ActionRestrict: String { return self._s[3616]! } + public var Checkout_Title: String { return self._s[3618]! } + public var Channel_DiscussionGroup_HeaderLabel: String { return self._s[3620]! } + public var Channel_AdminLog_CanChangeInfo: String { return self._s[3622]! } + public var Notification_RenamedGroup: String { return self._s[3623]! } + public var PeopleNearby_Groups: String { return self._s[3624]! } + public var Checkout_PayWithFaceId: String { return self._s[3625]! } + public var Channel_BanList_BlockedTitle: String { return self._s[3626]! } + public var SettingsSearch_Synonyms_Notifications_InAppNotificationsSound: String { return self._s[3628]! } + public var Checkout_WebConfirmation_Title: String { return self._s[3629]! } + public var Notifications_MessageNotificationsAlert: String { return self._s[3630]! } + public func Activity_RemindAboutGroup(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3631]!, self._r[3631]!, [_0]) + } + public var Stats_GroupGrowthTitle: String { return self._s[3632]! } + public var Profile_AddToExisting: String { return self._s[3634]! } + public func Profile_CreateEncryptedChatOutdatedError(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3635]!, self._r[3635]!, [_0, _1]) + } + public var Cache_Files: String { return self._s[3637]! } + public var Permissions_PrivacyPolicy: String { return self._s[3639]! } + public var SocksProxySetup_ConnectAndSave: String { return self._s[3640]! } + public var UserInfo_NotificationsDefaultDisabled: String { return self._s[3641]! } + public var AutoDownloadSettings_TypeContacts: String { return self._s[3643]! } + public var Appearance_ThemePreview_ChatList_1_Text: String { return self._s[3645]! } + public var Calls_NoCallsPlaceholder: String { return self._s[3646]! } + public func Wallet_Receive_ShareInvoiceUrlInfo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3647]!, self._r[3647]!, [_0]) + } + public var Channel_Username_RevokeExistingUsernamesInfo: String { return self._s[3648]! } + public var VoiceOver_AttachMedia: String { return self._s[3651]! } + public var Notifications_ExceptionsGroupPlaceholder: String { return self._s[3652]! } + public func PUSH_CHAT_MESSAGE_INVOICE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3653]!, self._r[3653]!, [_1, _2, _3]) + } + public var SettingsSearch_Synonyms_Notifications_GroupNotificationsSound: String { return self._s[3654]! } + public var Conversation_SetReminder_Title: String { return self._s[3655]! } + public var Passport_FieldAddressHelp: String { return self._s[3656]! } + public var Privacy_GroupsAndChannels_InviteToChannelMultipleError: String { return self._s[3657]! } + public var PUSH_REMINDER_TITLE: String { return self._s[3658]! } + public func Login_TermsOfService_ProceedBot(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3659]!, self._r[3659]!, [_0]) + } + public var Channel_AdminLog_EmptyTitle: String { return self._s[3660]! } + public var Privacy_Calls_NeverAllow_Title: String { return self._s[3661]! } + public var Login_UnknownError: String { return self._s[3662]! } + public var Group_UpgradeNoticeText2: String { return self._s[3665]! } + public var Watch_Compose_AddContact: String { return self._s[3666]! } + public var ClearCache_StorageServiceFiles: String { return self._s[3667]! } + public var Web_Error: String { return self._s[3668]! } + public var Paint_Neon: String { return self._s[3669]! } + public var Gif_Search: String { return self._s[3670]! } + public var Profile_MessageLifetime1h: String { return self._s[3671]! } + public var CheckoutInfo_ReceiverInfoEmailPlaceholder: String { return self._s[3672]! } + public var Channel_Username_CheckingUsername: String { return self._s[3673]! } + public var CallFeedback_ReasonSilentRemote: String { return self._s[3674]! } + public var AutoDownloadSettings_TypeChannels: String { return self._s[3675]! } + public var Channel_AboutItem: String { return self._s[3676]! } + public var Privacy_GroupsAndChannels_AlwaysAllow_Placeholder: String { return self._s[3679]! } + public var VoiceOver_Chat_VoiceMessage: String { return self._s[3680]! } + public var GroupInfo_SharedMedia: String { return self._s[3681]! } + public func Channel_AdminLog_MessagePromotedName(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3682]!, self._r[3682]!, [_1]) + } + public var Call_PhoneCallInProgressMessage: String { return self._s[3683]! } + public func PUSH_CHANNEL_ALBUM(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3684]!, self._r[3684]!, [_1]) + } + public var ChatList_UndoArchiveRevealedText: String { return self._s[3685]! } + public var GroupInfo_InviteLink_RevokeAlert_Text: String { return self._s[3686]! } + public var Conversation_SearchByName_Placeholder: String { return self._s[3687]! } + public var CreatePoll_AddOption: String { return self._s[3688]! } + public var GroupInfo_Permissions_SearchPlaceholder: String { return self._s[3689]! } + public var Group_UpgradeNoticeHeader: String { return self._s[3690]! } + public var Channel_Management_AddModerator: String { return self._s[3691]! } + public var AutoDownloadSettings_MaxFileSize: String { return self._s[3692]! } + public var StickerPacksSettings_ShowStickersButton: String { return self._s[3693]! } + public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[3694]! } + public var Theme_Colors_Background: String { return self._s[3695]! } + public var NotificationsSound_Hello: String { return self._s[3698]! } + public var SocksProxySetup_SavedProxies: String { return self._s[3700]! } + public var Channel_Stickers_Placeholder: String { return self._s[3702]! } + public func Login_EmailCodeBody(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3703]!, self._r[3703]!, [_0]) + } + public var PrivacyPolicy_DeclineDeclineAndDelete: String { return self._s[3704]! } + public var Channel_Management_AddModeratorHelp: String { return self._s[3705]! } + public var ContactInfo_BirthdayLabel: String { return self._s[3706]! } + public var ChangePhoneNumberCode_RequestingACall: String { return self._s[3707]! } + public var AutoDownloadSettings_Channels: String { return self._s[3708]! } + public var Passport_Language_mn: String { return self._s[3709]! } + public var Settings_ChatFolders: String { return self._s[3710]! } + public func ChatList_AddedToFolderTooltip(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3711]!, self._r[3711]!, [_1, _2]) + } + public var Notifications_ResetAllNotificationsHelp: String { return self._s[3714]! } + public var GroupInfo_Permissions_SlowmodeValue_Off: String { return self._s[3715]! } + public var Passport_Language_ja: String { return self._s[3717]! } + public var Settings_About_Title: String { return self._s[3718]! } + public var Settings_NotificationsAndSounds: String { return self._s[3719]! } + public var ChannelInfo_DeleteGroup: String { return self._s[3720]! } + public var Settings_BlockedUsers: String { return self._s[3721]! } + public func Time_MonthOfYear_m4(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3722]!, self._r[3722]!, [_0]) + } + public var EditTheme_Create_Preview_OutgoingText: String { return self._s[3723]! } + public var Wallet_Weekday_Today: String { return self._s[3724]! } + public var ChatListFolderSettings_AddRecommended: String { return self._s[3725]! } + public var AutoDownloadSettings_PreloadVideo: String { return self._s[3726]! } + public var Widget_ApplicationLocked: String { return self._s[3727]! } + public var Passport_Address_AddResidentialAddress: String { return self._s[3728]! } + public var Channel_Username_Title: String { return self._s[3729]! } + public func Notification_RemovedGroupPhoto(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3730]!, self._r[3730]!, [_0]) + } + public var AttachmentMenu_File: String { return self._s[3732]! } + public var AppleWatch_Title: String { return self._s[3733]! } + public var Activity_RecordingVideoMessage: String { return self._s[3734]! } + public func Channel_DiscussionGroup_PublicChannelLink(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3735]!, self._r[3735]!, [_1, _2]) + } + public var Theme_Colors_Messages: String { return self._s[3736]! } + public var Weekday_Saturday: String { return self._s[3737]! } + public var WallpaperPreview_SwipeColorsTopText: String { return self._s[3738]! } + public var Conversation_Timer_Send: String { return self._s[3739]! } + public var Settings_CancelUpload: String { return self._s[3740]! } + public var Profile_CreateEncryptedChatError: String { return self._s[3741]! } + public var Common_Next: String { return self._s[3743]! } + public var Channel_Stickers_YourStickers: String { return self._s[3745]! } + public var Message_Theme: String { return self._s[3746]! } + public var Call_AudioRouteHeadphones: String { return self._s[3747]! } + public var TwoStepAuth_EnterPasswordForgot: String { return self._s[3749]! } + public var Watch_Contacts_NoResults: String { return self._s[3751]! } + public var PhotoEditor_TintTool: String { return self._s[3754]! } + public var LoginPassword_ResetAccount: String { return self._s[3756]! } + public var Settings_SavedMessages: String { return self._s[3757]! } + public var SettingsSearch_Synonyms_Appearance_Animations: String { return self._s[3758]! } + public var Bot_GenericSupportStatus: String { return self._s[3759]! } + public var StickerPack_Add: String { return self._s[3760]! } + public var Checkout_TotalAmount: String { return self._s[3761]! } + public var Your_cards_number_is_invalid: String { return self._s[3762]! } + public var SettingsSearch_Synonyms_Appearance_AutoNightTheme: String { return self._s[3763]! } + public var VoiceOver_Chat_VideoMessage: String { return self._s[3764]! } + public func ChangePhoneNumberCode_CallTimer(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3765]!, self._r[3765]!, [_0]) + } + public func GroupPermission_AddedInfo(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3766]!, self._r[3766]!, [_1, _2]) + } + public var ChatSettings_ConnectionType_UseSocks5: String { return self._s[3767]! } + public func PUSH_CHAT_PHOTO_EDITED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3769]!, self._r[3769]!, [_1, _2]) + } + public func Conversation_RestrictedTextTimed(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3770]!, self._r[3770]!, [_0]) + } + public var GroupInfo_InviteLink_ShareLink: String { return self._s[3771]! } + public var StickerPack_Share: String { return self._s[3772]! } + public var Passport_DeleteAddress: String { return self._s[3773]! } + public var Settings_Passport: String { return self._s[3774]! } + public var SharedMedia_EmptyFilesText: String { return self._s[3775]! } + public var Conversation_DeleteMessagesForMe: String { return self._s[3776]! } + public var PasscodeSettings_AutoLock_IfAwayFor_1hour: String { return self._s[3777]! } + public var Contacts_PermissionsText: String { return self._s[3778]! } + public var Group_Setup_HistoryVisible: String { return self._s[3779]! } + public var Wallet_Month_ShortDecember: String { return self._s[3781]! } + public var PrivacySettings_AutoArchiveTitle: String { return self._s[3783]! } + public var Channel_EditAdmin_PermissionEnabledByDefault: String { return self._s[3784]! } + public var Passport_Address_AddRentalAgreement: String { return self._s[3785]! } + public var SocksProxySetup_Title: String { return self._s[3786]! } + public var Notification_Mute1h: String { return self._s[3787]! } + public func Passport_Email_CodeHelp(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3788]!, self._r[3788]!, [_0]) + } + public var NotificationSettings_ShowNotificationsAllAccountsInfoOff: String { return self._s[3789]! } + public func PUSH_PINNED_GEOLIVE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3790]!, self._r[3790]!, [_1]) + } + public var FastTwoStepSetup_PasswordSection: String { return self._s[3791]! } + public var NetworkUsageSettings_ResetStatsConfirmation: String { return self._s[3794]! } + public var InfoPlist_NSFaceIDUsageDescription: String { return self._s[3796]! } + public var DialogList_NoMessagesText: String { return self._s[3797]! } + public var Privacy_ContactsResetConfirmation: String { return self._s[3798]! } + public var Privacy_Calls_P2PHelp: String { return self._s[3799]! } + public var Channel_DiscussionGroup_SearchPlaceholder: String { return self._s[3801]! } + public var Your_cards_expiration_year_is_invalid: String { return self._s[3802]! } + public var Common_TakePhotoOrVideo: String { return self._s[3803]! } + public var Wallet_Words_Text: String { return self._s[3804]! } + public var Call_StatusBusy: String { return self._s[3805]! } + public var Conversation_PinnedMessage: String { return self._s[3806]! } + public var AutoDownloadSettings_VoiceMessagesTitle: String { return self._s[3807]! } + public var ChatList_EmptyChatListNewMessage: String { return self._s[3808]! } + public var Wallet_Configuration_BlockchainNameChangedProceed: String { return self._s[3809]! } + public var TwoStepAuth_SetupPasswordConfirmFailed: String { return self._s[3810]! } + public var Undo_ChatCleared: String { return self._s[3811]! } + public var CreatePoll_Explanation: String { return self._s[3812]! } + public var AppleWatch_ReplyPresets: String { return self._s[3813]! } + public var Passport_DiscardMessageDescription: String { return self._s[3815]! } + public var Login_NetworkError: String { return self._s[3816]! } + public func Notification_PinnedRoundMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3817]!, self._r[3817]!, [_0]) + } + public func Channel_AdminLog_MessageRemovedChannelUsername(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3818]!, self._r[3818]!, [_0]) + } + public var SocksProxySetup_PasswordPlaceholder: String { return self._s[3819]! } + public var Wallet_WordCheck_ViewWords: String { return self._s[3821]! } + public var Login_ResetAccountProtected_LimitExceeded: String { return self._s[3822]! } + public func Watch_LastSeen_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3824]!, self._r[3824]!, [_0]) + } + public var Call_ConnectionErrorMessage: String { return self._s[3825]! } + public var VoiceOver_Chat_Music: String { return self._s[3826]! } + public var ChatListFolder_CategoryContacts: String { return self._s[3827]! } + public var SettingsSearch_Synonyms_Notifications_MessageNotificationsSound: String { return self._s[3828]! } + public var Compose_GroupTokenListPlaceholder: String { return self._s[3830]! } + public var ConversationMedia_Title: String { return self._s[3831]! } + public var EncryptionKey_Title: String { return self._s[3833]! } + public var TwoStepAuth_EnterPasswordTitle: String { return self._s[3834]! } + public var Notification_Exceptions_AddException: String { return self._s[3835]! } + public var PrivacySettings_BlockedPeersEmpty: String { return self._s[3836]! } + public var Profile_MessageLifetime1m: String { return self._s[3837]! } + public func Channel_AdminLog_MessageUnkickedName(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3838]!, self._r[3838]!, [_1]) + } + public var Month_GenMay: String { return self._s[3839]! } + public func LiveLocationUpdated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3840]!, self._r[3840]!, [_0]) + } + public var PeopleNearby_Users: String { return self._s[3841]! } + public var Wallet_Send_AddressInfo: String { return self._s[3842]! } + public var ChannelMembers_WhoCanAddMembersAllHelp: String { return self._s[3843]! } + public var AutoDownloadSettings_ResetSettings: String { return self._s[3844]! } + public func Wallet_Updated_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3846]!, self._r[3846]!, [_0]) + } + public var Stats_LoadingTitle: String { return self._s[3847]! } + public var Conversation_EmptyPlaceholder: String { return self._s[3848]! } + public var Passport_Address_AddPassportRegistration: String { return self._s[3849]! } + public var Notifications_ChannelNotificationsAlert: String { return self._s[3850]! } + public var ChatSettings_AutoDownloadUsingCellular: String { return self._s[3851]! } + public var Camera_TapAndHoldForVideo: String { return self._s[3852]! } + public var Channel_JoinChannel: String { return self._s[3855]! } + public var Appearance_Animations: String { return self._s[3858]! } + public func Notification_MessageLifetimeChanged(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3859]!, self._r[3859]!, [_1, _2]) + } + public var Stickers_GroupStickers: String { return self._s[3861]! } + public var Appearance_ShareTheme: String { return self._s[3862]! } + public var TwoFactorSetup_Hint_Placeholder: String { return self._s[3863]! } + public var ConvertToSupergroup_HelpTitle: String { return self._s[3867]! } + public var StickerPackActionInfo_RemovedTitle: String { return self._s[3868]! } + public var Passport_Address_Street: String { return self._s[3869]! } + public var Conversation_AddContact: String { return self._s[3870]! } + public var Login_PhonePlaceholder: String { return self._s[3871]! } + public var Channel_Members_InviteLink: String { return self._s[3873]! } + public var Bot_Stop: String { return self._s[3874]! } + public var SettingsSearch_Synonyms_Proxy_UseForCalls: String { return self._s[3876]! } + public var Notification_PassportValueAddress: String { return self._s[3877]! } + public var Month_ShortJuly: String { return self._s[3878]! } + public var Passport_Address_TypeTemporaryRegistrationUploadScan: String { return self._s[3879]! } + public var Channel_AdminLog_BanSendMedia: String { return self._s[3880]! } + public var Passport_Identity_ReverseSide: String { return self._s[3881]! } + public func Call_ParticipantVideoVersionOutdatedError(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3883]!, self._r[3883]!, [_0]) + } + public var Watch_Stickers_Recents: String { return self._s[3886]! } + public var PrivacyLastSeenSettings_EmpryUsersPlaceholder: String { return self._s[3888]! } + public var Map_SendThisLocation: String { return self._s[3889]! } + public func Time_MonthOfYear_m1(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3890]!, self._r[3890]!, [_0]) + } + public func InviteText_SingleContact(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3891]!, self._r[3891]!, [_0]) + } + public var ConvertToSupergroup_Note: String { return self._s[3892]! } + public var Wallet_Intro_NotNow: String { return self._s[3893]! } + public var Stats_GroupMembers: String { return self._s[3894]! } + public func FileSize_MB(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3895]!, self._r[3895]!, [_0]) + } + public var NetworkUsageSettings_GeneralDataSection: String { return self._s[3896]! } + public func Compatibility_SecretMediaVersionTooLow(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3897]!, self._r[3897]!, [_0, _1]) + } + public var Login_CallRequestState3: String { return self._s[3899]! } + public var Wallpaper_SearchShort: String { return self._s[3900]! } + public var SettingsSearch_Synonyms_Appearance_ColorTheme: String { return self._s[3902]! } + public var PasscodeSettings_UnlockWithFaceId: String { return self._s[3903]! } + public var Channel_BotDoesntSupportGroups: String { return self._s[3904]! } + public func PUSH_CHAT_MESSAGE_GEOLIVE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3905]!, self._r[3905]!, [_1, _2]) + } + public var Channel_AdminLogFilter_Title: String { return self._s[3906]! } + public var Appearance_ThemePreview_Chat_4_Text: String { return self._s[3908]! } + public var Notifications_GroupNotificationsExceptions: String { return self._s[3911]! } + public func FileSize_B(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3912]!, self._r[3912]!, [_0]) + } + public var Passport_CorrectErrors: String { return self._s[3913]! } + public var VoiceOver_Chat_YourAnonymousPoll: String { return self._s[3914]! } public func Channel_MessageTitleUpdated(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3915]!, self._r[3915]!, [_0]) + } + public var Map_SendMyCurrentLocation: String { return self._s[3916]! } + public var Channel_DiscussionGroup: String { return self._s[3918]! } + public var TwoFactorSetup_Email_SkipConfirmationSkip: String { return self._s[3919]! } + public func PUSH_PINNED_CONTACT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3920]!, self._r[3920]!, [_1, _2]) + } + public var SharedMedia_SearchNoResults: String { return self._s[3921]! } + public var Permissions_NotificationsText_v0: String { return self._s[3922]! } + public var Channel_EditAdmin_PermissionDeleteMessagesOfOthers: String { return self._s[3923]! } + public var Appearance_AppIcon: String { return self._s[3924]! } + public var Appearance_ThemePreview_ChatList_3_AuthorName: String { return self._s[3925]! } + public var LoginPassword_FloodError: String { return self._s[3926]! } + public var Wallet_Send_OwnAddressAlertProceed: String { return self._s[3928]! } + public var Group_Setup_HistoryHiddenHelp: String { return self._s[3929]! } + public func TwoStepAuth_PendingEmailHelp(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3930]!, self._r[3930]!, [_0]) + } + public var Passport_Language_bn: String { return self._s[3931]! } + public func DialogList_SingleUploadingPhotoSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3932]!, self._r[3932]!, [_0]) + } + public var ChatList_Context_Pin: String { return self._s[3933]! } + public func Notification_PinnedAudioMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3934]!, self._r[3934]!, [_0]) + } + public func Channel_AdminLog_MessageChangedGroupStickerPack(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3935]!, self._r[3935]!, [_0]) + } + public var Wallet_Navigation_Close: String { return self._s[3936]! } + public var GroupInfo_InvitationLinkGroupFull: String { return self._s[3940]! } + public var Group_EditAdmin_PermissionChangeInfo: String { return self._s[3942]! } + public var Wallet_Month_GenDecember: String { return self._s[3943]! } + public var Contacts_PermissionsAllow: String { return self._s[3944]! } + public var ReportPeer_ReasonCopyright: String { return self._s[3945]! } + public var Channel_EditAdmin_PermissinAddAdminOn: String { return self._s[3946]! } + public var WallpaperPreview_Pattern: String { return self._s[3947]! } + public var Paint_Duplicate: String { return self._s[3948]! } + public var Passport_Address_Country: String { return self._s[3949]! } + public var Notification_RenamedChannel: String { return self._s[3951]! } + public var DialogList_UnknownPinLimitError: String { return self._s[3952]! } + public var CheckoutInfo_ErrorPostcodeInvalid: String { return self._s[3953]! } + public var ChatList_Context_Unmute: String { return self._s[3954]! } + public var KeyCommand_SearchInChat: String { return self._s[3955]! } + public var Group_MessagePhotoUpdated: String { return self._s[3956]! } + public var Channel_BanUser_PermissionSendMedia: String { return self._s[3957]! } + public var Conversation_ContextMenuBan: String { return self._s[3958]! } + public var TwoStepAuth_EmailSent: String { return self._s[3959]! } + public var Settings_SetProfilePhotoOrVideo: String { return self._s[3960]! } + public var MessagePoll_NoVotes: String { return self._s[3961]! } + public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[3962]! } + public var Passport_Language_is: String { return self._s[3964]! } + public var PeopleNearby_UsersEmpty: String { return self._s[3966]! } + public var Tour_Text5: String { return self._s[3967]! } + public func Call_GroupFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3970]!, self._r[3970]!, [_1, _2]) + } + public var Undo_SecretChatDeleted: String { return self._s[3971]! } + public var SocksProxySetup_ShareQRCode: String { return self._s[3972]! } + public func VoiceOver_Chat_Size(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3973]!, self._r[3973]!, [_0]) + } + public var Forward_ErrorDisabledForChat: String { return self._s[3974]! } + public var LogoutOptions_ChangePhoneNumberText: String { return self._s[3976]! } + public var Paint_Edit: String { return self._s[3978]! } + public var ScheduledMessages_ReminderNotification: String { return self._s[3980]! } + public var Undo_DeletedGroup: String { return self._s[3982]! } + public var LoginPassword_ForgotPassword: String { return self._s[3983]! } + public var Wallet_WordImport_IncorrectTitle: String { return self._s[3984]! } + public var GroupInfo_GroupNamePlaceholder: String { return self._s[3985]! } + public func Notification_Kicked(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3986]!, self._r[3986]!, [_0, _1]) + } + public var AppWallet_TransactionInfo_FeeInfoURL: String { return self._s[3987]! } + public var Conversation_InputTextCaptionPlaceholder: String { return self._s[3988]! } + public var Conversation_ContextMenuMention: String { return self._s[3989]! } + public var AutoDownloadSettings_VideoMessagesTitle: String { return self._s[3990]! } + public var Conversation_PinMessageAlertGroup: String { return self._s[3991]! } + public var Passport_Language_uz: String { return self._s[3992]! } + public var SettingsSearch_Synonyms_Privacy_GroupsAndChannels: String { return self._s[3993]! } + public var Channel_MessageVideoUpdated: String { return self._s[3995]! } + public var Map_StopLiveLocation: String { return self._s[3996]! } + public var VoiceOver_MessageContextSend: String { return self._s[3998]! } + public var PasscodeSettings_Help: String { return self._s[3999]! } + public var NotificationsSound_Input: String { return self._s[4000]! } + public var ProfilePhoto_MainVideo: String { return self._s[4002]! } + public var Share_Title: String { return self._s[4004]! } + public var LogoutOptions_Title: String { return self._s[4005]! } + public var Wallet_Send_AddressText: String { return self._s[4006]! } + public var Login_TermsOfServiceAgree: String { return self._s[4007]! } + public var Compose_NewEncryptedChatTitle: String { return self._s[4008]! } + public var Channel_AdminLog_TitleSelectedEvents: String { return self._s[4009]! } + public var Channel_EditAdmin_PermissionEditMessages: String { return self._s[4010]! } + public var EnterPasscode_EnterTitle: String { return self._s[4011]! } + public func Call_PrivacyErrorMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4012]!, self._r[4012]!, [_0]) + } + public var Settings_CopyPhoneNumber: String { return self._s[4013]! } + public var Conversation_AddToContacts: String { return self._s[4014]! } + public func VoiceOver_Chat_ReplyFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4015]!, self._r[4015]!, [_0]) + } + public var NotificationsSound_Keys: String { return self._s[4016]! } + public func Call_ParticipantVersionOutdatedError(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4017]!, self._r[4017]!, [_0]) + } + public var Notification_MessageLifetime1w: String { return self._s[4018]! } + public var Message_Video: String { return self._s[4019]! } + public var AutoDownloadSettings_CellularTitle: String { return self._s[4020]! } + public func PUSH_CHANNEL_MESSAGE_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4021]!, self._r[4021]!, [_1]) + } + public var Wallet_Receive_AmountInfo: String { return self._s[4024]! } + public var Stats_Overview: String { return self._s[4025]! } + public func Notification_JoinedChat(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4026]!, self._r[4026]!, [_0]) + } + public func PrivacySettings_LastSeenContactsPlus(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4027]!, self._r[4027]!, [_0]) + } + public var ChatListFolder_ExcludeChatsTitle: String { return self._s[4028]! } + public var Passport_Language_mk: String { return self._s[4029]! } + public var ChatListFolder_CategoryNonContacts: String { return self._s[4030]! } + public func Wallet_Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4031]!, self._r[4031]!, [_1, _2, _3]) + } + public var CreatePoll_CancelConfirmation: String { return self._s[4032]! } + public var MessagePoll_LabelAnonymousQuiz: String { return self._s[4033]! } + public var Conversation_SilentBroadcastTooltipOn: String { return self._s[4035]! } + public var PrivacyPolicy_Decline: String { return self._s[4036]! } + public var Passport_Identity_DoesNotExpire: String { return self._s[4037]! } + public var Channel_AdminLogFilter_EventsRestrictions: String { return self._s[4038]! } + public var AuthSessions_AddDeviceIntro_Action: String { return self._s[4039]! } + public var Permissions_SiriAllow_v0: String { return self._s[4041]! } + public var Wallet_Month_ShortAugust: String { return self._s[4042]! } + public var Appearance_ThemeCarouselNight: String { return self._s[4043]! } + public func LOCAL_CHAT_MESSAGE_FWDS(_ _1: String, _ _2: Int) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4044]!, self._r[4044]!, [_1, "\(_2)"]) + } + public func Notification_RenamedChat(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4045]!, self._r[4045]!, [_0]) + } + public var Paint_Regular: String { return self._s[4046]! } + public var ChatSettings_AutoDownloadReset: String { return self._s[4047]! } + public var SocksProxySetup_ShareLink: String { return self._s[4048]! } + public var Wallet_Qr_Title: String { return self._s[4049]! } + public var BlockedUsers_SelectUserTitle: String { return self._s[4050]! } + public var VoiceOver_Chat_RecordModeVoiceMessage: String { return self._s[4052]! } + public var Wallet_Settings_Configuration: String { return self._s[4053]! } + public var GroupInfo_InviteByLink: String { return self._s[4054]! } + public var MessageTimer_Custom: String { return self._s[4055]! } + public var UserInfo_NotificationsDefaultEnabled: String { return self._s[4056]! } + public var Conversation_StopQuizConfirmationTitle: String { return self._s[4057]! } + public var Passport_Address_TypeTemporaryRegistration: String { return self._s[4059]! } + public var Conversation_SendMessage_SetReminder: String { return self._s[4060]! } + public var VoiceOver_Chat_Selected: String { return self._s[4061]! } + public var Paint_Pen: String { return self._s[4062]! } + public var ChatSettings_AutoDownloadUsingWiFi: String { return self._s[4063]! } + public var Channel_Username_InvalidTaken: String { return self._s[4064]! } + public var Conversation_ClousStorageInfo_Description3: String { return self._s[4065]! } + public var Wallet_WordCheck_TryAgain: String { return self._s[4066]! } + public var Wallet_Info_TransactionPendingHeader: String { return self._s[4067]! } + public var Settings_ChatBackground: String { return self._s[4068]! } + public var Channel_Subscribers_Title: String { return self._s[4069]! } + public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[4070]! } + public var ApplyLanguage_ChangeLanguageTitle: String { return self._s[4071]! } + public var Watch_ConnectionDescription: String { return self._s[4072]! } + public var OldChannels_NoticeText: String { return self._s[4075]! } + public var Wallet_Configuration_ApplyErrorTitle: String { return self._s[4076]! } + public var IntentsSettings_SuggestBy: String { return self._s[4078]! } + public var Theme_ThemeChangedText: String { return self._s[4079]! } + public var ChatList_ArchivedChatsTitle: String { return self._s[4080]! } + public var Wallpaper_ResetWallpapers: String { return self._s[4081]! } + public var Wallet_Send_TransactionInProgress: String { return self._s[4082]! } + public var Conversation_SendDice: String { return self._s[4083]! } + public var EditProfile_Title: String { return self._s[4084]! } + public var NotificationsSound_Bamboo: String { return self._s[4086]! } + public var Channel_AdminLog_MessagePreviousMessage: String { return self._s[4088]! } + public var Login_SmsRequestState2: String { return self._s[4089]! } + public var Passport_Language_ar: String { return self._s[4090]! } + public func Message_AuthorPinnedGame(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4091]!, self._r[4091]!, [_0]) + } + public var SettingsSearch_Synonyms_EditProfile_Title: String { return self._s[4092]! } + public var Wallet_Created_Text: String { return self._s[4093]! } + public var Conversation_MessageDialogEdit: String { return self._s[4095]! } + public var Wallet_Created_Proceed: String { return self._s[4096]! } + public var Wallet_Words_Done: String { return self._s[4097]! } + public var VoiceOver_Media_PlaybackPause: String { return self._s[4098]! } + public var ChatListFolder_NameChannels: String { return self._s[4099]! } + public func PUSH_AUTH_UNKNOWN(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4100]!, self._r[4100]!, [_1]) + } + public var Common_Close: String { return self._s[4102]! } + public var GroupInfo_PublicLink: String { return self._s[4103]! } + public var Channel_OwnershipTransfer_ErrorPrivacyRestricted: String { return self._s[4104]! } + public var SettingsSearch_Synonyms_Notifications_GroupNotificationsPreview: String { return self._s[4105]! } + public var Conversation_ContextMenuOpenChannel: String { return self._s[4109]! } + public func Channel_AdminLog_MessageToggleInvitesOff(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4110]!, self._r[4110]!, [_0]) + } + public var UserInfo_About_Placeholder: String { return self._s[4111]! } + public func Conversation_FileHowToText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4112]!, self._r[4112]!, [_0]) + } + public var GroupInfo_Permissions_SectionTitle: String { return self._s[4113]! } + public var Channel_Info_Banned: String { return self._s[4115]! } + public func Time_MonthOfYear_m11(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4116]!, self._r[4116]!, [_0]) + } + public var Appearance_Other: String { return self._s[4117]! } + public var Passport_Language_my: String { return self._s[4118]! } + public var Group_Setup_BasicHistoryHiddenHelp: String { return self._s[4119]! } + public func Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4120]!, self._r[4120]!, [_1, _2, _3]) + } + public var SettingsSearch_Synonyms_Privacy_PasscodeAndFaceId: String { return self._s[4121]! } + public var IntentsSettings_SuggestedAndSpotlightChatsInfo: String { return self._s[4122]! } + public var Preview_CopyAddress: String { return self._s[4123]! } + public func DialogList_SinglePlayingGameSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4124]!, self._r[4124]!, [_0]) + } + public var KeyCommand_JumpToPreviousChat: String { return self._s[4125]! } + public var UserInfo_BotSettings: String { return self._s[4126]! } + public var LiveLocation_MenuStopAll: String { return self._s[4128]! } + public var Passport_PasswordCreate: String { return self._s[4129]! } + public var StickerSettings_MaskContextInfo: String { return self._s[4130]! } + public var Message_PinnedLocationMessage: String { return self._s[4131]! } + public var Map_Satellite: String { return self._s[4132]! } + public var Watch_Message_Unsupported: String { return self._s[4133]! } + public var Username_TooManyPublicUsernamesError: String { return self._s[4134]! } + public var TwoStepAuth_EnterPasswordInvalid: String { return self._s[4135]! } + public func Notification_PinnedTextMessage(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4136]!, self._r[4136]!, [_0, _1]) + } + public func Conversation_OpenBotLinkText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4137]!, self._r[4137]!, [_0]) + } + public var Wallet_WordImport_Continue: String { return self._s[4138]! } + public func TwoFactorSetup_EmailVerification_Text(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4139]!, self._r[4139]!, [_0]) + } + public var Notifications_ChannelNotificationsHelp: String { return self._s[4140]! } + public var Privacy_Calls_P2PContacts: String { return self._s[4141]! } + public var NotificationsSound_None: String { return self._s[4142]! } + public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[4143]! } + public var Channel_DiscussionGroup_UnlinkGroup: String { return self._s[4145]! } + public var AccessDenied_VoiceMicrophone: String { return self._s[4146]! } + public func ApplyLanguage_ChangeLanguageAlreadyActive(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4147]!, self._r[4147]!, [_1]) + } + public var Cache_Indexing: String { return self._s[4148]! } + public var DialogList_RecentTitlePeople: String { return self._s[4150]! } + public var DialogList_EncryptionRejected: String { return self._s[4151]! } + public var GroupInfo_Administrators: String { return self._s[4152]! } + public var Passport_ScanPassportHelp: String { return self._s[4153]! } + public var Application_Name: String { return self._s[4154]! } + public var Channel_AdminLogFilter_ChannelEventsInfo: String { return self._s[4155]! } + public var Conversation_Timer_Title: String { return self._s[4156]! } + public var ChatList_PeerTypeGroup: String { return self._s[4157]! } + public var PeopleNearby_MakeVisible: String { return self._s[4159]! } + public var Appearance_ThemeCarouselDay: String { return self._s[4160]! } + public var Stats_GrowthTitle: String { return self._s[4161]! } + public var Passport_Identity_TranslationHelp: String { return self._s[4162]! } + public func VoiceOver_Chat_VideoMessageFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4163]!, self._r[4163]!, [_0]) + } + public func Notification_JoinedGroupByLink(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4164]!, self._r[4164]!, [_0]) + } + public func DialogList_EncryptedChatStartedOutgoing(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4165]!, self._r[4165]!, [_0]) + } + public var Channel_EditAdmin_PermissionDeleteMessages: String { return self._s[4166]! } + public var Privacy_ChatsTitle: String { return self._s[4167]! } + public var DialogList_ClearHistoryConfirmation: String { return self._s[4168]! } + public var SettingsSearch_Synonyms_Data_Storage_ClearCache: String { return self._s[4169]! } + public var Watch_Suggestion_HoldOn: String { return self._s[4170]! } + public var Group_EditAdmin_TransferOwnership: String { return self._s[4171]! } + public var WebBrowser_Title: String { return self._s[4172]! } + public var Group_LinkedChannel: String { return self._s[4173]! } + public var VoiceOver_Chat_SeenByRecipient: String { return self._s[4174]! } + public var SocksProxySetup_RequiredCredentials: String { return self._s[4175]! } + public var Passport_Address_TypeRentalAgreementUploadScan: String { return self._s[4176]! } + public var Appearance_TextSize_UseSystem: String { return self._s[4177]! } + public var TwoStepAuth_EmailSkipAlert: String { return self._s[4178]! } + public var ScheduledMessages_RemindersTitle: String { return self._s[4180]! } + public var Channel_Setup_TypePublic: String { return self._s[4182]! } + public func Channel_AdminLog_MessageToggleInvitesOn(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4183]!, self._r[4183]!, [_0]) + } + public var Channel_TypeSetup_Title: String { return self._s[4185]! } + public var MessagePoll_ViewResults: String { return self._s[4186]! } + public var Map_OpenInMaps: String { return self._s[4188]! } + public func PUSH_PINNED_NOTEXT(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4189]!, self._r[4189]!, [_1]) + } + public var NotificationsSound_Tremolo: String { return self._s[4191]! } + public func Date_ChatDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4192]!, self._r[4192]!, [_1, _2, _3]) + } + public var ConversationProfile_UnknownAddMemberError: String { return self._s[4193]! } + public var Channel_OwnershipTransfer_PasswordPlaceholder: String { return self._s[4194]! } + public var Passport_PasswordHelp: String { return self._s[4196]! } + public var Login_CodeExpiredError: String { return self._s[4197]! } + public var Channel_EditAdmin_PermissionChangeInfo: String { return self._s[4198]! } + public var Conversation_TitleUnmute: String { return self._s[4199]! } + public var Passport_Identity_ScansHelp: String { return self._s[4200]! } + public var Passport_Language_lo: String { return self._s[4201]! } + public var Camera_FlashAuto: String { return self._s[4202]! } + public var Conversation_OpenBotLinkOpen: String { return self._s[4203]! } + public var Common_Cancel: String { return self._s[4204]! } + public var DialogList_SavedMessagesTooltip: String { return self._s[4205]! } + public var TwoStepAuth_SetupPasswordTitle: String { return self._s[4206]! } + public var Appearance_TintAllColors: String { return self._s[4207]! } + public func PUSH_MESSAGE_FWD(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4208]!, self._r[4208]!, [_1]) + } + public var Conversation_ReportSpamConfirmation: String { return self._s[4209]! } + public var ChatSettings_Title: String { return self._s[4211]! } + public var Passport_PasswordReset: String { return self._s[4212]! } + public var SocksProxySetup_TypeNone: String { return self._s[4213]! } + public var EditTheme_Title: String { return self._s[4216]! } + public var PhoneNumberHelp_Help: String { return self._s[4217]! } + public var Checkout_EnterPassword: String { return self._s[4218]! } + public var Activity_UploadingDocument: String { return self._s[4220]! } + public var Share_AuthTitle: String { return self._s[4221]! } + public var State_Connecting: String { return self._s[4222]! } + public var Profile_MessageLifetime1w: String { return self._s[4223]! } + public var Conversation_ContextMenuReport: String { return self._s[4224]! } + public var CheckoutInfo_ReceiverInfoPhone: String { return self._s[4225]! } + public var AutoNightTheme_ScheduledTo: String { return self._s[4226]! } + public func VoiceOver_Chat_AnonymousPollFrom(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[4227]!, self._r[4227]!, [_0]) } - public var ChatList_Context_Unmute: String { return self._s[4228]! } - public var Chat_SlowmodeAttachmentLimitReached: String { return self._s[4229]! } - public var Stickers_GroupStickersHelp: String { return self._s[4230]! } - public var Wallet_Configuration_BlockchainIdPlaceholder: String { return self._s[4231]! } - public var Checkout_Title: String { return self._s[4232]! } - public var Activity_RecordingAudio: String { return self._s[4233]! } - public var SettingsSearch_Synonyms_Notifications_GroupNotificationsPreview: String { return self._s[4234]! } - public var BlockedUsers_BlockTitle: String { return self._s[4235]! } - public var Wallet_Month_ShortFebruary: String { return self._s[4237]! } - public var Calls_All: String { return self._s[4238]! } - public var DialogList_SavedMessagesHelp: String { return self._s[4240]! } - public var Settings_FAQ_Button: String { return self._s[4241]! } - public func Time_MonthOfYear_m5(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4243]!, self._r[4243]!, [_0]) + public var AuthSessions_Terminate: String { return self._s[4228]! } + public var Wallet_WordImport_CanNotRemember: String { return self._s[4229]! } + public var PeerInfo_PaneAudio: String { return self._s[4230]! } + public func Message_ForwardedPsa_covid(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4231]!, self._r[4231]!, [_0]) } - public var Conversation_ReportGroupLocation: String { return self._s[4244]! } - public var Passport_Scans_Upload: String { return self._s[4245]! } - public var Channel_EditAdmin_PermissionPinMessages: String { return self._s[4247]! } - public var ChatList_UnarchiveAction: String { return self._s[4248]! } - public var Stats_GroupTopInviter_History: String { return self._s[4249]! } - public var GroupInfo_Permissions_Title: String { return self._s[4250]! } - public var Passport_Language_el: String { return self._s[4251]! } - public func Wallet_Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4252]!, self._r[4252]!, [_1, _2, _3]) + public var Checkout_NewCard_CardholderNamePlaceholder: String { return self._s[4233]! } + public var KeyCommand_JumpToPreviousUnreadChat: String { return self._s[4234]! } + public var PhotoEditor_Set: String { return self._s[4235]! } + public var EmptyGroupInfo_Title: String { return self._s[4236]! } + public var Login_PadPhoneHelp: String { return self._s[4238]! } + public var AutoDownloadSettings_TypeGroupChats: String { return self._s[4240]! } + public var PrivacyPolicy_DeclineLastWarning: String { return self._s[4242]! } + public var NotificationsSound_Complete: String { return self._s[4243]! } + public var SettingsSearch_Synonyms_Privacy_Data_Title: String { return self._s[4244]! } + public var Group_Info_AdminLog: String { return self._s[4245]! } + public var GroupPermission_NotAvailableInPublicGroups: String { return self._s[4246]! } + public func Wallet_Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4247]!, self._r[4247]!, [_1, _2, _3]) } - public var GroupInfo_ActionPromote: String { return self._s[4253]! } - public var Group_OwnershipTransfer_ErrorLocatedGroupsTooMuch: String { return self._s[4254]! } - public func TwoStepAuth_PendingEmailHelp(_ _0: String) -> (String, [(Int, NSRange)]) { + public var Channel_AdminLog_InfoPanelAlertText: String { return self._s[4248]! } + public var Group_Location_CreateInThisPlace: String { return self._s[4250]! } + public var Conversation_Admin: String { return self._s[4251]! } + public var Conversation_GifTooltip: String { return self._s[4252]! } + public var Passport_NotLoggedInMessage: String { return self._s[4253]! } + public func AutoDownloadSettings_OnFor(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[4255]!, self._r[4255]!, [_0]) } - public var VoiceOver_Chat_Reply: String { return self._s[4256]! } - public var Month_GenMay: String { return self._s[4257]! } - public var DialogList_DeleteBotConversationConfirmation: String { return self._s[4258]! } - public var Chat_PsaTooltip_covid: String { return self._s[4259]! } - public var Watch_Suggestion_CantTalk: String { return self._s[4260]! } - public var Privacy_GroupsAndChannels_NeverAllow_Title: String { return self._s[4261]! } - public var AppUpgrade_Running: String { return self._s[4262]! } - public var PasscodeSettings_UnlockWithFaceId: String { return self._s[4265]! } - public var Notification_Exceptions_PreviewAlwaysOff: String { return self._s[4266]! } - public var SharedMedia_EmptyText: String { return self._s[4267]! } - public var Passport_Address_EditResidentialAddress: String { return self._s[4268]! } - public var SettingsSearch_Synonyms_Notifications_GroupNotificationsAlert: String { return self._s[4269]! } - public var Message_PinnedGame: String { return self._s[4270]! } - public var KeyCommand_SearchInChat: String { return self._s[4271]! } - public var Appearance_ThemeCarouselNewNight: String { return self._s[4272]! } - public var ChatList_Search_FilterMedia: String { return self._s[4273]! } - public var Message_PinnedAudioMessage: String { return self._s[4274]! } - public var ChannelInfo_ConfirmLeave: String { return self._s[4275]! } - public func Channel_AdminLog_MessagePromotedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + public var Profile_MessageLifetimeForever: String { return self._s[4256]! } + public var SharedMedia_EmptyTitle: String { return self._s[4258]! } + public var Channel_Edit_PrivatePublicLinkAlert: String { return self._s[4260]! } + public var Username_Help: String { return self._s[4261]! } + public var DialogList_LanguageTooltip: String { return self._s[4263]! } + public var Map_LoadError: String { return self._s[4264]! } + public var Login_PhoneNumberAlreadyAuthorized: String { return self._s[4265]! } + public var Channel_AdminLog_AddMembers: String { return self._s[4266]! } + public var ArchivedChats_IntroTitle2: String { return self._s[4267]! } + public var Notification_Exceptions_NewException: String { return self._s[4268]! } + public var TwoStepAuth_EmailTitle: String { return self._s[4269]! } + public var WatchRemote_AlertText: String { return self._s[4270]! } + public func Wallet_Send_ConfirmationText(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4271]!, self._r[4271]!, [_1, _2, _3]) + } + public var ChatSettings_ConnectionType_Title: String { return self._s[4275]! } + public func PUSH_PINNED_QUIZ(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[4276]!, self._r[4276]!, [_1, _2]) } - public var SocksProxySetup_ProxyStatusUnavailable: String { return self._s[4277]! } - public func Passport_Email_CodeHelp(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4278]!, self._r[4278]!, [_0]) + public func Settings_CheckPhoneNumberTitle(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4277]!, self._r[4277]!, [_0]) } - public var Wallet_Receive_AddressCopied: String { return self._s[4279]! } - public func Message_PinnedTextMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4280]!, self._r[4280]!, [_0]) + public var SettingsSearch_Synonyms_Calls_CallTab: String { return self._s[4278]! } + public var WebBrowser_DefaultBrowser: String { return self._s[4279]! } + public var Passport_Address_CountryPlaceholder: String { return self._s[4280]! } + public func DialogList_AwaitingEncryption(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4281]!, self._r[4281]!, [_0]) } - public var Settings_AddAccount: String { return self._s[4281]! } - public var Channel_AdminLog_CanDeleteMessages: String { return self._s[4282]! } - public var Conversation_DiscardVoiceMessageTitle: String { return self._s[4283]! } - public var Channel_JoinChannel: String { return self._s[4284]! } - public var Watch_UserInfo_Unblock: String { return self._s[4285]! } - public var PhoneLabel_Title: String { return self._s[4286]! } - public var Group_Setup_HistoryHiddenHelp: String { return self._s[4288]! } - public var Privacy_ProfilePhoto_AlwaysShareWith_Title: String { return self._s[4289]! } - public func Login_PhoneGenericEmailBody(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String, _ _6: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4290]!, self._r[4290]!, [_1, _2, _3, _4, _5, _6]) + public func Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4282]!, self._r[4282]!, [_1, _2, _3]) } - public var Wallet_Month_GenOctober: String { return self._s[4291]! } - public var Channel_AddBotErrorHaveRights: String { return self._s[4292]! } - public var ChatList_TabIconFoldersTooltipNonEmptyFolders: String { return self._s[4293]! } - public var DialogList_EncryptionProcessing: String { return self._s[4294]! } - public var WatchRemote_NotificationText: String { return self._s[4295]! } - public var EditTheme_ChangeColors: String { return self._s[4296]! } - public var GroupRemoved_ViewUserInfo: String { return self._s[4297]! } - public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[4298]! } - public var CallSettings_OnMobile: String { return self._s[4300]! } - public var Month_ShortFebruary: String { return self._s[4302]! } - public var VoiceOver_MessageContextReply: String { return self._s[4303]! } - public func PUSH_VIDEO_CALL_REQUEST(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4304]!, self._r[4304]!, [_1]) + public var Group_AdminLog_EmptyText: String { return self._s[4283]! } + public var SettingsSearch_Synonyms_Appearance_Title: String { return self._s[4284]! } + public var Conversation_PrivateChannelTooltip: String { return self._s[4286]! } + public var Wallet_Created_ExportErrorText: String { return self._s[4287]! } + public var ChatList_UndoArchiveText1: String { return self._s[4288]! } + public var ChatListFolder_IncludedSectionHeader: String { return self._s[4289]! } + public var AccessDenied_VideoMicrophone: String { return self._s[4290]! } + public var Conversation_ContextMenuStickerPackAdd: String { return self._s[4291]! } + public var Stats_GroupTopInviter_History: String { return self._s[4292]! } + public var Cache_ClearNone: String { return self._s[4293]! } + public var SocksProxySetup_FailedToConnect: String { return self._s[4294]! } + public var Call_ShareStats: String { return self._s[4295]! } + public func Channel_AdminLog_MessageEdited(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4296]!, self._r[4296]!, [_0]) } - public var Group_Location_ChangeLocation: String { return self._s[4305]! } - public var Passport_Address_TypeBankStatementUploadScan: String { return self._s[4306]! } - public var Wallet_Send_EncryptComment: String { return self._s[4307]! } - public var VoiceOver_Media_PlaybackStop: String { return self._s[4308]! } - public var SettingsSearch_Synonyms_Data_SaveIncomingPhotos: String { return self._s[4309]! } - public func Channel_AdminLog_MessageRestrictedUntil(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4311]!, self._r[4311]!, [_0]) + public var Permissions_NotificationsTitle_v0: String { return self._s[4297]! } + public var Passport_Identity_Country: String { return self._s[4298]! } + public func ChatSettings_AutoDownloadSettings_TypeFile(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4299]!, self._r[4299]!, [_0]) } - public var PhotoEditor_WarmthTool: String { return self._s[4312]! } - public var Login_InfoAvatarPhoto: String { return self._s[4313]! } - public var Notification_Exceptions_NewException_MessagePreviewHeader: String { return self._s[4314]! } - public var Permissions_CellularDataAllowInSettings_v0: String { return self._s[4315]! } - public var Map_PlacesInThisArea: String { return self._s[4316]! } - public var VoiceOver_Chat_ContactEmail: String { return self._s[4317]! } - public var Notifications_InAppNotificationsSounds: String { return self._s[4318]! } - public func PUSH_PINNED_NOTEXT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4319]!, self._r[4319]!, [_1]) + public func Notification_CreatedChat(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4300]!, self._r[4300]!, [_0]) } - public var ShareMenu_Send: String { return self._s[4320]! } - public var Username_InvalidStartsWithNumber: String { return self._s[4321]! } - public var Appearance_AppIconClassicX: String { return self._s[4322]! } - public func PUSH_CHANNEL_MESSAGE_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4323]!, self._r[4323]!, [_1]) + public var Exceptions_AddToExceptions: String { return self._s[4301]! } + public var AccessDenied_Settings: String { return self._s[4302]! } + public var Passport_Address_TypeUtilityBillUploadScan: String { return self._s[4303]! } + public var Month_ShortMay: String { return self._s[4305]! } + public var Compose_NewGroup: String { return self._s[4307]! } + public var Group_Setup_TypePrivate: String { return self._s[4309]! } + public var Login_PadPhoneHelpTitle: String { return self._s[4311]! } + public var Appearance_ThemeDayClassic: String { return self._s[4312]! } + public var Channel_AdminLog_MessagePreviousCaption: String { return self._s[4313]! } + public var AutoDownloadSettings_OffForAll: String { return self._s[4314]! } + public var Privacy_GroupsAndChannels_WhoCanAddMe: String { return self._s[4315]! } + public var Conversation_typing: String { return self._s[4317]! } + public var Undo_ScheduledMessagesCleared: String { return self._s[4318]! } + public var Paint_Masks: String { return self._s[4319]! } + public var Contacts_DeselectAll: String { return self._s[4320]! } + public func Wallet_Updated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4321]!, self._r[4321]!, [_0]) } - public var Conversation_StopPoll: String { return self._s[4324]! } - public var InfoPlist_NSLocationAlwaysUsageDescription: String { return self._s[4326]! } - public var Passport_Identity_EditIdentityCard: String { return self._s[4327]! } - public var Appearance_ThemePreview_ChatList_3_Name: String { return self._s[4328]! } - public var Wallet_WordCheck_Title: String { return self._s[4329]! } - public var Conversation_Timer_Title: String { return self._s[4330]! } - public var Common_Next: String { return self._s[4331]! } - public var Notification_Exceptions_NewException: String { return self._s[4332]! } - public func Generic_OpenHiddenLinkAlert(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4333]!, self._r[4333]!, [_0]) - } - public var AccessDenied_CallMicrophone: String { return self._s[4334]! } - public var SettingsSearch_Synonyms_Data_AutoDownloadUsingCellular: String { return self._s[4335]! } - public var ChangePhoneNumberCode_Help: String { return self._s[4336]! } - public var Passport_Identity_OneOfTypeIdentityCard: String { return self._s[4337]! } - public var Channel_AdminLogFilter_EventsLeaving: String { return self._s[4338]! } - public var BlockedUsers_LeavePrefix: String { return self._s[4339]! } - public func Passport_RequestHeader(_ _0: String) -> (String, [(Int, NSRange)]) { + public var CreatePoll_MultipleChoiceQuizAlert: String { return self._s[4322]! } + public var Stats_GroupMembersTitle: String { return self._s[4323]! } + public var Username_InvalidTaken: String { return self._s[4324]! } + public var Call_StatusNoAnswer: String { return self._s[4325]! } + public var TwoStepAuth_EmailAddSuccess: String { return self._s[4326]! } + public var SettingsSearch_Synonyms_Privacy_BlockedUsers: String { return self._s[4327]! } + public var Passport_Identity_Selfie: String { return self._s[4328]! } + public var Login_InfoLastNamePlaceholder: String { return self._s[4329]! } + public var Privacy_SecretChatsLinkPreviewsHelp: String { return self._s[4330]! } + public var Conversation_ClearSecretHistory: String { return self._s[4331]! } + public var PeopleNearby_Description: String { return self._s[4333]! } + public var NetworkUsageSettings_Title: String { return self._s[4334]! } + public var Your_cards_security_code_is_invalid: String { return self._s[4336]! } + public var Stats_EnabledNotifications: String { return self._s[4337]! } + public func Notification_LeftChannel(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[4340]!, self._r[4340]!, [_0]) } - public var Group_About_Help: String { return self._s[4341]! } - public var TwoStepAuth_ChangePasswordDescription: String { return self._s[4342]! } - public var Tour_Title3: String { return self._s[4343]! } - public var Watch_Conversation_Unblock: String { return self._s[4344]! } - public var Watch_UserInfo_Block: String { return self._s[4345]! } - public var Notifications_ChannelNotificationsAlert: String { return self._s[4346]! } - public var TwoFactorSetup_Hint_Action: String { return self._s[4347]! } - public var IntentsSettings_SuggestedChatsInfo: String { return self._s[4348]! } - public var Wallet_Alert_Cancel: String { return self._s[4349]! } - public var TextFormat_AddLinkTitle: String { return self._s[4350]! } - public var GroupInfo_InviteLink_RevokeAlert_Revoke: String { return self._s[4351]! } - public var TwoStepAuth_EnterPasswordTitle: String { return self._s[4352]! } - public var FastTwoStepSetup_PasswordSection: String { return self._s[4353]! } - public var Compose_ChannelMembers: String { return self._s[4354]! } - public var Conversation_ForwardTitle: String { return self._s[4355]! } - public func Wallet_Updated_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4356]!, self._r[4356]!, [_0]) + public func Call_CallInProgressMessage(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4341]!, self._r[4341]!, [_1, _2]) } - public var Conversation_PinnedPoll: String { return self._s[4358]! } - public func VoiceOver_Chat_AnonymousPollFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4359]!, self._r[4359]!, [_0]) + public var SaveIncomingPhotosSettings_From: String { return self._s[4343]! } + public var VoiceOver_Navigation_Search: String { return self._s[4344]! } + public var Map_LiveLocationTitle: String { return self._s[4345]! } + public var Login_InfoAvatarAdd: String { return self._s[4346]! } + public var Passport_Identity_FilesView: String { return self._s[4347]! } + public var ChatListFolderSettings_Title: String { return self._s[4348]! } + public var UserInfo_GenericPhoneLabel: String { return self._s[4349]! } + public var Privacy_Calls_NeverAllow: String { return self._s[4350]! } + public var VoiceOver_Chat_File: String { return self._s[4351]! } + public var Wallet_Settings_DeleteWalletInfo: String { return self._s[4352]! } + public func Contacts_AddPhoneNumber(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4353]!, self._r[4353]!, [_0]) } - public var SettingsSearch_Synonyms_EditProfile_AddAccount: String { return self._s[4360]! } - public var Conversation_ContextMenuStickerPackAdd: String { return self._s[4361]! } - public var Stats_Overview: String { return self._s[4362]! } - public var Map_HomeAndWorkTitle: String { return self._s[4363]! } - public var Wallet_Intro_Terms: String { return self._s[4364]! } - public func Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4365]!, self._r[4365]!, [_1, _2, _3]) - } - public var Passport_Address_CityPlaceholder: String { return self._s[4366]! } - public var InfoPlist_NSLocationAlwaysAndWhenInUseUsageDescription: String { return self._s[4367]! } - public var Privacy_PhoneNumber: String { return self._s[4368]! } - public var ChatList_Search_FilterFiles: String { return self._s[4369]! } - public var ChatList_DeleteForEveryoneConfirmationAction: String { return self._s[4370]! } - public var ChannelIntro_CreateChannel: String { return self._s[4371]! } - public var Conversation_InputTextAnonymousPlaceholder: String { return self._s[4372]! } - public func Login_EmailCodeBody(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4373]!, self._r[4373]!, [_0]) - } - public var Weekday_ShortMonday: String { return self._s[4374]! } - public var Passport_Language_ar: String { return self._s[4376]! } - public var SettingsSearch_Synonyms_EditProfile_Title: String { return self._s[4377]! } - public var TwoFactorSetup_Done_Title: String { return self._s[4378]! } - public var Calls_RatingFeedback: String { return self._s[4379]! } - public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsPreview: String { return self._s[4380]! } - public var AutoDownloadSettings_ResetSettings: String { return self._s[4383]! } - public var Watch_Compose_Send: String { return self._s[4384]! } - public var PasscodeSettings_ChangePasscode: String { return self._s[4385]! } - public var WebSearch_RecentSectionClear: String { return self._s[4386]! } - public func Contacts_AccessDeniedHelpPortrait(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4387]!, self._r[4387]!, [_0]) - } - public var WallpaperSearch_ColorTeal: String { return self._s[4388]! } - public var Wallpaper_SetCustomBackgroundInfo: String { return self._s[4389]! } - public var Permissions_ContactsTitle_v0: String { return self._s[4390]! } - public var Checkout_PasswordEntry_Pay: String { return self._s[4392]! } - public var Settings_SavedMessages: String { return self._s[4393]! } - public var TwoStepAuth_ReEnterPasswordDescription: String { return self._s[4394]! } - public var Month_ShortMarch: String { return self._s[4395]! } - public var Message_Location: String { return self._s[4396]! } - public func PUSH_MESSAGE_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4397]!, self._r[4397]!, [_1]) - } - public func Notification_CallTimeFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4398]!, self._r[4398]!, [_1, _2]) - } - public var VoiceOver_Chat_VoiceMessage: String { return self._s[4400]! } - public func Channel_AdminLog_MessageChangedUnlinkedChannel(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4401]!, self._r[4401]!, [_1, _2]) - } - public var GroupPermission_NoSendMedia: String { return self._s[4402]! } - public var Conversation_ClousStorageInfo_Description2: String { return self._s[4403]! } - public var SharedMedia_CategoryDocs: String { return self._s[4404]! } - public var Appearance_RemoveThemeConfirmation: String { return self._s[4405]! } - public var Paint_Framed: String { return self._s[4406]! } - public var Channel_EditAdmin_PermissionAddAdmins: String { return self._s[4407]! } - public var Passport_Identity_DoesNotExpire: String { return self._s[4408]! } - public var Channel_SignMessages: String { return self._s[4409]! } - public var Contacts_AccessDeniedHelpON: String { return self._s[4410]! } - public var Conversation_ContextMenuStickerPackInfo: String { return self._s[4411]! } - public func PUSH_CHAT_LEFT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4412]!, self._r[4412]!, [_1, _2]) - } - public var GroupInfo_UpgradeButton: String { return self._s[4413]! } - public var Channel_EditAdmin_PermissionInviteMembers: String { return self._s[4414]! } - public var AutoDownloadSettings_Files: String { return self._s[4415]! } - public func Notification_ChangedGroupName(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4416]!, self._r[4416]!, [_0, _1]) - } - public var Login_SendCodeViaSms: String { return self._s[4418]! } - public var Update_UpdateApp: String { return self._s[4419]! } - public var Channel_Setup_TypePublic: String { return self._s[4420]! } - public var Watch_Compose_CreateMessage: String { return self._s[4421]! } + public var ChatList_EmptyChatList: String { return self._s[4355]! } + public var ContactInfo_PhoneNumberHidden: String { return self._s[4356]! } + public var TwoStepAuth_ConfirmationText: String { return self._s[4357]! } + public var ChatSettings_AutomaticVideoMessageDownload: String { return self._s[4358]! } public func PUSH_CHAT_MESSAGE_VIDEOS(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4422]!, self._r[4422]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[4359]!, self._r[4359]!, [_1, _2, _3]) } - public var StickerPacksSettings_ManagingHelp: String { return self._s[4423]! } - public func Wallet_Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4424]!, self._r[4424]!, [_1, _2, _3]) + public var Channel_AdminLogFilter_AdminsAll: String { return self._s[4360]! } + public var Wallet_Intro_CreateErrorText: String { return self._s[4361]! } + public var Tour_Title2: String { return self._s[4362]! } + public var Wallet_Sent_ViewWallet: String { return self._s[4363]! } + public var Stats_GroupMessagesTitle: String { return self._s[4364]! } + public var Conversation_FileOpenIn: String { return self._s[4365]! } + public var Checkout_ErrorPrecheckoutFailed: String { return self._s[4366]! } + public var Wallet_Send_ErrorInvalidAddress: String { return self._s[4367]! } + public var Wallpaper_Set: String { return self._s[4368]! } + public var Passport_Identity_Translations: String { return self._s[4371]! } + public func Channel_AdminLog_MessageChangedChannelAbout(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4372]!, self._r[4372]!, [_0]) } - public var VoiceOver_Chat_Video: String { return self._s[4425]! } - public var Forward_ChannelReadOnly: String { return self._s[4426]! } - public var StickerPack_HideStickers: String { return self._s[4427]! } - public var ChatListFolder_NameContacts: String { return self._s[4428]! } - public var Profile_BotInfo: String { return self._s[4429]! } - public var Document_TargetConfirmationFormat: String { return self._s[4430]! } - public var GroupInfo_InviteByLink: String { return self._s[4431]! } - public var Channel_AdminLog_BanSendStickersAndGifs: String { return self._s[4432]! } - public var Watch_Stickers_RecentPlaceholder: String { return self._s[4433]! } - public var Broadcast_AdminLog_EmptyText: String { return self._s[4434]! } - public var Passport_NotLoggedInMessage: String { return self._s[4435]! } - public var Conversation_StopQuizConfirmation: String { return self._s[4436]! } - public var Checkout_PaymentMethod: String { return self._s[4437]! } - public var ChatList_ArchivedChatsTitle: String { return self._s[4441]! } - public var TwoStepAuth_SetupPasswordConfirmFailed: String { return self._s[4442]! } - public var VoiceOver_Chat_RecordPreviewVoiceMessage: String { return self._s[4443]! } - public var PrivacyLastSeenSettings_GroupsAndChannelsHelp: String { return self._s[4444]! } - public var SettingsSearch_Synonyms_Privacy_Data_ContactsReset: String { return self._s[4445]! } - public var Camera_Title: String { return self._s[4446]! } - public var Map_Directions: String { return self._s[4447]! } - public var Wallet_Intro_ImportExisting: String { return self._s[4448]! } - public var Stats_MessagePublicForwardsTitle: String { return self._s[4449]! } - public var Privacy_ProfilePhoto_WhoCanSeeMyPhoto: String { return self._s[4451]! } - public var Profile_EncryptionKey: String { return self._s[4452]! } - public func LOCAL_CHAT_MESSAGE_FWDS(_ _1: String, _ _2: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4453]!, self._r[4453]!, [_1, "\(_2)"]) + public var Channel_LeaveChannel: String { return self._s[4374]! } + public func PINNED_INVOICE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4375]!, self._r[4375]!, [_1]) } - public func Compatibility_SecretMediaVersionTooLow(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4454]!, self._r[4454]!, [_0, _1]) + public var SettingsSearch_Synonyms_Proxy_AddProxy: String { return self._s[4377]! } + public var PhotoEditor_HighlightsTint: String { return self._s[4378]! } + public var MessagePoll_LabelPoll: String { return self._s[4379]! } + public var Passport_Email_Delete: String { return self._s[4380]! } + public var Conversation_Mute: String { return self._s[4382]! } + public var Channel_AddBotAsAdmin: String { return self._s[4383]! } + public var Channel_AdminLog_CanSendMessages: String { return self._s[4385]! } + public var Wallet_Configuration_BlockchainNameChangedText: String { return self._s[4386]! } + public var ChatSettings_IntentsSettings: String { return self._s[4388]! } + public var Channel_Management_LabelOwner: String { return self._s[4389]! } + public func Notification_PassportValuesSentMessage(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4390]!, self._r[4390]!, [_1, _2]) } - public var Passport_Identity_TypePassport: String { return self._s[4455]! } - public var CreatePoll_QuizOptionsHeader: String { return self._s[4457]! } - public var Common_No: String { return self._s[4458]! } - public var Conversation_SendMessage_ScheduleMessage: String { return self._s[4459]! } - public var SettingsSearch_Synonyms_Privacy_LastSeen: String { return self._s[4460]! } - public var Settings_AboutEmpty: String { return self._s[4461]! } - public var TwoStepAuth_FloodError: String { return self._s[4463]! } - public var SettingsSearch_Synonyms_Appearance_TextSize: String { return self._s[4464]! } - public func Channel_AdminLog_MessageUnkickedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4466]!, self._r[4466]!, [_1]) + public var Calls_CallTabDescription: String { return self._s[4391]! } + public var Passport_Identity_NativeNameHelp: String { return self._s[4392]! } + public var Common_No: String { return self._s[4393]! } + public var Weekday_Sunday: String { return self._s[4394]! } + public var Notification_Reply: String { return self._s[4395]! } + public var Conversation_ViewMessage: String { return self._s[4396]! } + public func Checkout_SavePasswordTimeoutAndFaceId(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4397]!, self._r[4397]!, [_0]) } - public var Conversation_Edit: String { return self._s[4469]! } - public var CheckoutInfo_SaveInfo: String { return self._s[4470]! } - public var VoiceOver_Chat_AnonymousPoll: String { return self._s[4471]! } - public var Call_CameraTooltip: String { return self._s[4473]! } - public var InstantPage_FeedbackButtonShort: String { return self._s[4474]! } - public var Contacts_InviteToTelegram: String { return self._s[4475]! } - public var Wallet_WordImport_CanNotRemember: String { return self._s[4476]! } - public var Notifications_ResetAllNotifications: String { return self._s[4477]! } - public var Calls_NewCall: String { return self._s[4478]! } - public var VoiceOver_Chat_Music: String { return self._s[4481]! } - public var Channel_Members_AddAdminErrorNotAMember: String { return self._s[4482]! } - public var Channel_Edit_AboutItem: String { return self._s[4483]! } - public var Message_VideoExpired: String { return self._s[4484]! } - public var Passport_Address_TypeTemporaryRegistrationUploadScan: String { return self._s[4485]! } - public func PUSH_CHAT_RETURNED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4486]!, self._r[4486]!, [_1, _2]) + public func Map_LiveLocationPrivateDescription(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4398]!, self._r[4398]!, [_0]) } - public var NotificationsSound_Input: String { return self._s[4488]! } - public var Notifications_ClassicTones: String { return self._s[4489]! } - public var Conversation_StatusTyping: String { return self._s[4490]! } - public var Checkout_ErrorProviderAccountInvalid: String { return self._s[4491]! } - public var ChatSettings_AutoDownloadSettings_Delimeter: String { return self._s[4492]! } - public var Wallet_Month_ShortSeptember: String { return self._s[4493]! } - public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedChats: String { return self._s[4494]! } - public var UserInfo_TapToCall: String { return self._s[4495]! } - public var EnterPasscode_EnterNewPasscodeNew: String { return self._s[4496]! } - public var Conversation_ClearAll: String { return self._s[4498]! } - public var UserInfo_NotificationsDefault: String { return self._s[4499]! } - public var Wallet_Send_OwnAddressAlertText: String { return self._s[4500]! } - public var Map_ChooseAPlace: String { return self._s[4501]! } - public func Wallet_Receive_ShareInvoiceUrlInfo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4502]!, self._r[4502]!, [_0]) + public func Wallet_Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4399]!, self._r[4399]!, [_1, _2, _3]) } - public var GroupInfo_AddParticipantTitle: String { return self._s[4503]! } - public var ChatList_PeerTypeNonContact: String { return self._s[4504]! } - public var Conversation_SlideToCancel: String { return self._s[4505]! } - public var Month_ShortJuly: String { return self._s[4506]! } - public var SocksProxySetup_ProxyType: String { return self._s[4507]! } - public func ChatList_DeleteChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4508]!, self._r[4508]!, [_0]) + public var SettingsSearch_Synonyms_EditProfile_AddAccount: String { return self._s[4400]! } + public var Wallet_Send_Title: String { return self._s[4401]! } + public var Message_PinnedDocumentMessage: String { return self._s[4402]! } + public var Wallet_Info_RefreshErrorText: String { return self._s[4403]! } + public var DialogList_TabTitle: String { return self._s[4405]! } + public var ChatSettings_AutoPlayTitle: String { return self._s[4406]! } + public var Passport_FieldEmail: String { return self._s[4407]! } + public var Conversation_UnpinMessageAlert: String { return self._s[4408]! } + public var Passport_Address_TypeBankStatement: String { return self._s[4409]! } + public var Wallet_SecureStorageReset_Title: String { return self._s[4410]! } + public var Passport_Identity_ExpiryDate: String { return self._s[4411]! } + public var Privacy_Calls_P2P: String { return self._s[4412]! } + public func CancelResetAccount_Success(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4414]!, self._r[4414]!, [_0]) } - public var ChatList_EditFolders: String { return self._s[4509]! } - public var TwoStepAuth_SetPasswordHelp: String { return self._s[4510]! } - public var Wallet_Send_ConfirmationConfirm: String { return self._s[4512]! } - public var Wallet_Created_ExportErrorTitle: String { return self._s[4513]! } - public func GroupPermission_ApplyAlertText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4514]!, self._r[4514]!, [_0]) + public var SocksProxySetup_UseForCallsHelp: String { return self._s[4415]! } + public func PUSH_CHAT_ALBUM(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4416]!, self._r[4416]!, [_1, _2]) } - public var Permissions_PeopleNearbyTitle_v0: String { return self._s[4515]! } - public var ScheduledMessages_RemindersTitle: String { return self._s[4516]! } - public var Your_cards_expiration_year_is_invalid: String { return self._s[4517]! } - public var Wallet_Info_TransactionPendingHeader: String { return self._s[4519]! } - public var UserInfo_ShareMyContactInfo: String { return self._s[4520]! } - public var Passport_DeleteAddress: String { return self._s[4522]! } - public var Passport_DeletePassportConfirmation: String { return self._s[4523]! } - public var Passport_Identity_ReverseSide: String { return self._s[4524]! } - public var CheckoutInfo_ErrorEmailInvalid: String { return self._s[4525]! } - public var Login_InfoLastNamePlaceholder: String { return self._s[4526]! } - public var Passport_FieldAddress: String { return self._s[4527]! } - public var SettingsSearch_Synonyms_Calls_Title: String { return self._s[4528]! } - public var Passport_Identity_ResidenceCountryPlaceholder: String { return self._s[4530]! } - public var Map_Home: String { return self._s[4532]! } - public var PollResults_Title: String { return self._s[4533]! } - public var ArchivedChats_IntroText2: String { return self._s[4535]! } - public var PasscodeSettings_SimplePasscodeHelp: String { return self._s[4536]! } - public var VoiceOver_Chat_ContactPhoneNumber: String { return self._s[4537]! } - public var CallFeedback_ReasonSilentRemote: String { return self._s[4539]! } - public var Passport_Identity_AddPersonalDetails: String { return self._s[4541]! } - public var Group_Info_AdminLog: String { return self._s[4543]! } - public var ChatSettings_AutoPlayTitle: String { return self._s[4544]! } - public var Appearance_Animations: String { return self._s[4545]! } - public var Appearance_TextSizeSetting: String { return self._s[4546]! } - public func Call_ShortMinutes(_ value: Int32) -> String { + public var Stickers_ClearRecent: String { return self._s[4417]! } + public var EnterPasscode_ChangeTitle: String { return self._s[4418]! } + public var TwoFactorSetup_Email_Title: String { return self._s[4419]! } + public var Passport_InfoText: String { return self._s[4420]! } + public var Checkout_NewCard_SaveInfoEnableHelp: String { return self._s[4421]! } + public func Login_InvalidPhoneEmailSubject(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4422]!, self._r[4422]!, [_0]) + } + public func Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4423]!, self._r[4423]!, [_1, _2, _3]) + } + public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedChannels: String { return self._s[4424]! } + public var ScheduledMessages_PollUnavailable: String { return self._s[4425]! } + public var VoiceOver_Navigation_Compose: String { return self._s[4426]! } + public var Passport_Identity_EditDriversLicense: String { return self._s[4427]! } + public var Conversation_TapAndHoldToRecord: String { return self._s[4429]! } + public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedChats: String { return self._s[4430]! } + public func Notification_CallTimeFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4431]!, self._r[4431]!, [_1, _2]) + } + public var Channel_EditAdmin_PermissionInviteViaLink: String { return self._s[4434]! } + public var ChatSettings_OpenLinksIn: String { return self._s[4435]! } + public var Map_HomeAndWorkTitle: String { return self._s[4436]! } + public func Generic_OpenHiddenLinkAlert(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4438]!, self._r[4438]!, [_0]) + } + public var DialogList_Unread: String { return self._s[4439]! } + public func PUSH_CHAT_MESSAGE_GIF(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4440]!, self._r[4440]!, [_1, _2]) + } + public var User_DeletedAccount: String { return self._s[4441]! } + public var ChatList_TabIconFoldersTooltipEmptyFolders: String { return self._s[4442]! } + public var OwnershipTransfer_SetupTwoStepAuth: String { return self._s[4443]! } + public func Watch_Time_ShortYesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4444]!, self._r[4444]!, [_0]) + } + public var UserInfo_NotificationsDefault: String { return self._s[4445]! } + public var SharedMedia_CategoryMedia: String { return self._s[4446]! } + public var SocksProxySetup_ProxyStatusUnavailable: String { return self._s[4447]! } + public var Channel_AdminLog_MessageRestrictedForever: String { return self._s[4448]! } + public var Watch_ChatList_Compose: String { return self._s[4449]! } + public var Notifications_MessageNotificationsExceptionsHelp: String { return self._s[4450]! } + public var AutoDownloadSettings_Delimeter: String { return self._s[4451]! } + public var Watch_Microphone_Access: String { return self._s[4452]! } + public var Cache_MaximumCacheSize: String { return self._s[4453]! } + public var Group_Setup_HistoryHeader: String { return self._s[4454]! } + public var Map_SetThisLocation: String { return self._s[4455]! } + public var Appearance_ThemePreview_Chat_2_ReplyName: String { return self._s[4456]! } + public var Activity_UploadingPhoto: String { return self._s[4457]! } + public var Conversation_Edit: String { return self._s[4459]! } + public var Group_ErrorSendRestrictedMedia: String { return self._s[4460]! } + public var Login_TermsOfServiceDecline: String { return self._s[4461]! } + public var Message_PinnedContactMessage: String { return self._s[4462]! } + public func Channel_AdminLog_MessageRestrictedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4463]!, self._r[4463]!, [_1, _2]) + } + public func Login_PhoneBannedEmailBody(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4464]!, self._r[4464]!, [_1, _2, _3, _4, _5]) + } + public var Appearance_LargeEmoji: String { return self._s[4465]! } + public var TwoStepAuth_AdditionalPassword: String { return self._s[4467]! } + public var EditTheme_Edit_Preview_IncomingReplyText: String { return self._s[4468]! } + public func PUSH_CHAT_DELETE_YOU(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4469]!, self._r[4469]!, [_1, _2]) + } + public var Passport_Phone_EnterOtherNumber: String { return self._s[4470]! } + public var Message_PinnedPhotoMessage: String { return self._s[4471]! } + public var Passport_FieldPhone: String { return self._s[4472]! } + public var TwoStepAuth_RecoveryEmailAddDescription: String { return self._s[4473]! } + public var Stats_NotificationsTitle: String { return self._s[4474]! } + public var ChatSettings_AutoPlayGifs: String { return self._s[4475]! } + public var InfoPlist_NSCameraUsageDescription: String { return self._s[4477]! } + public var Conversation_Call: String { return self._s[4478]! } + public var Common_TakePhoto: String { return self._s[4480]! } + public var Group_EditAdmin_RankTitle: String { return self._s[4481]! } + public var Wallet_Receive_CommentHeader: String { return self._s[4482]! } + public var Channel_NotificationLoading: String { return self._s[4483]! } + public func Notification_Exceptions_Sound(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4484]!, self._r[4484]!, [_0]) + } + public func ScheduledMessages_ScheduledDate(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4485]!, self._r[4485]!, [_0]) + } + public func PUSH_CHANNEL_MESSAGE_VIDEO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4486]!, self._r[4486]!, [_1]) + } + public var Permissions_SiriTitle_v0: String { return self._s[4487]! } + public func VoiceOver_Chat_VoiceMessageFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4488]!, self._r[4488]!, [_0]) + } + public func Login_ResetAccountProtected_Text(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4489]!, self._r[4489]!, [_0]) + } + public var Channel_MessagePhotoRemoved: String { return self._s[4490]! } + public var Wallet_Info_ReceiveGrams: String { return self._s[4491]! } + public var ClearCache_FreeSpace: String { return self._s[4492]! } + public var Appearance_BubbleCorners_Apply: String { return self._s[4493]! } + public var Common_edit: String { return self._s[4494]! } + public var PrivacySettings_AuthSessions: String { return self._s[4495]! } + public func PUSH_VIDEO_CALL_REQUEST(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4496]!, self._r[4496]!, [_1]) + } + public var Month_ShortJune: String { return self._s[4497]! } + public var PrivacyLastSeenSettings_AlwaysShareWith_Placeholder: String { return self._s[4498]! } + public var Call_ReportSend: String { return self._s[4499]! } + public var Watch_LastSeen_JustNow: String { return self._s[4500]! } + public var Notifications_MessageNotifications: String { return self._s[4501]! } + public var WallpaperSearch_ColorGreen: String { return self._s[4502]! } + public var BroadcastListInfo_AddRecipient: String { return self._s[4504]! } + public var Group_Status: String { return self._s[4505]! } + public func AutoNightTheme_LocationHelp(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4506]!, self._r[4506]!, [_0, _1]) + } + public var TextFormat_AddLinkTitle: String { return self._s[4507]! } + public var ShareMenu_ShareTo: String { return self._s[4508]! } + public var Conversation_Moderate_Ban: String { return self._s[4509]! } + public func Conversation_DeleteMessagesFor(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4510]!, self._r[4510]!, [_0]) + } + public var SharedMedia_ViewInChat: String { return self._s[4511]! } + public var Map_LiveLocationFor8Hours: String { return self._s[4512]! } + public func PUSH_PINNED_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4513]!, self._r[4513]!, [_1]) + } + public func PUSH_PINNED_POLL(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4514]!, self._r[4514]!, [_1, _2]) + } + public func Map_AccurateTo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4516]!, self._r[4516]!, [_0]) + } + public var Map_OpenInHereMaps: String { return self._s[4517]! } + public var Appearance_ReduceMotion: String { return self._s[4518]! } + public func PUSH_MESSAGE_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4519]!, self._r[4519]!, [_1, _2]) + } + public var Channel_Setup_TypePublicHelp: String { return self._s[4520]! } + public var Passport_Identity_EditInternalPassport: String { return self._s[4521]! } + public var PhotoEditor_Skip: String { return self._s[4522]! } + public func Stats_GroupShowMoreTopPosters(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue) } - public func AttachmentMenu_SendPhoto(_ value: Int32) -> String { + public func MuteFor_Days(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue) } - public func Notification_GameScoreSimple(_ value: Int32) -> String { + public func MessageTimer_Hours(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[2 * 6 + Int(form.rawValue)]!, stringValue) } - public func MessagePoll_QuizCount(_ value: Int32) -> String { + public func StickerPack_AddStickerCount(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[3 * 6 + Int(form.rawValue)]!, stringValue) } - public func MessageTimer_ShortMinutes(_ value: Int32) -> String { + public func Call_ShortSeconds(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[4 * 6 + Int(form.rawValue)]!, stringValue) } - public func Notifications_ExceptionMuteExpires_Days(_ value: Int32) -> String { + public func SharedMedia_Link(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[5 * 6 + Int(form.rawValue)]!, stringValue) } - public func OldChannels_InactiveYear(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[6 * 6 + Int(form.rawValue)]!, stringValue) + public func PUSH_CHANNEL_MESSAGE_FWDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[6 * 6 + Int(form.rawValue)]!, _1, _2) } - public func Stats_MessageViews(_ value: Int32) -> String { + public func MessageTimer_ShortMinutes(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[7 * 6 + Int(form.rawValue)]!, stringValue) } - public func Media_ShareItem(_ value: Int32) -> String { + public func Contacts_InviteContacts(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[8 * 6 + Int(form.rawValue)]!, stringValue) } - public func Stats_GroupTopInviterInvites(_ value: Int32) -> String { + public func VoiceOver_Chat_ContactPhoneNumberCount(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[9 * 6 + Int(form.rawValue)]!, stringValue) } - public func Stats_GroupTopAdminDeletions(_ value: Int32) -> String { + public func Conversation_LiveLocationMembersCount(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[10 * 6 + Int(form.rawValue)]!, stringValue) } - public func SharedMedia_File(_ value: Int32) -> String { + public func ForwardedLocations(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[11 * 6 + Int(form.rawValue)]!, stringValue) } - public func MessageTimer_ShortWeeks(_ value: Int32) -> String { + public func Stats_MessageForwards(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[12 * 6 + Int(form.rawValue)]!, stringValue) } - public func StickerPack_RemoveMaskCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[13 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ChatList_SelectedChats(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[14 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_MESSAGE_ROUNDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + public func PUSH_MESSAGE_PHOTOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[15 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func AttachmentMenu_SendVideo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[16 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Stats_GroupShowMoreTopPosters(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[17 * 6 + Int(form.rawValue)]!, stringValue) - } - public func GroupInfo_ShowMoreMembers(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[18 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ChatList_MessagePhotos(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[19 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_Seconds(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[20 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_Years(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[21 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Wallet_Updated_HoursAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[22 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_MESSAGES(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[23 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func CreatePoll_AddMoreOptions(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[24 * 6 + Int(form.rawValue)]!, stringValue) - } - public func LastSeen_HoursAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[25 * 6 + Int(form.rawValue)]!, stringValue) - } - public func SharedMedia_Video(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[26 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_Days(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[27 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ChatListFilter_ShowMoreChats(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[28 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[13 * 6 + Int(form.rawValue)]!, _1, _2) } public func Notification_GameScoreSelfExtended(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[29 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHAT_MESSAGE_FWDS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[30 * 6 + Int(form.rawValue)]!, _2, _1, _3) - } - public func MessageTimer_Minutes(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[31 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedFiles(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[32 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Call_Minutes(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[33 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Stats_MessageForwards(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[34 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_ShortSeconds(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[35 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Forward_ConfirmMultipleFiles(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[36 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ServiceMessage_GameScoreExtended(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[37 * 6 + Int(form.rawValue)]!, stringValue) - } - public func LiveLocationUpdated_MinutesAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[38 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Notifications_ExceptionMuteExpires_Hours(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[39 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedPolls(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[40 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Notifications_Exceptions(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[41 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessagePoll_VotedCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[42 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedLocations(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[43 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PeopleNearby_ShowMorePeople(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[44 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ServiceMessage_GameScoreSimple(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[45 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Call_ShortSeconds(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[46 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Stats_GroupTopPosterMessages(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[47 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PasscodeSettings_FailedAttempts(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[48 * 6 + Int(form.rawValue)]!, stringValue) - } - public func OldChannels_InactiveMonth(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[49 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Watch_LastSeen_HoursAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[50 * 6 + Int(form.rawValue)]!, stringValue) - } - public func VoiceOver_Chat_ContactEmailCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[51 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Map_ETAHours(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[52 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Watch_UserInfo_Mute(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[53 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedVideos(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[54 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_Months(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[55 * 6 + Int(form.rawValue)]!, stringValue) - } - public func StickerPack_RemoveStickerCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[56 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Passport_Scans(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[57 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Conversation_StatusOnline(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[58 * 6 + Int(form.rawValue)]!, stringValue) - } - public func StickerPack_StickerCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[59 * 6 + Int(form.rawValue)]!, stringValue) - } - public func UserCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[60 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Call_Seconds(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[61 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_MESSAGE_VIDEOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[62 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func Invitation_Members(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[63 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHANNEL_MESSAGES(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[64 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func SharedMedia_DeleteItemsConfirmation(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[65 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ServiceMessage_GameScoreSelfSimple(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[66 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHANNEL_MESSAGE_PHOTOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[67 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func AttachmentMenu_SendItem(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[68 * 6 + Int(form.rawValue)]!, stringValue) - } - public func SharedMedia_Generic(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[69 * 6 + Int(form.rawValue)]!, stringValue) - } - public func StickerPack_AddMaskCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[70 * 6 + Int(form.rawValue)]!, stringValue) - } - public func VoiceOver_Chat_ContactPhoneNumberCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[71 * 6 + Int(form.rawValue)]!, stringValue) - } - public func OldChannels_GroupFormat(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[72 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_Hours(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[73 * 6 + Int(form.rawValue)]!, stringValue) - } - public func LiveLocation_MenuChatsCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[74 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Stats_GroupTopAdminKicks(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[75 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[14 * 6 + Int(form.rawValue)]!, stringValue) } public func Stats_GroupTopAdminBans(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[76 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[15 * 6 + Int(form.rawValue)]!, stringValue) } - public func ChatList_DeleteConfirmation(_ value: Int32) -> String { + public func Media_ShareVideo(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[77 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[16 * 6 + Int(form.rawValue)]!, stringValue) } - public func Media_SharePhoto(_ value: Int32) -> String { + public func ForwardedVideos(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[78 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[17 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Watch_LastSeen_HoursAgo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[18 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedContacts(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[19 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MuteExpires_Days(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[20 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ChatList_SelectedChats(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[21 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_Years(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[22 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_Months(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[23 * 6 + Int(form.rawValue)]!, stringValue) } public func Stats_GroupTopPosterChars(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[79 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[24 * 6 + Int(form.rawValue)]!, stringValue) + } + public func LiveLocation_MenuChatsCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[25 * 6 + Int(form.rawValue)]!, stringValue) + } + public func VoiceOver_Chat_PollOptionCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[26 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_ShortDays(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[27 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Stats_GroupShowMoreTopAdmins(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[28 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ChatList_DeleteConfirmation(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[29 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHANNEL_MESSAGES(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[30 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func Stats_GroupShowMoreTopInviters(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[31 * 6 + Int(form.rawValue)]!, stringValue) } public func MessageTimer_Weeks(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[80 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[32 * 6 + Int(form.rawValue)]!, stringValue) } - public func MuteFor_Days(_ value: Int32) -> String { + public func ChatList_MessagePhotos(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[81 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[33 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Invitation_Members(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[34 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Map_ETAHours(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[35 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Watch_LastSeen_MinutesAgo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[36 * 6 + Int(form.rawValue)]!, stringValue) + } + public func GroupInfo_ParticipantCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[37 * 6 + Int(form.rawValue)]!, stringValue) + } + public func OldChannels_Leave(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[38 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Theme_UsersCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[39 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ServiceMessage_GameScoreExtended(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[40 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ServiceMessage_GameScoreSimple(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[41 * 6 + Int(form.rawValue)]!, stringValue) + } + public func StickerPack_RemoveStickerCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[42 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedVideoMessages(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[43 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Stats_GroupTopAdminDeletions(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[44 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Conversation_StatusOnline(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[45 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Media_SharePhoto(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[46 * 6 + Int(form.rawValue)]!, stringValue) } public func PUSH_CHAT_MESSAGE_PHOTOS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[82 * 6 + Int(form.rawValue)]!, _2, _1, _3) + return String(format: self._ps[47 * 6 + Int(form.rawValue)]!, _2, _1, _3) + } + public func Watch_UserInfo_Mute(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[48 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_Minutes(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[49 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Wallet_Updated_HoursAgo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[50 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Notifications_ExceptionMuteExpires_Days(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[51 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_ShortHours(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[52 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_Seconds(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[53 * 6 + Int(form.rawValue)]!, stringValue) } public func MuteExpires_Hours(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[83 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[54 * 6 + Int(form.rawValue)]!, stringValue) } - public func Notification_GameScoreExtended(_ value: Int32) -> String { + public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[84 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[55 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_CHANNEL_MESSAGE_VIDEOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[85 * 6 + Int(form.rawValue)]!, _1, _2) + public func PasscodeSettings_FailedAttempts(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[56 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_CHAT_MESSAGES(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + public func Stats_GroupTopPosterMessages(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[57 * 6 + Int(form.rawValue)]!, stringValue) + } + public func StickerPack_StickerCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[58 * 6 + Int(form.rawValue)]!, stringValue) + } + public func AttachmentMenu_SendVideo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[59 * 6 + Int(form.rawValue)]!, stringValue) + } + public func CreatePoll_AddMoreOptions(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[60 * 6 + Int(form.rawValue)]!, stringValue) + } + public func AttachmentMenu_SendItem(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[61 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ServiceMessage_GameScoreSelfSimple(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[62 * 6 + Int(form.rawValue)]!, stringValue) + } + public func StickerPack_AddMaskCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[63 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Media_ShareItem(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[64 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHANNEL_MESSAGE_ROUNDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[86 * 6 + Int(form.rawValue)]!, _2, _1, _3) + return String(format: self._ps[65 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func PrivacyLastSeenSettings_AddUsers(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[66 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Notification_GameScoreSimple(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[67 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHAT_MESSAGE_ROUNDS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[68 * 6 + Int(form.rawValue)]!, _2, _1, _3) + } + public func Conversation_StatusSubscribers(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[69 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MuteExpires_Minutes(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[70 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Stats_GroupTopAdminKicks(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[71 * 6 + Int(form.rawValue)]!, stringValue) + } + public func SharedMedia_File(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[72 * 6 + Int(form.rawValue)]!, stringValue) + } + public func OldChannels_InactiveYear(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[73 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHAT_MESSAGE_FWDS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[74 * 6 + Int(form.rawValue)]!, _2, _1, _3) + } + public func AttachmentMenu_SendPhoto(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[75 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Wallpaper_DeleteConfirmation(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[76 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedAuthorsOthers(_ selector: Int32, _ _0: String, _ _1: String) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[77 * 6 + Int(form.rawValue)]!, _0, _1) + } + public func MessageTimer_ShortSeconds(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[78 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PeopleNearby_ShowMorePeople(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[79 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Conversation_StatusMembers(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[80 * 6 + Int(form.rawValue)]!, stringValue) + } + public func LastSeen_HoursAgo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[81 * 6 + Int(form.rawValue)]!, stringValue) + } + public func StickerPack_RemoveMaskCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[82 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedPhotos(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[83 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHANNEL_MESSAGE_PHOTOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[84 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func VoiceOver_Chat_PollVotes(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[85 * 6 + Int(form.rawValue)]!, stringValue) + } + public func SharedMedia_Video(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[86 * 6 + Int(form.rawValue)]!, stringValue) } public func InviteText_ContactsCountText(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[87 * 6 + Int(form.rawValue)]!, stringValue) } - public func Wallpaper_DeleteConfirmation(_ value: Int32) -> String { + public func Notifications_ExceptionMuteExpires_Minutes(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[88 * 6 + Int(form.rawValue)]!, stringValue) } - public func SharedMedia_Photo(_ value: Int32) -> String { + public func ChatList_MessageVideos(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[89 * 6 + Int(form.rawValue)]!, stringValue) } - public func ForwardedContacts(_ value: Int32) -> String { + public func PUSH_MESSAGES(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[90 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func MessagePoll_QuizCount(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[90 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[91 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_CHAT_MESSAGE_VIDEOS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[91 * 6 + Int(form.rawValue)]!, _2, _1, _3) - } - public func OldChannels_Leave(_ value: Int32) -> String { + public func Notification_GameScoreExtended(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[92 * 6 + Int(form.rawValue)]!, stringValue) } - public func MuteExpires_Minutes(_ value: Int32) -> String { + public func MuteFor_Hours(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[93 * 6 + Int(form.rawValue)]!, stringValue) } - public func SharedMedia_Link(_ value: Int32) -> String { + public func Stats_MessageViews(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[94 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_CHAT_MESSAGE_ROUNDS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[95 * 6 + Int(form.rawValue)]!, _2, _1, _3) - } - public func MuteExpires_Days(_ value: Int32) -> String { + public func Notifications_ExceptionMuteExpires_Hours(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[96 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ChatList_Search_Messages(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[97 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Map_ETAMinutes(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[98 * 6 + Int(form.rawValue)]!, stringValue) - } - public func InstantPage_Views(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[99 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Theme_UsersCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[100 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Watch_LastSeen_MinutesAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[101 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedPhotos(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[102 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Stats_GroupShowMoreTopInviters(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[103 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedGifs(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[104 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ServiceMessage_GameScoreSelfExtended(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[105 * 6 + Int(form.rawValue)]!, stringValue) - } - public func QuickSend_Photos(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[106 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Conversation_SelectedMessages(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[107 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Notifications_ExceptionMuteExpires_Minutes(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[108 * 6 + Int(form.rawValue)]!, stringValue) - } - public func StickerPack_AddStickerCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[109 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Conversation_LiveLocationMembersCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[110 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedAuthorsOthers(_ selector: Int32, _ _0: String, _ _1: String) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[111 * 6 + Int(form.rawValue)]!, _0, _1) - } - public func PrivacyLastSeenSettings_AddUsers(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[112 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[113 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedAudios(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[114 * 6 + Int(form.rawValue)]!, stringValue) - } - public func OldChannels_InactiveWeek(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[115 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedMessages(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[116 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_ShortDays(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[117 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[95 * 6 + Int(form.rawValue)]!, stringValue) } public func AttachmentMenu_SendGif(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[118 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[96 * 6 + Int(form.rawValue)]!, stringValue) } - public func Notification_GameScoreSelfSimple(_ value: Int32) -> String { + public func Conversation_SelectedMessages(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[119 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[97 * 6 + Int(form.rawValue)]!, stringValue) } - public func Stats_GroupShowMoreTopAdmins(_ value: Int32) -> String { + public func VoiceOver_Chat_ContactEmailCount(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[120 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[98 * 6 + Int(form.rawValue)]!, stringValue) } - public func MuteFor_Hours(_ value: Int32) -> String { + public func Map_ETAMinutes(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[121 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[99 * 6 + Int(form.rawValue)]!, stringValue) } - public func Contacts_InviteContacts(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[122 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHANNEL_MESSAGE_FWDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + public func PUSH_MESSAGE_VIDEOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[123 * 6 + Int(form.rawValue)]!, _1, _2) + return String(format: self._ps[100 * 6 + Int(form.rawValue)]!, _1, _2) } - public func ForwardedStickers(_ value: Int32) -> String { + public func ForwardedFiles(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[124 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[101 * 6 + Int(form.rawValue)]!, stringValue) } - public func LastSeen_MinutesAgo(_ value: Int32) -> String { + public func ChatList_DeletedChats(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[125 * 6 + Int(form.rawValue)]!, stringValue) - } - public func GroupInfo_ParticipantCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[126 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_MESSAGE_PHOTOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[127 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func ForwardedVideoMessages(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[128 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Chat_DeleteMessagesConfirmation(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[129 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[102 * 6 + Int(form.rawValue)]!, stringValue) } public func DialogList_LiveLocationChatsCount(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[130 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[103 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_CHANNEL_MESSAGE_ROUNDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + public func OldChannels_InactiveWeek(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[104 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedAudios(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[105 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ChatListFilter_ShowMoreChats(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[106 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Stats_GroupTopInviterInvites(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[107 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_MESSAGE_ROUNDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[131 * 6 + Int(form.rawValue)]!, _1, _2) + return String(format: self._ps[108 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func SharedMedia_DeleteItemsConfirmation(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[109 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_Days(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[110 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_ShortWeeks(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[111 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ServiceMessage_GameScoreSelfExtended(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[112 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedMessages(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[113 * 6 + Int(form.rawValue)]!, stringValue) + } + public func UserCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[114 * 6 + Int(form.rawValue)]!, stringValue) + } + public func LiveLocationUpdated_MinutesAgo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[115 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_MESSAGE_FWDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[116 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func SharedMedia_Photo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[117 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedGifs(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[118 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Call_ShortMinutes(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[119 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedStickers(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[120 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessagePoll_VotedCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[121 * 6 + Int(form.rawValue)]!, stringValue) + } + public func InstantPage_Views(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[122 * 6 + Int(form.rawValue)]!, stringValue) + } + public func GroupInfo_ShowMoreMembers(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[123 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Notifications_Exceptions(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[124 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Forward_ConfirmMultipleFiles(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[125 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Notification_GameScoreSelfSimple(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[126 * 6 + Int(form.rawValue)]!, stringValue) + } + public func SharedMedia_Generic(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[127 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Call_Seconds(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[128 * 6 + Int(form.rawValue)]!, stringValue) + } + public func OldChannels_InactiveMonth(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[129 * 6 + Int(form.rawValue)]!, stringValue) } public func PollResults_ShowMore(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[130 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHAT_MESSAGES(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[131 * 6 + Int(form.rawValue)]!, _2, _1, _3) + } + public func OldChannels_GroupFormat(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[132 * 6 + Int(form.rawValue)]!, stringValue) } - public func Conversation_StatusMembers(_ value: Int32) -> String { + public func ForwardedPolls(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[133 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_MESSAGE_FWDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[134 * 6 + Int(form.rawValue)]!, _1, _2) + public func QuickSend_Photos(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[134 * 6 + Int(form.rawValue)]!, stringValue) } - public func Conversation_StatusSubscribers(_ value: Int32) -> String { + public func Call_Minutes(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[135 * 6 + Int(form.rawValue)]!, stringValue) } - public func VoiceOver_Chat_PollVotes(_ value: Int32) -> String { + public func Chat_DeleteMessagesConfirmation(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[136 * 6 + Int(form.rawValue)]!, stringValue) } - public func ChatList_MessageVideos(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[137 * 6 + Int(form.rawValue)]!, stringValue) + public func PUSH_CHAT_MESSAGE_VIDEOS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[137 * 6 + Int(form.rawValue)]!, _2, _1, _3) } - public func VoiceOver_Chat_PollOptionCount(_ value: Int32) -> String { + public func Passport_Scans(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[138 * 6 + Int(form.rawValue)]!, stringValue) } - public func MessageTimer_ShortHours(_ value: Int32) -> String { + public func LastSeen_MinutesAgo(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[139 * 6 + Int(form.rawValue)]!, stringValue) @@ -5718,15 +5697,9 @@ public final class PresentationStrings: Equatable { let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[140 * 6 + Int(form.rawValue)]!, stringValue) } - public func Media_ShareVideo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[141 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ChatList_DeletedChats(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[142 * 6 + Int(form.rawValue)]!, stringValue) + public func PUSH_CHANNEL_MESSAGE_VIDEOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[141 * 6 + Int(form.rawValue)]!, _1, _2) } public init(primaryComponent: PresentationStringsComponent, secondaryComponent: PresentationStringsComponent?, groupingSeparator: String) { diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift index f3f64c5426..065fbfd7b5 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift @@ -47,7 +47,7 @@ private func generateClockMinImage(color: UIColor) -> UIImage? { }) } -public func chatBubbleActionButtonImage(fillColor: UIColor, strokeColor: UIColor, foregroundColor: UIColor, image: UIImage?, iconOffset: CGPoint = CGPoint()) -> UIImage? { +private func chatBubbleActionButtonImage(fillColor: UIColor, strokeColor: UIColor, foregroundColor: UIColor, image: UIImage?, iconOffset: CGPoint = CGPoint()) -> UIImage? { return generateImage(CGSize(width: 29.0, height: 29.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(fillColor.cgColor) @@ -162,10 +162,6 @@ public final class PrincipalThemeEssentialGraphics { public let outgoingDateAndStatusImpressionIcon: UIImage public let mediaImpressionIcon: UIImage public let freeImpressionIcon: UIImage - public let incomingDateAndStatusRepliesIcon: UIImage - public let outgoingDateAndStatusRepliesIcon: UIImage - public let mediaRepliesIcon: UIImage - public let freeRepliesIcon: UIImage public let dateStaticBackground: UIImage public let dateFloatingBackground: UIImage @@ -319,12 +315,6 @@ public final class PrincipalThemeEssentialGraphics { self.mediaImpressionIcon = generateTintedImage(image: impressionCountImage, color: .white)! self.freeImpressionIcon = generateTintedImage(image: impressionCountImage, color: serviceColor.primaryText)! - let repliesImage = UIImage(bundleImageName: "Chat/Message/ReplyCount")! - self.incomingDateAndStatusRepliesIcon = generateTintedImage(image: repliesImage, color: theme.message.incoming.secondaryTextColor)! - self.outgoingDateAndStatusRepliesIcon = generateTintedImage(image: repliesImage, color: theme.message.outgoing.secondaryTextColor)! - self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)! - self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)! - self.radialIndicatorFileIconIncoming = emptyImage self.radialIndicatorFileIconOutgoing = emptyImage } else { @@ -420,12 +410,6 @@ public final class PrincipalThemeEssentialGraphics { self.mediaImpressionIcon = generateTintedImage(image: impressionCountImage, color: .white)! self.freeImpressionIcon = generateTintedImage(image: impressionCountImage, color: serviceColor.primaryText)! - let repliesImage = UIImage(bundleImageName: "Chat/Message/ReplyCount")! - self.incomingDateAndStatusRepliesIcon = generateTintedImage(image: repliesImage, color: theme.message.incoming.secondaryTextColor)! - self.outgoingDateAndStatusRepliesIcon = generateTintedImage(image: repliesImage, color: theme.message.outgoing.secondaryTextColor)! - self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)! - self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)! - self.radialIndicatorFileIconIncoming = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocument"), color: .black)! self.radialIndicatorFileIconOutgoing = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocument"), color: .black)! } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 402d5d6a56..8ba11022da 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -229,9 +229,6 @@ public enum PresentationResourceKey: Int32 { case groupInfoMembersIcon case emptyChatListCheckIcon - - case chatFreeCommentButtonBackground - case chatFreeCommentButtonIcon } public enum PresentationResourceParameterKey: Hashable { @@ -259,8 +256,4 @@ public enum PresentationResourceParameterKey: Hashable { case chatMessageLike(incoming: Bool, isSelected: Bool) case chatMessageFreeLike(isSelected: Bool) case chatMessageMediaLike(isSelected: Bool) - - case chatMessageCommentsIcon(incoming: Bool) - case chatMessageCommentsArrowIcon(incoming: Bool) - case chatMessageRepliesIcon(incoming: Bool) } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index d19f50e512..42eaae3348 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -1090,41 +1090,4 @@ public struct PresentationResourcesChat { } }) } - - public static func chatMessageCommentsIcon(_ theme: PresentationTheme, incoming: Bool) -> UIImage? { - return theme.image(PresentationResourceParameterKey.chatMessageCommentsIcon(incoming: incoming), { theme in - let messageTheme = incoming ? theme.chat.message.incoming : theme.chat.message.outgoing - - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BubbleComments"), color: messageTheme.accentTextColor) - }) - } - - public static func chatMessageRepliesIcon(_ theme: PresentationTheme, incoming: Bool) -> UIImage? { - return theme.image(PresentationResourceParameterKey.chatMessageRepliesIcon(incoming: incoming), { theme in - let messageTheme = incoming ? theme.chat.message.incoming : theme.chat.message.outgoing - - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BubbleReplies"), color: messageTheme.accentTextColor) - }) - } - - public static func chatMessageCommentsArrowIcon(_ theme: PresentationTheme, incoming: Bool) -> UIImage? { - return theme.image(PresentationResourceParameterKey.chatMessageCommentsArrowIcon(incoming: incoming), { theme in - let messageTheme = incoming ? theme.chat.message.incoming : theme.chat.message.outgoing - - return generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: messageTheme.accentTextColor) - }) - } - - public static func chatFreeCommentButtonBackground(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? { - return theme.image(PresentationResourceKey.chatFreeCommentButtonBackground.rawValue, { _ in - let strokeColor = bubbleVariableColor(variableColor: theme.chat.message.shareButtonStrokeColor, wallpaper: wallpaper) - return generateStretchableFilledCircleImage(diameter: 30.0, color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonFillColor, wallpaper: wallpaper), strokeColor: strokeColor, strokeWidth: strokeColor.alpha.isZero ? nil : 1.0) - }) - } - - public static func chatFreeCommentButtonIcon(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? { - return theme.image(PresentationResourceKey.chatFreeCommentButtonIcon.rawValue, { _ in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FreeRepliesIcon"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper)) - }) - } } diff --git a/submodules/TelegramUI/BUCK b/submodules/TelegramUI/BUCK index 3ecb8c9add..0689ad24b9 100644 --- a/submodules/TelegramUI/BUCK +++ b/submodules/TelegramUI/BUCK @@ -206,11 +206,6 @@ framework( "//submodules/ManagedAnimationNode:ManagedAnimationNode", "//submodules/TooltipUI:TooltipUI", "//submodules/AuthTransferUI:AuthTransferUI", - "//submodules/ListMessageItem:ListMessageItem", - "//submodules/FileMediaResourceStatus:FileMediaResourceStatus", - "//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge", - "//submodules/GalleryData:GalleryData", - "//submodules/ChatInterfaceState:ChatInterfaceState", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 5c56e9e3df..b52fe63c9b 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -201,11 +201,6 @@ swift_library( "//submodules/ManagedAnimationNode:ManagedAnimationNode", "//submodules/TooltipUI:TooltipUI", "//submodules/AuthTransferUI:AuthTransferUI", - "//submodules/ListMessageItem:ListMessageItem", - "//submodules/FileMediaResourceStatus:FileMediaResourceStatus", - "//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge", - "//submodules/GalleryData:GalleryData", - "//submodules/ChatInterfaceState:ChatInterfaceState", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Images.xcassets/Avatar/RepliesMessagesIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Avatar/RepliesMessagesIcon.imageset/Contents.json deleted file mode 100644 index bbb2d808f4..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Avatar/RepliesMessagesIcon.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "repliesavatar.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Arrow.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Arrow.imageset/Contents.json deleted file mode 100644 index 9952bcc0b6..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Arrow.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "ic_addresult.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Arrow.imageset/ic_addresult.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Arrow.imageset/ic_addresult.pdf deleted file mode 100644 index 61096012ce17feabf108e180cd6068be96e54a42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4138 zcmai%c|6oz`^N`U7_yXzRNvf&tTSU~F!p7TC2M47Fc^DG*2YqnY}vCELiV+rB5U@F z#}Gn=WXqOF*5o%@es}l%JkRU-zRt||yguhT*PJ=m=a2UV)y1lcAViT6XvggO>{8+9 z`~4kV5EK9h+-;m7GBN;63-9JYbOg{8Ngsfz5?qLQPs-Z`N5o_Cw(fR#Ku!+gMfAku zTp_+xc!M784me}ume6%oo>9N~UJj4}q*zg{zK0N=!-x@9|Iub;ue1 zD9MAaG_fy%@S;lU6M`KUb%I4l7EdeK6A0b|njrhdu|C!>0%6z%8VikhsJOpNS6Z6X zBRa7wj35MW0Pra4X`7u?LA!9(tuZYQt=oEKEic@pL)u*2p3#J_hmIPzErMfbK>iJn zR%PbS=y<^-!6fMNM8Zv3wi?>Ji3ALhIX4h}tf!Vf}iu6_d!x=x)eR6E1fc<_@%YYE+xSZazOn}e!MoZ0EFNWcSo6a|AHGq@?~8|1vES@)+0O$Hz;tmA z-@iTaZbSh2!y5W{FZXMnwsdlX$ z!FCOAgj;m^yj~pyCGbcn^Bx63REEW60{J**sgn(=J2}M;z%MpZ1jND-V(dBexn(@l z9hEMdg>ym;&(l)*W^%Yp`Zya=SuqW|6B*E{yclqfc^dScL*8{-eA8w14AAV>g~7q? zds-kkTe?;6JYSAWq?igD=f}u(Y7kv=2d2@JDS8Lvm<94JaF~e#N2=Qtu#Th=WD1bK-eG-D-lOGs`esU_j$MwawvA)z^e2#T zm{nU6ox(FZngLZ~y3=b|rFdgrD%SJP2|N#|i&Zk>nI4>e5_>V>>I}`%h#XwnDfTYT z=?IgUDH|i)D;o{?aGGtJkrv0CxIGIpWamb{&G5qlWBxLEZ+daoyRjl2M%HoGW>u*V zX-09&(%eklPdVRndj!^9QwQh=`|sj)tz`O_+n$E=jA;G}URsk~By#wIpQf=hW%vOp&M8Z&0Z^Uf zkyM{lE75FztMfq`-wM9gQR}rTv>JruRT<@|PSv!@hN-J$-434QFbwj zD}$?2c)LYGw)5|W;e$b-UVixq` zX+QAVI$H5=Yf(;AP75vzmp5hmM@d5IfMA|!o^+R_?c=LaYjV?Od{vQ|>WS*H&o3E_ z4ozIPt|!-!8J*`G`B0_khVI;|wydrEM zvLt-YM8M>Tv1`p@)o%OijtplDTT*wp)3j}MU%~j?qw->rN$-{wl5~be)i`^Db_02n zX_IGt97ajt6fqFP)SwMOf?oZ zsh&}dF^vHVWZBQ1J{iF4?OD9_a$GZFdigzM`lBe){7&`qYtKis#XaPTc&k>EtAb}w zpF1jOi({X9RC{9arzhQ_CWiFx z>s2*dJ65mGbtyej(wk}$=`&+;>2x{ivNCR8UGDTImB(`6i%rk_sP{V1w~0%WCZsDn zQ+q<5-UfXbGx!DM3cGa4h5N8)x zBUbchlSZYJ+Hm3GgT)Z8xQw}`#emwC%-7v-Mp`Cag9fQ$X*y};&a!bg20h!-buL$Q z?ZmyEDr+Y%^71^)f)&By(wQKmVs-oA=_{6L%#pcOE= z5ImT9rtgf1{N01=8!?;d8PzWwG|35lC-2?GWcviJD|LmfMMrW(NL?Yy)z{3t%&e%Y znCJ;ft+ZTw;#vHrY459HYNLp3k64qdZ#l$;*D1peQg$kJ+K-Tn$wI0%s*7snYUG%mm~sDy z8=aqeVp0dE!&(HsJ>E(gD9sM^*=usJdEW8Kd0;7BUSp!D8MFIp({=NFNfKzh!1Ge23&~ z6!Jy@FinD;7X_XV5uK9lH$X-lhW|aIGLDFIad-HE?_NK-{Vyy>{I-FD<#yL>DV^R0 zSDdYm0bqpp^dh*s0SLG#0xgOL%r7WiBe>WB2#hKMjxZMh^seDNiT(hE;lE?QFHwMk z?v$pGNZEy@a5&@wR!R&m28T@~ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Calendar.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Calendar.imageset/Contents.json deleted file mode 100644 index 65cbf323d2..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Calendar.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "ic_search_calendar (2).pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Calendar.imageset/ic_search_calendar (2).pdf b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Calendar.imageset/ic_search_calendar (2).pdf deleted file mode 100644 index 278f33aad875b83c8ddf90e60aaac8f8447ad448..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4358 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 diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Contents.json deleted file mode 100644 index 6e965652df..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Contents.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "provides-namespace" : true - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Files.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Files.imageset/Contents.json deleted file mode 100644 index 9c2974799f..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Files.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "ic_search_docs.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Files.imageset/ic_search_docs.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Files.imageset/ic_search_docs.pdf deleted file mode 100644 index bd6b9cd16c2fc2f6d3747aa9c4bffa3c7aece788..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4216 zcmai%2{hDg`^PO)7=_AGQhvlpiJ4&rm9Y(zBwJ)>W(>yO*lBE8!pPpBkhScJO4jU3 zPm*jELPPc}A;aSx)$@OP-uL~V_ji70?)zN#b$`!2_x1gq^SQ+J(3)pp(z0OjR@yRc z_WsJfH?8epIRFZ{;T*x|&jT`-2(I>?4gi7?G5};WiO!w`5@U46dJ@nCyqhfnK%u}M zo+JX+1?@Wp=Fwn>NC&8h4hij2rvu93t|`8D$K z+3&X^~|Z z;}|=SnrJhM9jMVhmSl%5^|<(v==y-pT(TdV$p%Fp;&8d@>jEjLU%MsOJ5>T@i}Ewx z%MxKl7CDAgvzRs7JcXhSlLP|RXnCXh2~q(lJt(@rFXQVIzNCc|?$!1Lg?KEbv9B~@ zMnw3qf1S`DJV>{%<4K`hxd?fYyWwY=b(`IhQH%{jEYW$O6tJ+6^L_)g1`l*o;y^zgmt#<{OF*F z8xtGrvLWy<+~gp7^A@>u{-QPaCZy1@j60AQJ}4YNbvACSXK{soXeh9U9;93Okqjod z+WxWvW47y{UEBN^Ves9>-6{Mxb6xRsCjc^fSoM0U&<{ z)ZJV?8S5Uvt~m{MP4`Q0cl|H3>XF>=h6GQ*oFR(V1S|m=bvI`>lA${mPXKn^tnL8= zYNK%s7yY>aOWs8IA{J&;)NHo?w7c``_O}#%-S~hC}?3-SzVI zZw`tA;;VxG9KhRi7x+N>QCh)zED>Q4Z}c8b-UE`xwpX4rVkp!evv#-!=$$r3TpN=VGfqdHsM{EZL&CcsH=6I z0sO14^oYK5Rd+ypafBQb+NrnV2wWH z`9O#OjV0Nzvh4_b2lR3^MG_tXgY#!|I_sBxjG7A%X za;st;i7V#2qC+N=Z-M>iXi$_EuHe8xDv|c3({6LA((pSDb~?F5XY8i$rS8?LeYc_G%X^rrM((a0k8rk1Y#rYx!5K3*=j_3pMhtkM@)h$^5*s;EIGuqtej+53j0JJY)m z|2Lq^p-A_=7ND!o_q%iM3&yB`Bbv0K%p)O0_Rz09d^xH`@%(w*X=?pEN6py%)K&Eq zcv;)jqvClIRldK0JUD#nMp3r9p3>?GPr({?%Nv{b*xmqgd$*a%VFyxKda?TlnVtj> zRh$!I$250+Li_UFWfOS*VeDcxKZ@&Vv%uKVuS`y^wJ2Gwa^SY;5Xb{`Mmik*1U}oH4c^cM;l`icdAZBru7z zO`MH+1@$58Aa?GY5Ld_ZBjZBufz^jC;I4== z&{OUKN^S6L%l!ArEvtMI(6pS}kyr4{r^s)@cbrE+_G7MTiL)h)JeZgpPR8 z)S3)4-Fh_nB3TBLBuibPPP`~hDo$`pnWYAZ>n0CSeW=#bSrXPFK^Oll*s5mHZ&GP8 z49Tr9&ej~OYE}x@(n!xq!QI5Afm{7)Cyi-kUVtwkrIkewBkDPB5pPqzTakX_YSx_BKOpHe?Ns|al|?fInw*2 zi(|X*cl%^^XIRVaeBO$jsi=wauikY0)-t0slO&if*dTaB&`6Lf*N!-n?wbB0ot8c& z*HPtQ#`zdzMl+jwYz0k8TX=QFPP{TkF}t*E{{ERBRNip0QYrpHrHOs+<;vHGEEA>8 z%D)76$Sk+Ckjmvf)MSAlnD4wHJWja3Vg5~OXVDb~L3 z+3$=#q)eF2ZobkPh@J9ob!iOJ%Fp&wOa0fu!0< zdg4_3>8aQy%MHR-0*D&+D69ufZMBTTYn5s7X-%-`sy_bY^~icT7v%}Zk6g3+NIm7au4Cp}EXCVi7g^_)soUD~i8$4XkdXLDw18tsFugV6IAmJ+%# z;h6l6<~XmubMFS^AIK*on`0B(A5L8BaNGX$O+DaH@wA?7?z0V$f{5bMc*lnDV&=Z{ zLw?cZqG^fkthFa^%%aV*`#<(aJ;_5711<%OP6zj99D9B2jPjkG>#LNtwDiiC_S)SE zulerYQOxoQTvlrjUy6wmh&+4M6IE07`E5p7McGJK2({d5=^3f;Lp^=Vh+1r4rf7MN zoI$RikJygwKVF-8+j=gLIQ@9hrGDDIa{Kl~>8*x64tzEtB*?4$dFaF6=(7S*1CHDaCYCzEg(%AFj52?V?b7C&C*g|9rBZ(o>uj=tHl! z$GvF%>eMrvrhIYaL4)FE|C-B`?U&EJKE%a>`_UuC4a#dF+u3ydYDdEKobHBhN>;40 z&vlNiWnYMk-Q?(c*b4nW`FWQ@e?T+xZ-CzA*WDNca<$ae)vz7}TVNMp4FJo(n0ObW z|6=058QTMp(IMCpv1)Fdw+;QCh# zii9(&@XwemLXL6m|BOMQaK>H!TMP+hRO{bj^3eaH_uum586DL>_sYsC{D+<_68_tn zJxN%iGlBG@oi`-<6Bz#&kTGy`W0ZWitcd-zh&GJt6lYPsU>`};)HBuDOkR?JH z`%)n>WJ{K8L;j<;_y6|(mgo0)?;OPiRQACD-xH<;wiN;e}EYcILinha{(15%=*uxW# zM!JA~sjsA6aYe~6MQjCvt$^6G+Iwbmpz1E_co6FY;a6>2bZZ8W$$pSime(}3zC3SuYld!s((cSH{*xRY~&rolk zDy#Mr{D@49;t*~mt6)1<*)lEmcqgG5d#n8nS+nl=oz_k<|3Z)myFr#qi!XQAGud13 zu1()4LVD?sxyj|Pl)}Jf0rwc(dpta{%hJicjt$Xzr%H4+1uH7L#A@n%N*3~89wB3f z-hIcu?}+3+d2x>jFu>U&^Z7;6pef^eubc^ct&iqAALjSTGRku{__}GQ%jrEEbq|j& z9$gjDzMo8(t9;^WwQ%(F#--$!u42oZUym6z?Mjh-?S_ltXy*cP{%#L;M!pj^y>{My z*LPy~J}q8F6@`bK)Wx#baEY|k$Pb_GG``f~E#jAi!r~joUXmLWF8dSQET%d^8`>Zt zhhBAXU2-6`&B=xW`S)88PkWIwGb}w+RcY9Tg6k3i-(y?twP#6kZ88Ffp#Gv3x^hN0c%I`Y-^FNnA>gZMvN#@J&t+#d` zbx5<+DKLrLHZeR`q&3&|1RmA1&}ad={k_=xs&YoUQTHhkz}mBZu5%PLt}7*dSbRE$)E!V>Lz*jCMu+N;gXH zFiQ_p^RShbmOUIi>`MHWz}NiT(15r;(&6V9k9PG0;D08wA=(4yg||a{0MdU7RB*1I zlyMK>umBhyCiX9ThvR=01bsZt&Is)ZSWt|rssmPlxC#!7!yCCF?a;ts#Zd8p0W!Y~ z{ANS;Hygi-3gV}h_*Ke+#FY*Uiz}t70OG1>Z;TzgF+9w6gkg?K`t}1M62<3NcwUR zgp)**YE=E2TjBuJwUH(w5e1Xr$YaQVBrx6f7`s_Kd%mIbZU$r~kH@Ts`=w+~e1rZ( zme5o|JSflduFxlkf}3<`!ZocdD$>^3fr0J&x>QiMOq;iJkUVURgc=+7*O)aLD*Dtm z#7jJL+z!Gqhsw9eVI~$7qh(ukV&pD{^yQ8H?rOEsjxFqbYU`D$TfVx)jT)xgP|lFY zxO6pYAv2du$E4*7zU5`J;aSI>rm@LWK}P#>Z~V!|tB@sPj#7M%*(7%9kvM^aYE#hs z-pT65G-Ik7m!pcLsYiM%0M*(Ns+x)W>!+fy*2!(3nS>eRmX@#{)Va=L=fB91bHi|5 zO;L!g%6PRoU4&v zj19h|GDO5UHbhW~$1nx#V0Z>JRXtL0z(~4=*U=%@5mE=dMz)G-DYVU9-H>$x<|6b75 zFlo1=mZ0mej=3>1g=i>(qndS~)Dxi?`mk>-?D2B=0pzh!s^=kNRhRha zHCnnqsrs_tr{jF}VM@D>L!P;|g>#Dc8J_b zk@$v?#nxj}kX7H8=O)-z!j3n4e6ziIx)B`A7kp|mk-xo3_zr_P$T{rz>2yF4D|n5C zhklNJKjK7-q(Nwjo~J+*i(i|it;mzxqe?He_#jtP*tDVqISmCxGHXJwX*mg4LF6EW z(lxFX6)ELO&S{Go0YZAIBN;v!He$KLHm8HNe-!Q2(HJx#C=WdJ>#wW1 zQgiV=`aSfdhD;%OAZ7mb2LBhr1_N3MZ9zIq;v?~ps=Ex2wm@%e!wtC~#;3$L#3!yx z&b}Z{6F*2l##kS`6iM4T=X&mDnn0R;8g*J?nXWNzh;1ml%%x0k5b6YRLOP8(k%z>} z6Ntq`pB!SgjpV`1Ui3m$eT;wIuG9C{1-XS3o-Ce59&R3Eo(#!$Def%Stj;V_)|_NV zjiWi^GmtsSeD0YwG%fS%+iUhh)p3Ztipu5U3w`p1kpuUT~>h*6z7`?5g~<1*9q_TPs;Bq4SE-=-|Y)8}-B*B9rs1BSf-Xs-Yvl>Z6(2 zBlY12h}O$*ZWj36+d0oD7iSum%`whlBf2bVbYW4H-%P~ph^b4>Le*~T`?f4+E4#ps zD5q(=>Yk$U*=H4{7bd-%mIE(kSyhd5G+b>UZZdBQkRfCkom{kUG=X_*)YJQHLqy=v ziN!Bdbv>)`({#cC!cPXYiYK$pdPI7T^%O8Naem_*yugL;bDyf?!zYQM#Nusy-SP&F zKcr2YFYdm*GLkUo-R9CZJ}Ni1Uq?QFT<3U_MqEc~ampMIkL1sM zo?iM4p8hHZw@j~IdXIlbD(xm#-mz&mb3b>Am;dA>%RvllODRdWa>M3@4+%7q(hyBf zo@+lpm#}KJjowQFWkfuU=u^#Tvx>3Ps?=iFnx@fHdiKfdsm)sGOlj(!)B+KBX_wbGr=Qo|xU3$(nKPKH0h6Ne+^-m-6v)X!QOOwbv~5)tR{>a=6m+!;7mv zR?YQO-y0VbT>ux&v2We#-3e&kN!QqkL4yYdRgE_st5;^*m0u_uOc5^hSTJK>W7)CG zvo^gbWQ=j+!^Gi}YR z$9gM2SyhnUuH~=QHe6q7G(lRp-#Cs*L?zZplsqM9KX%d>DqbjD2<1u4nk6g*)GlYg z@Axp%H0csNK%GGQnoj-{8{f;|H(UD76-q9zksqfXwGtQe7wVfFf^C9Tmo-weAq)N9WB)31AcJILLfBLQxz*;`94=;GJ4U#>LSpD3w&A*bvba~7# zHn417ct3aZd9Qh_dEW5H;h5)z(wG39=RG9!x???x~F`7%}jrGWmV-wcWB0A>(v+d(hmgko^eLGLnXrMQebu< zVL56)Vfa)-PP)xf5N7__iVI=ht$IIwy5e@@k-)iKE3){+W_=*(_CYo;ZQRk_uVRe> z2i70u<^w18`{+uKTPwWvm)&>Vw_BRd7Y6H{%PCW+*dN*+u`eZTEIk;xL9D*zkQh~` zB7Tsz^H{I-2(grSUcE+rL8C&07{3!g?*C-t^|$W$jDhLMCXpY{x6=B`bAx=y1P9yB zwr|dTiq~KD3J@88=358QOfA|2df61T- zS&EDQDTB)*DAD?-3<{N?`0`(52x-a{=ASYcRQf;I`wx0BIK{93oC}wg{5N}WgzRr? z_QWGGSTz3UFB2n-Kbo>%K->_AqlEl0tdw6TTCVmuO8o!qt5QzDWrUO@3~C33%gRVY w5eQo}T2jUiZVR=OwnxiC5fTV_@c&l%^8|T#Qeyej(ULHP6j(?|O-~*Ce~-QlM*si- diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Contents.json deleted file mode 100644 index e4bf34c537..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "Files.png", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Files.png b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Files.png deleted file mode 100644 index 9785f3ba7c148bc3d5bfdffab29ae7906c2b33c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18218 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 WsKVVMK#sxx1ZT^R&3VQLkN!Vv3v@gH diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Contents.json deleted file mode 100644 index 1169595a34..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "Links.png", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Links.png b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Links.png deleted file mode 100644 index cd6782f0c9f4acadb1cb25d1a901c3faef764f40..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17164 zcmeHu2~<<(*7gA`INPdK>wpxCGzx?nf*@M8Y7npyR1|^%0!A4V#$W(z)!J6D8i`^E zwi-|nBch;S00m1Gg$gpskQ9&<0|X2qLJ0i(B!G6fcin&exBvIuwQkq4#xuNUdiUPX zexBz&vE60!tm%uVBM35U>z0iK1erpHKLb-WVawK=pGx4bX`x#@Xb3W69{NWENntNS zkoV*ix832powwQfh6I_A{6c)lCecBma5RG0J4T0+d=HYt^?b+ylwb#aNnx?R9>vc= zf0vcBsdMNC@_xz|I+eVG?&9W4Kj@3|(|25}XCG|`0|b%7NqW&ifx$GpXa{}OxOVUx z-Hg@OQymh1&_RDadO^M2&fE1igiy(PRwmZQzGjxTdNw!{Giysz3u_}ib5k=M*3=ej zW?^h#AZndd}ovEq%P-mD4exz{HzjknlAH_fBUpds-*=}nvEu0kWOWwNCK_70)ghKJN zv$nRfBH7sb8k>=QNXC{{rWVE|Gnfgs7QQ$uYhPb$b2DGneZlz~LwqC97l@v(p16J? zzA(n*JF@ff^D#B`xAcS4Eqsmr{Vj3EKGrx}V{;#03k!1`nQUuqGclSgl>)It3Ve4| z^sfA1L_d<5g}J4TnK9YQ!r$1^!p926F*7qZw>2eM`1x2`!vFR4{Cw^FL#RO{m?@MX zQUDnn8XTan_x5f!gan3AokRTKom;%Sed~q|+o>V`lt9=)BW(UmZ|kNF)@C?sYh!a0 zGj!s^ZNTGhq0rzf$Bc*44szJ|uRw~PYJS?0e9^FQ(Dy~>0~u~ey_xbCulZklAoclC z`^hlqKba-!!)PJ?;ZYZ6Z?MKhB6!(jjT%~bo|$zBtOLrO${PUd3x$7D7q2gA@*$kpYS=6(-pzHsZt^={GZ zo|cx#rk|3pe6e8q#cQp~QP&r~tM%hvd0xy0wg}=%M<|4?tZU4W14%caUrc+YVuQG2MIbpcWYWk1<1U!a&T};3; z1agKVei?$;=04AKO{Xj_595d|bgFq=CbhEEbgChO?Ei5+_NjDaXb4v&txQtfqgIB5 zgy2MVM-ZgsZg-1QT3T9uQ&83BLm$0Id0EbdZ_DwTZI z_5})Ab@7-oq+X~jVoT$d_3^hkP<&lT8Hy9}hYo$sJ$_C!)ZTtcb@24qfohQ`qoPlD ze@c2)RaM;%xTH9%)k$h!c)zr?CD=wJMv#knulOC?wis9&A;|Nc`K)vbb@AoXF&wX# zQxU|ywuc+GG%HSdCO1X{xtLkN@8}@7TVoN#%{>PWeIYFkb-luDJ8h33S2BXJwTiZD zfqv+3A1vuA`1KhNaJpndai2G$ouLsF^KO>m|}ewMJ-(NPo=>@c+i z!;U27HNgdPp6-zvj&P-KMvNGP5ZOl?XCGg} zFVp7E4{<_@x(p=Gri3nC;GgwEwYA35Lx~7(#c6Lr4^BSXm^p}!eV9Av#)(wzbq<3$ z-UD+t=NLJOKS&7h$rfYA_Ow3=T%VkZcpy6YvxBpZ$Qc z(7$jQoF`v&?AIq)e`@u-Z~)MK6Ki{Z*_*ZGQ@ z61}$0`K2sofxluodh%Y^BSc<3L?~rXGMD-Kh2Y1Ms);!iwRdkq9x<9!HyC<}^~USq z%fb_xGD6`ij@N~=}j&|QHf9N#Obd`_SK#zGYctc6<0**je(I+s}u`%+fMnjsfY z%Qh_A>#8w!oam!fXo_51_3f-gh=cn#jj}f*2=V+w?ah~14J8kwk#+71mMlFv{>`kn zU~cPbAkk#iHlHKcrC(_vt8O+3_uH>%yeLuBtw0do6Q3iOV8;F;=jV1H>kKdk{IX{d z-&-)VN`IkGM}$_hN`+@V(91oQ(QiXu(lz0R^NDaf@$jH4xdWr4Cg+g^otq`X8v}DM zykrYnTf3jZLv6wE`TUpcy>m1*_+<{qNPYV6BYlcG3#V4Sr=+`D)1n^Z#>C!Z1uent zw#kG&Nm_{gdD&V*>*A;Py9|2sIS$1{UX7)5%`GS@o7QL`@rLjPUlm-J#HQ_du;WUD z6UM+v^dj*F%fovqb@4JJp?*id(rNSV$Hn#&S=CGAKex8=X9U@vBz3BK`nfieKwmXT z5EP!@7-4M=>3l~%i> z0WM%+y9T`DTFK|i!NHx1k1HP?dk;x?ktrus4$=8_am?JiM4n4C91|3K=tWmk(}mUL zx>qEzhA{p<9Vj<1sWI{KQENLsrdCQM5=ae5!j3ImW{Yljd%JH+dW&waY=D7HhNEqu z!o8#pjf~uD365RcR&j^OGbz zV6$ZkLOcgSx7v3Uzex+hZiC7~Hv8ChCL(n2GO!M%o;YQwjr3(gf!MuZ-s>_5#)+SqI9wEE zV~+^kR2cG!QWxfg4;|(Qq_zM4{F~kbL#$y^WBF9-IeJNEdy+f$Gwv;FUiAfvttlSK zE0acz&=?`wLVB4uiw#z`2Cj!<;=6~NX8#EvKby$inen60 z*_51lC1y}E)<%YYb@Eq>cv*?V&Ri=Y*rE4!yDgDdIZ4VkIy4(7-hHPD{7dLd ztpxbMnLyH+!P5bMFa)s%T5rz7qHD&>yKtH(zg({87bjt|LM zw9!H_+4YX{X-J%OOeO}rJ@hR>gGFQUKhG45Q5016OkF605Dfbq4MlB5;mCNOV5vP^_G(HBcIw<+YLr+6S9icM&OxqVAe$ZWC zEP9ye4Z{ug8t7hk$9kEt2#;+OrS|!Y5nCoZcXgM>9<&h>0l?-{i*I*3VbJaix?Ayy z1Z{}Hg36#EjTiKxqaInMCy8xQ22uAl5nF8l&9r#*-T$W{<#+D)ZFwep;4xzCUNvN< zD09RGMUr~&2j2rY+lBwAyg!SHAaOn7+FG(lXH0KF@FMNnGIXb9mDDy7KMlF#iUE{D z_BclR87h;GPit|R!%t!JosbgAUhJ-q%5!9T??Z*Nu9DhUp#|z|*~S|Cs%)LZ2}GX@ zvQ2s@dOr1@_Hn3)KNZbHu8^FeK9{83?&k8A;71?Tj}3RGDGLQ-R|K??p{U%}r~Wm8 z0S%*_Ht6S$lU^A6SXu}un)%8B18^Y;?>%%FkHIJhGa6S`-)V~WiRpjN|4-4jKA@@E}qxJRmF%J;rLyy2vOV0Od3;^}sKj*$0ZpYN> zAXhGn?dsdZEbEE_+gIdkB;buKZ1wnM)))hYIU@Y^7zqG}766E3S|tvW2NJz(=fb7Q z#5P@MKP%vMeHHQE=FZulG-E`~5iPcf6*q5j-b14CUiEipCLO79YQ~~rsh-RPKnWip zeN*bz^bRt3Oxv~mG6_`&7!BfCxr#ViHF$d7wKX@s2UbIuKhQjplp|8VRc7X*TRGok z1}?j zs#$24IJEfEko~M|iDXYab1Q=U;;BI4obAfAPoR3ibOi`x#-H-<{wdx&r=(^z_lfi# zN}$P%+|WkHM8qZ>aF~IVxSX~!iCWFwhIT^Y^$qUz5ez}rN<=tQ)tAPkKv?{;O&|x# z427>;)*Yb5AeV8z{E1r0kfab1;n}+atqV`3M#*=gq0Sm!gv8CLA^^huhLXa*q^-iZ zOLEH6qVb3d8BPLpj?##0(n=lz zTDOH>(jOPV+LuFQccnruISAAGk|cJ<%QF?Y!ntz*=UxQ@Qk&zB_Wgu!0-nWOW_lje zLD~)NP_ei)fbg`|LOk!v$Oa+3H^W1Pg%CkoozKhSbLgq znM{H>Y_a0SRA}u&#&}xL^Cj2qY!vKT*?;f>pzJ-AUx06W#P;ot%LSf$IRU*2MAsvd7-HMq6S34C2kl@p?-@XC2JhjP(#WvYkPg zU2_GcckAQ6rrjh#60_zHTnpR{*fTpAuxJM@dDT{oa}AI&2+$@Gv6Qhnr4AonSco8* zFJw}*0pe_VNTl9wm;|YJ_XO&wsDySE>U!sGPgGJ)faLO*7EYL#Y=%&G9gW8~JxB$> z*UV$KX){V*X7JYwqlz0i#nH`%Yw&HAMoH_i}V;P-9HDcNvsR~3pX=U3h{Z4#kKES*Vxu+TIiJDH5@ z>0h$XW`c;k^F;g2%EB25FS;pMna!8bn&wZCGxUbTGaS9a9nl{Oys!7o3 zkF*-f#;MXuv`ScB{0B{8gB~!`Q-tS#H{dDSVCu9(JAjpFrcX~`ktf*FQv_;8yzl}Z zGrI$k?LL=%0A(%JNkE8e@Is#ZyT)EE{y;^a7F?I6nDxzwoR27MhejjQii?Ooxw7fc zYT^Pki}QMy-lI+1w|oMELuOFSi`%Oy_?|#zOfBJ5-ZgFNfwyvJM`a8nk<+J`_a-Z1 z7f@doPKaWSn)-f793HOU6Ms6c>$S~Tf#X=?&SH^Ar@$LeS#->?rQ-FiCc#Lj`IN%$pxXLoTtLP--RsOYmpL$1n{-8=9wVLFHpdPB5ekAq zUL1}rNOv-K_yz@CE!}e6MUMs)Wzb)CQ|e>2x1jAQH;lev}FSfKyl(` zA*6`Nx{IuxDDCK0!^^(#vk1s?H5o}S=n}I6Os=Gj8MC*;xScQLXmfAY0|_0zi{X!d zQ;57Xf2-;5XQGLD2Oh*Nq+#pLCohA!OiBvjwUOQTva~`;Z0!_;efX>i@c%I$$tcdM zj~K^UFVz#6%blky3N0tM{}UkMa|EjBJ4E8|VU~@X9(T=X5nso z10-RginV_p!Lc6UT^F-GH38xs#R6|{{5=$AjDZB*vUe5t7UysVv~nOZWL!LdT?}8l z?==KBibJ71E(X#P2mB#mP?SHc$~CP1=-NUd#sCr8|Cc!&=StPI-dTbPt73Qs;QI1O zsI$lYn{C6JQK0kUfEwuBlO9=H$ff~}hsxV1jc1$a@@b19Xtx_w&Ctfk9&S@lUXw3B zQ!IOdMzQU;%HAZK#P;AM*@M8(QNGSa#A18UjRMX?H0s2%y{Y()UjqrSXT6M=B5Ita zi^jO_5mk)y-QMBrsx6(}i$@<#C?6mbzN&Y`sd0}at_^Ze6&J|439PdthvjLUrzHoz zIf3q(ByUDP`SVL}19mdT8k=f`PYSMA;6Q2vaxC&4^keRPM!sDGSe1&?gjWipm4;>5 zv7;VmN>7UW??8{%Zd)`c|K!G240&mJ3+E>Vp+F>*fUz&-Kt$JY3O<8?yN&&w%DTUGW*R_~tlC^%L2;#HR5bc(H^@xVLZS zp)(HJ>)a!AvtBf<2aWBK>l@9zjql>3&=*njH03!p$d&T7gHE;1KfHbJKXc@NDKJ9{ zS%FkdASn=?iD)R}ufko@Zj>We9tTId$FBL!Ag@bNB%2D`3Xv;MIfI1w6|ik91~)Wg zYloUKBJ3wRh$I!v^EBcVZsLZ2{m8kHpM0IVZs#!u6Wef3#AK<55{6URS zA>t1@kDwWEA89bUwfNuO4FiJ!O5fw*Gks&D?UJEX!B{F!nwtjXo~~F=Dv)o2locwMsGSG<%#YBKN)+@QEA)d*=+3JC9^&r+ zX$K$G0kF@#uK_~VIZ-1KC^kQS73f~VF#!KJP*cTQekznY4W^|^Du$QzI|`&s8^isb zk{rP?Mb>RXHRpg&mUOW9CLzc@^KlLg|0oLpl>snPx=ELE2FVuyBBA?NR?M8e1=Y45 z0o50Mr%00O*_NP^lfCxN1Rk*VJ>UWFY!=8X&EXMAUiZI`IY7-|)9NEoX11EX01;v$ zyhP^qvt2;VXI7ieocwz z%jV6m8#vo=15)f>8SybL_QKOr)asP8Pw^XO03C`9-V)EcYEUD29~g=oxo-Hzp^rr|oOFsvhY`sT6yAsFJude` zwywS8_$@l6f|t!k5NAs<7kkhaSnbX;OUvkwe%$syfUZ!*iszk|di=N(Cfl(KWP1e< zSeASD{30q^P(>!0VE=993JCPY$JSeEAOROyXx6W>j2Q(uc2%Na)5Pn+8&!>F`}7!& zLrkR+*fv^BXQEK;%Fjw&RCvkRh#`9*jF`bEW)Z67NFU;ZCv6dMC^dH(HAsr zLIqyPFZ%*bN;eLV`yxTj-Ftr4a8byg&e{SEFVGV9$Jx0DH-TV0E;Z+&z5buErWdas z9&vLNpP1^YdFAPYz~i8J`sA{6#pJ`@&;%Oj0}&^E5SWGvp`b&tM;^`;N<4YH&{xvM zo{`%!+vX~h(B%Z~TesoIBi-QCt=+(a9L5&itwla{=7x9Kt9Tnn_$LyM_yrn{^pda;Y zN!}cv`DWSJsJwVgZf2cV&MC5Jx-W{4j}K8rSo$|2R?$~1?y|Bd|23l{fHP)oK7`_p zk`I3SpK?+H`!4J{O8_$o5O+66Z^||zCF{A&y5+P=hJaW6)d~<&cue!e_Fcf!jl8JO z%Br}zt%eCY=z6O#zX_KFb1o8^#&dF0J5Frh2a_m7*d^eF;=ED6*2(q;`ukuP1%Ggi zn%yg_Kau{I=0}UfON@_@zECmaG8ACcOn8ym=7~eLpSaLV1&VFfy&%Ax0gM%pg;bDK z*fDp4$!jQiWH4ca{8OP;dmY|lPUxwpA|}ITk&4k4<_{ZXLc-r8^kWAPeUBSTJW0!V( z@AqogjaTRz#TRE)zuhGUHlJKkxy?{-&cbkr@E3E!T-5e)~RF>#v1Yy5X3*J*%0m}Z!b?mJHZl6cfIN9F8K+V-3>cdal412 z!L~28mkfQ4T_Nf=2UA2tO)DQ4uwkVAKYCyp=hMOH^m=*Faf*p@bEU^hC^mb0VgVov&o$owwEMT=nh6d=ef@01 zw)@rb9c)FBE9N$|6#EPxy-F@r8S3`nvja$m2d&_FRCFP+%|&V-9~VbQku2!e#_t*w z!7KO=Nvs86S7bO$sG=O*PLhsEz*?4pXX^I z(e8gX^EF%)+moh7qg>(RRmW$c47l)XdowkC2|i(HQ!%#5&hlfipK<4_P_IKo?A6wV z0c15-5DUn7Sp(`ecYR2F-J3H0TULd-3!(|Inx}`5vur781usAK&G5BswP*2n&S%Sk z(=&4Oj$inOUskaiaHx&VXJ6IqDV9U$FB(GGpLjmeecrlxSg^&#qD-jT=G!hS`uH$} zDS)bU)Eo@_B4FtX^#Tx-Dgx8t_)?zb@gu5L3W!U`S2J=QQ2*o8I*T7NhGDHv2w%@n zm4MzZJZZXS75@OWD|Ua142Hc{_NZJ?THzp}p(&}93 zy8p_(h%V^>Y|A|Di(BU~XMwwDr*AT5jeW4p2<3<*#z22mzv)BtJONNLO@Jra+$oeLu18bf#S55 zVXL(LqandD&=F=iv=+PJw_=o(yYWMmmiW7#b%Gm3F=CNmENV=-FiDo=EfswfH|ik9 zrUj3BAF_f<8|Bu`O)C{jhC4)pz9i?@aG<21O)}c;eSD;o!Y!?w1N8q5xw1d*nPC)G zSzPmB((7)NfE9@1VAt>Y!hbto{Qu(0n&4*D@B>{Gh4O{ASJZ1GU^#-i+>aifC`yT! zKG#Hwd>NDok7N)E;S&JZn>0R_mmeo+BE3@C1F=F83&8V_2pLv+0H1S2rFGj^0Y*Yu zov#UxRi5p?^geb*7B;eg^UNj_SiD2ce{|&V_YHJ{QEY1DmxGdrx!)26{U{OT&T% zrP2%KZT$}4CjnNeKz-?rmte#91p0UxfQ1aPGfRb=^bnyuJ0SVq`jk?t&Rh`AN9&76 zBMMtk&g+)04iaq+{lYz~6x7Zb;t(6hCuXSa@q}+8HIY%52|8hI##b5JbHH$%TzPvL z37cDF+hJ)304JkO!AiWrjE_;T{{gbM^64jPL!+*U#kp{DCOAA{x)()RoOq=+JqBZO ziZ9&pTY*bk=MW)Kw$W0uCRt$Jn#uJ-ptRsl;i;7-KfyAc%uK<_Vel*tZeX981>V8i z?h-CrDx+sBKMjE7lX?e^c`^9rR!x4s&8|d;?E<-r2~@AyP`xT%z8)RzJ@oU24z@rb zfL0wrGEU(!g$@nxtav4=_+rom;pm~En8KF9LuITmKG6%jZbJ=a;BT=y8O&2f%h>JD6>?ZHt(M%9uskQ5BGL$6b)=lvP|KSZhjI>$(A5HCG>j w5}RabUvJeGp;}iW1y<8SblWdeO(I3Sg_h9A{T9;{_90t0xoo`t*}gCT4?wt4?EnA( diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Contents.json deleted file mode 100644 index 573448e1ce..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "Music.png", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Music.png b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Music.png deleted file mode 100644 index 7e65a1657c1bf6edaa69cbe9a024ec92664c6677..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23163 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 UYAt^pw9Hu;6f7d5sjmh87o$@_>Hq)$ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/Contents.json deleted file mode 100644 index e381a41f26..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "ic_viewreplies.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/ic_viewreplies.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/ic_viewreplies.pdf deleted file mode 100644 index db1722552c52859579aa1c55bfd2daacf73450f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4676 zcmai&2UHW;8pkP7ASj~JMHvx-C_+L)iPC!jse*)RfB>Nd61q~Q2n6XOU5ZMPCa83z zND-Du7Zng8AYIB*MPG2;x9;2b&YLr5=AQ3<_kZr(Z{~k~a|QKO)Gon9;b6fw${JYj$j!XKujC!YVY9yNKiutfS4N2*#k?U-kmWXSQV@d-WChU$$^O; z1T4k{>_cmjtXRYgJEF4X4=$s37twmU=&2KD!~$hKvCR}37}%LucXQ^X9n8zKP9Rxx zxrt)7W0u=lWaG1Akvo4!ZaAi`-8aMSi?mgu(U~p6aE|PT&*==Kp~Q7_nWzyih$Ozv z(Z_p1Rhmumdd213x#^yQPfqQt8Y0RuYhIT|i0o21BYhV6Dei`MFPB6@2nH=|HNgud zJtI*9o;o!&MRCK|&%jfoKPbpp(^g3s#&ytwv>+a(RXiZPYQ8A<73LTY5W_;oKw-v~ zTu1J zFUXKxUNV)@#9udC5kh}--%N}0%{U}9`;4RsW$NPUx6j@f*4};Fkc4ej7&J{!)!=pR zBy=m1*GI=oHRYU4=bo8Gr6=f^M)E2#NIb|gQPjV1Jc(znB95Cevx6h-BuKsg1+4j6 z%3SEhqA-W4fa5GlbG=`s8Vo3kP$(0#_-Cow(|^t7bcK1lfg0TpQ0S7z>`cADtYk=RMrYbmAQp ze1dd8QF!RGP%_b_-nOrnHK02oNn6)b5#_}MQ)=z!>#YvzOji?){1ZK?A{d| z*LvTyrM}a1t8e}K;;=dThHD5O@Af|`0|8XT?T|EH!AEse|CE`5^HdrDc`A0w* z@9IJACjtjXV{l*~KlKj!|1=&w0^Y_D>j9WiMOD-Q3qVX6?~ErHx?yavz=1O<6JdbV zPk~=Fq<_))X;7y#O&4tQXD(YoM<5|9vgx1iy_c8}scD>k+0kR=xIS zeHB&87|Drgr9@(VJZ--NJuy1_Vr0~Q#ebxhM(=9~sKvvCHVBbp!nVB7Y<}WociR>n z7b`7`3GK^CJ3Em2whGuN22{7E3?eyC-T7@i8C3jlZ|MqTn;NxCZFi805ofdXsX6D*3X1HTPC!9IwHgzy}0O1q|J5`z4%#bFFOSP zx+xsFmD;_$XME)mr+n}^6gwQNN_q77k(NqN&k>dsvhDsB>-VXbNZfB-ts)uOFF9Ak zJ{#$OPGSm;bf^oH2{wrnwFQR;4owDfNeVG=2V^?2Ie1#p_=h3&s!*zXNU zCq&ZiusP`Z%cE}0M}pK9z~RkWP}=ce9An5AR`y)QqBxHHW2s8Rtfx#FeU%mUq}k|S zDM!Y!CMfK@ffR6ExLuT^tS7s1&V#3x(c<>*1BN#M;_yDLf9Uc1bOV^9Z)u(fjh0{G zWmIqJ{;1-^p25KR^8KV{HHRF_ix$qwQ(tIA!mL^n85KGi>HE}77*DOLD8-ow&>lbQ z6wjYeSE!m6$C7`lCPpLPeVYDoL=NUY7e_nyR0NtdX>E)dw$_9W-M6`Ktj#$iX~zl= z-U)eVJyh|?MCggU7n9`i^q5O+#y4Vbn3bng(7(kjUg2fwc**^V*Dau$%Us-5q7?Mv z*a)dEXrcA!BxKd6{=zuhO31Nh;uq^%XB)wRyn$yXV)@#egyNV?K~5pZ&fWtAoCU72 z@-WUa?uQ<4LFfk;=z8#nv--A~TMJj*eXCfH;)UoWvZ2BSI1L1ZQ>%inp&a=wAhM8# zvx!}oZPf`}Xa!5%Qh{v1O7K{Myk63I2%D}Zud33h4pOs5!B@do`Jutl0@p#4qWiU( zrn(I({+j+`>WT1_^^~culEmV8r{sl{n}WJYBPre~R-)NLR%Zh>zdia|O{d?i&}X#4CNl`h?(w7bkQJ73RwO1s|T}Q5tyY+h1LE zwd(Ra>^tabb*cQlfyB928+@M|Hkh!2STo9b3NM9+Qu*58a0~RtHr#;wAt{kmM~YoX z%+!#l$nPb~aF*Zeg_CzKxL&xG%%5zROq(2Aq-}&BVjIdVaw*asggQbTF^;2-dqbkd zG33W&?<{hr72^B!*NFM@+DO0ZUB};B=Vj*;dD3_qdANCucv2AU65MI7X-jfyyqGnQx zFU(&UdMoK_iJ6F5mFchK$mwU7Wal@jH<<;#e)$G?cOzOcy}2MKDyIpPg~^+=Iad^4 z+;<_*H1A5gw9Rw(s8zWsGe~)4CMp3H({O zL~Zy1vQ?(@R-SMA&P8U~Xp`tnjxi1^ktGqsOA8`=Xkqjr6PK#_^4->VZD~#xHvS#q zj#D<3y^qFbo|P0{n(%5`^1qU1Q9i~|r&CAXWZC53gY3Z=WFvec8d$d8dU&0$3-up5 zzVLanx_6Z{#UON3sA2&1cp?+sE8KguH;?%U=NHbwOI(CL_sMErLcFN0D9OslEoac^ zee#s)!fwyik(gPpHkY=sx3Z)A)q59@X&sAKkM2l%oH)xvX12VOQg%K5dfAJFE$bD> zmB(_Id`SAS{&YLUFxq?ht#jC-c(Id)k-lDL03+zkxWUzBUuimv3N zX4@~$#;jUwW533OQbM1G_Nk<_Swz~PN>S{nDLP%nXCFPETCD|77be9e4JZj>qRKB?z-5IR#99$~Q03a!xS?gEler3g@!IE!XuG=mcfV=>u0aw=7rc#T zu5^4lZm*8bey!hgd}d=hZQ8Z_bk};i$YjnIV znrkP2Z(N9V0bDdjzjUj0$6!xQxJHc+>OasgZ@l49xiZtPRHLLn*>I`XjK%qtGrRNB zm|bOwOyFW#J(iQxWurPu4zrHipvl+V7@No0tp7%#DM(+<}bG7Rxsn%%A z(Kn?ZElMaoYq@JL>aH&~8e`1dZydwL+QwFi7d&myEOS&JdOV*$AIuY*Hq$VF^Tkr; zyN>rGO%pDG1GF*puNdUcu<_Ohc5dl8l_<0NFNwRUeREECZ{kTz4(Dqq`ck>h*W zGt!aS-T`Y$?P06Yk(?2iuY1VVR!#S3mX?=}cL%4GS+3R)3g0*EeKkrcwl76mT=CEJ zZ&(W7j~PBwmvzr-F#tFBY{jKv&aHC)-c-rm#zX$IyB2$5RU*a*BmcsMD7l6y3E>so*wYVtvq@hHD25(zZtxrvuCr>5kI%6yRDm?9V733 zi|Ol{55&c8W@0OJeea+Ad_bW;pji_3Z-74F*Mk@pa#704N*E&67B~P{1Hj@>CO&}Z zKbiP1#wG${SFyG@j1t}lFoRN4#1Yi<2PAt^$r}cUY2j>%RCqo>bgJ4ffDAiG|GT0x z#slMwxBmm*i9fmhFD!@sGJuNZww^ZBJ%IBr7#m$fz!*y);_$8j3@Qqf5S0MT&ntQ2 zoNWOZQVj-$nF|B@o*06MA3$aJAK35XAxuSg>UMz#b(9K~!@=iOE{j9Op-^dYDVR7E z0T(xgLWQU&HSa3k_8px;J$$#j;Fevh581_z~OL&t+WK( z1}2FRN7^FcNE=%?24N=wm4+dut)X(@|DW>58$|S=*7CHaGj83ALs8OOtH%LVEUJ?>Q)QEmf zL??)7(QDL*cjV@NH}|{Wx8AePI%ltE|M#=@+0Xv3_1lL{9VK@Q#>)?3Yn|JgTh8Bp z{-L!EA^<=ECv#hfs3^dvf^oERwFVG`kS4$f&t7G4>!JjEl;(Yj@gOo`HLIfj>zgmt`_a0>gbfvYz|WW ztlEw6H>ez!{S^ldKkDih-BROW*)<(+L;g=UDEO{LF6_R@^O+k1ULmPFAm7hhT8(^b z(;QF>5$ESDe3UD?e2v-6vDhf=kFAN^;ICsB&cA%k<2L!o{ZbG%S2RJ-PPb$wOHE5z z%ufXAJ4T~4MlU&2pQ0lBx^*sj$TLVdkjxJh0tcmYVKW6e%u`mcSIVFz%f=#27^-?o z;w=S)@9&(^Y)ICQ7Ryc&1y{!ojE@%-*42Q=X^e;prLwdZ=meSnX#Tidg&Qc&v14It zT0A7NLrjKgN6;7TU*_sJI`Ui{T16&O4j?37;#Pb+7ro>F1uL1RisvNX21!)vxL%g`-e^jBehcH$?ukoJWq&yQ zxeKdpFPvDR@TCGoUI1c{mcBD}b1Sw&lZUAF>T0G^Z51{Ht{)%+34AivI~2DDTG{1PYfo>%#;z~5Vb4WyvmwmlNUE|JuCY&hIc6ZbnIhqTcx|Z5a z#PVS*7P2(w9tN2YlW$S0w>4{^WalQL?8w(k5!;h#$L;!4qYKr;qgLy_!?i@}--Ey{ zu6o1)0-1U=Ys<|>7cX_U?tv~-6I1FDH%())VEKI%XyALjSZZp4LR;nT8gGDQ-8fI9 zI=joziGLi6pbYC-B8coDQq+%~eh!qVRr#I)egf{=N#cZu!r*k7|)`g3_d# z_AIbx+K0nsX&BbRw?IT>iLJtKohc&^gsqDr>?Or?570IKQ2)v<4%a4(r%gfJGM)mYiP~z~G z$9vQ#Gfl!ae{{a%d79BgzZvpXN8@WEd2qOOU5IF)UM#OAB-np&+W#_=gOtH9-HyiE z&6LPDL|DB>k+j1}j7a7N39F-oH^_I{kQxT*Xn1Q~8dvTT1NZ*1aL9blD8g}CMT$jh zewQM~(wQxbhar0^*uEKbF_zTzIYc0k7YgDrJI@rre~)AmAPNpppe9Ks;Z*?HNP=HV ziH{ILq+lb&?hkG65e)=-e*oVJLf$)T1issJ{@yu?0C`DBXtOeucrwt2ENFw8HcP4~ zmM(`XReFf}iUFCIjFh?%4as|%@L1}2$%7B<1(&%V7G=t)i|yQSWvnGLet7tt^aCJp z_L$f=_(BS(AANp==ykwog$NUwd`tIdlqc;|Qu?OxX{8!Eamwlz`sphhL_8s;EeT|j z9b_bZa(ZM}Hc`^CdaT43uG_`2h`T>aayBdzopPD3jbSECKWf@s z7d>RI1RYGVNYPcHpGRV;`2!Dva?J-{UiN76FaG0yS5bP-?GMBXiCdk4L25Dl)O(NuW^- zWu@0-VJdJly=@7sPuFwzu(t@zodA z3MCgSKC3EfG)Oa; zUgS`uHUPC{w?*5I+8zz^7Dwar@gC3c>81iFv){v(Dr&>MYYuI{y;~AnN?=T5Y+z(y z)M3O4v>_PM9Md|}=F%1f+N-P$&b^KbQu=S&of zm0A>3>RDyqsqDRE9M5YgC{=A#t>q!Drp%|uXIieYo++-8QJRs{DBo!4-_`U17&D8M zdeU5w8IjqDeumDTwzyFgSKP;yZICU}CS>vYe#EBuj3IkPc)DV|Vsxjf*2uu5wplH{ z3Qu7-Z_O@HjHqkRs`#X@Um`d3T=<=6$AfIICkNc;#3J<~)9EJYOnFv$v~Df)Fza*b zpV4!uTB1-DQ8T0*J{&Ll74xUk^?Nyc&-Z4ZqoCiQAGmed zx$pjT4U=;muO)Akspq}S0iE%r8H44+9@XLK1@~5m)`=0Z(c_vUZYpJ}IQhu-#QcN> zM*KOGN4WC4ad*qB5oc!V$H}Jb=;^w*_X1YuB$eK~0*kICrL=(#YzGn+Q+*nPEgyYt8ofkk+@Sv9!- z2>srSShJ%n4H+sk8h>-g!??M2`dh#V=3k``$g6=-CZktbkot4Z3K<`({)&F_tk^@Pe21O69il zgZWE2OM#3rY4i0-LquV6+fHBSL+V-i}xyzlV?gFH=OZZI5a-u zo7}DSoqK$eeuX6R?BN=3gU^Y{C$UA}$>ToKLMju99&e#z>tl;@iZ z9$DgXhy=|e*y0)zn;bjkgF&o zBaL>!SOTX2s|gtYB{_xYznJ)M#&!YtR56w|XlW-;zz|B1zy%2RPe^tnkT(qAQ?{{m zA;9w~q7$7yUQSx3vm1^1{D-096SCQ;}?Vx*6p7$C=~whn4ln`g8xe=B>3-q$p1qp zEI>H^{BtZn67jEm{DQ*2&Dqr%ZDWsd{`ucQ%f=f+_+Efd)5(cY^3$>s{y!)>Vx0)} z|FexE>;O?L0*>X!AQ0wgK>+sA1N1O$|(fO15NASEH81f+KbX@Z2Fgn)E~(4|U|CS96<6sb}a6a)g& zi%LYAfHdh{1nJ0|sQ0_xx$igcnKL;%&;Iv6o3p#m{GP+3qpT_f5r%?zT4%q_E*ES* z`Ow-9f&pN_8D$5$dKD1S#5mbtZv$e4kRBkSYU_Z-xDcKWNGwJfgLbyU0J5?mSF8&L z=?L;9HA^yicwT~5`PhefB!F3uKH_j2beMF4)f1xD43f#i9=YFe+R^jO{KSDKd~MN|5g zXlJR8gwHz5l=3y+;kDK^`nRHG4KF!@p&Px|%E`nz@&n|9D101!d*cgz!=vtEQa#GFnow!w%qVHc;$PLs@L>g zDGBuk^tq&Q2E8v7j$c6@$cqneBRgn?S`aGv&lk@+!k3#P~-;S>|}<+YpmY_FWqH6d%3R5ZAvCkoarIZFD`M6mNT?10n;mBoZ>b`9Ph zY`Q4^X_BZ=kRxMm{~7hwV~*QQUb{j|2Tm`$=qobnytizk%CTrAjLGE9jP6d zV*h=n;pGI}tacuzxtpnDrAycTA}Q!{hnp|6S6nRKT6v|eCZ(Tjb>XzRXMiw+}rCOseG+o}sEe6(Y|Oj(VR7TF^KL&3-MVWF zrJz13vi$F1(vE1lnVZSlqzF+r&369v9PSdK1p+ zW76GWviA|69>k9FcaxSd8RwABpSG2{Y|es7(he^Y4h{f1>=MTpuaMJ9>&$#%>|8d1;l>^l$@Qg zgnn0mm^Ji>+2FSyv7hKm%py82&S-rM7BC?+l~n;VKt##e!P!Os4ib$4hyo>72q5v> z;13VUKRkYCBKS|0$PL1TL==eG$cd1O01;)3yDb`{r>6M7lT^kdkDL0#EMa(@1n$Eb zE`Vo?&6^tdD6P&+p&PCdphFfK%zoFI5H>7G|Txb<0n)^YhA?j}oI_*um z$||#CQTEj9rDB`oX-6H1iQ)O`p<$bK-=SI(ox>oCW~>ou04&>xX>Ga5^bF(M)*Z65 z45V~Mq;ICIttr%YmC5=KaWbi?h3f5<+iSuBR&}F-jXJ!pgU9}HToOv$CrLmm1L9Zx zcv)x367?(F&WRpVbZ;f`i-tl(S+c2fo^egLmOE@0%w4JLN=fFO$!0_JoU4arM%C$z zr}0eXMp0y&rtnPK(GZY}&983|Ql zE44- z?)*rFln3FMdOL2llw)-jF*tX7uW@+df}j48OsDsO!3OUNK2sqo6EWei@=OHBLbAI9n9lfztHm0M6RqjxRPes3tw4~ILHce z?rsc)@1(w6J1~^aXO#=Qq`?fusLbXU8E>(ppab~?+DC|x4w zc9QcZ^IbM!fPgw1>Tj3EmAl4>djFW;=cF->aGKIo;L@Mlrj4<3;mHv^mpc*c&_s47 zmJ<5}1Pc@flL=a$;s}7=As+`wf&+vb%%yFKusPL+H@UEIv!|C74(&XIY*&5mL-oqRdJAk-I&TtNkK=F ziM&lIJeDC|e(wWs;aP$E#o0)MVIqdJy^$}L+L*#Vi2mXufZKul zCIcJQJk?R~>1LR2V4*gaE0n>j)fC15{NX2sdJPWV8wpGrq1>!`-2ACkfj2emxXgHE zcySjKx}>bsT+Slo&9%$8vj7Vo`gl2=#LK))+SfQ#6o+rXuf3M{lJ`=2s&}f;so$tL zr8dJ@8>j4h%~wP%0h+v-Jl$2AP!eaKw4Ch2qn$XE?2&9CoW*Bx(f`_y{KFbD-6r`a z{lMG`!)(>5s%DuG4VAQC@YV0`r%?IQWbijO=`FS052J@xun zQ>9fUHG&xdpI4K}JLpSTY}?{oz->`ucrYfjmuER<*=8%d^-eZ}Eq9@M=blC-MAb#b zY{KSV40p24MpJ4$zo;eI#s;CY3uGzQy-m)aKl)#q8*1&d-&44W#)-HA~%_*%bZ8mKl z)=_oanC2yg@vQOuOLK5i>e{=T);yJw@a)pE)dHbj*}Sn5nNoCNrIAhUjmjQIvv^?> z359CYYJCqy?du{&A{OPk>)EopS*2NdjcSc1{@rgr03()>3h7OS*%8@|$V_DJ6#7ze zTuHA$u5qq(yCnM6-G~j@X%pUx@C=Q3jp!~d{ZIYlH!W-NRd`zax!b(360y3DoQlr~ z#52{wC-9c5o%eFR()X^=$V3`NX0VL0SO~5P>I*Fkaw7N1kd#hvX*e99cqnd*&^w;U*)FL|)3liqp@HFNRlFM(!-72q+-$AY0 ztry5j$%e~D%Q7~&H>`iIj@?++blQ%F)gqh0{zQK(WFQp(bZQlB~D zM$ytl+Ef?BDe}>R+q9pBOz}|3J>)927_AxezUx_GLD)0i6|WitPd`r&3L%t=vQlfE zdQ9iXPPNErmg_7Ys$FVrDg$a-Dn2U7D%X>Z1QC@ps!_&KK)wtMCp)tbx4TQx&d0Iq zVbd#LK+|i&P}4`1D5O)PGuydZDU^otEY%ZGBEZeep?J-L+lu#FT z5I^63Wj=btY!`DFN0A)-BDhyMxz#Kjtx=}ItT9cdt?+Wv?S;j+z?q`N*u-3ZXi;y` zd)wfu-KsuC9%Mwt6*H&|A5NqNmJ3#@?Y13QGhob-?~T*E?253iOL_Q1^SgQ!cRJ5~ z1Wl#gi*Xw@Ojftp-x>U+a-j3eNvKK%8q>f2}x`-D@(c)#uw-HHaw+m-8c?TW7zb*FGbJtlMxZ4S&1 zt7F!crFNsqT;{#sQE$IQe9`9l9KSq)@V&J+_HH|P758-e>4C?jJC(=hvBla=qf`{a zeE37zq*>|gyKgz)s_Sm8G#DaH?po5@##qHvi59-VT`RX!8z@-HTMA^0Nt?qh`BblF zjC70+HBLDC_mM`Ew^7PoVB)Cv@7&R`FI8}CLw=rm)`DNoS*mTc@wf0-URB?S!>fg; zJ?&_YaT}2CACf4PNEC05j&FZHeYeB;X!5&~4`a!q4m9`mE`{Vp_{NuxUC#AP+N;A} z5x&KXd`DT^uRa(@7-tWD9t?k#CvNMb=`*ny(3f$(=e&?y`tiN3sO{9W%5IzM__!YC z$La7a55I4U?I9bH;jCd&x3IFcRWlzm$|}mn-v%a^n{T{!DH_Ec92z8-*p$J|qcJj!R`)k4t zKF8*tWfpzMk9sMK=*{Kcc}pJMK0>cd=kS5F1u~1}N{;NSdAq|l$xEDjO<3wVkAN{kU8S3^lj5$TGt0*C;s2bldOAwu+D zO#C-vy8sfaO+hXhK1M*%67>)&~qRF0QuDP5=Zf3=tC+2TU(3y4gBd z0T8&FC|DF?$`9zeAziTE00H8Ea=#~*pTO>f;vY*Grc3~F&}C&Qf)osfgC!vnU`eQ` zF&NB8xCuHfXDgxv`2Qt;&(hNcV+{fW5C{nTe-|JwCI%G)tbyM#7{MO+*$X)R6@!4G zgt~)x{T+kC#RxC%pD{34^j|Ru7(!UWf9fC*$ba$sZ#qdr{qxVUP)Yc|{9s_{A9KdK zAZ;BmE?$6SlxrBoYIWu$B-5qu~-r vFbXOGvlh2Tf-x8j3T-W64Uq-?Z^^GM@YW)n diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/FreeRepliesIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/FreeRepliesIcon.imageset/Contents.json deleted file mode 100644 index c8af669573..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Message/FreeRepliesIcon.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "replies_sticker.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/FreeRepliesIcon.imageset/replies_sticker.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/FreeRepliesIcon.imageset/replies_sticker.pdf deleted file mode 100644 index adbf0484c61c6410e34668aad4f204dc100e3429..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3999 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 diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/Contents.json deleted file mode 100644 index 358678e10d..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "replies.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/replies.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/replies.pdf deleted file mode 100644 index f55a72204ea6de627d9b6d809a60809d47017a07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4117 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 Dn(I$q delta 864 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 diff --git a/submodules/TelegramUI/Resources/Animations/ChatListNoResults.tgs b/submodules/TelegramUI/Resources/Animations/ChatListNoResults.tgs deleted file mode 100644 index 62a21279fb766aa9aa25ddc077936f2f51f34031..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8590 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 diff --git a/submodules/TelegramUI/Resources/PresentationStrings.mapping b/submodules/TelegramUI/Resources/PresentationStrings.mapping index 6326d969a3e38fa86cdb10779c35ad9d34b76cac..9861c49aa27ba097e2e8052a4c7bed5e31592dbb 100644 GIT binary patch delta 52179 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 LLgup;TFw6l8df0) delta 52520 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

02y@J z#?xX-n;Sw6RccdyWpe0QJFQCsclZsM+Jk4v-Ookm{sCa6>bBb8`qYGAteV^^A^!z{ zY`LhtlkUBPC9uEM{?mQ6%q;#vOMx8!RJMFOH-`VqXx<6Ptmhad^yKZl=h3S011K~L zYF}`&(g{KhjqOoyxB%L4lC|XqPQEi>35?6bOFZ~@PQM&(lH`>ou}>B4ucjj*6PO9}R=()nU4v&uDnV8-Qi_Yc7FXif=l;^>!_ zp(++1+ll>&Lk~CfetYrAU`=ppLscziWQa$tCYJT}kP)luCItf{rv+<+eZ#iH1T^Q< z3@7)>W$O~!C!(khUD47J)pLUZC7c*iP^;K$D-mf`T+%pANauD-2L+bS<<=CZumifS zte@|aQx~QQCrV&gFm{*6VX7{^x>pvAV>JH;7T_K(hsOB1k5jyvDdR64I)IgAp-;YA zyN_~hJYHm@Y^Oy>BGN(*hsh^Pimyb{N zL)WDD?Pw_EYbUWZdEOC2=bgaac3e)aFW^O3f}fS}5>8#Z0IWTSzMsuIaC*20$b3gG zhb{I{?<{7a-@5Pw(Fvb*$c?AcvLh-aR&MBwN_AzmMMUpTM^)X0(+y^D&90oxk3)X$ z#^vASb9V={lK7N8acBttHui5Z>0icWB@XywsMz#$~_H{d_p)y8)w) zdloiZX{+z>V3npxV>qR41gE-^D;r;1K90+c zC58MdPQ{tLm|u;(JRcC(Vt`DlT80btGf&}FoJ!VWCnsVC-FUH>gl0EvIw?sbs%8ci zmuwMjCv&A)7Dee34FU{ST&RHiT}gZjm$P0A@TpuToDT46zUBs~2G?=<%ECN8ozse4 zpw&u=O6mtDHD_XIvbO8iU-^bDt`Zk1cFZC*F^~l4{rpjHP*{H_=Zsm1cuAlAT zAx_61gVb9O2>4Y#Nl7ki2tvZW66QB}L)6*MzzpEuIr)b9qEVp%;7ao-(|Vy+5&PBF zH|Z%fa*B)Ph5jbMDC(d|LO{86po9cU>+5SYSTk3uP>Glcuq-;X25j|3Bae@X_zY(0 zYHi^8B?yK$8|6HRAU@gsi#R?9vrlsQ=b}r!oX&pQChu z2-94^Y13ThmP=kp;0rm;-V5@w2z|1Yg-i~eZ;NXoYWSAnCNB4Fans7K+`@0xbRmzf z*bnpZtz7oJH`jW5WKuHmbR6ycS`l7 zu&JvYhOUxnYWjC_@;0zeVlBW5VaCmp>qSBqp`=)0MDYytgAYg>{|7Y%HH)~5Q+*+> z_}yHVP`0=S@IZ0tmMc?*=GN;_Q!M?1ob;dhES9?W=J9+z=;JLci`^$Tjdo;dxn(23 z)P^YZ36801f=8zH`reP*@Cp=<2LO|=98Ybv;`bnzZ@lkzJ%rk@#G_0Y<;{&@iuDMp z-LR3kMvlLtVi2tJ3YA|7pRO_sX6{YlFLLT- z$93L`E`G(Y>DU66Aa@tsJiZ6S_6j95gPiU1u~+1K z1G@6pxb*FFh}Y3qd2dtXOBFV{J|E2Q4KBB*r&96t%uKZ_fWrH59nYrI!dxCl>-R95 zcoUtRdOu^J<~D^i#=XVqzQb^izRe}OC0V{6A1~ekoSRBgKsY~tA)dV}&-5$ihp?t} z@bUK`ku+jf?}NE?bny?k^gNs!`yslhTM0FCHEsC>EKJGi zr*A{|Pazw@WdHA6mPEUzLz6uWcpt@gWsWvVTNznh8`J{g=ctdt$YB>2d=E{{dfrD+ z?`XA4MDs7W+yqtq>Q`LOqi9kxO=VRzjMWPJQ7{}wtt%{Fjr$=^e2F3q5+%?XPUPbI zU!m-z#4lKCQbp6WU;}Vkf9Gq|d*!hQJyBPTs2HjS`UZEjJNV~!oc=uoneTh9or`qe zw=C871HiRN4x?DBDB5}{euStoqaFVlb8VX{E$_wi)0~dHiK}>q%d%P7{4AG&XI%79 z8sv^&FjT61>4aThoL{*-HX~E~rlu1n%yYW%B=HBzb~^JeB=2_5!L0R{oLlMSe{XJ{*><0Vja zR7gTt&*G5|mym4zs~XdYHV3gYEQ2a@nUxEpKJaFn!m8^b_8R3Et6ahguynciphGXx zW&{rBh$vJlABa=N-4-Lg{umx@q)kslU|zT}MZ}=hBYW+0(D>JJaB(Om${mYR^a$}r zT9v|+Z7mvag4{kMg`OG%qi&*6&d<#j$$)pu9m`Y5y$vEwiji!;;S^JiveyzPPcxD$ z4H8Z|pi*#GQ&J({sP$x^Ld7|R$(&qNnZg}L@{WL};55?wv+%>Y00K+>W_TA*cC-sO zikU5ntn%ctPr@A4)<|zn2MKCtRBk7EbUJvJkygG84W!(~ zETRY_c&O?(=594-l;DI$K^ALol*L9!(*WNB!hLSIjdwK4FZXp2ozTgtZ+L9W>2yI= zCLO(vg&n_Ljk4+|JL@jrotnchGg1Pvw&HTYxRen%UK@d1gtQ(;+Wiz*Ur#jq$y3Pe zsR*jj-nkZ!S}*lMxpk@|Y5-QMR^Ho4r5k~OJwA-$Wky;)jrA7&0F^_PgBV9mrKp=${=0_Q2$tEu!W2EoC!l%a? zi50^;ekH(tvexN}9aJ~7swS+Lj{~A6LV>#4sH|yO{2C)&Gn3`IuLW>ejDl4@OfLI0 zH);Yr-l*bSD6Sgxrpib&&LFyFqETMBGax3RSBCV?2uIOOHp<Tn1Hlpu(~L5qOIqUp_;9O@w8I1=|8?@jb@^gChHy$Y&!Ihis#lFs zdX{*3EikyqN&DBb6jmqqEU;6;1Q5%c|{YH!^ z9nf!l|1o{_Va$%SwuprRZ7^%mLgpChpEk_poeL;s1yF(}{J)`^P{T!8=X#@3W0NI} zb7DRK9kkue(-I=`mpbtUsLj@_mlmf&s91<{fwGRvQ*~hj$s*L0$i)2~`g{s=SZ_qp ziH`D%TfF=xBh?U;rkjm)ECE#TU>~bMn_;nBHZMoq0?@E@>pzgnZ#B{j4iKf=j7q}n z!f!WHVOyxgON=tE%*U4+i3Tz+TP_ppoqVMc-ug_j8Y5(?*=5j%C~%%TQEsO^@U%Y@ zTKXEJoa)ZyYmM^wvOIp5Q5jXU+1+w{T^_&3D1R*};P)D(?Nu*dXQTx-2)*kuK&m`b znzD8xNdJ9Cc^!g7HX7xQ=hL+KJu4KBUD*T-U(GJ(_Zw-avbj8Hq+#daD%!b@C1gH? zL6l6Pkhn20q^fRevkEL~C87@->1`KG36B^l<`hJV%|^vl>CYjMr5?q=4M2s*jq=d_ zKCuNu<;aCWU!u}@s_U9HHF!dOKps4lu7lpT8mV^%^NMY#&C|?h{theE7sK|l-3V`% zk8TZuU+gdvten~WNu!)~&c~ktMd`{ta^p!`ba_={IQ-~Y+{rvh@y}s7dqHm2!-w%7 zIcAF6^&$q>L>amfROeY2jgmWYIX?=BT>$8+CGVpnaUAzN0S>ntb+B9^kf_B!XQlDc z+$vr|gM++j_{5Kv7{13y>leW2u-8Zx-(f>u1`YlQX!A-KJ!3B+O|RnGw9gc;VSrrO z^KysWJ1&{O4)_AzPP~CySYT(T$@YI|us7wRh7=XtVxjqcVDWs*C@1(_{B3>jRcKSR z#>IE!l{Yx~y9%K)Xm@=a9k}-|)nq7JPeQe)g7<-PId3;=;qON8R;>#4?e_*7=b3pSx#EJWpDHH?Q+`E^V zJs)8lC6q^k-;@!yl~8?blnW-b6Q2O2o&JQ{}O*`H}+|?E%xbc@p`N8r${*{qF^MPo8 zjb^7Z$T(VAr+wu z52NgU&YSrs`l!pPVg+YbH_ZtEljlI6Z{f867|GNDHs*f;S0E=eX3+j*1bm${%9R70 zF_3K(RXmTzb}c@_G+H_YRQ^Jk#*yzfX4R)+FwwFO2wq_(b%#Ne5-aM1buF?rMlo>t zvX zfDhk9JAQ%rEy_f13;aLYB#)0xjvihyYN*!mVoXYv%oDK~2jVwGKC0OcB#1LnV<((` zg8tkkQ9Sf-3<8xBFP{1=x zvgZyTcbKSeA)GQ!^k|J_oUB&wGRaVMXYMx9iL=a;5RUHCs*48$dgN7Jzt-u!y0mhs zU+=2VM1K^+j-P2#cEz?l3kz<7;JRR4+$Hg!*(SNk8@6c4L963Te7+pqp!9LgriscR87)DmY`Z;TT3LYwmOb%4mP#~#F}L66n}?K0Ml|+ zxOZ7is8JL8&ZuwkY$)Uzq^U?3)Y`QMDd*?dcvq7=e?yVzhT8TZz$H{M8n?asrTeX= z=gUmW>5$EPnCOG=VNdL7lH2yQ;k`^WV+ojLZ(znW$jM{ILy##okuwj5%04EFe;F!B znMnsIPEb)MedU5%vorePbC)c*ZzLFVpowe)SXSm0XjU}hQVCPbk!s%tqtQ)I%wax# zq~#_#_ANgj2E5to62sBx(hkIe;gM}0fhr}*DNk3+{RwEjTBFNI6RjEmOYJC=WZk`d zw28hbVLiNKB2%~+054SXrPbG(Xy|a(S&Rp820T=b=ssG4oq%$V+<(q3 zU%w$AGXD8|}V`8@L7)>L|mJ zW36IJtx10SyN}nIv@hDyRc%7ZL^POgrW`F@2ISbs&bAAHi~3=_)6gGTn%lVsPD@SCfe2+O5r?{yerei z=bPk9*;#y{iB@F7xU~pl`KkUZP{KVgFiSi(H+D$qS>&xIUWpry-o zCfc|aL1*hxn@=0xg4FQmXAp|+L)ocQETUSx8yiey{{iNNjcCf&wsN_%DV=Y^o#+Cd zaX+r-sbq@bJc&IZtM0JN-t8>%&AJ%zAUY_cU`yy|0)N;f5AF8yM@+H=Ol-4>UN{M{ z=~0uMP-oZb$yDw5P{;nbiE4J>9JgSQOxPIG=p863{0WmR^|{4X)aI!eR@vj8SXw?5 z=(P=1IW3$$Udw>nW%?a%zQZK1I+??tGHGoSf@6FP#E7PFeAqK#jhp)$;vm| z@&A};_w%e1f5Ak_-?B{cA_h*Am8MjxYXb_r(?s$Vw7FejTx@7ov)yfyI~{HKOD1{j zicG%OB;8MDikHzbpN9PjP3-SjSI<{b{C}=*Yck1ek=9m1*uQkx_(63|4IwrD8<^S< zU)nwZSy9v%q+d7(=Pi@GrqL;nJ(|ehHqrK*frJN4^6%z6??FH-g#E9$RMl0Dc*q3L zr~P80&wJn#2(1?HqmzdaRtG0fs1|ngrXZ%&T(A0k5Me%hevF~ZJP@fr0&oru8_u$6 zf`eP#AET@op1LAKLSgrS>i7xjVMmWi4UC4$UQwq}Q#E{w1~tWSz)l&QkWCG9%8&t} z8V{Rfb)UB4Gr(kPQEB^bmJstfib@kzTtn`taq=T3>bilsqB^KPN71T-P^86{!H*cu-Xus0-v>J8Ij=GF~Nxn;shYn8WHWrQ~6JN4>RbN zdZ>jzo0MU{NSp$QSBK8jpJY**d!9DY@}BTloI#UvY1zsB527~DqUck>p5E3#u3t>6 z`*q>Jnuz{l9r*7iVwDgu|I|Y&AJa2i68T@cHaDuhn$X{9N~eQ=v($^n|4$geqBrqh zlPrI(#D5NO$$*b=TCEcxL8T#*>U7>jKjgzKbiqW2KE^#!*(Q7gIc4u z;sq6bhRc&Exbhf-LbgL60ARb0l5Rp#ao9A0%FI zbsA=b7j5H7hfvY=FsUMJv9L8ONG;0|nY+3oVcBm?)!d+JaiS$nrK5|80OtoqwOP)%hYdJU5J2Md-)fOEpXdaDOjv5{$%rPOu1qZm6{*uIXlEeAEJz?-0OF!kOJN{Ac6Su- zD&%*@eBMpa`uCv*cSoyNM{&gmMog-LY#mN#xJ)Pw$rGo-6=jn!W?HZg z+I&PJ=^^OXJn*%ig1&hKbiS8R9tzqXWO1T5`h+8ZXhn42ggIQ?DKsJtCT%Aci_W-FE7JXGhC4WV;EOP2&#M^F@r6N z&q$%fHETGOY!nJz)5U0vlcgxsn%5CEH%8E{<+w&;1mBm`xC}$YQ7Xhgg!{#wvhi%k81e zi==C!kd-sNe3DRJQ@ZdPT#b_jeFAqj4+?s{2Mk|RguHI8pHCICB0il@6SDJLK3*;8 z{V$+uUMJ+t1(|HRv~TnB8c=~g+{bF=p_fzS9!rcIKi8}!raGZqK5cnOghMh*6cW`7 zvJL~nHwe1jiG6Dn^wVgT>1xI_6@_Y15c?x~EgbJOQ_$YWF{N383aa7Sm@VjdDr>VQ z2$%L8AqOWq`3*uRbhwO3>GLp-TDK0~R{yAv&KLAtAKdl@XiTR=H^JN%y4}`gA&LcP zR*dogOiCQqjMM+0nEOS*!*BEXje>SP1pC2F7z6a{tn1?T-Yn$o6FK}Aee$$oATuLw zcK(YO14nNc6r04{*7jNuULsnI`c|5df&{3QsWJQxArnn0Vkt&-C!jy%;X+H<0nGB zStn@EIo7r{!Dl^gQU|!h4)g-9-iN+fVW;NMpINeZ1IkL03;W(8QCNW%Q`1Iyd{>Fs z1Q;K!jN$ggAyth{W2zeJA}Z1Sf?VU7KlcGZDA^=?dP&{YKxuD9U}Zk>bOY82cq^ii^4VmRh0lQ|Z zY-e_gr_ck@2k|WZB8EIIsCEDj?ip~{wV2Vfpo+(!!W=}nx!l;0=zb2Jln*HE0Pove zj~JPV4d8j8yel)~1wbgl7S={Mi9%Z1d=YiUw7)wmif!?J>_n9hOvn>A0MUgS-*%zO zL7|s%R(0c8lxsK2>gFlo73P*eI8q4}hL?mqIN!zh06~``kmM`)IroBlFJc*)EhO<} zbXKx~maj)3UJ=>Fs_QF)PUZkhUIl|!7T}Zfkq-JAfV1i7N<_h_Og^|RTN9gJ7s_&* zo`~}t8S0N*ib#a8fZe{B?-P`^4AkjOh#1N6CXMe3CcEWNT!^J_Oo?^aTDMJ~THK+3z(_QN!%+ z1G)WK2UYlawEaUhaka|-Ary3|Eem_CKN5tnfMj`BVlw|&Q2v$p?x%t-%v08Atj*3c zVn)>=2269(&xK5#jvOxx+m=eOAJl&$Y^z@gnia=$`B5ROXSu{NwH!s@GsyQOGn;kS zG9doOMDle!*X~X+ zH2)pMOa6oV<3J>NtsAQ8zi3ZYQt3sH`#CjtEvODzhMRj{(DGlvEiVWP4P#xo!5og= z>-{T?!yK}ei+oWqWn6D(@lGSku{giVaL8FeVXM9iCbP;kR6%%@Fc+$-Fe`tAJou^~ zo4)@nOAmivS$vd-!KJPc~D{%V3)d`_$!yUl|G7$}1ljHGBvv45?;{`vMrAW+rDmMD27lowvhY zY&Xk!-xcxE|YbQ$3#jh3I z`spkdNn%mQ9K3^>QiBjTI-2R)Wvm_RBz<4C=UvP+eJAj_tC>DM3Owv)roul_?rtW> z3$Qm`W|rqYp3KWJqEb0827{Fi?^il991Ye}f9|?^Ps~M6e=oC4EX)?Y(Uc{(-0w`R zXc%2RIoKM4T51N9@yez1t#W>CJnv)1ivlThv?EK>7WXpq+9#j^_CXGv#ERPpz#4gX$E0LW5y2Ap8_n z#GUbDs++?3uL_5Un31}hFV8(6E6M>9cBgdxj-|RPP}ElJDizMBBTrR_VW@V>$`g)O zHa}e75USkD+@29=)kZcIixzP^wIqp;G}F;6<{{+-m7iT6D@Fl0O)WK7&2ThImqs{7 zGGUR;INGH_O5fj^uJYEKv=EOzU-;V5X`etRt_&p^tI$N#@mgelZ!}fVbm1xUcR9 z9SS1&ZVU6XDRR@B8S=Zuc0Lv9Rw@apgW8d4fX<~wYmia-PeLNE27)HAY~OTLt9zrA z)S;oUQ+Oz%_a|zrca2%+5xCxFNv+x9wPx8a(Zxe%iubZSM?D5mj5N&g5$O|Qq%+Jk zaWZmoFLtm%U~k^1`2)p5SjnNf{ip1*QTK%K?P~0hI@!CfjVLBOk)&G}BD} z`&dC#i=};*S$VN?WpNLin2jNvD%w9q$84yCcbchmIf&O9 zv$V&##9Fk5vm|Li7IN+hWh6L0oDn;^y0Lmp*Iq*?ERuXoPD`N`k43DBQL#q~h1O&Cm%rv_T6qm=%)MFo*#TGN7 zhtg@!F!*x2kB#C_m}UL*Zm|u$!nXdn)=0SRsB>R*T3i&dcbHY`2>tmCM_?4cVN+Pm}N|3w%CVGc)4nEGTrbb^!#_tbTSJmJ^Mi!GQflnm}&kC5aAAjT;GFK zo8srATNRr_W_fs)m%nG0KmO+C?}IHD^7i5b446xYknkzvlT-X3qNp%Xi3oUXVRCh7 zI1=B31HlW`Nq=OfRj=U=e+;lxorHF@3UbybW~CMc#HXmvlnEXuO$mYa9@ZOH5Y)ut zGt~t{XajLePy>=LS}lv8n`vJXZ!3-fFb8>|ZaHsnG=4W2zEFqXjvqBs7M_h_UrO&A zuIMq-B2U}MWA(p)DO4LLJD#=kb7r|R-IIPEm5S!Vj2_UmO6|o3Gfj;GH8oi1`j1&V!7SJ< zEo3B(Y7CA*id|zT1&8Oyl*sKvnxF-VEJlmmIKnGTsMUeIN~egX&>9xYg@sPOg$P8m zg`V%jJRS>R)K$NTmzCj2Lp7{bzV&Ywx8Wg_q9Pt;(K3GQ;NToh2cpq4{5r#yZ&O7K zN^XrKh#?d*+dT9Xn()6<|(N&1DqSCF-uU1kJuqbU^hsl8)>G>1^*h}Lx5tAH&S6?c& z(A)c=_u}-*+dAUMMF{s`Yh^ zK^VbK3)(TKIwivuC}!)Iyb5kesYEeHYcwA{%o6RxP*m|Q;p9YUbtP&B!!7cq8`Jp+ zi`;pWtJ_GxToQPqZl3}gjcRopwKS)*?DcOWL#lBM8dWHEs+PKPRn^!CeB{nXqJxp) zs2Zplli3ZSyPYAuz)q+oXn=sHqJVTsTdZ9 z_;7}En7ChOq1~L@6NU%%(?4pOHGuMIZTvKWjnrBwZ!f-CXQ9*Yzz-inb9?>b4i(~X z)H^Zt7P)L~I-9ZjR0=sjn4*-BqK0a;Q1uK@oF)sM_u_Um;~p-8#5L0*{}5g=OAmFg ziRIF&k>dAwHt^nIIyO8w49G7Y|$#W?4ptjrIs=A)KCf$J?a=n%Bz8!WQ? zBPX9{k+q*>^Z6EeU@_wK@f0NW6d*ziEOPKWxnd!Pxn%Q`^#>z%9R1xz7J1ikZ~!=iX6CnRqLMBjD@@``EHv|Nn9P@5o+S}6BSwWU@dqvR>rCLrLl)_pk;flKd~Gx`PaXlF zPX}%ls=%%OFlMW5*^FwXB1S|vmC>z)_uWs2%+fqivs=BBG2r!^TQT-A+mc=s)pF;)0(K9D3E3jQ z?Vr!TwaEKYJMix;v|}vz^!FC>bPiQ=}HfjIp z&ldWp8&sFma@;v5KVzY*2XhxYE0163%zuR#M$U_20l!)3#vY)vzk_`bM$AC{l9+^G z6Ar7oE@Daj1DE(|m{~sF5zqgG$kr3)&c84pb#m$j2W6+gD<8pd_WIuzEhP8ug+POU z0Oi!rZp0x0xz&IDuZ7m%%RKgTXx7<<`YNjpuE)4DR=EzOCu$)WGqrT8wMfueaq zqI_0a{YJjXL|gbdA~`a447YNm7#-~yj}S8CeKx?3SiV+^)#tAycSt$-vG>@#41DE6I(xE&VWlN1ns`Tj}Q&P>2Rtsq}el%RsAq_G=#>WF`AnaDprJ z`ZK3gsUZjBgX+$-stTnOp`ojmAy&ES(`-==fQUunrQ>kk4n?_*4mo+L!YwLL(y2xE z~G>Dg~~lX_dCCKCRmK*rJXWSAAM0)$5%nu@pMVyqX9KUZ66 z{~EXguC>xT{jpu+)%|M2Cs=iU(by)KU8=0g;V0*JkKq%o^yaOwM^3WJ`lqw`WGfYI z!`%sD>>d{tp19Vq)+uQ4>m<$-Jt4YIwbCaAh#Q)Q>Pv$+%`phEikQx-t@P3b=1b8` z@8XLI)6peIPk+lhc3xwZZ=WmRwN{Fn2*sw(N^|f8o(KUbRikLw8Kw5L9(7(B`;aRp z%wbgB3@atNd5&m6TR6xex^z_EK{`*P(W)YxI`JlKL?D$nTj_^fgqX|(s5)spP`o(7 zqMsC;rBTP)sBKoWtyDP{>RorFbn!V>`E7M3pKF!pPv?v4RoC!cMZ``9zrjj%>k<7m zk233#j&z~*g78H|dydRD&g$TFm8{mwA`hsUDRQKDgRF;eI{v|Nmb;XSC zphuIS1T00rwk;8Z4zUcy^fe2ZL+f_SQ9^#>NA^onCstbN)?1m!w+e0Q!d?Yth~+jz!{=D=UT! zQA;8#Z9p`@nyuWH9DWufVnkJqcUjdFDZbRY7K=k9`0s8EsEp*EINbAa@aa9M%9h6~ z{FhhMwZuE!tLiXJt19w;zr)@7NbfqUj6a-~5Eg-kMDjz{TWM@BUeND84A#O<@XEP1 zeX0lSs%L6JX9JpBbzmii;SFKUuUnFbHv*tdOPnecg#-|vO{h!IPXxi3WiZ@tCGTK} zG7ng3`y}|%AGE3{i;NV-Y$JSE?aD*w1&zzcQe*HAQbbjG*eaLDXNgD9royDw@GMO72mqDa?i1oUjM7dA4kv}RhKqrR&s%BzIjAB3 zQ5>qhcoC5L87i-KM(jk^6D|nc4JQ`7WTkl}$Sd1xr5^>D z$jdD^tGNL_G}Y-9tL(O|4SSW|%~fJJ=KQmVzaHkbO`2f5fsbX-vcFp#=c?B})MbaA zBU8r4@i(o~I>OE0vdZG7O!2nvaHyQ8#*QD+4?b5NHT;g1`UEkBcf-VJ;#4)xeh~Gi zQF*{h6^HSR$wAtf$Q%eyj66MZ2t!>Krq_6*AcMbWrN=6fs`Wk&JM!2&ebI@9jR+rD z)pJ~a{*hJw`%6}I=~TSIsy_0umA-!*TF9puOnb%TAQ`j=K2^-w$hm6g8z0PFtRszOW<^$)fDBGvy!6WVYFLEmWpExt7_HLd|UQZ1=} z-=W_&=FDql2H#@}iuPPW&_>nOhf(eat9G3fdc)jmG0{RqxEmNL~~hB{Y(h|!@ByZNJ3g) znZ*CJ()=Tkzy8t}JDKL~Vlm-2snl`&jc>T<$SOpqEbI=y!9P}*?(xoRXFQkkZ@7p0 zlK{~rI;-Y9ptXgry)vmHZsB5B#sxGfQg!K@a!TQiTt|bAR&<8Q%WO0*7e;?>lesTv zaifj)3Z9>70!`Nzk+3r>pe3l5w|LZpI|vL3>tp7q79we}Q9lO+O{KZ=8?Aa1XBusj2ZT#*b4KwP8y^gi%$983{H$}u@=ya+dz*FSltD#dt*QanoezKd5kQGjnAgy4Rg64`~D*HJ2LSV?FCn= zStQS2eJRT(Ke;iJXWJ;Jlow{@08B+dTw;B?BuY~aoJ+mRpeE$mXvv)zKi@{DdMj(X zO}@PW)^zmGh`gH~!`s^A?dM(iQ!Q6_1zBrnqZ0xmd;n8Qhk>`q1_i8y7u%@IGZ?YN zCW~uxM0<>YmwEc}v{rXn2dw^fmKEC(O)A|&^H8d~18;$LLY0;n@laT@=!_B`6}*k5 zX%^STrgO}bE;Z+=C3LmX{;Lol(+%L+Em;D|{4yIJv@GD4+vLtT2k&8%|2ggxJpp5v zy_~pMHoS1u%O=Zj^6=g^I=LByq|_!I<2}5OO{vIo^rAFgW}{w-cq6&5jc$4X`c6Mv zIJd2n+U))|IcZ;8F#uy`(4L1_T1+^fK}~9)jix$qK?d2BU$Tf_VWSz7p{j=VM9H2N z@qDn2Y6{^%9|9DJMiM3Tab$1}#_Mh6HcERJ$T8GLUvFhTe}&owj2JO7MV_~y-MFA+WIwKeY_eYK)g~>Uh$~_a1pP#duR=qr}lXB^B@9Apq#&-)pG@pi9N;gjEG~njuu>g$4D+3y=>}k8-wp z%0o6KB#Id*WytLhA;@f}l{eVrifapalZ_^ffCkizM(wnfyI!&KnKo&b9-4U>i>HD# z79H0YqFzf7#cZ2=baPJj9Q4qKddVNV7<r#h@~sc{dl$Zdtq4hyUX#Is6--Lx`H7 z2Off;KM&;;D)|uEUp*IE#e5W<`t<>M{((5Yz$V%H);|%E6060{3Vjv}K}7;^1TR95 ze7PyrBcI=pn0g~hu=MDV9E9blFWzKBo^zJC87*%4giM$2?_>F5(Ef!iyWke}Jplc$ zXpD1%FxM((+-i$B^(PykPTTT!6X93?zbW+-dj%n5s^rdNFt2DaH6(5?#YCUO)k#z1B@Ky;9 zg13*jFb0i~x@tG_@K`Abe@lYW3UWLD;f;zZN$ETZh9YjIaDA={6!DJ z2Gr%@l{aX4Q&~crjVNZI%l~d`YpjkM?|uN~D6nBkzl$8k_j`Q7*P?tW)U%56+{6Of`tD) z)iblp{r>9%4_54&3a3t;@SdpMh9w?Xmuazh!Y8-x4$4oKx?;VL_Fn=zv;kzdDGWz9 z`sk0Q@D13EmN{@656K0e`ovQ{`r>ww&Zm8Je6*I6`V2~=4(JVm9-j3nQ$;%X1y*E> zk1DEg3qI$Arn$X%9`}A5$R1wsQQzxum|jE=5pe=zrZtEYUP6KAKJ2JX7JVJPjKdhQ zg7Gw!YOlSbh6&#hIVe6Lw)$k%wiFeCOfFj!uWh4y`{6OV-6!w393pk>?8g91`x@G` zjDm9{$85uQAUn;&uv*X)x{%j>vgn+Uc*7?*>EP=T$Ty-}bTHR{~A$ z3M2P-Fu*(0Y2P1!P1uR~S`2RIU7!3tCr7;Jqs%wJgTL>SqrS=1ci~G}{Pvc8((O1T z(y``p#x36Cdp9zIY|CIO5epNoJ>I^bzoohO&*L(T>99@7Q6`; zFi>3V^7sUm6BLAxdhUJdqkA*8cKTDV5?}imRHl{q2E^hO zTwC8oHcFqbq0jmroq2A56FTAD(@1^tAKcp~vc(TRIqg81_z|g}i}jySy@2Bh$+9h` zIN+1}k7SC2J~?G*f%w@cp9tmYhfpP3Zht)`fCKM&lzT_!7ayH>CuHdP9RuR9PY#;V zMmtJ7E3`KHZ)l=I4$<;&v3Y;@QEdhgbpP? z#W7zjxLr{EjY&WZ73G_K=AL-kkOJ=fxQ_-FfqnW1Tuf^Okvrw%2Pe(Sg%iPP9{?}1 zd$367vlXB~j!0DGMKk0*3-Sd(vhV+`LNqlfXBHlO&1eYFr>VBD1}o3P zIK4R?J@SNO$asX$a18<$Z$VlMR5Y>re4{JK<-Q7YIl++A2IuOD zC{0rgbc{7jGH7xJPDhGCh26j%rNZE`nO2mMhN{e^N3L-1Malar!ysp#7V4Ub3Vff! zz7I0n!OAqDk-92~Viv772A7CHa%8F5pv?=k<}_^pCUnNfK0O=tLF1Hvc`{@eU28!A z!v;+_344zm6jsEf??F8m=`a|DJavZh466PeBGr6D9{o>&UVy?f1t4-qE4ynUQhbSl z#(TzoaKdgljYTNs_hwYvjGMb2{*+4HUTi=N8x|#)V56NV^Oo?XpV9!{OTJ3%v{r`v z!3S}@L8n%0okSZWh6B*RCbcyv)Ccd7b_Okd8;|<-221a0{0U7L=NNK%uu$)SF(lER zXSBqaTWCjv!dHR{bVd>46e6&~=}c2}HRSPyzy>ks!QK!spNGO!Hh}P=UYu_r{7tqU zLz3@?;=CBjZi?t`c=Yj!{s6u2fx=wR(yvD$aQ%82RFW%-(|aSIA32e*TCua$#{ist zy8LOCQ_vSxJkgmKKM+=(JvpX~(gHDoyZ{x7X;W7bl>7hiIdyXJE=XJbH7(burKwGp}3DB3<8mWx1ap!4H!U0iI) zK?&tzw80=08HJ6#f5xB_{An}2f{nm+}QAtMgr$K27S{WgS*_2zjkkz zeFfU`%qmYt4nw0@M4p(dP!gnW8}aU1?YiP>Lzahoifasc&BSam&yYitTkF@NPJmq< z6V>sZZ>)5oR$5@l>^5oQIzx85AWtkbpbtr-CpzH*zuq9hn5Z`zicViu2|9g~5ramf z?Jt3(E;8i&P^oq^wFbgng9d-GArF1mR$D^LrojHG%k{YUml{;sQsiac8vPMb3#iy4 z-e$<|&zFl8293@}yqw#kyCumOc2(^}{+V|ea%^6nkl3|BxZd4~uCnOWd*Dr0vQ2BD z-LFe11Ct^5ba(8;b^Uhs`rFDxn@P*XE2rc-#+AjB652j7ZR@uUl$YM{c?8NO}MvR>dC-ZA8l zqYK1NgZ8e#JNR9+Na66aD{q7K?0ZOM(2x#VgLldq|p?YRCw%aZr3_$mfsci_Z-jKNt7+7ls_T zAv5huG~)ZeA#^}%?tiaAFMJ7>XCMA8W%CO|F6vjH??>%C+S^RzQr0NE3%@bwz$1A6 ze+#;DngQP8%cC;G?@*QH4v`t*P&`qqoG8cnJ!WPX?#2ILo=>Bf9}T(X(tPogy7G(; zd^h}zUKDUtW-0JFcx1mIM?r4DLqG*d>RC6Nv3Qh`usRgKg0=Y>uav{wmLFmby2c}_ zJ77zu5?sXM6gfF+0Chc2{DJ#=Q=$0Np!px+(EkO3+YM~hF|^2*d&-mO-0fN*`)?$@ zE8>!fwh26n`s#6%R48b1x;PPtS|@1NcOl5xo zPm~s-xk*hsX|2L>Xpw+2&u2v8l{FT4#GACO7;e!%lV-IO!At|y`A0UYUg=G~X;Mgt zV%DNZB&5QO6qSv2o9W?hCnGOc&fl3G!3k0ZOex6BljBM=_kWwNX6rTD!~g2+l&Vj5=< z2lk2X`@vkaVxCZOHDGO(Tg_vt$`{pOGI0fG9JW@(+;KC z^3@R>@UMDC-DI_A+nY3`C7!_l+>B-FU`C%letM)UZupKSwaFB@dM7mXOpxeEB5e3O zn+ku?9%+a!rW|)md(qXD{m&^8=bD6Yx?%l%G^kLPxj=3R>D`dbqA<_{<>}`wy*rZU zL_=T2(TTm#GW9T7_z;xqTYBE5Jxw|vr%>;Ox*^tO%U|zwL~m0L9-A%tm@-&fEc#+K z(lBrROj$WARrELEmj*CoQ$DpBb|Cysbz$|=MwM^R;Z?zMn@^3%qD z!9{lnYPVI-1sG>1%6kw#VyKBgMHSjGIsi;1Z8+I;q2d@}lJ65p$1X(uFm+v^<*7Sv zq$yjyQmS8sTvy5bRPHE~_LpI;E;i}$0a~d(8pZh>L!5j-Gm5BM0ST(KlqTw*H3-^L)_6HO16!HX|F z2^*H<SQL{@`P=?neG0w{q*De)-3{bWZ7RLQkoeO~et}YUN6iR$fNinE zINemyK9*kUh?zLWr?V(^KekJ)DR*|y5p^IYXS9~tv(PUS5!~B|#>QtNs<*!kC1rBR zj8Y?d?q)|vt8V6cQ&!~UiaA)bl%SYv%FywAaXEh}g$~98bmt0mkRQG162+CKyx^K# zag|9kQURQDjmeN?kWn@xDh6!JJk-vh!RxdXh4fuBgV1Wo|L)G~%X-#U>&Hf!?(PEYT8FO;%d;_W^dY)Reu}6pLH2xv$L<%S_q%Q=np) zG;kdrvdd8w08sFadHtRQu>zyAz)IY1$~#{!*Y7}K0R=h0NK(j!V+Ip|uA9;Vk-+y+ z1;L#rjrtT<{z{W7U@r#n3#NSh5NvpN-eoci7otao!l39LlTEQQtig!bG+F7A@5OW> z-dg%xL)?euK~#VR@D+~<7w$J{Tw9>iu13$PY~n5l-foGt=(7|Nj@Fsz zOzj!kxg1ZlXK~a*TBg1Qm9wdPvc@_=*eD~Jg&+YiM4j#G&vS~d2>`yHeM0#Tf5D7; zbtN(1)L1L>B5D_SpljKaft>Ra(!g}SI#q4kS4?@ds=e3>y6|v@*k)2n39KNtn{>f< zT5jl7)Qxe&%9DbnR!cVqkBhypcB3#%GfeS+w)P%%0k+pZq{pv^#_Zpy7%~4#O2Dc92#sO0el+FAg$3d#lMXRv(*cw2e+wXK z2dOoTDQVLz7}FmzSKbX((l6*G5l|wD^6=(3?N{1A3-Xi0rmUJ&CXSeL&!V!Nqo~5O z?>)^%)Yf}z>j_?eLt#FhS&SDdLg?Dj@Jqk*;5-%Ak>RHP2a?$|V}};>hzjrHKe7M! z<6@a*fdc+z%Db=66UR)tGJu`&H;BR?I2*^&4$-^5PFWR#sP%+N8=+6s|3NOK9e_V+ z)fCZ?PMY$*RzdAFl`jSTI)lOp^wBS-PiOfFVEbWDVm08gBO+T_Z%7H6TI`q&*orj0 z8R`{!I=ViOg6YsL3JnwK`4w1wHt52Dn>0rq^d2Edu?+|$sCVuN>OPc~czBsBZh-#L zKsuXaAz0usCwo}grbUxJ#2_t{rO^Jgh*h4Wl8|8}(X9^7t&4P76xH61>Ouq7s%x<~ zTc9vsF;H`^1(%m--H`(yK+6lb=+?qlCmA(z7EZjuv07#nuf1yS-Cx4-aF5S z>5z4q`63OW&nJSHN=K7S6~IuQoZts!k^)Ds{T8y2OiQjSFBL&cb}NQ0rA57lgU!vh zVBn`-Ms+0d}bnVKo5-v zKHU+GLm`TJ*C{H**uPiIX^3xLgu*Okic$-VaZzk3AlQTHFhM9mkqXe4O)LB0{ZPhL z`Mf5{syCWj4Jzbv%M)QP-wVM?g(V-CIijUS1BAevmX@#A(i-+^8;kligU4lCi!T01 z%ZYD?PMO}x{YMi;drJ<^Xc(i8T;vAUvqV+pxBZ1_Tx^DVjE5BR-4oalyH2o#5)WK@<4QDYB_hJOP_qNgPX zbj}pLEcwQXqRif?rQV@aCryUA;-I<_5K1XZ-p8V^e0WUvwP;^Ykss=Zn(DO(z#c@( zV8i|)bEjTC+(SU^Z^>P)^Ta?)7VHR#N=u$e17aMSU7-i92ufhuLVvDNgLnb-e0ymW}##j_ShO;&n zt%@5w=ViCWNn)Hu8|P_7;cApAzPgba&iII>`z0vL1N)FK-Hhg9q9uDjQ7R@`GNmS4 z)L3%DhaoZ9qEi;8e~Kk5hhL0Si^3XkAp6$UO`cJMv|7Z=EP3>~LNVKt51q^x^%iUO)8+1DL!X0| zp6wDlNqVp2xt6>P7s+Y*f5H|&XPZM%+VKe?;h-2 zx1+YW!6M(^;B;=Z7*V4n>n0R8-s3UPs6{A%B)(??P3;bs%9|~@9JVkBs07!P_~|ow zkr!L?lMbyRdVTUvh{4)%)~#q&t^^{VKL(-9vdEb#$xRZsS+t}xw!(6YmI2X3tgxs< zYq0;fTQEZ_5O-K?aRe>vzrl>JM7wmrj8vU&9$JMidAG*&R9=`Kh6ds*izu`jHs5!F zB5o_9^lmVWz1xDt0l?xgK`lkLelI#GQ^a}Y$N2tzmMj_&7WacbZ7A)uI?{WC355DT zwoT>Vvj)ZZqd438ib}lmyeoDs4r@E`s_VS@n5&#U9>5sF2(z&f>vpVMbG?uR4_flK zKVUp;(aei5@ekwY=YecJV#yy5BVw~fE1!mg-=p|SI%}yLWM7XVt6emVUPybK_DHQP z_X*@G@(@8xgArjwF2&>BfYLOMXOyYf%Z-SCb$y1u3ArJKbg<-2&C)j`sW1!ZtyTz^ z`ZSW+%GH=AOvdCtW6984nFgqPEqD1W;-6+~spY+DF0BTp4QD-v7JM2a*F+52r2kxx zls<3Ce~f(n1yswR+X)B*oA3;J$)am}Lqhj5vQ_LodFXGSc*SBUzqa~THLt^Jo7zNW7@2qFMPMnt?yB-oI&2V)T8>6rP#~6 za9flkptkc81P~vfiNa2DG5?XB$-~>jbC*He`oR$HLrZQO64w4r`|bfJ<{J$2)sHNi z^`n-a{V|&JMS7MMY!smF6L3K90fe>M6`x|1w`YpaEP3xC_~2Po(+Rqvy&!QtAb9!; zy?FQt_xzz*^0rnm0RV~ky+Z7_B;B=1g7#Q(X+7nIvs7iT``0?=`hyNQW>MQu^ zzgsX;hwRsqJ0|96e^T5dxMcoPoRJCDjpqt@%%a2&5NrL-gY*2ON5Z2r#vC5E{ zELBBwzwC0XP{eURQ2#73T_rN_Go!9fy~X3XpiKDu5TE1+jp!%f@jDE5f(Usur^XA* zYO0ZVh}#ybsaT)%)y0To?WgP)@VK!3@|(}ggySd3=|aNw!#4|@{35WL0l&QT0>E7O z~-40K1}ICy2KF{hw?Nz5C* zg-G+u`&MAK{IU`V7a1tcP?n_K;9V**{TzgsTseBmBQGwTElmL4GA;4m8uA$p>$K!s4JgZ(j|z5FqD*KDtNnBJ;}3I;jk2N?DB z@kd0GGx;Z9;HM3LVr>2V@`EwiVgQB!d<@ceA#Wh2z5>8;2ijPo(oeYywfykl$XJvH z8RTb<;tjoBFa%{KUKH`><6DTKewy413hQCqgce>5fI*&(;eJ|?&U`4cA){KD=x-Fj zZls?cJr}a>i%^(JOJJ>`l&air6n2U~M_lZeANI@_qy2LB);xU->g6a<`GTHvvD!1E zoZVOyutY0{cF`V*Cgc3%D-@Z96Hv;^E57MGctquRF>vZ7v#Y%gQ_VG?Z%Q=Ox#q?b zX~R=^WK8ly!<`qZK?T0BJv0t>9v)hA*<6I9PzT~t5Rsc;-1vFC--iIr+W{c_X@(^$r1;>~&meCCg))9Oc{3%kmXKnJ;nv8ZPAP?h&4 z^DCZ2reSYh3wrYh%*S^CBVj)3sX$FFm>4!T)L7sryB`kvb$+t?!?{Orrbf?f8WXwqa6IxS`vkV81j4Vzl%w;+e37N-O}wtQgq zB{eg+>S90jZ7)i|)qVr`E;4|_DVAdDfaY_nUopQ^)oEGghtqmkcDk>HejEByNJ>ex z>>kbRvK(op@%%p)+&yM^vEx>t1~WG?Bd$ti#C5x0IY#m7)>eBlC-3mn@-$ph610%5 zWh%RXYTrqT@8K1-lE34{o;*_2Okahh7i%^uM@F!Xx*=Mb4L$tiI#3xt9rydCGp4mz z?Wdf(vBqoAsD$H~bEMy(Sc{8bSt@Pq6>(Ks=a-+C1@#9|i;;ZOW!+T2c+gMBHUN3< zA(eZuY0zo#b$JANk#O-!GW#z-JvR%7;ZeVAwLM90njc@@y>?QJ6L<{O6}J%y56RJf zA!?37K|Jo4CoTXso1eP;ODoFXz@6haG`zw_Rl{Td%IGU>GnZ2pL@$M{6%@jgk?z&e zjDRt~tbjoo&`p!4{PNxB^29TK*(a$;JnNUwt<4e7`RPrL4f8w*1u46h?Qm9ozj$)0F~^WMNdfRR9_(cs44#PxoEN9}D2Jr9oQ9YvFK#ZEuv zmBCWvT|ZpN%EWtql3}f-{ysWOXQR?M4{~Q0wcd(N10hU+-Wh-^^#i}Gf2~mL_RH>f z7w7Fkbv64@)jWt>ZG%|nZmoy-5_d=#MxcBB2yd6}udMR| zc6@~fL2|~T#+mpAiIy|s*@K{ay#YdAeB z0Xv8iUc?6Li#EJqKl|y62e3ScFqju|#4jM~wOV=nuc*!YmG;d9MLY~Da%-V}1eqlq zAG3bM)h=RT_bkNfIagFj34hmC}#N!#^HdK$*8dV06kahddM1 zPosrG5(xUDK&WS^>nq?Jo7lurKdgaub<;=8;_+&>+=VDF!d~UVP^76%hcmU*JRQ%g zvur@la=hX1Hn*uKBLv6UG!n?YdOTXN`a4N}b#p-YY*wJCH4;|v2G=x3KAh*TJGD*tF+GEvzS^ zVH#wkX;Hauk`2jfGL3By*Es%J$*2(I5TGiRs=kx8A|qyGn`V0uhNsg_xq4;1NVkCn zo0=Pmg^_{!_!ALQ_hXV3(OhIAxcW}KA%ZAYQZ?zEh!(j&iCk z%+^!t<|@Xv(3ahE6UoORA&YDP_LkB9ad-t3+X}TzzTev~N^JUNDj;x5(MN{Tt(uBM z=CPI8^6+OVlr#b&?{bv4R4f-;yeGuws=}5(eqC#<<_z^ zy)(*Hlp#to@sRJLeoGn+xD-FEt4)u!1JOR$rcVq|{qt&Sx1)1^B&GKps+8KMTrc?xF|Ea}*$xy!R?w>q)17 z#yhqbw)wko_3ew&(x|_R6^m?r0kU&BtXu%{OPKu6feDC;f7j2J*VdGZ{m0oJ zhT5|4@hp8fsw) zOr$e=v=k+48D&$SLim7;MRpp!vI*?j^8O-D8%OtkuI1&7=h|ma9bOAHvu8wNyG*d< z&chj^+NSCkK};svbmVOu+DW#YvKR8+2oc!*dYnAc%hoSNZ&_@vVKGNnr#8S8I~wB0 zM-K3ALsL41IaXK42y^LS%a$v{Vu8&u>odf4Hl2T-D9T=l_WVRY%iB205ngZ0Rc#Rx+NK44@Orutbt8Io z#V_4tQ`Sn1f00cm*TMJTW?RYr@t)k%kkf*DKdH+QbiGPf_VDih!%HNc<1G|{B}rzSb+^) zE(-M9Q5#SS&t=QE+S&RYNO9yLFgKfVQ%Rfb%b>Wu)0Um?%FtHQd7ncqw~Cv1M({0< zXnwpOJ)oewkRMcG^JxEtu;{wmrgzIlW^p6E;ytLsG^LSj(-WrLi-J^sN|+!75wxiL z$o>qA*&YmvxZg%_tWvSs_F}VMo(#bLHT*q(NSO+!lL_!zn`YjL9rJ)kSZSXR%G3vK zj*S);582RN6zLD6SyAIdYl}y0^#t*PX*`O{?cTiN$5HKn`wl8}ZhWxGdYhL01-@(p zrs_Vxv23*I>9#Ps-ei0HStlOWX7rWD4w~^E1No#a*Y-{pPs37hI`+l0Hhb2#5?gGx zD5ED%!l&;!G$~UMDP@h9`gvsYN&9cd3-8(cf-RTb+D3m7m2x;fTAG}9Ydq~erp1R| zjx=OxY>XjN)_wddUco)_1dMFAqE@B{g?pgV7Ta+i`{lKH6}b@K#dHPHATO^)@|A?Q zk+^mT_7>m?8lbLU$8Gd_hIqr4S-sN5n>NjT91oqh(7Y^qTPFAc69$xFbr-*F(<}FC zc^U7b25*-LoN9yC={>bzaZ{9V>3uqN1l-0hR4(w03OAiXSP)zOr=~#Pja)$U{))L+ z^FVy?LnN6eR8JS)xDC(ce}fPL04~4)H`TQ~>1r$=VN2yfq4F^nw3jFfeiG>+Hi=;J zVC{%Ln|x}^6+^SdXSN*jZ5#2qEibbY#h13c3*jF3f(G=)asCQ*zG@rZhhCVe>^=tw z(^DAOpt7cRZUyTlxu>sfIL~+yI`)GQxo|e!Fh4B5!R7vGIOAJ1W(LNyDT{);e}`On zq|ZoqhfQRc=6bx7xv%eS+3E$L!r5}0RW5$8>2-F({?VrQj^l>@32eiR>~;sxI2V=G zQUald{rUUV;{(7l99#=W>Dd@!ubRsd6_7XdK~(p|9W7!AQv9u-k*~xyW{mnDvgN^Z z^0Z&+s~_>X!{D-(7K3fshumWpqQNnBSReN|5hAVu?R&8j}Mr z$Ukw);sfi-1oxom1 zbZHxx!PiHjL-+?A5kfzTlUOxhqRnw1crN%yowDVIxLo}-IxLIO)olF?QqapUO!EBU z-N(TWXc!XAZo0rbuhXAq8-Zk=H-i6vEGiGuK1NEKIZZ-_*?I0KGOiH_WQphP4& z99XuIpGbf^v3~)B0kqT}LwoR#%5~(b%xpc6M?ts4x1~Wmknh0!AV)7iQBYQ2oFs4k%@Ku; zyuN*oUZgT39vuNu?9irZ;2cUE+SyMO>1F&?nYGR1&&nOoN$JXZzpX4j`NI{c5(yFT zn5G3XS|Y7(!bk+h7+hQ{ht5sWGSXY40u$Yy>NTnq$ci4daloWD_`0-p@N7vYyAVQ+ zc8>i0v6kW-hYkl}Y1sb7=EA;CH|f zU|_!sl|z3whsqB_bJ!iNlR=#m8)m471J~p{(bJI!e#sWS9EGwJaZc=wAAL0dH^3k# z+Uzl?2HOXne*T=)%w#OMsg4tJn0 z4a!h{ycppiE^>x%#4JxEa3Oaejcn(LiyR8%LdSHmL#-didw8^i*mDR#;>fEP7m2YB zt%Ku%80XNnFX3f49v{O?-&04lkn`4=VuB+Vj4BY-4y_pj2I&&iC|7&@Psb_p+>4oN zPDDQQkPQ)Oy7%fee$?O*TsVmWO>x?G!zot&I?oo99rE>Hiwj3S_-9Ko)nP=~h9~$m zM{amHJ7YS!Y;;35__5Ex>ewZEEy|VKy@~+s6x1P|gs;W;hsY5D;q_RE*{Dz|2kgj| z*ODXZ9qGTdP|R^;=kb~PT$EM73OGp~t_^|bc28=>+7-0*4E#vuJ_cFLm6*WIc0 z(DzHhab4|@_9UqOH4Y_~Ab*}ir#?jfwT=wzF4PyIKlmn0&a@hAD6ej5s2Hn;SZ?EN!3>UQ5SD<-{3}2Z{hvT&Pa>+wP1KcA2 zi-t3l1owHT10{8ySm|(R#9Xlolo6KWEF>(5N~8$AF`bL?4&IHbe#c!7oqh>(b+<$F zSHg7RUerxhtoR`fVuSk}`T{W-#r+s23{s=-&=ys5F99O7VrNu0Yq0Kb7wBuzI;R2E zn?|{a{kjgBh+EfFq?j=|;RB95vk1O%4#F!Wlgt(=`a`HzsNluqp4kcFVMorKTcAI3 zR*y;AqqO)0e&%BiEo}-J(BqC=mtB_j1ZuG)FT$}!<&7I0I@AS{yiKTuU|e-igR0cQ zsZ=q?4of@vD$dZKLi3QqgRffZ!iv~2U^o4YBl|bc&UqGPOxn*rJ(ayHd-NX4{cLeo zO$TXs&Y{{*AhLWO)zelD$B#PIAJI`>aA;mRc?s&dx5Y@B zyyeKxpxu4Pk>?p9vD1;eN0#aDqFsp$?8=gNKAMp6K2prXc@|!SYU*ma;V$F?a1xr- z{Z)?m!11ssrtZ=#^)%drD{4hrpfRiv|MCxMUL4Fd{_U`07d~>sAddOSp|z8-E+0Eo z%7(F@IDZkukih%3EFd`Uo zY++isU=(un1XRo-#K+388XjGVE}Qt%?xUK|k66cSM$S7r$yE+&QJC=*R0Zx3cyJOH?u zK~B(>hXG@oJQkQA(xK)fL$1N<%C;HMUE?5+3~0)>|Uw*`SPW1KOj#2 z%l;aO*Lkjb^U~U6%||J#z*%&I1w(&Oh3;tH23~==60js~p!0;vD2kKN17!&qM$E>l zAvQFRvL}j_-d*-BhoOec_o%ExY}ei{ZJ&X@`=CNljw#EK-8%b3U)QrG=L_8OT)()N zey)7`mu$U1>ayfFAqtmGzH0Ed1CX7D=%zUBr08*vbK#FYcN_Gi)tCMfIMdX&`9{waR>|drk0T4 zAptg$<&1YdYawHp=J_O6yK=}!1^OlEh<)5W5RHj0t(lEmW|Av^ou4miTxGJ^dn@Sh zrLJ5MSEfy-ZsoYkr?_&=v$p;v5eAue<2@vF4byxGWREm(|2i&#cApsChl8lFL`($^lM=C7C zUna{l|860!bZKly)T{3q(5|8_$+#%4cIAN`aHezV{y5+`%yU=W1v&G4mx2a(j0I?3 ztZ-22=V^G8FLYU7RV=P|>C5}Ek8i*pIt7!$8&QuPSdxvI5OMJ;-sIAS*MqPua;f`y z*n2mlMp@LC&no>dr@C8E%9m&Co{HTri(Pr}Kv*nsz1Not<6~cgx8PD&zWiobUxt?H z9F7X8dMh+5{Whfem$Pi69{%}Cz_9|EnN*%D8Z>XWyRz=uppdRo#49z?ovzHfvP7%| zUAaD6kSljT+FIP@((li}GxToSSuS84?8@!$=WF*e?M;jKOh4{(l_a#h9aEM2@hMjQ zG|1n)WojfNUX3cPaSTIr%Wjuk;YoS2j8Kua6vrc zO8=fr@t6x_3s8%2A_#Luy_tv^V>Q#P;ROksdJr>UQO;BJ%&- z!z&p1Fql_0GC|(z(ztlYRJLOR&*1&@Dtah^OeK}h_2VeLhHdwDzS!Z?M;kEk*Il}e zAWnV5m0!HjL4OmKvpsu;O%7Z&-g2od4x8p}S7zT0M%<v(`Z8&*>Ma&`E90Zo6UL7Cr~Bn=g@9 z!V<|0dTlX`5BGu~ZNlsNEByN^2-7~4LtM8F0bELU8rs*C5*ESi{m5lNSHM(hV3fxPOXAoaKr2j)j4 zRp2}&HkI4|cEwLFYhA6jv4BPgT)FeR5M21cb%Hhg5qAIEGlehpXS8kfwj303pE^V< zN^rCM;?nePuwVPtrLV37jXn%=)vud4;sO^ZGscS^b(J67p!tS;>n?=52E97n*wDuF z$^6}wC${8>KU~@V$B_8bl_&2_7k|0(rsS4s$0DO>uvJk{zQ5g-Y54WWT?)OdC7DW1 z$TN0A@fqH8g<&hiKQ7$H^YoKyA~^W-%lTS--YF#c#6@iX6+&Bu2m8jooJMhqtZJ4- z1NuX?c?QYCC_Ztr04mV#-@%o_9I5zMm=TJAynJ~`G!4id)w!ZsfNuR0>XznU^&-4U zRLu|<@PYws&xeOqd_ZY+$w?HJd-j3%1=O?sNH>ds?-LM4fKD1B75rrXZs4)40Q`Lb ze-ZH9>lLB511h%cs!Jfw`nV4S&KT20-Gm#E0|o?(0~pKyCU7y+n-&48NYu^|2?5#u z*F2FJXkZJ|V<8Cs>#j_KP1d*IJCXy^gx`E>03z+QI3=M;3oy=STag|Bz+i5E1_r^8 z3hypNTqjtM^g-04O8~#jlb(t4?hG?mLJJUzQ2GCg>;R4X6YuI!KnBmt)WfKw94BZD zhsw(d(6b+d?&JpKq3cRTUV!CGFrlqh_iTQE4!;gFg@OQkwWkLZwTw987oj6~N8JuT z$p~gfae&S{fHy};KsJ4>Rp(Mvz_Oo>Mgrq!9%)&$EFw&9j5k^yp!-WfS1SUH2SEGI zhcv$xmikq&1aASmP_zy(Hb*{9yAReSZO~JM_o{lsFWRaZZyDEyztS#1E4tyDX^%QN zP&a4L4~Xy?JO@d>kv&O9pV@fADO%A1`S6!Lq-CESO0i>rHd=5=?G%vJV}RAhUzCTh z%QOcM;;(c;x)3g}KsfB)LUawt`z}ot=VD&p(8AhzbQDnvVtbZUMRB zU|~)VH0MS2$RyJ(){nuscbKQ$?HQ0?U79X>1*kfNhgk1`d}AMiuLKagJJnIKd51A2 z?~882908rSzW~?D3wV&qGhR+;p^JV2@FGbKXj%OO^6RI=Vn9Hiz9mN=s4BZ-A$odm zy=Da~k%n9#rn(0&AAMO53dpCAl~BN^uzZ+Ax+dKHp<3m_Qt9S}GVK{7-i9M@6< z|FzeL24vlu0x>K=$BIDuh6mUXuT&r5b$2i1TDI0qj0(`a7AU?rK%0+1%{)3lgCB-- z*O-8M8q1GXr|DzSqzx-Xisim9+;-!Tr5+hg zCIsZlTk=IUj(M{ZF%dicV#xC+1?0ftFe(Yq20z%FO9RyYAIKOcqZ?1AzqP$9rUvLu z#PZdq(U8tSm7X4uW0tfLGXel+Ya?c2&{MTUv-iwdbCkBXEP!`Jf?}q=11@d1x0zV=sl;@ z)z#N@SNo7_uR>W49V^tr%734a4h6^=2iEMafc(55U)&84P~cj(!q{tuL#J9h(R&u$ zgO+?M8Zlk{=SGY{P{d*GQjMs?;C%t=TLI0={U}M4n^TkH)TUb`!7^i zINIb3K$7(+l6*-qAI%3=KKWgWcsxM&{EY4Q1SazNB)R6({|n8$KA^0TKZ87kIZ)^Af;!K3 z{n-GO=&*5lE}$gNazaqopI2Qi3?)+6JUHaPfMi%+_alri3moxM04Ds!;*|if;w!|q z0DWC3g4%Yn!Jmp(v48$z>2^RC4$BYYTl^o|t81}BosCM*GJ_X4m5qRtn=7VMP~C8m zt2L!*BSog(45evw*YG#92$9`}LA?1j#L_xAfO^-~ zg;Q&;sOMj+wndF{5rQ924M$}F!uAkEE&94##Kn7&Yk;_jtX#&bQoB~Vj{O_cMQYK z1+nSK!F;G6!dsb3&KfGONhWm}g>?+lPnEcqvzjQ;tKMg1uVLAgHdJJkhR}+segr|R zW|F#ed9QeP1bpcuOP)iUE3p^YlEm8!@sYX6quSB&g@GgptE6VrOm(edH5MiL4 zQih29^a|ABU(Sr4x1%GYYN>XWTwBuIANfLkE7bECI(L|`_0~wHQL7OmrMV(hZD_(E zk)^ez!54|bq;@FfDd6j{_wep&4cgP`AtJr)ImqUH92=WjgZd80Vm|+@MNmE0ccgPM zSkbA8tUi<|IyV9FPor%^MDxB~(13SDL}^i@GlDqdH0C9I(JxY}5Y$TCG8AC*-0qwxBsWTBLsHfcD zEYqyIVDRw4!+N0{;N+kIIv>m4^x|lduJxf`hQTGgFA57HWXEi-Msfjl8-w}kM>7VB zG^q9S28*0-15nTVJ$(9=C}?=yEG70HfL~QVkUxUI9(6dY9F|%2m9z=H1ese@*HQze zP-JN^^3&vmyp;A3ILxlELXtPhSu?IE!Vu)D)q_k9%T&Q z3kIRb(dr|Sr5Hn7t3+!1v8b;$1_smn%30v8*fCyN#EnA{|Io(Wu~2lp>SlpQwkAX> zF*;PVJ^!F;ML9Zb;m9W$td)O(f_1K)!{JOpL_nUIv*)oy z5OT2L;_B5x*U&XpAd8n$?S(%!>^MSe0rPMEgE4grT(m`d8kqBLztr77Y|RdM&c^<>t6dT3jhCt&TR1fbaV(4-LBxoiJgG&5R-s(M4HE>&C$6_ImpAB2lE>z~_&;4HwO|8_5_WGPIk> zy-) -> ChatLocationInput { - switch location { - case let .peer(peerId): - return .peer(peerId) - case let .replyThread(messageId, _, maxReadMessageId): - let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxReadMessageId: maxReadMessageId) - return .external(messageId.peerId, context.state) - } - } - - public func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic, messageIndex: MessageIndex) { - switch location { - case .peer: - let _ = applyMaxReadIndexInteractively(postbox: self.account.postbox, stateManager: self.account.stateManager, index: messageIndex).start() - case let .replyThread(messageId, _, maxReadMessageId): - let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxReadMessageId: maxReadMessageId) - context.applyMaxReadIndex(messageIndex: messageIndex) - } - } -} - -private func chatLocationContext(holder: Atomic, account: Account, messageId: MessageId, maxReadMessageId: MessageId?) -> ReplyThreadHistoryContext { - let holder = holder.modify { current in - if let current = current as? ChatLocationContextHolderImpl { - return current - } else { - return ChatLocationContextHolderImpl(account: account, messageId: messageId, maxReadMessageId: maxReadMessageId) - } - } as! ChatLocationContextHolderImpl - return holder.context -} - -private final class ChatLocationContextHolderImpl: ChatLocationContextHolder { - let context: ReplyThreadHistoryContext - - init(account: Account, messageId: MessageId, maxReadMessageId: MessageId?) { - self.context = ReplyThreadHistoryContext(account: account, peerId: messageId.peerId, threadMessageId: messageId, maxReadMessageId: maxReadMessageId) - } } func getAppConfiguration(transaction: Transaction) -> AppConfiguration { diff --git a/submodules/TelegramUI/Sources/AudioWaveformNode.swift b/submodules/TelegramUI/Sources/AudioWaveformNode.swift index 691bab2228..1d490215c5 100644 --- a/submodules/TelegramUI/Sources/AudioWaveformNode.swift +++ b/submodules/TelegramUI/Sources/AudioWaveformNode.swift @@ -4,6 +4,7 @@ import Display import AsyncDisplayKit private final class AudioWaveformNodeParameters: NSObject { + let waveform: AudioWaveform? let color: UIColor? let gravity: AudioWaveformNode.Gravity? @@ -20,7 +21,9 @@ private final class AudioWaveformNodeParameters: NSObject { } final class AudioWaveformNode: ASDisplayNode { + enum Gravity { + case bottom case center } diff --git a/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift index 8cece700e8..33a435c828 100644 --- a/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift @@ -46,11 +46,7 @@ private func actionForPeer(peer: Peer, isMuted: Bool) -> SubscriberAction? { } } } else { - if isMuted { - return .unmuteNotifications - } else { - return .muteNotifications - } + return nil } } @@ -182,7 +178,7 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { self.badgeBackground.image = PresentationResourcesChatList.badgeBackgroundActive(interfaceState.theme, diameter: 20.0) } - /*if previousState?.peerDiscussionId != interfaceState.peerDiscussionId { + if previousState?.peerDiscussionId != interfaceState.peerDiscussionId { let signal: Signal if let peerDiscussionId = interfaceState.peerDiscussionId, let context = self.context { let key = PostboxViewKey.unreadCounts(items: [.peer(peerDiscussionId)]) @@ -226,9 +222,9 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { strongSelf.badgeText.isHidden = false } })) - }*/ + } - if let peer = interfaceState.renderedPeer?.peer, previousState?.renderedPeer?.peer == nil || !peer.isEqual(previousState!.renderedPeer!.peer!) || previousState?.theme !== interfaceState.theme || previousState?.strings !== interfaceState.strings || previousState?.peerIsMuted != interfaceState.peerIsMuted /*|| previousState?.peerDiscussionId != interfaceState.peerDiscussionId*/ { + if let peer = interfaceState.renderedPeer?.peer, previousState?.renderedPeer?.peer == nil || !peer.isEqual(previousState!.renderedPeer!.peer!) || previousState?.theme !== interfaceState.theme || previousState?.strings !== interfaceState.strings || previousState?.peerIsMuted != interfaceState.peerIsMuted || previousState?.peerDiscussionId != interfaceState.peerDiscussionId { if let action = actionForPeer(peer: peer, isMuted: interfaceState.peerIsMuted) { self.action = action let (title, color) = titleAndColorForAction(action, theme: interfaceState.theme, strings: interfaceState.strings) @@ -237,12 +233,12 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { self.action = nil } - /*if interfaceState.peerDiscussionId != nil { + if interfaceState.peerDiscussionId != nil { self.discussButtonText.attributedText = NSAttributedString(string: interfaceState.strings.Channel_DiscussionGroup_HeaderLabel, font: Font.regular(17.0), textColor: interfaceState.theme.chat.inputPanel.panelControlAccentColor) self.discussButton.isHidden = false - } else {*/ + } else { self.discussButton.isHidden = true - //} + } } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 725eaeb617..7f473217a4 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -60,20 +60,6 @@ import UrlWhitelist import TelegramIntents import TooltipUI import StatisticsUI -import MediaResources -import GalleryData -import ChatInterfaceState - -extension ChatLocation { - var peerId: PeerId { - switch self { - case let .peer(peerId): - return peerId - case let .replyThread(messageId, _, _): - return messageId.peerId - } - } -} public enum ChatControllerPeekActions { case standard @@ -89,7 +75,6 @@ public final class ChatControllerOverlayPresentationData { private enum ChatLocationInfoData { case peer(Promise) - case replyThread(Promise) //case group(Promise) } @@ -289,9 +274,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private var checkedPeerChatServiceActions = false - private var didAppear = false - private var scheduledActivateInput = false - private var raiseToListen: RaiseToListenManager? private var voicePlaylistDidEndTimestamp: Double = 0.0 @@ -344,8 +326,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private var hasEmbeddedTitleContent = false private var isEmbeddedTitleContentHidden = false - - private let chatLocationContextHolder = Atomic(value: nil) public override var customData: Any? { return self.chatLocation @@ -370,18 +350,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case let .peer(peerId): locationBroadcastPanelSource = .peer(peerId) self.chatLocationInfoData = .peer(Promise()) - case let .replyThread(messageId, _, _): + /*case .group: locationBroadcastPanelSource = .none - let promise = Promise() - let key = PostboxViewKey.messages([messageId]) - promise.set(context.account.postbox.combinedView(keys: [key]) - |> map { views -> Message? in - guard let view = views.views[key] as? MessagesView else { - return nil - } - return view.messages[messageId] - }) - self.chatLocationInfoData = .replyThread(promise) + self.chatLocationInfoData = .group(Promise())*/ } self.presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -479,7 +450,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: strongSelf.chatLocation, chatLocationContextHolder: strongSelf.chatLocationContextHolder, message: message, standalone: false, reverseMessageGalleryOrder: false, mode: mode, navigationController: strongSelf.effectiveNavigationController, dismissInput: { + return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, message: message, standalone: false, reverseMessageGalleryOrder: false, mode: mode, navigationController: strongSelf.effectiveNavigationController, dismissInput: { self?.chatDisplayNode.dismissInput() }, present: { c, a in self?.present(c, in: .window(.root), with: a, blockInteraction: true) @@ -709,7 +680,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, openMessageContextActions: { message, node, rect, gesture in gesture?.cancel() }, navigateToMessage: { [weak self] fromId, id in - self?.navigateToMessage(from: fromId, to: .id(id), forceInCurrentChat: fromId.peerId == id.peerId) + self?.navigateToMessage(from: fromId, to: .id(id)) }, tapMessage: nil, clickThroughMessage: { [weak self] in self?.chatDisplayNode.dismissInput() }, toggleMessagesSelection: { [weak self] ids, value in @@ -1111,8 +1082,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if (peerId.namespace == Namespaces.Peer.CloudChannel || peerId.namespace == Namespaces.Peer.CloudGroup) { postAsReply = true } - case .replyThread: - postAsReply = true + /*case .group: + postAsReply = true*/ } } @@ -1169,9 +1140,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return .single(nil) } } - } else { - resolveSignal = context.account.postbox.loadedPeerWithId(strongSelf.chatLocation.peerId) + } else if case let .peer(peerId) = strongSelf.chatLocation { + resolveSignal = context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) + } else { + resolveSignal = .single(nil) } var cancelImpl: (() -> Void)? let presentationData = strongSelf.presentationData @@ -1451,8 +1424,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G actionSheet?.dismissAnimated() if let strongSelf = self { let peerSignal: Signal - peerSignal = strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.chatLocation.peerId) - |> map(Optional.init) + if case let .peer(peerId) = strongSelf.chatLocation { + peerSignal = strongSelf.context.account.postbox.loadedPeerWithId(peerId) + |> map(Optional.init) + } else { + peerSignal = .single(nil) + } let _ = (peerSignal |> deliverOnMainQueue).start(next: { peer in if let strongSelf = self { @@ -1628,9 +1605,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch strongSelf.chatLocation { case let .peer(peerId): strongSelf.navigateToMessage(from: nil, to: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp - Int32(NSTimeZone.local.secondsFromGMT()))), scrollPosition: .bottom(0.0), rememberInStack: false, animated: true, completion: nil) - case let .replyThread(messageId, _, _): - let peerId = messageId.peerId - strongSelf.navigateToMessage(from: nil, to: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp - Int32(NSTimeZone.local.secondsFromGMT()))), scrollPosition: .bottom(0.0), rememberInStack: false, animated: true, completion: nil) } }, requestRedeliveryOfFailedMessages: { [weak self] id in guard let strongSelf = self else { @@ -2174,40 +2148,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node)), items: items, reactionItems: [], gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) }) - }, openMessageReplies: { [weak self] messageId in - guard let strongSelf = self else { - return - } - - let foundIndex = Promise() - foundIndex.set(fetchChannelReplyThreadMessage(account: strongSelf.context.account, messageId: messageId)) - - var cancelImpl: (() -> Void)? - let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: { - cancelImpl?() - })) - strongSelf.present(statusController, in: .window(.root)) - - let disposable = (foundIndex.get() - |> take(1) - |> deliverOnMainQueue).start(next: { [weak statusController] result in - statusController?.dismiss() - - guard let strongSelf = self else { - return - } - - if let result = result { - if let navigationController = strongSelf.navigationController as? NavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(threadMessageId: result.messageId, isChannelPost: true, maxReadMessageId: result.maxReadMessageId), activateInput: true, keepStack: .always)) - } - } - }) - - cancelImpl = { [weak statusController] in - disposable.dispose() - statusController?.dismiss() - } }, requestMessageUpdate: { [weak self] id in if let strongSelf = self { strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) @@ -2267,7 +2207,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let chatInfoButtonItem: UIBarButtonItem switch chatLocation { - case .peer, .replyThread: + case .peer: let avatarNode = ChatAvatarNavigationNode() avatarNode.chatController = self avatarNode.contextAction = { [weak self] node, gesture in @@ -2338,497 +2278,290 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) - let chatLocationPeerId: PeerId = chatLocation.peerId - - do { - let peerId = chatLocationPeerId - if case let .peer(peerView) = self.chatLocationInfoData { - peerView.set(context.account.viewTracker.peerView(peerId)) - var onlineMemberCount: Signal = .single(nil) - var hasScheduledMessages: Signal = .single(false) - - if peerId.namespace == Namespaces.Peer.CloudChannel { - let recentOnlineSignal: Signal = peerView.get() - |> map { view -> Bool? in - if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { - if case .broadcast = peer.info { - return nil - } else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 { + switch chatLocation { + case let .peer(peerId): + if case let .peer(peerView) = self.chatLocationInfoData { + peerView.set(context.account.viewTracker.peerView(peerId)) + var onlineMemberCount: Signal = .single(nil) + var hasScheduledMessages: Signal = .single(false) + + if peerId.namespace == Namespaces.Peer.CloudChannel { + let recentOnlineSignal: Signal = peerView.get() + |> map { view -> Bool? in + if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { + if case .broadcast = peer.info { + return nil + } else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 { + return true + } else { + return false + } + } else { + return false + } + } + |> distinctUntilChanged + |> mapToSignal { isLarge -> Signal in + if let isLarge = isLarge { + if isLarge { + return context.peerChannelMemberCategoriesContextsManager.recentOnline(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) + |> map(Optional.init) + } else { + return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) + |> map(Optional.init) + } + } else { + return .single(nil) + } + } + onlineMemberCount = recentOnlineSignal + + self.reportIrrelvantGeoNoticePromise.set(context.account.postbox.transaction { transaction -> Bool? in + if let _ = transaction.getNoticeEntry(key: ApplicationSpecificNotice.irrelevantPeerGeoReportKey(peerId: peerId)) as? ApplicationSpecificBoolNotice { return true } else { return false } - } else { - return false - } + }) + } else { + self.reportIrrelvantGeoNoticePromise.set(.single(nil)) } - |> distinctUntilChanged - |> mapToSignal { isLarge -> Signal in - if let isLarge = isLarge { - if isLarge { - return context.peerChannelMemberCategoriesContextsManager.recentOnline(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) - |> map(Optional.init) - } else { - return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) - |> map(Optional.init) - } - } else { - return .single(nil) - } - } - onlineMemberCount = recentOnlineSignal - self.reportIrrelvantGeoNoticePromise.set(context.account.postbox.transaction { transaction -> Bool? in - if let _ = transaction.getNoticeEntry(key: ApplicationSpecificNotice.irrelevantPeerGeoReportKey(peerId: peerId)) as? ApplicationSpecificBoolNotice { - return true - } else { - return false - } - }) - } else { - self.reportIrrelvantGeoNoticePromise.set(.single(nil)) - } - - if case .peer = chatLocation, !isScheduledMessages, peerId.namespace != Namespaces.Peer.SecretChat { - let chatLocationContextHolder = self.chatLocationContextHolder - hasScheduledMessages = peerView.get() - |> take(1) - |> mapToSignal { view -> Signal in - if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendMessages) { - return .single(false) - } else { - return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder)) - |> map { view, _, _ in - return !view.entries.isEmpty + if !isScheduledMessages && peerId.namespace != Namespaces.Peer.SecretChat { + hasScheduledMessages = peerView.get() + |> take(1) + |> mapToSignal { view -> Signal in + if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendMessages) { + return .single(false) + } else { + return context.account.viewTracker.scheduledMessagesViewForLocation(chatLocation) + |> map { view, _, _ in + return !view.entries.isEmpty + } } } } - } - - let isReplyThread: Bool - let replyThreadType: ChatTitleContent.ReplyThreadType? - switch chatLocation { - case let .peer(peerId): - //TODO:localize - isReplyThread = peerId.isReplies - replyThreadType = nil - case let .replyThread(_, _, readMessageId): - isReplyThread = true - if readMessageId != nil { - replyThreadType = .comments - } else { - replyThreadType = .replies - } - } - - self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, hasScheduledMessages, self.reportIrrelvantGeoNoticePromise.get()) - |> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, hasScheduledMessages, peerReportNotice in - if let strongSelf = self { - if let peer = peerViewMainPeer(peerView) { - strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages) - let imageOverride: AvatarNodeImageOverride? - if strongSelf.context.account.peerId == peer.id { - imageOverride = .savedMessagesIcon - } else if peer.id.isReplies { - imageOverride = .repliesIcon - } else if peer.isDeleted { - imageOverride = .deletedIcon - } else { - imageOverride = nil - } - (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: peer, overrideImage: imageOverride) - (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil - } - - if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages { - return - } - - strongSelf.reportIrrelvantGeoNotice = peerReportNotice - strongSelf.hasScheduledMessages = hasScheduledMessages - - var upgradedToPeerId: PeerId? - if let previous = strongSelf.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.migrationReference == nil, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, let migrationReference = updatedGroup.migrationReference { - upgradedToPeerId = migrationReference.peerId - } - var wasGroupChannel: Bool? - if let previousPeerView = strongSelf.peerView, let info = (previousPeerView.peers[previousPeerView.peerId] as? TelegramChannel)?.info { - if case .group = info { - wasGroupChannel = true - } else { - wasGroupChannel = false - } - } - var isGroupChannel: Bool? - if let info = (peerView.peers[peerView.peerId] as? TelegramChannel)?.info { - if case .group = info { - isGroupChannel = true - } else { - isGroupChannel = false - } - } - let firstTime = strongSelf.peerView == nil - strongSelf.peerView = peerView - if wasGroupChannel != isGroupChannel { - if let isGroupChannel = isGroupChannel, isGroupChannel { - let (recentDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.recent(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) - let (adminsDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.admins(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) - let disposable = DisposableSet() - disposable.add(recentDisposable) - disposable.add(adminsDisposable) - strongSelf.chatAdditionalDataDisposable.set(disposable) - } else { - strongSelf.chatAdditionalDataDisposable.set(nil) - } - } - if strongSelf.isNodeLoaded { - strongSelf.chatDisplayNode.peerView = peerView - } - var peerIsMuted = false - if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { - peerIsMuted = true - } - } - var peerDiscussionId: PeerId? - var peerGeoLocation: PeerGeoLocation? - if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { - if case .broadcast = peer.info { - if case let .known(value) = cachedData.linkedDiscussionPeerId { - peerDiscussionId = value + + self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, hasScheduledMessages, self.reportIrrelvantGeoNoticePromise.get()) + |> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, hasScheduledMessages, peerReportNotice in + if let strongSelf = self { + if let peer = peerViewMainPeer(peerView) { + strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages) + let imageOverride: AvatarNodeImageOverride? + if strongSelf.context.account.peerId == peer.id { + imageOverride = .savedMessagesIcon + } else if peer.isDeleted { + imageOverride = .deletedIcon + } else { + imageOverride = nil } - } else { - peerGeoLocation = cachedData.peerGeoLocation + (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: peer, overrideImage: imageOverride) + (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil } - } - var renderedPeer: RenderedPeer? - var contactStatus: ChatContactStatus? - if let peer = peerView.peers[peerView.peerId] { - if let cachedData = peerView.cachedData as? CachedUserData { - contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil) - } else if let cachedData = peerView.cachedData as? CachedGroupData { - var invitedBy: Peer? - if let invitedByPeerId = cachedData.invitedBy { - if let peer = peerView.peers[invitedByPeerId] { - invitedBy = peer - } - } - contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) - } else if let cachedData = peerView.cachedData as? CachedChannelData { - var canReportIrrelevantLocation = true - if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member { - canReportIrrelevantLocation = false - } - if let peerReportNotice = peerReportNotice, peerReportNotice { - canReportIrrelevantLocation = false - } - var invitedBy: Peer? - if let invitedByPeerId = cachedData.invitedBy { - if let peer = peerView.peers[invitedByPeerId] { - invitedBy = peer - } - } - contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) + + if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages { + return } - var peers = SimpleDictionary() - peers[peer.id] = peer - if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] { - peers[associatedPeer.id] = associatedPeer + strongSelf.reportIrrelvantGeoNotice = peerReportNotice + strongSelf.hasScheduledMessages = hasScheduledMessages + + var upgradedToPeerId: PeerId? + if let previous = strongSelf.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.migrationReference == nil, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, let migrationReference = updatedGroup.migrationReference { + upgradedToPeerId = migrationReference.peerId } - renderedPeer = RenderedPeer(peerId: peer.id, peers: peers) - } - - var isNotAccessible: Bool = false - if let cachedChannelData = peerView.cachedData as? CachedChannelData { - isNotAccessible = cachedChannelData.isNotAccessible - } - - if firstTime && isNotAccessible { - strongSelf.context.account.viewTracker.forceUpdateCachedPeerData(peerId: peerView.peerId) - } - - var hasBots: Bool = false - if let peer = peerView.peers[peerView.peerId] { - if let cachedGroupData = peerView.cachedData as? CachedGroupData { - if !cachedGroupData.botInfos.isEmpty { - hasBots = true - } - } else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let channel = peer as? TelegramChannel, case .group = channel.info { - if !cachedChannelData.botInfos.isEmpty { - hasBots = true + var wasGroupChannel: Bool? + if let previousPeerView = strongSelf.peerView, let info = (previousPeerView.peers[previousPeerView.peerId] as? TelegramChannel)?.info { + if case .group = info { + wasGroupChannel = true + } else { + wasGroupChannel = false } } - } - - let isArchived: Bool = peerView.groupId == Namespaces.PeerGroup.archive - - var explicitelyCanPinMessages: Bool = false - if let cachedUserData = peerView.cachedData as? CachedUserData { - explicitelyCanPinMessages = cachedUserData.canPinMessages - } else if peerView.peerId == context.account.peerId { - explicitelyCanPinMessages = true - } - - var animated = false - if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, let updated = renderedPeer?.peer as? TelegramSecretChat, peer.embeddedState != updated.embeddedState { - animated = true - } - if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, let updated = renderedPeer?.peer as? TelegramChannel { - if peer.participationStatus != updated.participationStatus { - animated = true - } - } - - var didDisplayActionsPanel = false - if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { - if !peerStatusSettings.flags.isEmpty { - if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { - didDisplayActionsPanel = true - } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { - didDisplayActionsPanel = true - } else if peerStatusSettings.contains(.canShareContact) { - didDisplayActionsPanel = true - } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { - didDisplayActionsPanel = true + var isGroupChannel: Bool? + if let info = (peerView.peers[peerView.peerId] as? TelegramChannel)?.info { + if case .group = info { + isGroupChannel = true + } else { + isGroupChannel = false } } - } - - var displayActionsPanel = false - if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { - if !peerStatusSettings.flags.isEmpty { - if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.canShareContact) { - displayActionsPanel = true - } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { - displayActionsPanel = true + let firstTime = strongSelf.peerView == nil + strongSelf.peerView = peerView + if wasGroupChannel != isGroupChannel { + if let isGroupChannel = isGroupChannel, isGroupChannel { + let (recentDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.recent(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) + let (adminsDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.admins(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) + let disposable = DisposableSet() + disposable.add(recentDisposable) + disposable.add(adminsDisposable) + strongSelf.chatAdditionalDataDisposable.set(disposable) + } else { + strongSelf.chatAdditionalDataDisposable.set(nil) } } - } - - if displayActionsPanel != didDisplayActionsPanel { - animated = true - } - - if strongSelf.preloadHistoryPeerId != peerDiscussionId { - strongSelf.preloadHistoryPeerId = peerDiscussionId - if let peerDiscussionId = peerDiscussionId { - strongSelf.preloadHistoryPeerIdDisposable.set(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId)) - } else { - strongSelf.preloadHistoryPeerIdDisposable.set(nil) + if strongSelf.isNodeLoaded { + strongSelf.chatDisplayNode.peerView = peerView } - } - - strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, { - return $0.updatedPeer { _ in - return renderedPeer - }.updatedIsNotAccessible(isNotAccessible).updatedContactStatus(contactStatus).updatedHasBots(hasBots).updatedIsArchived(isArchived).updatedPeerIsMuted(peerIsMuted).updatedPeerDiscussionId(peerDiscussionId).updatedPeerGeoLocation(peerGeoLocation).updatedExplicitelyCanPinMessages(explicitelyCanPinMessages).updatedHasScheduledMessages(hasScheduledMessages) - }) - if !strongSelf.didSetChatLocationInfoReady { - strongSelf.didSetChatLocationInfoReady = true - strongSelf._chatLocationInfoReady.set(.single(true)) - } - strongSelf.updateReminderActivity() - if let upgradedToPeerId = upgradedToPeerId { - if let navigationController = strongSelf.effectiveNavigationController { - var viewControllers = navigationController.viewControllers - if let index = viewControllers.firstIndex(where: { $0 === strongSelf }) { - viewControllers[index] = ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(upgradedToPeerId)) - navigationController.setViewControllers(viewControllers, animated: false) + var peerIsMuted = false + if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { + if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + peerIsMuted = true } } - } - } - })) - } else if case let .replyThread(messagePromise) = self.chatLocationInfoData { - let onlineMemberCount: Signal = .single(nil) - let hasScheduledMessages: Signal = .single(false) - self.reportIrrelvantGeoNoticePromise.set(.single(nil)) - - let isReplyThread: Bool - let replyThreadType: ChatTitleContent.ReplyThreadType - switch chatLocation { - case .peer: - replyThreadType = .replies - case let .replyThread(_, _, readMessageId): - isReplyThread = true - if readMessageId != nil { - replyThreadType = .comments - } else { - replyThreadType = .replies - } - } - - let peerView = context.account.viewTracker.peerView(peerId) - - self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(), - peerView, - messagePromise.get(), - hasScheduledMessages - ) - |> deliverOnMainQueue).start(next: { [weak self] peerView, message, onlineMemberCount in - if let strongSelf = self { - strongSelf.chatTitleView?.titleContent = .replyThread(type: replyThreadType, text: message?.text ?? "") - - let firstTime = strongSelf.peerView == nil - strongSelf.peerView = peerView - - if strongSelf.isNodeLoaded { - strongSelf.chatDisplayNode.peerView = peerView - } - var peerIsMuted = false - if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { - peerIsMuted = true - } - } - var peerDiscussionId: PeerId? - var peerGeoLocation: PeerGeoLocation? - if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { - if case .broadcast = peer.info { - if case let .known(value) = cachedData.linkedDiscussionPeerId { - peerDiscussionId = value + var peerDiscussionId: PeerId? + var peerGeoLocation: PeerGeoLocation? + if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { + if case .broadcast = peer.info { + peerDiscussionId = cachedData.linkedDiscussionPeerId + } else { + peerGeoLocation = cachedData.peerGeoLocation } - } else { - peerGeoLocation = cachedData.peerGeoLocation } - } - var renderedPeer: RenderedPeer? - var contactStatus: ChatContactStatus? - if let peer = peerView.peers[peerView.peerId] { - if let cachedData = peerView.cachedData as? CachedUserData { - contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil) - } else if let cachedData = peerView.cachedData as? CachedGroupData { - var invitedBy: Peer? - if let invitedByPeerId = cachedData.invitedBy { - if let peer = peerView.peers[invitedByPeerId] { - invitedBy = peer + var renderedPeer: RenderedPeer? + var contactStatus: ChatContactStatus? + if let peer = peerView.peers[peerView.peerId] { + if let cachedData = peerView.cachedData as? CachedUserData { + contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil) + } else if let cachedData = peerView.cachedData as? CachedGroupData { + var invitedBy: Peer? + if let invitedByPeerId = cachedData.invitedBy { + if let peer = peerView.peers[invitedByPeerId] { + invitedBy = peer + } } - } - contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) - } else if let cachedData = peerView.cachedData as? CachedChannelData { - var canReportIrrelevantLocation = true - if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member { - canReportIrrelevantLocation = false - } - canReportIrrelevantLocation = false - var invitedBy: Peer? - if let invitedByPeerId = cachedData.invitedBy { - if let peer = peerView.peers[invitedByPeerId] { - invitedBy = peer + contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) + } else if let cachedData = peerView.cachedData as? CachedChannelData { + var canReportIrrelevantLocation = true + if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member { + canReportIrrelevantLocation = false } + if let peerReportNotice = peerReportNotice, peerReportNotice { + canReportIrrelevantLocation = false + } + var invitedBy: Peer? + if let invitedByPeerId = cachedData.invitedBy { + if let peer = peerView.peers[invitedByPeerId] { + invitedBy = peer + } + } + contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) } - contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) + + var peers = SimpleDictionary() + peers[peer.id] = peer + if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] { + peers[associatedPeer.id] = associatedPeer + } + renderedPeer = RenderedPeer(peerId: peer.id, peers: peers) } - var peers = SimpleDictionary() - peers[peer.id] = peer - if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] { - peers[associatedPeer.id] = associatedPeer + var isNotAccessible: Bool = false + if let cachedChannelData = peerView.cachedData as? CachedChannelData { + isNotAccessible = cachedChannelData.isNotAccessible } - renderedPeer = RenderedPeer(peerId: peer.id, peers: peers) - } - - var isNotAccessible: Bool = false - if let cachedChannelData = peerView.cachedData as? CachedChannelData { - isNotAccessible = cachedChannelData.isNotAccessible - } - - if firstTime && isNotAccessible { - strongSelf.context.account.viewTracker.forceUpdateCachedPeerData(peerId: peerView.peerId) - } - - var hasBots: Bool = false - if let peer = peerView.peers[peerView.peerId] { - if let cachedGroupData = peerView.cachedData as? CachedGroupData { - if !cachedGroupData.botInfos.isEmpty { - hasBots = true - } - } else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let channel = peer as? TelegramChannel, case .group = channel.info { - if !cachedChannelData.botInfos.isEmpty { - hasBots = true + + if firstTime && isNotAccessible { + strongSelf.context.account.viewTracker.forceUpdateCachedPeerData(peerId: peerView.peerId) + } + + var hasBots: Bool = false + if let peer = peerView.peers[peerView.peerId] { + if let cachedGroupData = peerView.cachedData as? CachedGroupData { + if !cachedGroupData.botInfos.isEmpty { + hasBots = true + } + } else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let channel = peer as? TelegramChannel, case .group = channel.info { + if !cachedChannelData.botInfos.isEmpty { + hasBots = true + } } } - } - - let isArchived: Bool = peerView.groupId == Namespaces.PeerGroup.archive - - var explicitelyCanPinMessages: Bool = false - if let cachedUserData = peerView.cachedData as? CachedUserData { - explicitelyCanPinMessages = cachedUserData.canPinMessages - } else if peerView.peerId == context.account.peerId { - explicitelyCanPinMessages = true - } - - var animated = false - if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, let updated = renderedPeer?.peer as? TelegramSecretChat, peer.embeddedState != updated.embeddedState { - animated = true - } - if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, let updated = renderedPeer?.peer as? TelegramChannel { - if peer.participationStatus != updated.participationStatus { + + let isArchived: Bool = peerView.groupId == Namespaces.PeerGroup.archive + + var explicitelyCanPinMessages: Bool = false + if let cachedUserData = peerView.cachedData as? CachedUserData { + explicitelyCanPinMessages = cachedUserData.canPinMessages + } else if peerView.peerId == context.account.peerId { + explicitelyCanPinMessages = true + } + + var animated = false + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, let updated = renderedPeer?.peer as? TelegramSecretChat, peer.embeddedState != updated.embeddedState { animated = true } - } - - var didDisplayActionsPanel = false - if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { - if !peerStatusSettings.flags.isEmpty { - if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { - didDisplayActionsPanel = true - } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { - didDisplayActionsPanel = true - } else if peerStatusSettings.contains(.canShareContact) { - didDisplayActionsPanel = true - } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { - didDisplayActionsPanel = true + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, let updated = renderedPeer?.peer as? TelegramChannel { + if peer.participationStatus != updated.participationStatus { + animated = true + } + } + + var didDisplayActionsPanel = false + if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { + if !peerStatusSettings.flags.isEmpty { + if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { + didDisplayActionsPanel = true + } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { + didDisplayActionsPanel = true + } else if peerStatusSettings.contains(.canShareContact) { + didDisplayActionsPanel = true + } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { + didDisplayActionsPanel = true + } + } + } + + var displayActionsPanel = false + if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { + if !peerStatusSettings.flags.isEmpty { + if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.canShareContact) { + displayActionsPanel = true + } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { + displayActionsPanel = true + } + } + } + + if displayActionsPanel != didDisplayActionsPanel { + animated = true + } + + if strongSelf.preloadHistoryPeerId != peerDiscussionId { + strongSelf.preloadHistoryPeerId = peerDiscussionId + if let peerDiscussionId = peerDiscussionId { + strongSelf.preloadHistoryPeerIdDisposable.set(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId)) + } else { + strongSelf.preloadHistoryPeerIdDisposable.set(nil) + } + } + + strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, { + return $0.updatedPeer { _ in + return renderedPeer + }.updatedIsNotAccessible(isNotAccessible).updatedContactStatus(contactStatus).updatedHasBots(hasBots).updatedIsArchived(isArchived).updatedPeerIsMuted(peerIsMuted).updatedPeerDiscussionId(peerDiscussionId).updatedPeerGeoLocation(peerGeoLocation).updatedExplicitelyCanPinMessages(explicitelyCanPinMessages).updatedHasScheduledMessages(hasScheduledMessages) + }) + if !strongSelf.didSetChatLocationInfoReady { + strongSelf.didSetChatLocationInfoReady = true + strongSelf._chatLocationInfoReady.set(.single(true)) + } + strongSelf.updateReminderActivity() + if let upgradedToPeerId = upgradedToPeerId { + if let navigationController = strongSelf.effectiveNavigationController { + var viewControllers = navigationController.viewControllers + if let index = viewControllers.firstIndex(where: { $0 === strongSelf }) { + viewControllers[index] = ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(upgradedToPeerId)) + navigationController.setViewControllers(viewControllers, animated: false) + } } } } - - var displayActionsPanel = false - if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { - if !peerStatusSettings.flags.isEmpty { - if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.canShareContact) { - displayActionsPanel = true - } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { - displayActionsPanel = true - } - } - } - - if displayActionsPanel != didDisplayActionsPanel { - animated = true - } - - if strongSelf.preloadHistoryPeerId != peerDiscussionId { - strongSelf.preloadHistoryPeerId = peerDiscussionId - if let peerDiscussionId = peerDiscussionId { - strongSelf.preloadHistoryPeerIdDisposable.set(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId)) - } else { - strongSelf.preloadHistoryPeerIdDisposable.set(nil) - } - } - - strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, { - return $0.updatedPeer { _ in - return renderedPeer - }.updatedIsNotAccessible(isNotAccessible).updatedContactStatus(contactStatus).updatedHasBots(hasBots).updatedIsArchived(isArchived).updatedPeerIsMuted(peerIsMuted).updatedPeerDiscussionId(peerDiscussionId).updatedPeerGeoLocation(peerGeoLocation).updatedExplicitelyCanPinMessages(explicitelyCanPinMessages).updatedHasScheduledMessages(false) - }) - if !strongSelf.didSetChatLocationInfoReady { - strongSelf.didSetChatLocationInfoReady = true - strongSelf._chatLocationInfoReady.set(.single(true)) - } - } - })) - } + })) + } } self.botCallbackAlertMessageDisposable = (self.botCallbackAlertMessage.get() @@ -3193,7 +2926,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } override public func loadDisplayNode() { - self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, controller: self) + self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, controller: self) self.chatDisplayNode.historyNode.didScrollWithOffset = { [weak self] offset, transition, itemNode in guard let strongSelf = self else { @@ -3218,14 +2951,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self, let combinedInitialData = combinedInitialData else { return } - if var interfaceState = combinedInitialData.initialData?.chatInterfaceState as? ChatInterfaceState { - switch strongSelf.chatLocation { - case .peer: - break - default: - interfaceState = ChatInterfaceState() - } - + if let interfaceState = combinedInitialData.initialData?.chatInterfaceState as? ChatInterfaceState { var pinnedMessageId: MessageId? var peerIsBlocked: Bool = false var callsAvailable: Bool = true @@ -3247,11 +2973,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G pinnedMessageId = cachedData.pinnedMessageId } else if let _ = combinedInitialData.cachedData as? CachedSecretChatData { } - - if case .replyThread = strongSelf.chatLocation { - pinnedMessageId = nil - } - var pinnedMessage: Message? if let pinnedMessageId = pinnedMessageId { if let cachedDataMessages = combinedInitialData.cachedDataMessages { @@ -3350,18 +3071,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) let hasPendingMessages: Signal - let chatLocationPeerId = self.chatLocation.peerId - hasPendingMessages = self.context.account.pendingMessageManager.hasPendingMessages - |> mapToSignal { peerIds -> Signal in - let value = peerIds.contains(chatLocationPeerId) - if value { - return .single(true) - } else { - return .single(false) - |> delay(0.1, queue: .mainQueue()) + if case let .peer(peerId) = self.chatLocation { + hasPendingMessages = self.context.account.pendingMessageManager.hasPendingMessages + |> mapToSignal { peerIds -> Signal in + let value = peerIds.contains(peerId) + if value { + return .single(true) + } else { + return .single(false) + |> delay(0.1, queue: .mainQueue()) + } } + |> distinctUntilChanged + } else { + hasPendingMessages = .single(false) } - |> distinctUntilChanged self.cachedDataDisposable = combineLatest(queue: .mainQueue(), self.chatDisplayNode.historyNode.cachedPeerDataAndMessages, hasPendingMessages).start(next: { [weak self] cachedDataAndMessages, hasPendingMessages in if let strongSelf = self { @@ -3391,10 +3115,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else if let _ = cachedData as? CachedSecretChatData { } - if case .replyThread = strongSelf.chatLocation { - pinnedMessageId = nil - } - var pinnedMessage: Message? if let pinnedMessageId = pinnedMessageId { pinnedMessage = messages?[pinnedMessageId] @@ -3411,7 +3131,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let callsDataUpdated = strongSelf.presentationInterfaceState.callsAvailable != callsAvailable || strongSelf.presentationInterfaceState.callsPrivate != callsPrivate - if strongSelf.presentationInterfaceState.pinnedMessageId != pinnedMessageId || strongSelf.presentationInterfaceState.pinnedMessage?.stableVersion != pinnedMessage?.stableVersion || strongSelf.presentationInterfaceState.peerIsBlocked != peerIsBlocked || pinnedMessageUpdated || callsDataUpdated || strongSelf.presentationInterfaceState.slowmodeState != slowmodeState { + if strongSelf.presentationInterfaceState.pinnedMessageId != pinnedMessageId || strongSelf.presentationInterfaceState.pinnedMessage?.stableVersion != pinnedMessage?.stableVersion || strongSelf.presentationInterfaceState.peerIsBlocked != peerIsBlocked || pinnedMessageUpdated || callsDataUpdated || strongSelf.presentationInterfaceState.slowmodeState != slowmodeState { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in return state .updatedPinnedMessageId(pinnedMessageId) @@ -3595,8 +3315,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } self.chatDisplayNode.sendMessages = { [weak self] messages, silentPosting, scheduleTime, isAnyMessageTextPartitioned in - if let strongSelf = self { - let peerId = strongSelf.chatLocation.peerId + if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation { strongSelf.commitPurposefulAction() if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isRestrictedBySlowmode { @@ -3630,23 +3349,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G transformedMessages = strongSelf.transformEnqueueMessages(messages) } - for message in transformedMessages { - let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: [message]) - |> deliverOnMainQueue).start(next: { messageIds in - - }) - - } - -// let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: transformedMessages) -// |> deliverOnMainQueue).start(next: { messageIds in -// if let strongSelf = self { -// if strongSelf.presentationInterfaceState.isScheduledMessages { -// } else { -// strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() -// } -// } -// }) + let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: transformedMessages) + |> deliverOnMainQueue).start(next: { messageIds in + if let strongSelf = self { + if strongSelf.presentationInterfaceState.isScheduledMessages { + } else { + strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() + } + } + }) donateSendMessageIntent(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, intentContext: .chat, peerIds: [peerId]) } @@ -3670,7 +3381,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } return } - if let messageId = strongSelf.presentationInterfaceState.interfaceState.editMessage?.messageId { + if case .peer = strongSelf.chatLocation, let messageId = strongSelf.presentationInterfaceState.interfaceState.editMessage?.messageId { let _ = (strongSelf.context.account.postbox.transaction { transaction -> Message? in return transaction.getMessage(messageId) } |> deliverOnMainQueue).start(next: { message in @@ -3750,8 +3461,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() } else if case let .peer(peerId) = strongSelf.chatLocation { strongSelf.navigateToMessage(messageLocation: .upperBound(peerId), animated: true) - } else if case .replyThread = strongSelf.chatLocation { - strongSelf.scrollToEndOfHistory() } else { strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() } @@ -4186,14 +3895,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.updateItemNodesSearchTextHighlightStates() if let navigateIndex = navigateIndex { switch strongSelf.chatLocation { - case .peer, .replyThread: + case .peer: strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex), forceInCurrentChat: true) + /*case .group: + strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex))*/ } } } }, openCalendarSearch: { [weak self] in - if let strongSelf = self { - let peerId = strongSelf.chatLocation.peerId + if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation { strongSelf.chatDisplayNode.dismissInput() let controller = ChatDateSelectionSheet(presentationData: strongSelf.presentationData, completion: { timestamp in @@ -4232,8 +3942,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) strongSelf.updateItemNodesSearchTextHighlightStates() } - }, navigateToMessage: { [weak self] messageId, dropStack in - self?.navigateToMessage(from: nil, to: .id(messageId), dropStack: dropStack) + }, navigateToMessage: { [weak self] messageId in + self?.navigateToMessage(from: nil, to: .id(messageId)) }, navigateToChat: { [weak self] peerId in guard let strongSelf = self else { return @@ -4249,8 +3959,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, openPeerInfo: { [weak self] in self?.navigationButtonAction(.openChatInfo(expandAvatar: false)) }, togglePeerNotifications: { [weak self] in - if let strongSelf = self { - let peerId = strongSelf.chatLocation.peerId + if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation { let _ = togglePeerMuted(account: strongSelf.context.account, peerId: peerId).start() } }, sendContextResult: { [weak self] results, result, node, rect in @@ -5025,160 +4734,150 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G unarchiveAutomaticallyArchivedPeer(account: strongSelf.context.account, peerId: peerId) strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .succeed(text: strongSelf.presentationData.strings.Conversation_UnarchiveDone), elevatedLayout: false, action: { _ in return false }), in: .current) - }, viewReplies: { [weak self] sourceMessageId, replyThreadResult in - guard let strongSelf = self else { - return - } - - if let navigationController = strongSelf.effectiveNavigationController { - let subject: ChatControllerSubject? = sourceMessageId.flatMap(ChatControllerSubject.message) - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(threadMessageId: replyThreadResult.messageId, isChannelPost: false, maxReadMessageId: replyThreadResult.maxReadMessageId), subject: subject, keepStack: .always)) - } }, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get(), inlineSearch: self.performingInlineSearch.get())) - do { - let peerId = self.chatLocation.peerId - if let subject = self.subject, case .scheduledMessages = subject { - } else if case .replyThread = self.chatLocation { - } else { - let unreadCountsKey: PostboxViewKey = .unreadCounts(items: [.peer(peerId), .total(nil)]) - let notificationSettingsKey: PostboxViewKey = .peerNotificationSettings(peerIds: Set([peerId])) - self.chatUnreadCountDisposable = (self.context.account.postbox.combinedView(keys: [unreadCountsKey, notificationSettingsKey]) - |> deliverOnMainQueue).start(next: { [weak self] views in - if let strongSelf = self { - var unreadCount: Int32 = 0 - var totalChatCount: Int32 = 0 - - let inAppSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 } - if let view = views.views[unreadCountsKey] as? UnreadMessageCountsView { - if let count = view.count(for: .peer(peerId)) { - unreadCount = count - } - if let (_, state) = view.total() { - let (count, _) = renderedTotalUnreadCount(inAppSettings: inAppSettings, totalUnreadState: state) - totalChatCount = count - } - } - - strongSelf.chatDisplayNode.navigateButtons.unreadCount = unreadCount - - if let view = views.views[notificationSettingsKey] as? PeerNotificationSettingsView, let notificationSettings = view.notificationSettings[peerId] { - var globalRemainingUnreadChatCount = totalChatCount - if !notificationSettings.isRemovedFromTotalUnreadCount(default: false) && unreadCount > 0 { - if case .messages = inAppSettings.totalUnreadCountDisplayCategory { - globalRemainingUnreadChatCount -= unreadCount - } else { - globalRemainingUnreadChatCount -= 1 + switch self.chatLocation { + case let .peer(peerId): + if let subject = self.subject, case .scheduledMessages = subject { + } else { + let unreadCountsKey: PostboxViewKey = .unreadCounts(items: [.peer(peerId), .total(nil)]) + let notificationSettingsKey: PostboxViewKey = .peerNotificationSettings(peerIds: Set([peerId])) + self.chatUnreadCountDisposable = (self.context.account.postbox.combinedView(keys: [unreadCountsKey, notificationSettingsKey]) + |> deliverOnMainQueue).start(next: { [weak self] views in + if let strongSelf = self { + var unreadCount: Int32 = 0 + var totalChatCount: Int32 = 0 + + let inAppSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 } + if let view = views.views[unreadCountsKey] as? UnreadMessageCountsView { + if let count = view.count(for: .peer(peerId)) { + unreadCount = count + } + if let (_, state) = view.total() { + let (count, _) = renderedTotalUnreadCount(inAppSettings: inAppSettings, totalUnreadState: state) + totalChatCount = count } } - if globalRemainingUnreadChatCount > 0 { - strongSelf.navigationItem.badge = "\(globalRemainingUnreadChatCount)" - } else { - strongSelf.navigationItem.badge = "" - } - } - } - }) - - self.chatUnreadMentionCountDisposable = (self.context.account.viewTracker.unseenPersonalMessagesCount(peerId: peerId) |> deliverOnMainQueue).start(next: { [weak self] count in - if let strongSelf = self { - if case let .standard(previewing) = strongSelf.presentationInterfaceState.mode, previewing { - strongSelf.chatDisplayNode.navigateButtons.mentionCount = 0 - } else { - strongSelf.chatDisplayNode.navigateButtons.mentionCount = count - } - } - }) - - let postbox = self.context.account.postbox - let previousPeerCache = Atomic<[PeerId: Peer]>(value: [:]) - self.peerInputActivitiesDisposable = (self.context.account.peerInputActivities(peerId: peerId) - |> mapToSignal { activities -> Signal<[(Peer, PeerInputActivity)], NoError> in - var foundAllPeers = true - var cachedResult: [(Peer, PeerInputActivity)] = [] - previousPeerCache.with { dict -> Void in - for (peerId, activity) in activities { - if let peer = dict[peerId] { - cachedResult.append((peer, activity)) - } else { - foundAllPeers = false - break - } - } - } - if foundAllPeers { - return .single(cachedResult) - } else { - return postbox.transaction { transaction -> [(Peer, PeerInputActivity)] in - var result: [(Peer, PeerInputActivity)] = [] - var peerCache: [PeerId: Peer] = [:] - for (peerId, activity) in activities { - if let peer = transaction.getPeer(peerId) { - result.append((peer, activity)) - peerCache[peerId] = peer + strongSelf.chatDisplayNode.navigateButtons.unreadCount = unreadCount + + if let view = views.views[notificationSettingsKey] as? PeerNotificationSettingsView, let notificationSettings = view.notificationSettings[peerId] { + var globalRemainingUnreadChatCount = totalChatCount + if !notificationSettings.isRemovedFromTotalUnreadCount(default: false) && unreadCount > 0 { + if case .messages = inAppSettings.totalUnreadCountDisplayCategory { + globalRemainingUnreadChatCount -= unreadCount + } else { + globalRemainingUnreadChatCount -= 1 + } + } + + if globalRemainingUnreadChatCount > 0 { + strongSelf.navigationItem.badge = "\(globalRemainingUnreadChatCount)" + } else { + strongSelf.navigationItem.badge = "" } } - let _ = previousPeerCache.swap(peerCache) - return result + } + }) + + self.chatUnreadMentionCountDisposable = (self.context.account.viewTracker.unseenPersonalMessagesCount(peerId: peerId) |> deliverOnMainQueue).start(next: { [weak self] count in + if let strongSelf = self { + if case let .standard(previewing) = strongSelf.presentationInterfaceState.mode, previewing { + strongSelf.chatDisplayNode.navigateButtons.mentionCount = 0 + } else { + strongSelf.chatDisplayNode.navigateButtons.mentionCount = count + } + } + }) + + let postbox = self.context.account.postbox + let previousPeerCache = Atomic<[PeerId: Peer]>(value: [:]) + self.peerInputActivitiesDisposable = (self.context.account.peerInputActivities(peerId: peerId) + |> mapToSignal { activities -> Signal<[(Peer, PeerInputActivity)], NoError> in + var foundAllPeers = true + var cachedResult: [(Peer, PeerInputActivity)] = [] + previousPeerCache.with { dict -> Void in + for (peerId, activity) in activities { + if let peer = dict[peerId] { + cachedResult.append((peer, activity)) + } else { + foundAllPeers = false + break + } + } + } + if foundAllPeers { + return .single(cachedResult) + } else { + return postbox.transaction { transaction -> [(Peer, PeerInputActivity)] in + var result: [(Peer, PeerInputActivity)] = [] + var peerCache: [PeerId: Peer] = [:] + for (peerId, activity) in activities { + if let peer = transaction.getPeer(peerId) { + result.append((peer, activity)) + peerCache[peerId] = peer + } + } + let _ = previousPeerCache.swap(peerCache) + return result + } } } + |> deliverOnMainQueue).start(next: { [weak self] activities in + if let strongSelf = self { + strongSelf.chatTitleView?.inputActivities = (peerId, activities) + } + }) } - |> deliverOnMainQueue).start(next: { [weak self] activities in + + self.sentMessageEventsDisposable.set((self.context.account.pendingMessageManager.deliveredMessageEvents(peerId: peerId) + |> deliverOnMainQueue).start(next: { [weak self] namespace in if let strongSelf = self { - strongSelf.chatTitleView?.inputActivities = (peerId, activities) + let inAppNotificationSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 } + if inAppNotificationSettings.playSounds { + serviceSoundManager.playMessageDeliveredSound() + } + if !strongSelf.presentationInterfaceState.isScheduledMessages && namespace == Namespaces.Message.ScheduledCloud { + strongSelf.openScheduledMessages() + } } - }) - } + })) - self.sentMessageEventsDisposable.set((self.context.account.pendingMessageManager.deliveredMessageEvents(peerId: peerId) - |> deliverOnMainQueue).start(next: { [weak self] namespace in - if let strongSelf = self { - let inAppNotificationSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 } - if inAppNotificationSettings.playSounds { - serviceSoundManager.playMessageDeliveredSound() + self.failedMessageEventsDisposable.set((self.context.account.pendingMessageManager.failedMessageEvents(peerId: peerId) + |> deliverOnMainQueue).start(next: { [weak self] reason in + if let strongSelf = self, strongSelf.currentFailedMessagesAlertController == nil { + let text: String + let moreInfo: Bool + switch reason { + case .flood: + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorFlood + moreInfo = true + case .publicBan: + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorGroupRestricted + moreInfo = true + case .mediaRestricted: + strongSelf.interfaceInteraction?.displayRestrictedInfo(.mediaRecording, .alert) + return + case .slowmodeActive: + text = strongSelf.presentationData.strings.Chat_SlowmodeSendError + moreInfo = false + case .tooMuchScheduled: + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorTooMuchScheduled + moreInfo = false + } + let actions: [TextAlertAction] + if moreInfo { + actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Generic_ErrorMoreInfo, action: { + self?.openPeerMention("spambot", navigation: .chat(textInputState: nil, subject: nil, peekData: nil)) + }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})] + } else { + actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})] + } + let controller = textAlertController(context: strongSelf.context, title: nil, text: text, actions: actions) + strongSelf.currentFailedMessagesAlertController = controller + strongSelf.present(controller, in: .window(.root)) } - if !strongSelf.presentationInterfaceState.isScheduledMessages && namespace == Namespaces.Message.ScheduledCloud { - strongSelf.openScheduledMessages() - } - } - })) - - self.failedMessageEventsDisposable.set((self.context.account.pendingMessageManager.failedMessageEvents(peerId: peerId) - |> deliverOnMainQueue).start(next: { [weak self] reason in - if let strongSelf = self, strongSelf.currentFailedMessagesAlertController == nil { - let text: String - let moreInfo: Bool - switch reason { - case .flood: - text = strongSelf.presentationData.strings.Conversation_SendMessageErrorFlood - moreInfo = true - case .publicBan: - text = strongSelf.presentationData.strings.Conversation_SendMessageErrorGroupRestricted - moreInfo = true - case .mediaRestricted: - strongSelf.interfaceInteraction?.displayRestrictedInfo(.mediaRecording, .alert) - return - case .slowmodeActive: - text = strongSelf.presentationData.strings.Chat_SlowmodeSendError - moreInfo = false - case .tooMuchScheduled: - text = strongSelf.presentationData.strings.Conversation_SendMessageErrorTooMuchScheduled - moreInfo = false - } - let actions: [TextAlertAction] - if moreInfo { - actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Generic_ErrorMoreInfo, action: { - self?.openPeerMention("spambot", navigation: .chat(textInputState: nil, subject: nil, peekData: nil)) - }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})] - } else { - actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})] - } - let controller = textAlertController(context: strongSelf.context, title: nil, text: text, actions: actions) - strongSelf.currentFailedMessagesAlertController = controller - strongSelf.present(controller, in: .window(.root)) - } - })) + })) } self.chatDisplayNode.updateHasEmbeddedTitleContent = { [weak self] in @@ -5308,21 +5007,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G override public func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - - if self.scheduledActivateInput { - self.scheduledActivateInput = false - - self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in - return state.updatedInputMode({ _ in .text }) - }) - } } override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - self.didAppear = true - self.chatDisplayNode.historyNode.preloadPages = true self.chatDisplayNode.historyNode.experimentalSnapScrollToItem = false self.chatDisplayNode.historyNode.canReadHistory.set(combineLatest(context.sharedContext.applicationBindings.applicationInForeground, self.canReadHistory.get()) |> map { a, b in @@ -5583,14 +5272,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G ), in: .window(.root)) })) } - - if self.scheduledActivateInput { - self.scheduledActivateInput = false - - self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in - return state.updatedInputMode({ _ in .text }) - }) - } } override public func viewWillDisappear(_ animated: Bool) { @@ -5658,6 +5339,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.validLayout = layout self.chatTitleView?.layout = layout + if self.hasScheduledMessages, let h = layout.inputHeight, h > 100.0 { + print() + } + switch self.presentationInterfaceState.mode { case .standard, .inline: break @@ -6101,8 +5786,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private func navigationButtonAction(_ action: ChatNavigationButtonAction) { switch action { - case .spacer: - break case .cancelMessageSelection: self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) case .clearHistory: @@ -6244,6 +5927,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: true) { strongSelf.effectiveNavigationController?.pushViewController(infoController) } + //strongSelf.effectiveNavigationController?.pushViewController(PeerMediaCollectionController(context: strongSelf.context, peerId: strongSelf.context.account.peerId)) } else { var expandAvatar = expandAvatar if peer.smallProfileImage == nil { @@ -6258,8 +5942,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } })) - case .replyThread: - break } case .search: self.interfaceInteraction?.beginMessageSearch(.everything, "") @@ -6464,8 +6146,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) })) - case .replyThread: - break } case .toggleInfoPanel: self.updateChatPresentationInterfaceState(animated: true, interactive: true, { @@ -6513,6 +6193,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func editMessageMediaWithLegacySignals(_ signals: [Any]) { + guard case .peer = self.chatLocation else { + return + } + let _ = (legacyAssetPickerEnqueueMessages(account: self.context.account, signals: signals) |> deliverOnMainQueue).start(next: { [weak self] messages in self?.editMessageMediaWithMessages(messages) @@ -6715,8 +6399,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, presentTimerPicker: { [weak self] done in if let strongSelf = self { - strongSelf.presentTimerPicker(style: .media, completion: { time in - done(time) + strongSelf.presentTimerPicker(style: .media, completion: { [weak self] time in + if let strongSelf = self { + done(time) + } }) } }, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in @@ -6926,7 +6612,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, presentTimerPicker: { [weak self] done in if let strongSelf = self { - strongSelf.presentTimerPicker(style: .media, completion: { time in + strongSelf.presentTimerPicker(style: .media, completion: { [weak self] time in done(time) }) } @@ -7418,7 +7104,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func presentPollCreation(isQuiz: Bool? = nil) { - if let peer = self.presentationInterfaceState.renderedPeer?.peer { + if case .peer = self.chatLocation, let peer = self.presentationInterfaceState.renderedPeer?.peer { self.effectiveNavigationController?.pushViewController(createPollController(context: self.context, peer: peer, isQuiz: isQuiz, completion: { [weak self] message in guard let strongSelf = self else { return @@ -7484,28 +7170,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func transformEnqueueMessages(_ messages: [EnqueueMessage], silentPosting: Bool, scheduleTime: Int32? = nil) -> [EnqueueMessage] { - var defaultReplyMessageId: MessageId? - switch self.chatLocation { - case .peer: - break - case let .replyThread(messageId, _, _): - defaultReplyMessageId = messageId - } - return messages.map { message in - var message = message - - if let defaultReplyMessageId = defaultReplyMessageId { - switch message { - case let .message(text, attributes, mediaReference, replyToMessageId, localGroupingKey): - if replyToMessageId == nil { - message = .message(text: text, attributes: attributes, mediaReference: mediaReference, replyToMessageId: defaultReplyMessageId, localGroupingKey: localGroupingKey) - } - case .forward: - break - } - } - if silentPosting || scheduleTime != nil { return message.withUpdatedAttributes { attributes in var attributes = attributes @@ -7531,12 +7196,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func sendMessages(_ messages: [EnqueueMessage], commit: Bool = false) { - let peerId: PeerId - switch self.chatLocation { - case let .peer(peerIdValue): - peerId = peerIdValue - case let .replyThread(messageId, _, _): - peerId = messageId.peerId + guard case let .peer(peerId) = self.chatLocation else { + return } if commit || !self.presentationInterfaceState.isScheduledMessages { @@ -7560,21 +7221,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func enqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil) { - self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(account: self.context.account, signals: signals!) - |> deliverOnMainQueue).start(next: { [weak self] messages in - if let strongSelf = self { - let messages = strongSelf.transformEnqueueMessages(messages, silentPosting: silentPosting, scheduleTime: scheduleTime) - 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(messages.map { $0.withUpdatedReplyToMessageId(replyMessageId) }) - } - })) + if case .peer = self.chatLocation { + self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(account: self.context.account, signals: signals!) + |> deliverOnMainQueue).start(next: { [weak self] messages in + if let strongSelf = self { + let messages = strongSelf.transformEnqueueMessages(messages, silentPosting: silentPosting, scheduleTime: scheduleTime) + 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(messages.map { $0.withUpdatedReplyToMessageId(replyMessageId) }) + } + })) + } } private func displayPasteMenu(_ images: [UIImage]) { @@ -7652,7 +7315,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false) { - let peerId = self.chatLocation.peerId + guard case let .peer(peerId) = self.chatLocation else { + return + } if let message = outgoingMessageWithChatContextResult(to: peerId, results: results, result: result, hideVia: hideVia), canSendMessagesToChat(self.presentationInterfaceState) { let replyMessageId = self.presentationInterfaceState.interfaceState.replyMessageId @@ -7732,7 +7397,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func requestVideoRecorder() { - let peerId = self.chatLocation.peerId + guard case let .peer(peerId) = self.chatLocation else { + return + } if self.videoRecorderValue == nil { if let currentInputPanelFrame = self.chatDisplayNode.currentInputPanelFrame() { @@ -7953,20 +7620,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } return nil } - var searchTopMsgId: MessageId? - switch self.chatLocation { - case .peer: - break - case let .replyThread(messageId, _, _): - searchTopMsgId = messageId - } switch search.domain { case .everything: - derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: self.chatLocation.peerId, fromId: nil, tags: nil, topMsgId: searchTopMsgId, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) + switch self.chatLocation { + case let .peer(peerId): + derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: nil, tags: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) + } case .members: derivedSearchState = nil case let .member(peer): - derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: self.chatLocation.peerId, fromId: peer.id, tags: nil, topMsgId: searchTopMsgId, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) + switch self.chatLocation { + case let .peer(peerId): + derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: peer.id, tags: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) + /*case .group: + derivedSearchState = nil*/ + } } } @@ -7977,7 +7645,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if previousSearchState?.query != searchState.query || previousSearchState?.location != searchState.location { var queryIsEmpty = false if searchState.query.isEmpty { - if case let .peer(_, fromId, _, _, _, _) = searchState.location { + if case let .peer(_, fromId, _) = searchState.location { if fromId == nil { queryIsEmpty = true } @@ -8037,8 +7705,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) if let navigateIndex = navigateIndex { switch strongSelf.chatLocation { - case .peer, .replyThread: + case .peer: strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex), forceInCurrentChat: true) + /*case .group: + strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex))*/ } } strongSelf.updateItemNodesSearchTextHighlightStates() @@ -8097,67 +7767,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } func scrollToEndOfHistory() { - let locationInput = ChatHistoryLocationInput(content: .Scroll(index: .upperBound, anchorIndex: .upperBound, sourceIndex: .lowerBound, scrollPosition: .top(0.0), animated: true), id: 0) - - let historyView = preloadedChatHistoryViewForLocation(locationInput, context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) - let signal = historyView - |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in - switch historyView { - case .Loading: - return .single((nil, true)) - case .HistoryView: - return .single((nil, false)) - } - } - |> take(until: { index in - return SignalTakeAction(passthrough: true, complete: !index.1) - }) - - var cancelImpl: (() -> Void)? - let presentationData = self.presentationData - let displayTime = CACurrentMediaTime() - let progressSignal = Signal { [weak self] subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { - if CACurrentMediaTime() - displayTime > 1.5 { - cancelImpl?() - } - })) - self?.present(controller, in: .window(.root)) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.05, queue: Queue.mainQueue()) - let progressDisposable = MetaDisposable() - var progressStarted = false - self.messageIndexDisposable.set((signal - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - |> deliverOnMainQueue).start(next: { [weak self] index in - if index.1 { - if !progressStarted { - progressStarted = true - progressDisposable.set(progressSignal.start()) - } - } - }, completed: { [weak self] in - if let strongSelf = self { - strongSelf.loadingMessage.set(false) - strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() - } - })) - cancelImpl = { [weak self] in - if let strongSelf = self { - strongSelf.loadingMessage.set(false) - strongSelf.messageIndexDisposable.set(nil) - } - } + self.chatDisplayNode.historyNode.scrollToEndOfHistory() } func updateDownButtonVisibility() { @@ -8173,17 +7783,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } - public func navigateToMessage(messageLocation: NavigateToMessageLocation, animated: Bool, forceInCurrentChat: Bool = false, dropStack: Bool = false, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil) { + public func navigateToMessage(messageLocation: NavigateToMessageLocation, animated: Bool, forceInCurrentChat: Bool = false, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil) { let scrollPosition: ListViewScrollPosition if case .upperBound = messageLocation { scrollPosition = .top(0.0) } else { scrollPosition = .center(.bottom) } - self.navigateToMessage(from: nil, to: messageLocation, scrollPosition: scrollPosition, rememberInStack: false, forceInCurrentChat: forceInCurrentChat, dropStack: dropStack, animated: animated, completion: completion, customPresentProgress: customPresentProgress) + self.navigateToMessage(from: nil, to: messageLocation, scrollPosition: scrollPosition, rememberInStack: false, forceInCurrentChat: forceInCurrentChat, animated: animated, completion: completion, customPresentProgress: customPresentProgress) } - private func navigateToMessage(from fromId: MessageId?, to messageLocation: NavigateToMessageLocation, scrollPosition: ListViewScrollPosition = .center(.bottom), rememberInStack: Bool = true, forceInCurrentChat: Bool = false, dropStack: Bool = false, animated: Bool = true, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil) { + private func navigateToMessage(from fromId: MessageId?, to messageLocation: NavigateToMessageLocation, scrollPosition: ListViewScrollPosition = .center(.bottom), rememberInStack: Bool = true, forceInCurrentChat: Bool = false, animated: Bool = true, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil) { if self.isNodeLoaded { var fromIndex: MessageIndex? @@ -8195,15 +7805,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - var forceInCurrentChat = forceInCurrentChat - if case let .peer(peerId) = self.chatLocation, messageLocation.peerId == peerId { forceInCurrentChat = true - } - if case let .peer(peerId) = self.chatLocation, let messageId = messageLocation.messageId, (messageId.peerId != peerId && !forceInCurrentChat) || (self.presentationInterfaceState.isScheduledMessages && messageId.id != 0 && !Namespaces.Message.allScheduled.contains(messageId.namespace)) { if let navigationController = self.effectiveNavigationController { self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageId.peerId), subject: .message(messageId), keepStack: .always)) } - } else if forceInCurrentChat { + } else if case let .peer(peerId) = self.chatLocation, (messageLocation.peerId == peerId || forceInCurrentChat) { if let _ = fromId, let fromIndex = fromIndex, rememberInStack { self.historyNavigationStack.add(fromIndex) } @@ -8234,9 +7840,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case let .index(index): searchLocation = .index(index) case .upperBound: - searchLocation = .index(MessageIndex.upperBound(peerId: self.chatLocation.peerId)) + searchLocation = .index(MessageIndex.upperBound(peerId: peerId)) } - let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50), id: 0), context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) + let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50), id: 0), account: self.context.account, chatLocation: self.chatLocation, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) let signal = historyView |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in switch historyView { @@ -8328,7 +7934,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.historyNavigationStack.add(fromIndex) } self.loadingMessage.set(true) - let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50), id: 0), context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) + let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50), id: 0), account: self.context.account, chatLocation: self.chatLocation, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) let signal = historyView |> mapToSignal { historyView -> Signal in switch historyView { @@ -8350,9 +7956,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.historyNode.scrollToMessage(from: fromIndex, to: index, animated: animated, scrollPosition: scrollPosition) completion?() } else { - if let navigationController = strongSelf.effectiveNavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(messageLocation.peerId), subject: messageLocation.messageId.flatMap { .message($0) })) - } + strongSelf.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(messageLocation.peerId), subject: messageLocation.messageId.flatMap { .message($0) })) completion?() } } @@ -8361,11 +7965,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.loadingMessage.set(false) } })) - } else { - if let navigationController = self.effectiveNavigationController { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageLocation.peerId), subject: messageLocation.messageId.flatMap { .message($0) })) - } - completion?() } } } else { @@ -8523,58 +8122,60 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } else { if let peerId = peerId { - do { - let selfPeerId = self.chatLocation.peerId - switch navigation { - case .info, .default: - let peerSignal: Signal - if let fromMessage = fromMessage { - peerSignal = loadedPeerFromMessage(account: self.context.account, peerId: peerId, messageId: fromMessage.id) - } else { - peerSignal = self.context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) - } - self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self, let peer = peer { - var mode: PeerInfoControllerMode = .generic - if let _ = fromMessage { - mode = .group(selfPeerId) - } - var expandAvatar = expandAvatar - if peer.smallProfileImage == nil { - expandAvatar = false - } - if let validLayout = strongSelf.validLayout, validLayout.deviceMetrics.type == .tablet { - expandAvatar = false - } - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: false) { - strongSelf.effectiveNavigationController?.pushViewController(infoController) - } + switch self.chatLocation { + case let .peer(selfPeerId): + switch navigation { + case .info, .default: + let peerSignal: Signal + if let fromMessage = fromMessage { + peerSignal = loadedPeerFromMessage(account: self.context.account, peerId: peerId, messageId: fromMessage.id) + } else { + peerSignal = self.context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) } - })) - case let .chat(textInputState, subject, peekData): - if let textInputState = textInputState { - let _ = (self.context.account.postbox.transaction({ transaction -> Void in - transaction.updatePeerChatInterfaceState(peerId, update: { currentState in - if let currentState = currentState as? ChatInterfaceState { - return currentState.withUpdatedComposeInputState(textInputState) - } else { - return ChatInterfaceState().withUpdatedComposeInputState(textInputState) + self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in + if let strongSelf = self, let peer = peer { + var mode: PeerInfoControllerMode = .generic + if let _ = fromMessage { + mode = .group(selfPeerId) + } + var expandAvatar = expandAvatar + if peer.smallProfileImage == nil { + expandAvatar = false + } + if let validLayout = strongSelf.validLayout, validLayout.deviceMetrics.type == .tablet { + expandAvatar = false + } + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: false) { + strongSelf.effectiveNavigationController?.pushViewController(infoController) + } + } + })) + case let .chat(textInputState, subject, peekData): + if let textInputState = textInputState { + let _ = (self.context.account.postbox.transaction({ transaction -> Void in + transaction.updatePeerChatInterfaceState(peerId, update: { currentState in + if let currentState = currentState as? ChatInterfaceState { + return currentState.withUpdatedComposeInputState(textInputState) + } else { + return ChatInterfaceState().withUpdatedComposeInputState(textInputState) + } + }) + }) + |> deliverOnMainQueue).start(completed: { [weak self] in + if let strongSelf = self, let navigationController = strongSelf.effectiveNavigationController { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject, updateTextInputState: textInputState, peekData: peekData)) } }) - }) - |> deliverOnMainQueue).start(completed: { [weak self] in - if let strongSelf = self, let navigationController = strongSelf.effectiveNavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject, updateTextInputState: textInputState, peekData: peekData)) - } - }) - } else { - self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), subject: subject)) - } - case let .withBotStartPayload(botStart): - self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), botStart: botStart)) - default: - break - } + } else { + self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), subject: subject)) + } + case let .withBotStartPayload(botStart): + self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), botStart: botStart)) + default: + break + } + /*case .group: + (self.navigationController as? NavigationController)?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), messageId: fromMessage?.id, botStart: nil))*/ } } else { switch navigation { @@ -9107,7 +8708,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let selectedTransitionNode = selectedTransitionNode { - if let previewData = chatMessagePreviewControllerData(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: self.effectiveNavigationController) { + if let previewData = chatMessagePreviewControllerData(context: self.context, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: self.effectiveNavigationController) { switch previewData { case let .gallery(gallery): gallery.setHintWillBePresentedInPreviewingContext(true) @@ -9197,9 +8798,113 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + @available(iOSApplicationExtension 9.0, iOS 9.0, *) + override public var previewActionItems: [UIPreviewActionItem] { + struct PreviewActionsData { + let notificationSettings: PeerNotificationSettings? + let peer: Peer? + } + let chatLocation = self.chatLocation + let data = Atomic(value: nil) + let semaphore = DispatchSemaphore(value: 0) + let _ = self.context.account.postbox.transaction({ transaction -> Void in + switch chatLocation { + case let .peer(peerId): + let _ = data.swap(PreviewActionsData(notificationSettings: transaction.getPeerNotificationSettings(peerId), peer: transaction.getPeer(peerId))) + /*case .group: + let _ = data.swap(PreviewActionsData(notificationSettings: nil, peer: nil))*/ + } + semaphore.signal() + }).start() + semaphore.wait() + + return data.with { [weak self] data -> [UIPreviewActionItem] in + var items: [UIPreviewActionItem] = [] + if let data = data, let strongSelf = self { + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + + switch strongSelf.peekActions { + case .standard: + if let peer = data.peer, peer.id != strongSelf.context.account.peerId { + if let _ = data.peer as? TelegramUser { + items.append(UIPreviewAction(title: "👍", style: .default, handler: { _, _ in + if let strongSelf = self { + let _ = enqueueMessages(account: strongSelf.context.account, peerId: peer.id, messages: strongSelf.transformEnqueueMessages([.message(text: "👍", attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)])).start() + } + })) + } + + if let notificationSettings = data.notificationSettings as? TelegramPeerNotificationSettings { + if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + items.append(UIPreviewAction(title: presentationData.strings.Conversation_Unmute, style: .default, handler: { _, _ in + if let strongSelf = self { + let _ = togglePeerMuted(account: strongSelf.context.account, peerId: peer.id).start() + } + })) + } else { + let muteInterval: Int32 + if let _ = data.peer as? TelegramChannel { + muteInterval = Int32.max + } else { + muteInterval = 1 * 60 * 60 + } + let title: String + if muteInterval == Int32.max { + title = presentationData.strings.Conversation_Mute + } else { + title = muteForIntervalString(strings: presentationData.strings, value: muteInterval) + } + + items.append(UIPreviewAction(title: title, style: .default, handler: { _, _ in + if let strongSelf = self { + let _ = updatePeerMuteSetting(account: strongSelf.context.account, peerId: peer.id, muteInterval: muteInterval).start() + } + })) + } + } + } + case let .remove(action): + items.append(UIPreviewAction(title: presentationData.strings.Common_Delete, style: .destructive, handler: { _, _ in + action() + })) + } + } + return items + } + } + + private func debugStreamSingleVideo(_ id: MessageId) { + let gallery = GalleryController(context: self.context, source: .peerMessagesAtId(id), streamSingleVideo: true, replaceRootController: { [weak self] controller, ready in + if let strongSelf = self { + strongSelf.effectiveNavigationController?.replaceTopController(controller, animated: false, ready: ready) + } + }, baseNavigationController: self.effectiveNavigationController) + + self.chatDisplayNode.dismissInput() + self.present(gallery, in: .window(.root), with: GalleryControllerPresentationArguments(transitionArguments: { [weak self] messageId, media in + if let strongSelf = self { + var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? + strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? ChatMessageItemView { + if let result = itemNode.transitionNode(id: messageId, media: media) { + transitionNode = result + } + } + } + if let transitionNode = transitionNode { + return GalleryTransitionArguments(transitionNode: transitionNode, addToTransitionSurface: { view in + if let strongSelf = self { + strongSelf.chatDisplayNode.historyNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.chatDisplayNode.historyNode.view) + } + }) + } + } + return nil + })) + } + private func presentBanMessageOptions(accountPeerId: PeerId, author: Peer, messageIds: Set, options: ChatAvailableMessageActionOptions) { - let peerId = self.chatLocation.peerId - do { + if case let .peer(peerId) = self.chatLocation { self.navigationActionDisposable.set((fetchChannelParticipant(account: self.context.account, peerId: peerId, participantId: author.id) |> deliverOnMainQueue).start(next: { [weak self] participant in if let strongSelf = self { @@ -9711,13 +9416,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } func activateInput() { - if self.didAppear { - self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in - return state.updatedInputMode({ _ in .text }) - }) - } else { - self.scheduledActivateInput = true - } + self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in + return state.updatedInputMode({ _ in .text }) + }) } private func clearInputText() { @@ -9894,3 +9595,115 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent func animatedIn() { } } + +func parseUrl(url: String, wasConcealed: Bool) -> (string: String, concealed: Bool) { + var parsedUrlValue: URL? + if url.hasPrefix("tel:") { + return (url, false) + } else if let parsed = URL(string: url) { + parsedUrlValue = parsed + } else if let parsed = URL(string: "https://" + url) { + parsedUrlValue = parsed + } else if let encoded = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let parsed = URL(string: encoded) { + parsedUrlValue = parsed + } + let host = parsedUrlValue?.host ?? url + + let rawHost = (host as NSString).removingPercentEncoding ?? host + var latin = CharacterSet() + latin.insert(charactersIn: "A"..."Z") + latin.insert(charactersIn: "a"..."z") + latin.insert(charactersIn: "0"..."9") + var punctuation = CharacterSet() + punctuation.insert(charactersIn: ".-/+_") + var hasLatin = false + var hasNonLatin = false + for c in rawHost { + if c.unicodeScalars.allSatisfy(punctuation.contains) { + } else if c.unicodeScalars.allSatisfy(latin.contains) { + hasLatin = true + } else { + hasNonLatin = true + } + } + var concealed = wasConcealed + if hasLatin && hasNonLatin { + concealed = true + } + + var rawDisplayUrl: String + if hasNonLatin { + rawDisplayUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? url + } else { + rawDisplayUrl = url + } + + if let parsedUrlValue = parsedUrlValue, isConcealedUrlWhitelisted(parsedUrlValue) { + concealed = false + } + + let whitelistedSchemes: [String] = [ + "tel", + ] + if let parsedUrlValue = parsedUrlValue, let scheme = parsedUrlValue.scheme, whitelistedSchemes.contains(scheme) { + concealed = false + } + + return (rawDisplayUrl, concealed) +} + +func openUserGeneratedUrl(context: AccountContext, url: String, concealed: Bool, present: @escaping (ViewController) -> Void, openResolved: @escaping (ResolvedUrl) -> Void) { + var concealed = concealed + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + let openImpl: () -> Void = { + let disposable = MetaDisposable() + var cancelImpl: (() -> Void)? + let progressSignal = Signal { subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + present(controller) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + cancelImpl = { + disposable.dispose() + } + disposable.set((context.sharedContext.resolveUrl(account: context.account, url: url) + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + |> deliverOnMainQueue).start(next: { result in + openResolved(result) + })) + } + + let (parsedString, parsedConcealed) = parseUrl(url: url, wasConcealed: concealed) + concealed = parsedConcealed + + if concealed { + var rawDisplayUrl: String = parsedString + let maxLength = 180 + if rawDisplayUrl.count > maxLength { + rawDisplayUrl = String(rawDisplayUrl[.. Void let greetingStickerNode: () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)? let openPeerContextMenu: (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void - let openMessageReplies: (MessageId) -> Void let requestMessageUpdate: (MessageId) -> Void let cancelInteractiveKeyboardGestures: () -> Void @@ -130,78 +138,7 @@ public final class ChatControllerInteraction { var searchTextHighightState: (String, [MessageIndex])? var seenOneTimeAnimatedMedia = Set() - init( - openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, - openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, - openPeerMention: @escaping (String) -> Void, - openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, - openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, - navigateToMessage: @escaping (MessageId, MessageId) -> Void, - tapMessage: ((Message) -> Void)?, - clickThroughMessage: @escaping () -> Void, - toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, - sendCurrentMessage: @escaping (Bool) -> Void, - sendMessage: @escaping (String) -> Void, - sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, - sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, - sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, - requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void, - requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, - activateSwitchInline: @escaping (PeerId?, String) -> Void, - openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, - shareCurrentLocation: @escaping () -> Void, - shareAccountContact: @escaping () -> Void, - sendBotCommand: @escaping (MessageId?, String) -> Void, - openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, - openWallpaper: @escaping (Message) -> Void, - openTheme: @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?, - chatControllerNode: @escaping () -> ASDisplayNode?, - reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, - presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, - callPeer: @escaping (PeerId, Bool) -> Void, - longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, - openCheckoutOrReceipt: @escaping (MessageId) -> Void, - openSearch: @escaping () -> Void, - setupReply: @escaping (MessageId) -> Void, - canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction, - navigateToFirstDateMessage: @escaping(Int32) ->Void, - requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, - addContact: @escaping (String) -> Void, - rateCall: @escaping (Message, CallId, Bool) -> Void, - requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, - requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, - openAppStorePage: @escaping () -> Void, - displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, - seekToTimecode: @escaping (Message, Double, Bool) -> Void, - scheduleCurrentMessage: @escaping () -> Void, - sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, - editScheduledMessagesTime: @escaping ([MessageId]) -> Void, - performTextSelectionAction: @escaping (UInt32, NSAttributedString, TextSelectionAction) -> Void, - updateMessageLike: @escaping (MessageId, Bool) -> Void, - openMessageReactions: @escaping (MessageId) -> Void, - displaySwipeToReplyHint: @escaping () -> Void, - dismissReplyMarkupMessage: @escaping (Message) -> Void, - openMessagePollResults: @escaping (MessageId, Data) -> Void, - openPollCreation: @escaping (Bool?) -> Void, - displayPollSolution: @escaping (TelegramMediaPollResults.Solution, ASDisplayNode) -> Void, - displayPsa: @escaping (String, ASDisplayNode) -> Void, - displayDiceTooltip: @escaping (TelegramMediaDice) -> Void, - animateDiceSuccess: @escaping () -> Void, - greetingStickerNode: @escaping () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)?, - openPeerContextMenu: @escaping (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void, - openMessageReplies: @escaping (MessageId) -> Void, - requestMessageUpdate: @escaping (MessageId) -> Void, - cancelInteractiveKeyboardGestures: @escaping () -> Void, - automaticMediaDownloadSettings: MediaAutoDownloadSettings, - pollActionState: ChatInterfacePollActionState, - stickerSettings: ChatInterfaceStickerSettings - ) { + init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, tapMessage: ((Message) -> Void)?, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @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?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId, Bool) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId, Bool) -> Void, requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, NSAttributedString, TextSelectionAction) -> Void, updateMessageLike: @escaping (MessageId, Bool) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, dismissReplyMarkupMessage: @escaping (Message) -> Void, openMessagePollResults: @escaping (MessageId, Data) -> Void, openPollCreation: @escaping (Bool?) -> Void, displayPollSolution: @escaping (TelegramMediaPollResults.Solution, ASDisplayNode) -> Void, displayPsa: @escaping (String, ASDisplayNode) -> Void, displayDiceTooltip: @escaping (TelegramMediaDice) -> Void, animateDiceSuccess: @escaping () -> Void, greetingStickerNode: @escaping () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)?, openPeerContextMenu: @escaping (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) { self.openMessage = openMessage self.openPeer = openPeer self.openPeerMention = openPeerMention @@ -266,7 +203,6 @@ public final class ChatControllerInteraction { self.animateDiceSuccess = animateDiceSuccess self.greetingStickerNode = greetingStickerNode self.openPeerContextMenu = openPeerContextMenu - self.openMessageReplies = openMessageReplies self.requestMessageUpdate = requestMessageUpdate self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures @@ -315,7 +251,6 @@ public final class ChatControllerInteraction { }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in - }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 0a39157275..7f61599a74 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -13,7 +13,6 @@ import AccountContext import TelegramNotices import ReactionSelectionNode import TelegramUniversalVideoContent -import ChatInterfaceState final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode @@ -415,29 +414,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private var derivedLayoutState: ChatControllerNodeDerivedLayoutState? - private var isLoadingValue: Bool = false - private func updateIsLoading(isLoading: Bool, animated: Bool) { - if isLoading != self.isLoadingValue { - self.isLoadingValue = isLoading - if isLoading { - self.historyNodeContainer.supernode?.insertSubnode(self.loadingNode, aboveSubnode: self.historyNodeContainer) - self.loadingNode.layer.removeAllAnimations() - self.loadingNode.alpha = 1.0 - if animated { - self.loadingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - } - } else { - self.loadingNode.alpha = 0.0 - if animated { - self.loadingNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false) - self.loadingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { [weak self] completed in - if let strongSelf = self { - strongSelf.loadingNode.layer.removeAllAnimations() - if completed { - strongSelf.loadingNode.removeFromSupernode() - } - } - }) + private var isLoading: Bool = false { + didSet { + if self.isLoading != oldValue { + if self.isLoading { + self.historyNodeContainer.supernode?.insertSubnode(self.loadingNode, aboveSubnode: self.historyNodeContainer) } else { self.loadingNode.removeFromSupernode() } @@ -460,7 +441,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } private var didProcessExperimentalEmbedUrl: String? - init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, chatPresentationInterfaceState: ChatPresentationInterfaceState, automaticMediaDownloadSettings: MediaAutoDownloadSettings, navigationBar: NavigationBar?, controller: ChatControllerImpl?) { + init(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, chatPresentationInterfaceState: ChatPresentationInterfaceState, automaticMediaDownloadSettings: MediaAutoDownloadSettings, navigationBar: NavigationBar?, controller: ChatControllerImpl?) { self.context = context self.chatLocation = chatLocation self.controllerInteraction = controllerInteraction @@ -477,7 +458,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.inputContextPanelContainer = ChatControllerTitlePanelNodeContainer() - self.historyNode = ChatHistoryListNode(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tagMask: nil, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) + self.historyNode = ChatHistoryListNode(context: context, chatLocation: chatLocation, tagMask: nil, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) self.historyNode.rotated = true self.historyNodeContainer = ASDisplayNode() self.historyNodeContainer.addSubnode(self.historyNode) @@ -540,9 +521,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.historyNode.setLoadStateUpdated { [weak self] loadState, animated in if let strongSelf = self { if case .loading = loadState { - strongSelf.updateIsLoading(isLoading: true, animated: animated) + strongSelf.isLoading = true } else { - strongSelf.updateIsLoading(isLoading: false, animated: animated) + strongSelf.isLoading = false } var isEmpty = false @@ -2156,8 +2137,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { func loadInputPanels(theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) { if self.inputMediaNode == nil { - let peerId: PeerId? = self.chatPresentationInterfaceState.chatLocation.peerId - let inputNode = ChatMediaInputNode(context: self.context, peerId: peerId, chatLocation: self.chatPresentationInterfaceState.chatLocation, controllerInteraction: self.controllerInteraction, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, theme: theme, strings: strings, fontSize: fontSize, gifPaneIsActiveUpdated: { [weak self] value in + var peerId: PeerId? + if case let .peer(id) = self.chatPresentationInterfaceState.chatLocation { + peerId = id + } + let inputNode = ChatMediaInputNode(context: self.context, peerId: peerId, controllerInteraction: self.controllerInteraction, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, theme: theme, strings: strings, fontSize: fontSize, gifPaneIsActiveUpdated: { [weak self] value in if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { interfaceInteraction.updateInputModeAndDismissedButtonKeyboardMessageId { state in if case let .media(_, expanded) = state.inputMode { @@ -2710,8 +2694,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let effectiveInputText = effectivePresentationInterfaceState.interfaceState.composeInputState.inputText let trimmedInputText = effectiveInputText.string.trimmingCharacters(in: .whitespacesAndNewlines) - let peerId = effectivePresentationInterfaceState.chatLocation.peerId - if peerId.namespace != Namespaces.Peer.SecretChat, let interactiveEmojis = self.interactiveEmojis, interactiveEmojis.emojis.contains(trimmedInputText) { + if case let .peer(peerId) = effectivePresentationInterfaceState.chatLocation, peerId.namespace != Namespaces.Peer.SecretChat, let interactiveEmojis = self.interactiveEmojis, interactiveEmojis.emojis.contains(trimmedInputText) { messages.append(.message(text: "", attributes: [], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: trimmedInputText)), replyToMessageId: self.chatPresentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)) } else { let inputText = convertMarkdownToAttributes(effectiveInputText) @@ -2763,7 +2746,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - self.sendMessages(messages, silentPosting, scheduleTime, messages.count > 1) + if case .peer = self.chatLocation { + self.sendMessages(messages, silentPosting, scheduleTime, messages.count > 1) + } } } } diff --git a/submodules/TelegramUI/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Sources/ChatEmptyNode.swift index 64de0c4ed3..6ce1a049f6 100644 --- a/submodules/TelegramUI/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Sources/ChatEmptyNode.swift @@ -38,17 +38,7 @@ private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNod self.currentStrings = interfaceState.strings let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) - - let text: String - switch interfaceState.chatLocation { - case .peer: - text = interfaceState.isScheduledMessages ? interfaceState.strings.ScheduledMessages_EmptyPlaceholder : interfaceState.strings.Conversation_EmptyPlaceholder - case .replyThread: - //TODO:localize - text = "No comments here yet" - } - - self.textNode.attributedText = NSAttributedString(string: text, font: messageFont, textColor: serviceColor.primaryText) + self.textNode.attributedText = NSAttributedString(string: interfaceState.isScheduledMessages ? interfaceState.strings.ScheduledMessages_EmptyPlaceholder : interfaceState.strings.Conversation_EmptyPlaceholder, font: messageFont, textColor: serviceColor.primaryText) } let insets = UIEdgeInsets(top: 6.0, left: 10.0, bottom: 6.0, right: 10.0) @@ -649,9 +639,7 @@ final class ChatEmptyNode: ASDisplayNode { } let contentType: ChatEmptyNodeContentType - if case .replyThread = interfaceState.chatLocation { - contentType = .regular - } else if let peer = interfaceState.renderedPeer?.peer, !interfaceState.isScheduledMessages { + if let peer = interfaceState.renderedPeer?.peer, !interfaceState.isScheduledMessages { if peer.id == self.account.peerId { contentType = .cloud } else if let _ = peer as? TelegramSecretChat { diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index e2ee6a6a68..a896a83966 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -4,9 +4,6 @@ import TelegramCore import SyncCore import TemporaryCachedPeerDataManager import Emoji -import AccountContext -import TelegramPresentationData - func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, includeUnreadEntry: Bool, includeEmptyEntry: Bool, includeChatInfoEntry: Bool, includeSearchEntry: Bool, reverse: Bool, groupMessages: Bool, selectedMessages: Set?, presentationData: ChatPresentationData, historyAppearsCleared: Bool, associatedData: ChatMessageItemAssociatedData, updatingMedia: [MessageId: ChatUpdatingMessageMedia]) -> [ChatHistoryEntry] { if historyAppearsCleared { @@ -113,53 +110,6 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, } } - var addedThreadHead = false - if case let .replyThread(messageId, isChannelPost, _) = location, view.earlierId == nil, !view.isLoading { - loop: for entry in view.additionalData { - switch entry { - case let .message(id, message) where id == messageId: - if let message = message { - let selection: ChatHistoryMessageSelection - if let selectedMessages = selectedMessages { - selection = .selectable(selected: selectedMessages.contains(message.id)) - } else { - selection = .none - } - - var adminRank: CachedChannelAdminRank? - if let author = message.author { - adminRank = adminRanks[author.id] - } - - var contentTypeHint: ChatMessageEntryContentType = .generic - if presentationData.largeEmoji, message.media.isEmpty { - if stickersEnabled && message.text.count == 1, let _ = associatedData.animatedEmojiStickers[message.text.basicEmoji.0] { - contentTypeHint = .animatedEmoji - } else if message.text.count < 10 && messageIsElligibleForLargeEmoji(message) { - contentTypeHint = .largeEmoji - } - } - - var replyCount = 0 - for attribute in message.attributes { - if let attribute = attribute as? ReplyThreadMessageAttribute { - replyCount = Int(attribute.count) - } - } - - addedThreadHead = true - entries.insert(.MessageEntry(message, presentationData, false, nil, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id])), at: 0) - if view.entries.count > 0 { - entries.insert(.ReplyCountEntry(message.index, isChannelPost, replyCount, presentationData), at: 1) - } - } - break loop - default: - break - } - } - } - if includeChatInfoEntry { if view.earlierId == nil { var cachedPeerData: CachedPeerData? @@ -201,9 +151,6 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, } else { isEmpty = false } - if addedThreadHead { - isEmpty = false - } if isEmpty { entries.removeAll() } diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntry.swift b/submodules/TelegramUI/Sources/ChatHistoryEntry.swift index 02c589c44a..05488d9409 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntry.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntry.swift @@ -4,7 +4,28 @@ import SyncCore import TelegramPresentationData import MergeLists import TemporaryCachedPeerDataManager -import AccountContext + +public enum ChatHistoryMessageSelection: Equatable { + case none + case selectable(selected: Bool) + + public static func ==(lhs: ChatHistoryMessageSelection, rhs: ChatHistoryMessageSelection) -> Bool { + switch lhs { + case .none: + if case .none = rhs { + return true + } else { + return false + } + case let .selectable(selected): + if case .selectable(selected) = rhs { + return true + } else { + return false + } + } + } +} public enum ChatMessageEntryContentType { case generic @@ -37,7 +58,6 @@ enum ChatHistoryEntry: Identifiable, Comparable { case MessageEntry(Message, ChatPresentationData, Bool, MessageHistoryEntryMonthLocation?, ChatHistoryMessageSelection, ChatMessageEntryAttributes) case MessageGroupEntry(MessageGroupInfo, [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)], ChatPresentationData) case UnreadEntry(MessageIndex, ChatPresentationData) - case ReplyCountEntry(MessageIndex, Bool, Int, ChatPresentationData) case ChatInfoEntry(String, ChatPresentationData) case SearchEntry(PresentationTheme, PresentationStrings) @@ -58,12 +78,10 @@ enum ChatHistoryEntry: Identifiable, Comparable { return UInt64(groupInfo.stableId) | ((UInt64(2) << 40)) case .UnreadEntry: return UInt64(4) << 40 - case .ReplyCountEntry: - return UInt64(5) << 40 case .ChatInfoEntry: - return UInt64(6) << 40 + return UInt64(5) << 40 case .SearchEntry: - return UInt64(7) << 40 + return UInt64(6) << 40 } } @@ -75,8 +93,6 @@ enum ChatHistoryEntry: Identifiable, Comparable { return messages[messages.count - 1].0.index case let .UnreadEntry(index, _): return index - case let .ReplyCountEntry(index, _, _, _): - return index case .ChatInfoEntry: return MessageIndex.absoluteLowerBound() case .SearchEntry: @@ -187,12 +203,6 @@ enum ChatHistoryEntry: Identifiable, Comparable { } else { return false } - case let .ReplyCountEntry(lhsIndex, lhsIsComments, lhsCount, lhsPresentationData): - if case let .ReplyCountEntry(rhsIndex, rhsIsComments, rhsCount, rhsPresentationData) = rhs, lhsIndex == rhsIndex, lhsIsComments == rhsIsComments, lhsCount == rhsCount, lhsPresentationData === rhsPresentationData { - return true - } else { - return false - } case let .ChatInfoEntry(lhsText, lhsPresentationData): if case let .ChatInfoEntry(rhsText, rhsPresentationData) = rhs, lhsText == rhsText, lhsPresentationData === rhsPresentationData { return true diff --git a/submodules/TelegramUI/Sources/ChatHistoryGridNode.swift b/submodules/TelegramUI/Sources/ChatHistoryGridNode.swift new file mode 100644 index 0000000000..f66ed95d0d --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatHistoryGridNode.swift @@ -0,0 +1,513 @@ +import Foundation +import UIKit +import Postbox +import SwiftSignalKit +import Display +import AsyncDisplayKit +import TelegramCore +import SyncCore +import TelegramPresentationData +import TelegramUIPreferences +import AccountContext + +private class ChatGridLiveSelectorRecognizer: UIPanGestureRecognizer { + private let selectionGestureActivationThreshold: CGFloat = 2.0 + private let selectionGestureVerticalFailureThreshold: CGFloat = 5.0 + + var validatedGesture: Bool? = nil + var firstLocation: CGPoint = CGPoint() + + var shouldBegin: (() -> Bool)? + + override init(target: Any?, action: Selector?) { + super.init(target: target, action: action) + + self.maximumNumberOfTouches = 1 + } + + override func reset() { + super.reset() + + self.validatedGesture = nil + } + + override func touchesBegan(_ touches: Set, with event: UIEvent) { + super.touchesBegan(touches, with: event) + + if let shouldBegin = self.shouldBegin, !shouldBegin() { + self.state = .failed + } else { + let touch = touches.first! + self.firstLocation = touch.location(in: self.view) + } + } + + + override func touchesMoved(_ touches: Set, with event: UIEvent) { + let location = touches.first!.location(in: self.view) + let translation = CGPoint(x: location.x - self.firstLocation.x, y: location.y - self.firstLocation.y) + + if self.validatedGesture == nil { + if (fabs(translation.y) >= selectionGestureVerticalFailureThreshold) { + self.validatedGesture = false + } + else if (fabs(translation.x) >= selectionGestureActivationThreshold) { + self.validatedGesture = true + } + } + + if let validatedGesture = self.validatedGesture { + if validatedGesture { + super.touchesMoved(touches, with: event) + } + } + } +} + +struct ChatHistoryGridViewTransition { + let historyView: ChatHistoryView + let topOffsetWithinMonth: Int + let deleteItems: [Int] + let insertItems: [GridNodeInsertItem] + let updateItems: [GridNodeUpdateItem] + let scrollToItem: GridNodeScrollToItem? + let stationaryItems: GridNodeStationaryItems +} + +private func mappedInsertEntries(context: AccountContext, peerId: PeerId, controllerInteraction: ChatControllerInteraction, entries: [ChatHistoryViewTransitionInsertEntry], theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) -> [GridNodeInsertItem] { + return entries.map { entry -> GridNodeInsertItem in + switch entry.entry { + case let .MessageEntry(message, _, _, _, _, _): + return GridNodeInsertItem(index: entry.index, item: GridMessageItem(theme: theme, strings: strings, fontSize: fontSize, context: context, message: message, controllerInteraction: controllerInteraction), previousIndex: entry.previousIndex) + case .MessageGroupEntry: + return GridNodeInsertItem(index: entry.index, item: GridHoleItem(), previousIndex: entry.previousIndex) + case .UnreadEntry: + assertionFailure() + return GridNodeInsertItem(index: entry.index, item: GridHoleItem(), previousIndex: entry.previousIndex) + case .ChatInfoEntry, .SearchEntry: + assertionFailure() + return GridNodeInsertItem(index: entry.index, item: GridHoleItem(), previousIndex: entry.previousIndex) + } + } +} + +private func mappedUpdateEntries(context: AccountContext, peerId: PeerId, controllerInteraction: ChatControllerInteraction, entries: [ChatHistoryViewTransitionUpdateEntry], theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) -> [GridNodeUpdateItem] { + return entries.map { entry -> GridNodeUpdateItem in + switch entry.entry { + case let .MessageEntry(message, _, _, _, _, _): + return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridMessageItem(theme: theme, strings: strings, fontSize: fontSize, context: context, message: message, controllerInteraction: controllerInteraction)) + case .MessageGroupEntry: + return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridHoleItem()) + case .UnreadEntry: + assertionFailure() + return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridHoleItem()) + case .ChatInfoEntry, .SearchEntry: + assertionFailure() + return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridHoleItem()) + } + } +} + +private func mappedChatHistoryViewListTransition(context: AccountContext, peerId: PeerId, controllerInteraction: ChatControllerInteraction, transition: ChatHistoryViewTransition, from: ChatHistoryView?, presentationData: ChatPresentationData) -> ChatHistoryGridViewTransition { + var mappedScrollToItem: GridNodeScrollToItem? + if let scrollToItem = transition.scrollToItem { + let mappedPosition: GridNodeScrollToItemPosition + switch scrollToItem.position { + case .top: + mappedPosition = .top(0.0) + case .center: + mappedPosition = .center(0.0) + case .bottom: + mappedPosition = .bottom(0.0) + case .visible: + mappedPosition = .bottom(0.0) + } + let scrollTransition: ContainedViewLayoutTransition + if scrollToItem.animated { + switch scrollToItem.curve { + case .Default: + scrollTransition = .animated(duration: 0.3, curve: .easeInOut) + case let .Spring(duration): + scrollTransition = .animated(duration: duration, curve: .spring) + } + } else { + scrollTransition = .immediate + } + let directionHint: GridNodePreviousItemsTransitionDirectionHint + switch scrollToItem.directionHint { + case .Up: + directionHint = .up + case .Down: + directionHint = .down + } + mappedScrollToItem = GridNodeScrollToItem(index: scrollToItem.index, position: mappedPosition, transition: scrollTransition, directionHint: directionHint, adjustForSection: true, adjustForTopInset: true) + } + + var stationaryItems: GridNodeStationaryItems = .none + if let previousView = from { + if let stationaryRange = transition.stationaryItemRange { + var fromStableIds = Set() + for i in 0 ..< previousView.filteredEntries.count { + if i >= stationaryRange.0 && i <= stationaryRange.1 { + fromStableIds.insert(previousView.filteredEntries[i].stableId) + } + } + var index = 0 + var indices = Set() + for entry in transition.historyView.filteredEntries { + if fromStableIds.contains(entry.stableId) { + indices.insert(transition.historyView.filteredEntries.count - 1 - index) + } + index += 1 + } + stationaryItems = .indices(indices) + } else { + var fromStableIds = Set() + for i in 0 ..< previousView.filteredEntries.count { + fromStableIds.insert(previousView.filteredEntries[i].stableId) + } + var index = 0 + var indices = Set() + for entry in transition.historyView.filteredEntries { + if fromStableIds.contains(entry.stableId) { + indices.insert(transition.historyView.filteredEntries.count - 1 - index) + } + index += 1 + } + stationaryItems = .indices(indices) + } + } + + var topOffsetWithinMonth: Int = 0 + if let lastEntry = transition.historyView.filteredEntries.last { + switch lastEntry { + case let .MessageEntry(_, _, _, monthLocation, _, _): + if let monthLocation = monthLocation { + topOffsetWithinMonth = Int(monthLocation.indexInMonth) + } + default: + break + } + } + + return ChatHistoryGridViewTransition(historyView: transition.historyView, topOffsetWithinMonth: topOffsetWithinMonth, deleteItems: transition.deleteItems.map { $0.index }, insertItems: mappedInsertEntries(context: context, peerId: peerId, controllerInteraction: controllerInteraction, entries: transition.insertEntries, theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize), updateItems: mappedUpdateEntries(context: context, peerId: peerId, controllerInteraction: controllerInteraction, entries: transition.updateEntries, theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize), scrollToItem: mappedScrollToItem, stationaryItems: stationaryItems) +} + +private func gridNodeLayoutForContainerLayout(size: CGSize) -> GridNodeLayoutType { + let side = floorToScreenPixels((size.width - 3.0) / 4.0) + return .fixed(itemSize: CGSize(width: side, height: side), fillWidth: true, lineSpacing: 1.0, itemSpacing: 1.0) +} + +public final class ChatHistoryGridNode: GridNode, ChatHistoryNode { + private let context: AccountContext + private let peerId: PeerId + private let messageId: MessageId? + private let tagMask: MessageTags? + + private var historyView: ChatHistoryView? + + private let historyDisposable = MetaDisposable() + + private let messageViewQueue = Queue() + + private var dequeuedInitialTransitionOnLayout = false + private var enqueuedHistoryViewTransition: (ChatHistoryGridViewTransition, () -> Void)? + var layoutActionOnViewTransition: ((ChatHistoryGridViewTransition) -> (ChatHistoryGridViewTransition, ListViewUpdateSizeAndInsets?))? + + public let historyState = ValuePromise() + private var currentHistoryState: ChatHistoryNodeHistoryState? + + public var preloadPages: Bool = true { + didSet { + if self.preloadPages != oldValue { + + } + } + } + + private let _chatHistoryLocation = ValuePromise(ignoreRepeated: true) + private var chatHistoryLocation: Signal { + return self._chatHistoryLocation.get() + } + + private let galleryHiddenMesageAndMediaDisposable = MetaDisposable() + + private var presentationData: PresentationData + private let chatPresentationDataPromise = Promise() + + public private(set) var loadState: ChatHistoryNodeLoadState? + private var loadStateUpdated: ((ChatHistoryNodeLoadState, Bool) -> Void)? + private let controllerInteraction: ChatControllerInteraction + + public init(context: AccountContext, peerId: PeerId, messageId: MessageId?, tagMask: MessageTags?, controllerInteraction: ChatControllerInteraction) { + self.context = context + self.peerId = peerId + self.messageId = messageId + self.tagMask = tagMask + self.controllerInteraction = controllerInteraction + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + + super.init() + + self.chatPresentationDataPromise.set(context.sharedContext.presentationData + |> map { presentationData in + return ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper), fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations, largeEmoji: presentationData.largeEmoji, chatBubbleCorners: presentationData.chatBubbleCorners) + }) + + self.floatingSections = true + + let messageViewQueue = self.messageViewQueue + + let historyViewUpdate = self.chatHistoryLocation + |> distinctUntilChanged + |> mapToSignal { location in + return chatHistoryViewForLocation(ChatHistoryLocationInput(content: location, id: 0), account: context.account, chatLocation: .peer(peerId), scheduled: false, fixedCombinedReadStates: nil, tagMask: tagMask, additionalData: [], orderStatistics: [.locationWithinMonth]) + } + + let previousView = Atomic(value: nil) + + let historyViewTransition = combineLatest(queue: messageViewQueue, historyViewUpdate, self.chatPresentationDataPromise.get()) + |> mapToQueue { [weak self] update, chatPresentationData -> Signal in + switch update { + case .Loading: + Queue.mainQueue().async { [weak self] in + if let strongSelf = self { + let loadState: ChatHistoryNodeLoadState = .loading + if strongSelf.loadState != loadState { + strongSelf.loadState = loadState + strongSelf.loadStateUpdated?(loadState, false) + } + + let historyState: ChatHistoryNodeHistoryState = .loading + if strongSelf.currentHistoryState != historyState { + strongSelf.currentHistoryState = historyState + strongSelf.historyState.set(historyState) + } + } + } + return .complete() + case let .HistoryView(view, type, scrollPosition, flashIndicators, _, _, id): + let reason: ChatHistoryViewTransitionReason + switch type { + case let .Initial(fadeIn): + reason = ChatHistoryViewTransitionReason.Initial(fadeIn: fadeIn) + case let .Generic(genericType): + switch genericType { + case .InitialUnread, .Initial: + reason = ChatHistoryViewTransitionReason.Initial(fadeIn: false) + case .Generic: + reason = ChatHistoryViewTransitionReason.InteractiveChanges + case .UpdateVisible: + reason = ChatHistoryViewTransitionReason.Reload + case .FillHole: + reason = ChatHistoryViewTransitionReason.Reload + } + } + + let associatedData = ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: false) + let processedView = ChatHistoryView(originalView: view, filteredEntries: chatHistoryEntriesForView(location: .peer(peerId), view: view, includeUnreadEntry: false, includeEmptyEntry: false, includeChatInfoEntry: false, includeSearchEntry: false, reverse: false, groupMessages: false, selectedMessages: nil, presentationData: chatPresentationData, historyAppearsCleared: false, associatedData: associatedData, updatingMedia: [:]), associatedData: associatedData, lastHeaderId: 0, id: id) + let previous = previousView.swap(processedView) + + let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: false, chatLocation: .peer(peerId), controllerInteraction: controllerInteraction, scrollPosition: scrollPosition, initialData: nil, keyboardButtonsMessage: nil, cachedData: nil, cachedDataMessages: nil, readStateData: nil, flashIndicators: flashIndicators, updatedMessageSelection: false) + let mappedTransition = mappedChatHistoryViewListTransition(context: context, peerId: peerId, controllerInteraction: controllerInteraction, transition: rawTransition, from: previous, presentationData: chatPresentationData) + return .single(mappedTransition) + } + } + + let appliedTransition = historyViewTransition |> deliverOnMainQueue |> mapToQueue { [weak self] transition -> Signal in + if let strongSelf = self { + return strongSelf.enqueueHistoryViewTransition(transition) + } + return .complete() + } + + self.historyDisposable.set(appliedTransition.start()) + + if let messageId = messageId { + self._chatHistoryLocation.set(ChatHistoryLocation.InitialSearch(location: .id(messageId), count: 100)) + } else { + self._chatHistoryLocation.set(ChatHistoryLocation.Initial(count: 100)) + } + + self.visibleItemsUpdated = { [weak self] visibleItems in + if let strongSelf = self, let historyView = strongSelf.historyView, let top = visibleItems.top, let bottom = visibleItems.bottom, let visibleTop = visibleItems.topVisible, let visibleBottom = visibleItems.bottomVisible { + if top.0 < 5 && historyView.originalView.laterId != nil { + let lastEntry = historyView.filteredEntries[historyView.filteredEntries.count - 1 - visibleTop.0] + strongSelf._chatHistoryLocation.set(ChatHistoryLocation.Navigation(index: .message(lastEntry.index), anchorIndex: .message(lastEntry.index), count: 100)) + } else if bottom.0 >= historyView.filteredEntries.count - 5 && historyView.originalView.earlierId != nil { + let firstEntry = historyView.filteredEntries[historyView.filteredEntries.count - 1 - visibleBottom.0] + strongSelf._chatHistoryLocation.set(ChatHistoryLocation.Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: 100)) + } + } + } + + let selectorRecogizner = ChatGridLiveSelectorRecognizer(target: self, action: #selector(self.panGesture(_:))) + selectorRecogizner.shouldBegin = { [weak controllerInteraction] in + return controllerInteraction?.selectionState != nil + } + self.view.addGestureRecognizer(selectorRecogizner) + } + + public override func didLoad() { + super.didLoad() + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.historyDisposable.dispose() + } + + public func setLoadStateUpdated(_ f: @escaping (ChatHistoryNodeLoadState, Bool) -> Void) { + self.loadStateUpdated = f + } + + public func scrollToStartOfHistory() { + self._chatHistoryLocation.set(ChatHistoryLocation.Scroll(index: .lowerBound, anchorIndex: .lowerBound, sourceIndex: .upperBound, scrollPosition: .bottom(0.0), animated: true)) + } + + public func scrollToEndOfHistory() { + self._chatHistoryLocation.set(ChatHistoryLocation.Scroll(index: .upperBound, anchorIndex: .upperBound, sourceIndex: .lowerBound, scrollPosition: .top(0.0), animated: true)) + } + + public func scrollToMessage(from fromIndex: MessageIndex, to toIndex: MessageIndex, scrollPosition: ListViewScrollPosition = .center(.bottom)) { + self._chatHistoryLocation.set(ChatHistoryLocation.Scroll(index: .message(toIndex), anchorIndex: .message(toIndex), sourceIndex: .message(fromIndex), scrollPosition: .center(.bottom), animated: true)) + } + + public func messageInCurrentHistoryView(_ id: MessageId) -> Message? { + if let historyView = self.historyView { + for case let .MessageEntry(message, _, _, _, _, _) in historyView.filteredEntries where message.id == id { + return message + } + } + return nil + } + + private func enqueueHistoryViewTransition(_ transition: ChatHistoryGridViewTransition) -> Signal { + return Signal { [weak self] subscriber in + if let strongSelf = self { + if let _ = strongSelf.enqueuedHistoryViewTransition { + preconditionFailure() + } + + strongSelf.enqueuedHistoryViewTransition = (transition, { + subscriber.putCompletion() + }) + + if strongSelf.isNodeLoaded { + strongSelf.dequeueHistoryViewTransition() + } else { + let loadState: ChatHistoryNodeLoadState + if transition.historyView.filteredEntries.isEmpty { + loadState = .empty + } else { + loadState = .messages + } + if strongSelf.loadState != loadState { + strongSelf.loadState = loadState + strongSelf.loadStateUpdated?(loadState, false) + } + + let historyState: ChatHistoryNodeHistoryState = .loaded(isEmpty: transition.historyView.originalView.entries.isEmpty) + if strongSelf.currentHistoryState != historyState { + strongSelf.currentHistoryState = historyState + strongSelf.historyState.set(historyState) + } + } + } else { + subscriber.putCompletion() + } + + return EmptyDisposable + } |> runOn(Queue.mainQueue()) + } + + private func dequeueHistoryViewTransition() { + if let (transition, completion) = self.enqueuedHistoryViewTransition { + self.enqueuedHistoryViewTransition = nil + + let completion: (GridNodeDisplayedItemRange) -> Void = { [weak self] visibleRange in + if let strongSelf = self { + strongSelf.historyView = transition.historyView + + let loadState: ChatHistoryNodeLoadState + if let historyView = strongSelf.historyView { + if historyView.filteredEntries.isEmpty { + loadState = .empty + } else { + loadState = .messages + } + } else { + loadState = .loading + } + + if strongSelf.loadState != loadState { + strongSelf.loadState = loadState + strongSelf.loadStateUpdated?(loadState, false) + } + + let historyState: ChatHistoryNodeHistoryState = .loaded(isEmpty: transition.historyView.originalView.entries.isEmpty) + if strongSelf.currentHistoryState != historyState { + strongSelf.currentHistoryState = historyState + strongSelf.historyState.set(historyState) + } + + completion() + } + } + + if let layoutActionOnViewTransition = self.layoutActionOnViewTransition { + self.layoutActionOnViewTransition = nil + let (mappedTransition, updateSizeAndInsets) = layoutActionOnViewTransition(transition) + + var updateLayout: GridNodeUpdateLayout? + if let updateSizeAndInsets = updateSizeAndInsets { + updateLayout = GridNodeUpdateLayout(layout: GridNodeLayout(size: updateSizeAndInsets.size, insets: updateSizeAndInsets.insets, preloadSize: 400.0, type: .fixed(itemSize: CGSize(width: 200.0, height: 200.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: .immediate) + } + + self.transaction(GridNodeTransaction(deleteItems: mappedTransition.deleteItems, insertItems: mappedTransition.insertItems, updateItems: mappedTransition.updateItems, scrollToItem: mappedTransition.scrollToItem, updateLayout: updateLayout, itemTransition: .immediate, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: mappedTransition.topOffsetWithinMonth), completion: completion) + } else { + self.transaction(GridNodeTransaction(deleteItems: transition.deleteItems, insertItems: transition.insertItems, updateItems: transition.updateItems, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.topOffsetWithinMonth, synchronousLoads: true), completion: completion) + } + } + } + + public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) { + self.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: updateSizeAndInsets.size, insets: updateSizeAndInsets.insets, preloadSize: 400.0, type: gridNodeLayoutForContainerLayout(size: updateSizeAndInsets.size)), transition: .immediate), itemTransition: .immediate, stationaryItems: .none,updateFirstIndexInSectionOffset: nil), completion: { _ in }) + + if !self.dequeuedInitialTransitionOnLayout { + self.dequeuedInitialTransitionOnLayout = true + self.dequeueHistoryViewTransition() + } + + } + + public func disconnect() { + self.historyDisposable.set(nil) + } + + private var selectionPanState: (selecting: Bool, currentMessageId: MessageId)? + + @objc private func panGesture(_ recognizer: UIGestureRecognizer) -> Void { + guard let selectionState = self.controllerInteraction.selectionState else {return} + + switch recognizer.state { + case .began: + if let itemNode = self.itemNodeAtPoint(recognizer.location(in: self.view)) as? GridMessageItemNode, let messageId = itemNode.messageId { + self.selectionPanState = (selecting: !selectionState.selectedIds.contains(messageId), currentMessageId: messageId) + self.controllerInteraction.toggleMessagesSelection([messageId], !selectionState.selectedIds.contains(messageId)) + } + case .changed: + if let selectionPanState = self.selectionPanState, let itemNode = self.itemNodeAtPoint(recognizer.location(in: self.view)) as? GridMessageItemNode, let messageId = itemNode.messageId, messageId != selectionPanState.currentMessageId { + self.controllerInteraction.toggleMessagesSelection([messageId], selectionPanState.selecting) + self.selectionPanState?.currentMessageId = messageId + } + case .ended, .failed, .cancelled: + self.selectionPanState = nil + case .possible: + break + } + } +} diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 1dea8b502d..33aca30807 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -14,9 +14,6 @@ import TemporaryCachedPeerDataManager import ChatListSearchItemNode import Emoji import AppBundle -import ListMessageItem -import AccountContext -import ChatInterfaceState private class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer { private let selectionGestureActivationThreshold: CGFloat = 5.0 @@ -77,7 +74,7 @@ public enum ChatHistoryListDisplayHeaders { public enum ChatHistoryListMode: Equatable { case bubbles - case list(search: Bool, reversed: Bool, displayHeaders: ChatHistoryListDisplayHeaders, hintLinks: Bool, isGlobalSearch: Bool) + case list(search: Bool, reversed: Bool, displayHeaders: ChatHistoryListDisplayHeaders) } enum ChatHistoryViewScrollPosition { @@ -222,26 +219,6 @@ private func maxMessageIndexForEntries(_ view: ChatHistoryView, indexRange: (Int return (incoming, overall) } -extension ListMessageItemInteraction { - convenience init(controllerInteraction: ChatControllerInteraction) { - self.init(openMessage: { message, mode -> Bool in - return controllerInteraction.openMessage(message, mode) - }, openMessageContextMenu: { message, bool, node, rect, gesture in - controllerInteraction.openMessageContextMenu(message, bool, node, rect, gesture) - }, toggleMessagesSelection: { messageId, selected in - controllerInteraction.toggleMessagesSelection(messageId, selected) - }, openUrl: { url, param1, param2, message in - controllerInteraction.openUrl(url, param1, param2, message) - }, openInstantPage: { message, data in - controllerInteraction.openInstantPage(message, data) - }, longTap: { action, message in - controllerInteraction.longTap(action, message) - }, getHiddenMedia: { - return controllerInteraction.hiddenMedia - }) - } -} - private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, entries: [ChatHistoryViewTransitionInsertEntry]) -> [ListViewInsertItem] { return entries.map { entry -> ListViewInsertItem in switch entry.entry { @@ -250,7 +227,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca switch mode { case .bubbles: item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes)) - case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): + case let .list(_, _, displayHeaders): let displayHeader: Bool switch displayHeaders { case .none: @@ -260,7 +237,8 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca case .allButLast: displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != lastHeaderId } - item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) + + item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize, dateTimeFormat: presentationData.dateTimeFormat, context: context, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: message, selection: selection, displayHeader: displayHeader) } return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .MessageGroupEntry(_, messages, presentationData): @@ -268,15 +246,13 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca switch mode { case .bubbles: item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages)) - case let .list(_, _, _, _, _): + case let .list(_, _, _): assertionFailure() - item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false) + item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize, dateTimeFormat: presentationData.dateTimeFormat, context: context, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: messages[0].0, selection: .none, displayHeader: false) } return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .UnreadEntry(_, presentationData): return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatUnreadItem(index: entry.entry.index, presentationData: presentationData, context: context), directionHint: entry.directionHint) - case let .ReplyCountEntry(_, isComments, count, presentationData): - return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatReplyCountItem(index: entry.entry.index, isComments: isComments, count: count, presentationData: presentationData, context: context), directionHint: entry.directionHint) case let .ChatInfoEntry(text, presentationData): return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatBotInfoItem(text: text, controllerInteraction: controllerInteraction, presentationData: presentationData), directionHint: entry.directionHint) case let .SearchEntry(theme, strings): @@ -295,7 +271,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca switch mode { case .bubbles: item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes)) - case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): + case let .list(_, _, displayHeaders): let displayHeader: Bool switch displayHeaders { case .none: @@ -305,7 +281,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca case .allButLast: displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != lastHeaderId } - item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) + item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize, dateTimeFormat: presentationData.dateTimeFormat, context: context, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: message, selection: selection, displayHeader: displayHeader) } return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .MessageGroupEntry(_, messages, presentationData): @@ -313,15 +289,13 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca switch mode { case .bubbles: item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages)) - case let .list(_, _, _, _, _): + case let .list(_, _, _): assertionFailure() - item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false) + item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize, dateTimeFormat: presentationData.dateTimeFormat, context: context, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: messages[0].0, selection: .none, displayHeader: false) } return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .UnreadEntry(_, presentationData): return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatUnreadItem(index: entry.entry.index, presentationData: presentationData, context: context), directionHint: entry.directionHint) - case let .ReplyCountEntry(_, isComments, count, presentationData): - return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatReplyCountItem(index: entry.entry.index, isComments: isComments, count: count, presentationData: presentationData, context: context), directionHint: entry.directionHint) case let .ChatInfoEntry(text, presentationData): return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatBotInfoItem(text: text, controllerInteraction: controllerInteraction, presentationData: presentationData), directionHint: entry.directionHint) case let .SearchEntry(theme, strings): @@ -347,7 +321,6 @@ private final class ChatHistoryTransactionOpaqueState { private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], isScheduledMessages: Bool) -> ChatMessageItemAssociatedData { var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel var contactsPeerIds: Set = Set() - var channelDiscussionGroup: ChatMessageItemAssociatedData.ChannelDiscussionGroupStatus = .unknown if case let .peer(peerId) = chatLocation { if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat { var isContact = false @@ -372,15 +345,7 @@ private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHist if let channel = value as? TelegramChannel, case .group = channel.info { automaticMediaDownloadPeerType = .group } - } else if case let .cachedPeerData(dataPeerId, cachedData) = entry, dataPeerId == peerId { - if let cachedData = cachedData as? CachedChannelData { - switch cachedData.linkedDiscussionPeerId { - case let .known(value): - channelDiscussionGroup = .known(value) - case .unknown: - channelDiscussionGroup = .unknown - } - } + break } } if automaticMediaDownloadPeerType == .group { @@ -392,7 +357,7 @@ private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHist } } } - let associatedData = ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, isScheduledMessages: isScheduledMessages, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers) + let associatedData = ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, isScheduledMessages: isScheduledMessages, contactsPeerIds: contactsPeerIds, animatedEmojiStickers: animatedEmojiStickers) return associatedData } @@ -432,7 +397,6 @@ private struct ChatHistoryAnimatedEmojiConfiguration { public final class ChatHistoryListNode: ListView, ChatHistoryNode { private let context: AccountContext private let chatLocation: ChatLocation - private let chatLocationContextHolder: Atomic private let subject: ChatControllerSubject? private let tagMask: MessageTags? private let controllerInteraction: ChatControllerInteraction @@ -543,10 +507,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { private var loadedMessagesFromCachedDataDisposable: Disposable? - public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tagMask: MessageTags?, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles) { + public init(context: AccountContext, chatLocation: ChatLocation, tagMask: MessageTags?, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles) { self.context = context self.chatLocation = chatLocation - self.chatLocationContextHolder = chatLocationContextHolder self.subject = subject self.tagMask = tagMask self.controllerInteraction = controllerInteraction @@ -595,13 +558,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { let fixedCombinedReadStates = Atomic(value: nil) - var isScheduledMessages = false + var scheduled = false if let subject = subject, case .scheduledMessages = subject { - isScheduledMessages = true - } - var isAuxiliaryChat = isScheduledMessages - if case .replyThread = chatLocation { - isAuxiliaryChat = true + scheduled = true } var additionalData: [AdditionalMessageHistoryViewData] = [] @@ -617,26 +576,16 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { additionalData.append(.peerIsContact(peerId)) } } - if !isAuxiliaryChat { + if !scheduled { additionalData.append(.totalUnreadState) } - if case let .replyThread(messageId, _, _) = chatLocation { - additionalData.append(.cachedPeerData(messageId.peerId)) - additionalData.append(.peerNotificationSettings(messageId.peerId)) - if messageId.peerId.namespace == Namespaces.Peer.CloudChannel { - additionalData.append(.cacheEntry(cachedChannelAdminRanksEntryId(peerId: messageId.peerId))) - additionalData.append(.peer(messageId.peerId)) - } - - additionalData.append(.message(messageId)) - } let currentViewVersion = Atomic(value: nil) let historyViewUpdate = self.chatHistoryLocationPromise.get() |> distinctUntilChanged |> mapToSignal { location in - return chatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, scheduled: isScheduledMessages, fixedCombinedReadStates: fixedCombinedReadStates.with { $0 }, tagMask: tagMask, additionalData: additionalData) + return chatHistoryViewForLocation(location, account: context.account, chatLocation: chatLocation, scheduled: scheduled, fixedCombinedReadStates: fixedCombinedReadStates.with { $0 }, tagMask: tagMask, additionalData: additionalData) |> beforeNext { viewUpdate in switch viewUpdate { case let .HistoryView(view, _, _, _, _, _, _): @@ -728,9 +677,6 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } else { if let subject = subject, case let .message(messageId) = subject { strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0) - } else if var chatHistoryLocation = strongSelf.chatHistoryLocationValue { - chatHistoryLocation.id += 1 - strongSelf.chatHistoryLocationValue = chatHistoryLocation } else { strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Initial(count: 60), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0) } @@ -757,10 +703,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { strongSelf._initialData.set(.single(combinedInitialData)) } - let cachedData = initialData?.cachedData - let cachedDataMessages = initialData?.cachedDataMessages - - strongSelf._cachedPeerDataAndMessages.set(.single((cachedData, cachedDataMessages))) + strongSelf._cachedPeerDataAndMessages.set(.single((nil, nil))) let loadState: ChatHistoryNodeLoadState = .loading if strongSelf.loadState != loadState { @@ -787,7 +730,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { var reverse = false var includeSearchEntry = false - if case let .list(search, reverseValue, _, _, _) = mode { + if case let .list(search, reverseValue, _) = mode { includeSearchEntry = search reverse = reverseValue } @@ -881,9 +824,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } if apply { switch chatLocation { - case .peer, .replyThread: - if !context.sharedContext.immediateExperimentalUISettings.skipReadHistory { - context.applyMaxReadIndex(for: chatLocation, contextHolder: chatLocationContextHolder, messageIndex: messageIndex) + case .peer: + if !context.sharedContext.immediateExperimentalUISettings.skipReadHistory { + let _ = applyMaxReadIndexInteractively(postbox: context.account.postbox, stateManager: context.account.stateManager, index: messageIndex).start() } } } @@ -1071,10 +1014,6 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if message.id.namespace == Namespaces.Message.Cloud { messageIdsWithViewCount.append(message.id) } - } else if attribute is ReplyThreadMessageAttribute { - if message.id.namespace == Namespaces.Message.Cloud { - messageIdsWithViewCount.append(message.id) - } } else if let attribute = attribute as? ConsumableContentMessageAttribute, !attribute.consumed { hasUnconsumedContent = true } else if let _ = attribute as? ContentRequiresValidationMessageAttribute { @@ -1119,10 +1058,6 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if message.id.namespace == Namespaces.Message.Cloud { messageIdsWithViewCount.append(message.id) } - } else if attribute is ReplyThreadMessageAttribute { - if message.id.namespace == Namespaces.Message.Cloud { - messageIdsWithViewCount.append(message.id) - } } else if let attribute = attribute as? ConsumableContentMessageAttribute, !attribute.consumed { hasUnconsumedContent = true } @@ -1242,10 +1177,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if loaded.firstIndex < 5 && historyView.originalView.laterId != nil { self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .message(lastEntry.index), anchorIndex: .message(lastEntry.index), count: historyMessageCount), id: self.takeNextHistoryLocationId()) } else if loaded.firstIndex < 5, historyView.originalView.laterId == nil, !historyView.originalView.holeLater, let chatHistoryLocationValue = self.chatHistoryLocationValue, !chatHistoryLocationValue.isAtUpperBound, historyView.originalView.anchorIndex != .upperBound { - //TODO:localize - #if !DEBUG self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .upperBound, anchorIndex: .upperBound, count: historyMessageCount), id: self.takeNextHistoryLocationId()) - #endif } else if loaded.lastIndex >= historyView.filteredEntries.count - 5 && historyView.originalView.earlierId != nil { self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: historyMessageCount), id: self.takeNextHistoryLocationId()) } @@ -1319,8 +1251,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { case .known(0.0): break default: - let locationInput = ChatHistoryLocationInput(content: .Scroll(index: .upperBound, anchorIndex: .upperBound, sourceIndex: .lowerBound, scrollPosition: .top(0.0), animated: true), id: self.takeNextHistoryLocationId()) - self.chatHistoryLocationValue = locationInput + self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(index: .upperBound, anchorIndex: .upperBound, sourceIndex: .lowerBound, scrollPosition: .top(0.0), animated: true), id: self.takeNextHistoryLocationId()) } } @@ -1512,12 +1443,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } strongSelf.loadState = loadState - strongSelf.loadStateUpdated?(loadState, animated || transition.animateIn || animateIn) + strongSelf.loadStateUpdated?(loadState, animated) } - if let _ = visibleRange.loadedRange { + if let range = visibleRange.loadedRange { if let visible = visibleRange.visibleRange { - let visibleFirstIndex = visible.firstIndex + var visibleFirstIndex = visible.firstIndex /*if !visible.firstIndexFullyVisible { visibleFirstIndex += 1 }*/ @@ -1556,16 +1487,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if transition.animateIn || animateIn { let heightNorm = strongSelf.bounds.height - strongSelf.insets.top strongSelf.forEachVisibleItemNode { itemNode in - let delayFactor = itemNode.frame.minY / heightNorm - let delay = Double(delayFactor * 0.1) - if let itemNode = itemNode as? ChatMessageItemView { + let delayFactor = itemNode.frame.minY / heightNorm + let delay = Double(delayFactor * 0.1) + itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay) - itemNode.layer.animateScale(from: 0.94, to: 1.0, duration: 0.4, delay: delay, timingFunction: kCAMediaTimingFunctionSpring) - } else if let itemNode = itemNode as? ChatUnreadItemNode { - itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay) - } else if let itemNode = itemNode as? ChatReplyCountItemNode { - itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay) + itemNode.layer.animateScale(from: 0.9, to: 1.0, duration: 0.4, delay: delay, timingFunction: kCAMediaTimingFunctionSpring) } } strongSelf.forEachItemHeaderNode { itemNode in @@ -1573,7 +1500,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { let delay = Double(delayFactor * 0.2) itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay) - itemNode.layer.animateScale(from: 0.94, to: 1.0, duration: 0.4, delay: delay, timingFunction: kCAMediaTimingFunctionSpring) + itemNode.layer.animateScale(from: 0.9, to: 1.0, duration: 0.4, delay: delay, timingFunction: kCAMediaTimingFunctionSpring) } } @@ -1776,7 +1703,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { switch self.mode { case .bubbles: item = ChatMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes)) - case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): + case let .list(_, _, displayHeaders): let displayHeader: Bool switch displayHeaders { case .none: @@ -1786,7 +1713,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { case .allButLast: displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId } - item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) + item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize, dateTimeFormat: presentationData.dateTimeFormat, context: self.context, chatLocation: self.chatLocation, controllerInteraction: self.controllerInteraction, message: message, selection: selection, displayHeader: displayHeader) } let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil) self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) diff --git a/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift b/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift index 6d49b2ea67..a29c6a25d9 100644 --- a/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift @@ -11,7 +11,6 @@ import MergeLists import AccountContext import SearchUI import TelegramUIPreferences -import ListMessageItem private enum ChatHistorySearchEntryStableId: Hashable { case messageId(MessageId) @@ -89,7 +88,7 @@ private enum ChatHistorySearchEntry: Comparable, Identifiable { func item(context: AccountContext, peerId: PeerId, interaction: ChatControllerInteraction) -> ListViewItem { switch self { case let .message(message, theme, strings, dateTimeFormat, fontSize): - return ListMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: .builtin(WallpaperSettings())), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(peerId), interaction: ListMessageItemInteraction(controllerInteraction: interaction), message: message, selection: .none, displayHeader: true) + return ListMessageItem(theme: theme, strings: strings, fontSize: fontSize, dateTimeFormat: dateTimeFormat, context: context, chatLocation: .peer(peerId), controllerInteraction: interaction, message: message, selection: .none, displayHeader: true) } } } @@ -186,7 +185,7 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode { if let strongSelf = self { let signal: Signal<([ChatHistorySearchEntry], [MessageId: Message])?, NoError> if let query = query, !query.isEmpty { - let foundRemoteMessages: Signal<[Message], NoError> = searchMessages(account: context.account, location: .peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: nil, maxDate: nil), query: query, state: nil) + let foundRemoteMessages: Signal<[Message], NoError> = searchMessages(account: context.account, location: .peer(peerId: peerId, fromId: nil, tags: tagMask), query: query, state: nil) |> map { $0.0.messages } |> delay(0.2, queue: Queue.concurrentDefaultQueue()) diff --git a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift index 79d5ffc514..75ed081bcb 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift @@ -6,10 +6,9 @@ import SyncCore import SwiftSignalKit import Display import AccountContext -import ChatInterfaceState -func preloadedChatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal { - return chatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, scheduled: false, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, additionalData: additionalData, orderStatistics: orderStatistics) +func preloadedChatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal { + return chatHistoryViewForLocation(location, account: account, chatLocation: chatLocation, scheduled: false, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, additionalData: additionalData, orderStatistics: orderStatistics) |> castError(Bool.self) |> mapToSignal { update -> Signal in switch update { @@ -27,8 +26,7 @@ func preloadedChatHistoryViewForLocation(_ location: ChatHistoryLocationInput, c |> restartIfError } -func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, scheduled: Bool, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal { - let account = context.account +func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, scheduled: Bool, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal { if scheduled { var first = true var chatScrollPosition: ChatHistoryViewScrollPosition? @@ -36,7 +34,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: A let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up chatScrollPosition = .index(index: index, position: position, directionHint: directionHint, animated: animated) } - return account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), additionalData: additionalData) + return account.viewTracker.scheduledMessagesViewForLocation(chatLocation, additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation) @@ -67,9 +65,9 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: A var fadeIn = false let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> if let tagMask = tagMask { - signal = account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), index: .upperBound, anchorIndex: .upperBound, count: count, fixedCombinedReadStates: nil, tagMask: tagMask, orderStatistics: orderStatistics) + signal = account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: .upperBound, anchorIndex: .upperBound, count: count, fixedCombinedReadStates: nil, tagMask: tagMask, orderStatistics: orderStatistics) } else { - signal = account.viewTracker.aroundMessageOfInterestHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), count: count, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + signal = account.viewTracker.aroundMessageOfInterestHistoryViewForLocation(chatLocation, count: count, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) } return signal |> map { view, updateType, initialData -> ChatHistoryViewUpdate in @@ -142,9 +140,9 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: A let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> switch searchLocation { case let .index(index): - signal = account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), index: .message(index), anchorIndex: .message(index), count: count, fixedCombinedReadStates: nil, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + signal = account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: .message(index), anchorIndex: .message(index), count: count, fixedCombinedReadStates: nil, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) case let .id(id): - signal = account.viewTracker.aroundIdMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), count: count, messageId: id, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + signal = account.viewTracker.aroundIdMessageHistoryViewForLocation(chatLocation, count: count, messageId: id, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) } return signal |> map { view, updateType, initialData -> ChatHistoryViewUpdate in @@ -192,7 +190,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: A } case let .Navigation(index, anchorIndex, count): var first = true - return account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), index: index, anchorIndex: anchorIndex, count: count, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in + return account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: index, anchorIndex: anchorIndex, count: count, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation) let genericType: ViewUpdateType @@ -208,16 +206,10 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: A let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up let chatScrollPosition = ChatHistoryViewScrollPosition.index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated) var first = true - return account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), index: index, anchorIndex: anchorIndex, count: 128, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + return account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: index, anchorIndex: anchorIndex, count: 128, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation) - let combinedInitialData = ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData) - - if view.isLoading { - return ChatHistoryViewUpdate.Loading(initialData: combinedInitialData, type: .Generic(type: updateType)) - } - let genericType: ViewUpdateType let scrollPosition: ChatHistoryViewScrollPosition? = first ? chatScrollPosition : nil if first { @@ -226,7 +218,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: A } else { genericType = updateType } - return .HistoryView(view: view, type: .Generic(type: genericType), scrollPosition: scrollPosition, flashIndicators: animated, originalScrollPosition: chatScrollPosition, initialData: combinedInitialData, id: location.id) + return .HistoryView(view: view, type: .Generic(type: genericType), scrollPosition: scrollPosition, flashIndicators: animated, originalScrollPosition: chatScrollPosition, initialData: ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData), id: location.id) } } } @@ -236,9 +228,9 @@ private func extractAdditionalData(view: MessageHistoryView, chatLocation: ChatL cachedData: CachedPeerData?, cachedDataMessages: [MessageId: Message]?, readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]? -) { + ) { var cachedData: CachedPeerData? - var cachedDataMessages: [MessageId: Message] = [:] + var cachedDataMessages: [MessageId: Message]? var readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData] = [:] var notificationSettings: PeerNotificationSettings? @@ -256,20 +248,12 @@ private func extractAdditionalData(view: MessageHistoryView, chatLocation: ChatL case let .peerNotificationSettings(value): notificationSettings = value case let .cachedPeerData(peerIdValue, value): - if chatLocation.peerId == peerIdValue { + if case .peer(peerIdValue) = chatLocation { cachedData = value } case let .cachedPeerDataMessages(peerIdValue, value): if case .peer(peerIdValue) = chatLocation { - if let value = value { - for (_, message) in value { - cachedDataMessages[message.id] = message - } - } - } - case let .message(_, message): - if let message = message { - cachedDataMessages[message.id] = message + cachedDataMessages = value } case let .totalUnreadState(totalUnreadState): switch chatLocation { @@ -279,8 +263,8 @@ private func extractAdditionalData(view: MessageHistoryView, chatLocation: ChatL readStateData[peerId] = ChatHistoryCombinedInitialReadStateData(unreadCount: readState.count, totalState: totalUnreadState, notificationSettings: notificationSettings) } } - case .replyThread: - break + /*case .group: + break*/ } default: break diff --git a/submodules/TelegramUI/Sources/ChatInfo.swift b/submodules/TelegramUI/Sources/ChatInfo.swift index 126f2d43a1..0ddd82763a 100644 --- a/submodules/TelegramUI/Sources/ChatInfo.swift +++ b/submodules/TelegramUI/Sources/ChatInfo.swift @@ -5,3 +5,6 @@ import SyncCore import Display import AccountContext +func peerSharedMediaControllerImpl(context: AccountContext, peerId: PeerId) -> ViewController? { + return PeerMediaCollectionController(context: context, peerId: peerId) +} diff --git a/submodules/TelegramUI/Sources/ChatInfoTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatInfoTitlePanelNode.swift index 6d54ce28f0..9f8775fe8b 100644 --- a/submodules/TelegramUI/Sources/ChatInfoTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatInfoTitlePanelNode.swift @@ -163,8 +163,6 @@ final class ChatInfoTitlePanelNode: ChatTitleAccessoryPanelNode { } else { updatedButtons = [] } - case .replyThread: - updatedButtons = [] } var buttonsUpdated = false diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift index 1169d36b0e..e219fad30a 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift @@ -6,7 +6,6 @@ import Postbox import Display import AccountContext import Emoji -import ChatInterfaceState struct PossibleContextQueryTypes: OptionSet { var rawValue: Int32 diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift index f59f78096a..0440046386 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift @@ -21,7 +21,7 @@ func inputNodeForChatPresentationIntefaceState(_ chatPresentationInterfaceState: if case let .peer(id) = chatPresentationInterfaceState.chatLocation { peerId = id } - let inputNode = ChatMediaInputNode(context: context, peerId: peerId, chatLocation: chatPresentationInterfaceState.chatLocation, controllerInteraction: controllerInteraction, chatWallpaper: chatPresentationInterfaceState.chatWallpaper, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, gifPaneIsActiveUpdated: { [weak interfaceInteraction] value in + let inputNode = ChatMediaInputNode(context: context, peerId: peerId, controllerInteraction: controllerInteraction, chatWallpaper: chatPresentationInterfaceState.chatWallpaper, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, gifPaneIsActiveUpdated: { [weak interfaceInteraction] value in if let interfaceInteraction = interfaceInteraction { interfaceInteraction.updateInputModeAndDismissedButtonKeyboardMessageId { state in if case let .media(_, expanded) = state.inputMode { diff --git a/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift b/submodules/TelegramUI/Sources/ChatInterfaceState.swift similarity index 84% rename from submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift rename to submodules/TelegramUI/Sources/ChatInterfaceState.swift index bcc798afd9..2de35faf20 100644 --- a/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceState.swift @@ -6,23 +6,18 @@ import SyncCore import TextFormat import AccountContext -public enum ChatTextInputMediaRecordingButtonMode: Int32 { - case audio = 0 - case video = 1 -} - -public struct ChatInterfaceSelectionState: PostboxCoding, Equatable { - public let selectedIds: Set +struct ChatInterfaceSelectionState: PostboxCoding, Equatable { + let selectedIds: Set - public static func ==(lhs: ChatInterfaceSelectionState, rhs: ChatInterfaceSelectionState) -> Bool { + static func ==(lhs: ChatInterfaceSelectionState, rhs: ChatInterfaceSelectionState) -> Bool { return lhs.selectedIds == rhs.selectedIds } - public init(selectedIds: Set) { + init(selectedIds: Set) { self.selectedIds = selectedIds } - public init(decoder: PostboxDecoder) { + init(decoder: PostboxDecoder) { if let data = decoder.decodeBytesForKeyNoCopy("i") { self.selectedIds = Set(MessageId.decodeArrayFromBuffer(data)) } else { @@ -30,27 +25,27 @@ public struct ChatInterfaceSelectionState: PostboxCoding, Equatable { } } - public func encode(_ encoder: PostboxEncoder) { + func encode(_ encoder: PostboxEncoder) { let buffer = WriteBuffer() MessageId.encodeArrayToBuffer(Array(selectedIds), buffer: buffer) encoder.encodeBytes(buffer, forKey: "i") } } -public struct ChatEditMessageState: PostboxCoding, Equatable { - public let messageId: MessageId - public let inputState: ChatTextInputState - public let disableUrlPreview: String? - public let inputTextMaxLength: Int32? +struct ChatEditMessageState: PostboxCoding, Equatable { + let messageId: MessageId + let inputState: ChatTextInputState + let disableUrlPreview: String? + let inputTextMaxLength: Int32? - public init(messageId: MessageId, inputState: ChatTextInputState, disableUrlPreview: String?, inputTextMaxLength: Int32?) { + init(messageId: MessageId, inputState: ChatTextInputState, disableUrlPreview: String?, inputTextMaxLength: Int32?) { self.messageId = messageId self.inputState = inputState self.disableUrlPreview = disableUrlPreview self.inputTextMaxLength = inputTextMaxLength } - public init(decoder: PostboxDecoder) { + init(decoder: PostboxDecoder) { self.messageId = MessageId(peerId: PeerId(decoder.decodeInt64ForKey("mp", orElse: 0)), namespace: decoder.decodeInt32ForKey("mn", orElse: 0), id: decoder.decodeInt32ForKey("mi", orElse: 0)) if let inputState = decoder.decodeObjectForKey("is", decoder: { return ChatTextInputState(decoder: $0) }) as? ChatTextInputState { self.inputState = inputState @@ -61,7 +56,7 @@ public struct ChatEditMessageState: PostboxCoding, Equatable { self.inputTextMaxLength = decoder.decodeOptionalInt32ForKey("tl") } - public func encode(_ encoder: PostboxEncoder) { + func encode(_ encoder: PostboxEncoder) { encoder.encodeInt64(self.messageId.peerId.toInt64(), forKey: "mp") encoder.encodeInt32(self.messageId.namespace, forKey: "mn") encoder.encodeInt32(self.messageId.id, forKey: "mi") @@ -78,31 +73,31 @@ public struct ChatEditMessageState: PostboxCoding, Equatable { } } - public static func ==(lhs: ChatEditMessageState, rhs: ChatEditMessageState) -> Bool { + static func ==(lhs: ChatEditMessageState, rhs: ChatEditMessageState) -> Bool { return lhs.messageId == rhs.messageId && lhs.inputState == rhs.inputState && lhs.disableUrlPreview == rhs.disableUrlPreview && lhs.inputTextMaxLength == rhs.inputTextMaxLength } - public func withUpdatedInputState(_ inputState: ChatTextInputState) -> ChatEditMessageState { + func withUpdatedInputState(_ inputState: ChatTextInputState) -> ChatEditMessageState { return ChatEditMessageState(messageId: self.messageId, inputState: inputState, disableUrlPreview: self.disableUrlPreview, inputTextMaxLength: self.inputTextMaxLength) } - public func withUpdatedDisableUrlPreview(_ disableUrlPreview: String?) -> ChatEditMessageState { + func withUpdatedDisableUrlPreview(_ disableUrlPreview: String?) -> ChatEditMessageState { return ChatEditMessageState(messageId: self.messageId, inputState: self.inputState, disableUrlPreview: disableUrlPreview, inputTextMaxLength: self.inputTextMaxLength) } } -public struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { - public var closedButtonKeyboardMessageId: MessageId? - public var processedSetupReplyMessageId: MessageId? - public var closedPinnedMessageId: MessageId? - public var closedPeerSpecificPackSetup: Bool = false - public var dismissedAddContactPhoneNumber: String? +struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { + var closedButtonKeyboardMessageId: MessageId? + var processedSetupReplyMessageId: MessageId? + var closedPinnedMessageId: MessageId? + var closedPeerSpecificPackSetup: Bool = false + var dismissedAddContactPhoneNumber: String? - public var isEmpty: Bool { + var isEmpty: Bool { return self.closedButtonKeyboardMessageId == nil && self.processedSetupReplyMessageId == nil && self.closedPinnedMessageId == nil && self.closedPeerSpecificPackSetup == false && self.dismissedAddContactPhoneNumber == nil } - public init() { + init() { self.closedButtonKeyboardMessageId = nil self.processedSetupReplyMessageId = nil self.closedPinnedMessageId = nil @@ -110,7 +105,7 @@ public struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { self.dismissedAddContactPhoneNumber = nil } - public init(closedButtonKeyboardMessageId: MessageId?, processedSetupReplyMessageId: MessageId?, closedPinnedMessageId: MessageId?, closedPeerSpecificPackSetup: Bool, dismissedAddContactPhoneNumber: String?) { + init(closedButtonKeyboardMessageId: MessageId?, processedSetupReplyMessageId: MessageId?, closedPinnedMessageId: MessageId?, closedPeerSpecificPackSetup: Bool, dismissedAddContactPhoneNumber: String?) { self.closedButtonKeyboardMessageId = closedButtonKeyboardMessageId self.processedSetupReplyMessageId = processedSetupReplyMessageId self.closedPinnedMessageId = closedPinnedMessageId @@ -118,7 +113,7 @@ public struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { self.dismissedAddContactPhoneNumber = dismissedAddContactPhoneNumber } - public init(decoder: PostboxDecoder) { + init(decoder: PostboxDecoder) { if let closedMessageIdPeerId = decoder.decodeOptionalInt64ForKey("cb.p"), let closedMessageIdNamespace = decoder.decodeOptionalInt32ForKey("cb.n"), let closedMessageIdId = decoder.decodeOptionalInt32ForKey("cb.i") { self.closedButtonKeyboardMessageId = MessageId(peerId: PeerId(closedMessageIdPeerId), namespace: closedMessageIdNamespace, id: closedMessageIdId) } else { @@ -140,7 +135,7 @@ public struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { self.closedPeerSpecificPackSetup = decoder.decodeInt32ForKey("cpss", orElse: 0) != 0 } - public func encode(_ encoder: PostboxEncoder) { + func encode(_ encoder: PostboxEncoder) { if let closedButtonKeyboardMessageId = self.closedButtonKeyboardMessageId { encoder.encodeInt64(closedButtonKeyboardMessageId.peerId.toInt64(), forKey: "cb.p") encoder.encodeInt32(closedButtonKeyboardMessageId.namespace, forKey: "cb.n") @@ -181,21 +176,21 @@ public struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { } } -public struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable { - public let messageIndex: MessageIndex - public let relativeOffset: Double +struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable { + let messageIndex: MessageIndex + let relativeOffset: Double - public init(messageIndex: MessageIndex, relativeOffset: Double) { + init(messageIndex: MessageIndex, relativeOffset: Double) { self.messageIndex = messageIndex self.relativeOffset = relativeOffset } - public init(decoder: PostboxDecoder) { + init(decoder: PostboxDecoder) { self.messageIndex = MessageIndex(id: MessageId(peerId: PeerId(decoder.decodeInt64ForKey("m.p", orElse: 0)), namespace: decoder.decodeInt32ForKey("m.n", orElse: 0), id: decoder.decodeInt32ForKey("m.i", orElse: 0)), timestamp: decoder.decodeInt32ForKey("m.t", orElse: 0)) self.relativeOffset = decoder.decodeDoubleForKey("ro", orElse: 0.0) } - public func encode(_ encoder: PostboxEncoder) { + func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.messageIndex.timestamp, forKey: "m.t") encoder.encodeInt64(self.messageIndex.id.peerId.toInt64(), forKey: "m.p") encoder.encodeInt32(self.messageIndex.id.namespace, forKey: "m.n") @@ -203,7 +198,7 @@ public struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable { encoder.encodeDouble(self.relativeOffset, forKey: "ro") } - public static func ==(lhs: ChatInterfaceHistoryScrollState, rhs: ChatInterfaceHistoryScrollState) -> Bool { + static func ==(lhs: ChatInterfaceHistoryScrollState, rhs: ChatInterfaceHistoryScrollState) -> Bool { if lhs.messageIndex != rhs.messageIndex { return false } @@ -215,18 +210,18 @@ public struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable { } public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable { - public let timestamp: Int32 - public let composeInputState: ChatTextInputState - public let composeDisableUrlPreview: String? - public let replyMessageId: MessageId? - public let forwardMessageIds: [MessageId]? - public let editMessage: ChatEditMessageState? - public let selectionState: ChatInterfaceSelectionState? - public let messageActionsState: ChatInterfaceMessageActionsState - public let historyScrollState: ChatInterfaceHistoryScrollState? - public let mediaRecordingMode: ChatTextInputMediaRecordingButtonMode - public let silentPosting: Bool - public let inputLanguage: String? + let timestamp: Int32 + let composeInputState: ChatTextInputState + let composeDisableUrlPreview: String? + let replyMessageId: MessageId? + let forwardMessageIds: [MessageId]? + let editMessage: ChatEditMessageState? + let selectionState: ChatInterfaceSelectionState? + let messageActionsState: ChatInterfaceMessageActionsState + let historyScrollState: ChatInterfaceHistoryScrollState? + let mediaRecordingMode: ChatTextInputMediaRecordingButtonMode + let silentPosting: Bool + let inputLanguage: String? public var associatedMessageIds: [MessageId] { var ids: [MessageId] = [] @@ -264,7 +259,7 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata return result } - public var effectiveInputState: ChatTextInputState { + var effectiveInputState: ChatTextInputState { if let editMessage = self.editMessage { return editMessage.inputState } else { @@ -287,7 +282,7 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata self.inputLanguage = nil } - public init(timestamp: Int32, composeInputState: ChatTextInputState, composeDisableUrlPreview: String?, replyMessageId: MessageId?, forwardMessageIds: [MessageId]?, editMessage: ChatEditMessageState?, selectionState: ChatInterfaceSelectionState?, messageActionsState: ChatInterfaceMessageActionsState, historyScrollState: ChatInterfaceHistoryScrollState?, mediaRecordingMode: ChatTextInputMediaRecordingButtonMode, silentPosting: Bool, inputLanguage: String?) { + init(timestamp: Int32, composeInputState: ChatTextInputState, composeDisableUrlPreview: String?, replyMessageId: MessageId?, forwardMessageIds: [MessageId]?, editMessage: ChatEditMessageState?, selectionState: ChatInterfaceSelectionState?, messageActionsState: ChatInterfaceMessageActionsState, historyScrollState: ChatInterfaceHistoryScrollState?, mediaRecordingMode: ChatTextInputMediaRecordingButtonMode, silentPosting: Bool, inputLanguage: String?) { self.timestamp = timestamp self.composeInputState = composeInputState self.composeDisableUrlPreview = composeDisableUrlPreview @@ -442,17 +437,17 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata return lhs.composeInputState == rhs.composeInputState && lhs.replyMessageId == rhs.replyMessageId && lhs.selectionState == rhs.selectionState && lhs.editMessage == rhs.editMessage } - public func withUpdatedComposeInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { + func withUpdatedComposeInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { let updatedComposeInputState = inputState return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - public func withUpdatedComposeDisableUrlPreview(_ disableUrlPreview: String?) -> ChatInterfaceState { + func withUpdatedComposeDisableUrlPreview(_ disableUrlPreview: String?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: disableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - public func withUpdatedEffectiveInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { + func withUpdatedEffectiveInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { var updatedEditMessage = self.editMessage var updatedComposeInputState = self.composeInputState if let editMessage = self.editMessage { @@ -464,15 +459,15 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: updatedEditMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - public func withUpdatedReplyMessageId(_ replyMessageId: MessageId?) -> ChatInterfaceState { + func withUpdatedReplyMessageId(_ replyMessageId: MessageId?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - public func withUpdatedForwardMessageIds(_ forwardMessageIds: [MessageId]?) -> ChatInterfaceState { + func withUpdatedForwardMessageIds(_ forwardMessageIds: [MessageId]?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - public func withUpdatedSelectedMessages(_ messageIds: [MessageId]) -> ChatInterfaceState { + func withUpdatedSelectedMessages(_ messageIds: [MessageId]) -> ChatInterfaceState { var selectedIds = Set() if let selectionState = self.selectionState { selectedIds.formUnion(selectionState.selectedIds) @@ -483,7 +478,7 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - public func withToggledSelectedMessages(_ messageIds: [MessageId], value: Bool) -> ChatInterfaceState { + func withToggledSelectedMessages(_ messageIds: [MessageId], value: Bool) -> ChatInterfaceState { var selectedIds = Set() if let selectionState = self.selectionState { selectedIds.formUnion(selectionState.selectedIds) @@ -498,27 +493,27 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - public func withoutSelectionState() -> ChatInterfaceState { + func withoutSelectionState() -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: nil, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - public func withUpdatedTimestamp(_ timestamp: Int32) -> ChatInterfaceState { + func withUpdatedTimestamp(_ timestamp: Int32) -> ChatInterfaceState { return ChatInterfaceState(timestamp: timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - public func withUpdatedEditMessage(_ editMessage: ChatEditMessageState?) -> ChatInterfaceState { + func withUpdatedEditMessage(_ editMessage: ChatEditMessageState?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - public func withUpdatedMessageActionsState(_ f: (ChatInterfaceMessageActionsState) -> ChatInterfaceMessageActionsState) -> ChatInterfaceState { + func withUpdatedMessageActionsState(_ f: (ChatInterfaceMessageActionsState) -> ChatInterfaceMessageActionsState) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: f(self.messageActionsState), historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - public func withUpdatedHistoryScrollState(_ historyScrollState: ChatInterfaceHistoryScrollState?) -> ChatInterfaceState { + func withUpdatedHistoryScrollState(_ historyScrollState: ChatInterfaceHistoryScrollState?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - public func withUpdatedMediaRecordingMode(_ mediaRecordingMode: ChatTextInputMediaRecordingButtonMode) -> ChatInterfaceState { + func withUpdatedMediaRecordingMode(_ mediaRecordingMode: ChatTextInputMediaRecordingButtonMode) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 559b5454a1..3ca84785e2 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -155,8 +155,8 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS } else { canReply = true } - case .replyThread: - canReply = true + /*case .group: + break*/ } return canReply } @@ -297,12 +297,12 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: let message = messages[0] - if Namespaces.Message.allScheduled.contains(message.id.namespace) || message.id.peerId.isReplies { + if Namespaces.Message.allScheduled.contains(message.id.namespace) { canReply = false canPin = false } else if messages[0].flags.intersection([.Failed, .Unsent]).isEmpty { switch chatPresentationInterfaceState.chatLocation { - case .peer, .replyThread: + case .peer: if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel { if !isAction { canPin = channel.hasPermission(.pinMessages) @@ -371,32 +371,27 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: return transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration) as? LimitsConfiguration ?? LimitsConfiguration.defaultValue } - let cachedData = context.account.postbox.transaction { transaction -> CachedPeerData? in - return transaction.getPeerCachedData(peerId: messages[0].id.peerId) - } - - let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], CachedPeerData?), NoError> = combineLatest( + let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia]), NoError> = combineLatest( loadLimits, loadStickerSaveStatusSignal, loadResourceStatusSignal, context.sharedContext.chatAvailableMessageActions(postbox: context.account.postbox, accountPeerId: context.account.peerId, messageIds: Set(messages.map { $0.id })), context.account.pendingUpdateMessageManager.updatingMessageMedia - |> take(1), - cachedData + |> take(1) ) - |> map { limitsConfiguration, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, cachedData -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], CachedPeerData?) in + |> map { limitsConfiguration, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia]) in var canEdit = false if !isAction { let message = messages[0] canEdit = canEditMessage(context: context, limitsConfiguration: limitsConfiguration, message: message) } - return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia, cachedData) + return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia) } return dataSignal |> deliverOnMainQueue - |> map { data, updatingMessageMedia, cachedData -> [ContextMenuItem] in + |> map { data, updatingMessageMedia -> [ContextMenuItem] in var actions: [ContextMenuItem] = [] if let starStatus = data.starStatus { @@ -464,12 +459,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } - var isReplyThreadHead = false - if case let .replyThread(messageId, _, _) = chatPresentationInterfaceState.chatLocation { - isReplyThreadHead = messages[0].id == messageId - } - - if !isReplyThreadHead, data.canReply { + if data.canReply { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuReply, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reply"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in @@ -584,72 +574,6 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } - var threadId: Int64? - var threadMessageCount: Int = 0 - if case .peer = chatPresentationInterfaceState.chatLocation, let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, case .group = channel.info, let cachedData = cachedData as? CachedChannelData, case let .known(maybeValue) = cachedData.linkedDiscussionPeerId, let _ = maybeValue { - if let value = messages[0].threadId { - threadId = value - } else { - for attribute in messages[0].attributes { - if let attribute = attribute as? ReplyThreadMessageAttribute, attribute.count > 0 { - threadId = makeMessageThreadId(messages[0].id) - threadMessageCount = Int(attribute.count) - } - } - } - } - - if let threadId = threadId { - let replyThreadId = makeThreadIdMessageId(peerId: messages[0].id.peerId, threadId: threadId) - //TODO:localize - let text: String - if threadMessageCount != 0 { - if threadMessageCount == 1 { - text = "View 1 reply" - } else { - text = "View \(threadMessageCount) replies" - } - } else { - text = "View Thread" - } - actions.append(.action(ContextMenuActionItem(text: text, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replies"), color: theme.actionSheet.primaryTextColor) - }, action: { c, _ in - let foundIndex = Promise() - if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel, case .broadcast = channel.info { - foundIndex.set(fetchChannelReplyThreadMessage(account: context.account, messageId: messages[0].id)) - } - c.dismiss(completion: { - if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel { - if case .group = channel.info { - interfaceInteraction.viewReplies(messages[0].id, ChatReplyThreadMessage(messageId: replyThreadId, maxReadMessageId: nil)) - } else { - var cancelImpl: (() -> Void)? - let statusController = OverlayStatusController(theme: chatPresentationInterfaceState.theme, type: .loading(cancelled: { - cancelImpl?() - })) - controllerInteraction.presentController(statusController, nil) - - let disposable = (foundIndex.get() - |> take(1) - |> deliverOnMainQueue).start(next: { [weak statusController] result in - statusController?.dismiss() - - if let result = result { - interfaceInteraction.viewReplies(nil, result) - } - }) - - cancelImpl = { [weak statusController] in - disposable.dispose() - statusController?.dismiss() - } - } - } - }) - }))) - } - if data.canEdit { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_MessageDialogEdit, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor) @@ -686,7 +610,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } - if data.canPin, case .peer = chatPresentationInterfaceState.chatLocation { + if data.canPin { if chatPresentationInterfaceState.pinnedMessage?.id != messages[0].id { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_Pin, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pin"), color: theme.actionSheet.primaryTextColor) @@ -746,15 +670,11 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } - if let message = messages.first, message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, !(message.media.first is TelegramMediaAction), !isReplyThreadHead { + if let message = messages.first, message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, !(message.media.first is TelegramMediaAction) { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopyLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in - var threadMessageId: MessageId? - if case let .replyThread(replyThread, _, _) = chatPresentationInterfaceState.chatLocation { - threadMessageId = replyThread - } - let _ = (exportMessageLink(account: context.account, peerId: message.id.peerId, messageId: message.id, threadMessageId: threadMessageId) + let _ = (exportMessageLink(account: context.account, peerId: message.id.peerId, messageId: message.id) |> map { result -> String? in return result } @@ -763,15 +683,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: UIPasteboard.general.string = link let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - var warnAboutPrivate = false - if case let .peer = chatPresentationInterfaceState.chatLocation { - if channel.addressName == nil { - warnAboutPrivate = true - } - } - - if warnAboutPrivate { + if channel.addressName == nil { controllerInteraction.presentGlobalOverlayController(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Conversation_PrivateMessageLinkCopied, true)), nil) } else { controllerInteraction.presentGlobalOverlayController(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.GroupInfo_InviteLink_CopyAlert_Success, false)), nil) @@ -811,7 +723,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } } - if !isReplyThreadHead, !data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty && isAction { + if !data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty && isAction { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor) }, action: { controller, f in @@ -837,6 +749,94 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: }))) } + if canDiscuss { + actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuDiscuss, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor) + }, action: { c, _ in + let timestamp = messages[0].timestamp + let channelMessageId = messages[0].id + + enum DiscussMessageResult { + case message(MessageId) + case peer(PeerId) + } + + let signal = context.account.postbox.transaction { transaction -> PeerId? in + if let cachedData = transaction.getPeerCachedData(peerId: messages[0].id.peerId) as? CachedChannelData { + return cachedData.linkedDiscussionPeerId + } else { + return nil + } + } + |> mapToSignal { peerId -> Signal in + guard let peerId = peerId else { + return .single(nil) + } + let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp)), count: 30), id: 0), account: context.account, chatLocation: .peer(peerId), fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) + return historyView + |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in + switch historyView { + case .Loading: + return .single((nil, true)) + case let .HistoryView(view, _, _, _, _, _, _): + for entry in view.entries { + for attribute in entry.message.attributes { + if let attribute = attribute as? SourceReferenceMessageAttribute { + if attribute.messageId == channelMessageId { + return .single((entry.message.index, false)) + } + } + } + } + return .single((nil, false)) + } + } + |> take(until: { index in + return SignalTakeAction(passthrough: true, complete: !index.1) + }) + |> last + |> map { result -> DiscussMessageResult? in + if let index = result?.0 { + return .message(index.id) + } else { + return .peer(peerId) + } + } + } + + let foundIndex = Promise() + foundIndex.set(signal) + + c.dismiss(completion: { [weak interfaceInteraction] in + var cancelImpl: (() -> Void)? + let statusController = OverlayStatusController(theme: chatPresentationInterfaceState.theme, type: .loading(cancelled: { + cancelImpl?() + })) + controllerInteraction.presentController(statusController, nil) + + let disposable = (foundIndex.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak statusController] result in + statusController?.dismiss() + + if let result = result { + switch result { + case let .message(id): + interfaceInteraction?.navigateToMessage(id) + case let .peer(peerId): + interfaceInteraction?.navigateToChat(peerId) + } + } + }) + + cancelImpl = { [weak statusController] in + disposable.dispose() + statusController?.dismiss() + } + }) + }))) + } + if data.messageActions.options.contains(.report) { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuReport, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.actionSheet.primaryTextColor) @@ -846,10 +846,10 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } var clearCacheAsDelete = false - if let _ = message.peers[message.id.peerId] as? TelegramChannel { + if let peer = message.peers[message.id.peerId] as? TelegramChannel { clearCacheAsDelete = true } - if !isReplyThreadHead, (!data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty || clearCacheAsDelete) && !isAction { + if (!data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty || clearCacheAsDelete) && !isAction { let title: String var isSending = false var isEditing = false @@ -1063,7 +1063,7 @@ func chatAvailableMessageActionsImpl(postbox: Postbox, accountPeerId: PeerId, me } } } else if let user = peer as? TelegramUser { - if !isScheduled && message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.containsSecretMedia && !isAction && !message.id.peerId.isReplies { + if !isScheduled && message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.containsSecretMedia && !isAction { if !(message.flags.isSending || message.flags.contains(.Failed)) { optionsMap[id]!.insert(.forward) } @@ -1075,9 +1075,6 @@ func chatAvailableMessageActionsImpl(postbox: Postbox, accountPeerId: PeerId, me } else if limitsConfiguration.canRemoveIncomingMessagesInPrivateChats { canDeleteGlobally = true } - if user.botInfo != nil { - canDeleteGlobally = false - } let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) if isDice && Int64(message.timestamp) + 60 * 60 * 24 > Int64(timestamp) { @@ -1089,7 +1086,7 @@ func chatAvailableMessageActionsImpl(postbox: Postbox, accountPeerId: PeerId, me if canDeleteGlobally { optionsMap[id]!.insert(.deleteGlobally) } - if user.botInfo != nil && !user.id.isReplies { + if user.botInfo != nil { optionsMap[id]!.insert(.report) } } else if let _ = peer as? TelegramSecretChat { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift index 7eaca60e1b..a3773f5f6a 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift @@ -71,17 +71,6 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState var displayInputTextPanel = false if let peer = chatPresentationInterfaceState.renderedPeer?.peer { - if peer.id.isReplies { - if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) { - return (currentPanel, nil) - } else { - let panel = ChatChannelSubscriberInputPanelNode() - panel.interfaceInteraction = interfaceInteraction - panel.context = context - return (panel, nil) - } - } - if let secretChat = peer as? TelegramSecretChat { switch secretChat.embeddedState { case .handshake: @@ -120,9 +109,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState case .member: isMember = true case .left: - if case .replyThread = chatPresentationInterfaceState.chatLocation { - isMember = true - } + break } if isMember && channel.hasBannedPermission(.banSendMessages) != nil { @@ -153,15 +140,13 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState case .group: switch channel.participationStatus { case .kicked, .left: - if !isMember { - if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) { - return (currentPanel, nil) - } else { - let panel = ChatChannelSubscriberInputPanelNode() - panel.interfaceInteraction = interfaceInteraction - panel.context = context - return (panel, nil) - } + if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) { + return (currentPanel, nil) + } else { + let panel = ChatChannelSubscriberInputPanelNode() + panel.interfaceInteraction = interfaceInteraction + panel.context = context + return (panel, nil) } case .member: break diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift index 0dc0a99395..597862487c 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift @@ -14,7 +14,6 @@ enum ChatNavigationButtonAction: Equatable { case search case dismiss case toggleInfoPanel - case spacer } struct ChatNavigationButton: Equatable { @@ -28,9 +27,6 @@ struct ChatNavigationButton: Equatable { func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: ChatPresentationInterfaceState, subject: ChatControllerSubject?, strings: PresentationStrings, currentButton: ChatNavigationButton?, target: Any?, selector: Selector?) -> ChatNavigationButton? { if let _ = presentationInterfaceState.interfaceState.selectionState { - if case .replyThread = presentationInterfaceState.chatLocation { - return nil - } if let currentButton = currentButton, currentButton.action == .clearHistory { return currentButton } else if let peer = presentationInterfaceState.renderedPeer?.peer { @@ -76,50 +72,6 @@ func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Ch } } - var hasMessages = false - if let chatHistoryState = presentationInterfaceState.chatHistoryState { - if case .loaded(false) = chatHistoryState { - hasMessages = true - } - } - - if case .replyThread = presentationInterfaceState.chatLocation { - if hasMessages { - if case .search = currentButton?.action { - return currentButton - } else { - let buttonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(presentationInterfaceState.theme), style: .plain, target: target, action: selector) - buttonItem.accessibilityLabel = strings.Conversation_Search - return ChatNavigationButton(action: .search, buttonItem: buttonItem) - } - } else { - if case .spacer = currentButton?.action { - return currentButton - } else { - return ChatNavigationButton(action: .spacer, buttonItem: UIBarButtonItem(title: "", style: .plain, target: target, action: selector)) - } - } - } - if case let .peer(peerId) = presentationInterfaceState.chatLocation { - if peerId.isReplies { - if hasMessages { - if case .search = currentButton?.action { - return currentButton - } else { - let buttonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(presentationInterfaceState.theme), style: .plain, target: target, action: selector) - buttonItem.accessibilityLabel = strings.Conversation_Search - return ChatNavigationButton(action: .search, buttonItem: buttonItem) - } - } else { - if case .spacer = currentButton?.action { - return currentButton - } else { - return ChatNavigationButton(action: .spacer, buttonItem: UIBarButtonItem(title: "", style: .plain, target: target, action: selector)) - } - } - } - } - if presentationInterfaceState.isScheduledMessages { return chatInfoNavigationButton } diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index 076c0acc6f..5302a782b6 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -17,7 +17,6 @@ import ContextUI import GalleryUI import OverlayStatusController import PresentationDataUtils -import ChatInterfaceState struct PeerSpecificPackData { let peer: Peer @@ -475,7 +474,7 @@ final class ChatMediaInputNode: ChatInputNode { return self._ready.get() } - init(context: AccountContext, peerId: PeerId?, chatLocation: ChatLocation?, controllerInteraction: ChatControllerInteraction, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, gifPaneIsActiveUpdated: @escaping (Bool) -> Void) { + init(context: AccountContext, peerId: PeerId?, controllerInteraction: ChatControllerInteraction, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, gifPaneIsActiveUpdated: @escaping (Bool) -> Void) { self.context = context self.peerId = peerId self.controllerInteraction = controllerInteraction @@ -753,7 +752,7 @@ final class ChatMediaInputNode: ChatInputNode { let inputNodeInteraction = self.inputNodeInteraction! let peerSpecificPack: Signal<(PeerSpecificPackData?, CanInstallPeerSpecificPack), NoError> - if let peerId = peerId, case .peer = chatLocation { + if let peerId = peerId { self.dismissedPeerSpecificStickerPack.set(context.account.postbox.transaction { transaction -> Bool in guard let state = transaction.getPeerChatInterfaceState(peerId) as? ChatInterfaceState else { return false @@ -1010,7 +1009,7 @@ final class ChatMediaInputNode: ChatInputNode { return } - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: PeerId(namespace: 0, id: 0), namespace: Namespaces.Message.Local, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file.file.media], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: PeerId(namespace: 0, id: 0), namespace: Namespaces.Message.Local, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file.file.media], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: []) let gallery = GalleryController(context: strongSelf.context, source: .standaloneMessage(message), streamSingleVideo: true, replaceRootController: { _, _ in }, baseNavigationController: nil) diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index 91e88121cb..b27c890dba 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -32,101 +32,6 @@ extension AnimatedStickerNode: GenericAnimatedStickerNode { } -class ChatMessageShareButton: HighlightableButtonNode { - private let backgroundNode: ASImageNode - private let iconNode: ASImageNode - - private var theme: PresentationTheme? - private var isReplies: Bool = false - - private var textNode: ImmediateTextNode? - - init() { - self.backgroundNode = ASImageNode() - self.iconNode = ASImageNode() - - super.init(pointerStyle: nil) - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.iconNode) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(presentationData: ChatPresentationData, message: Message, account: Account) -> CGSize { - var isReplies = false - var replyCount = 0 - if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { - for attribute in message.attributes { - if let attribute = attribute as? ReplyThreadMessageAttribute { - replyCount = Int(attribute.count) - isReplies = true - break - } - } - } - - if self.theme !== presentationData.theme.theme || self.isReplies != isReplies { - self.theme = presentationData.theme.theme - self.isReplies = isReplies - - let graphics = PresentationResourcesChat.additionalGraphics(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper, bubbleCorners: presentationData.chatBubbleCorners) - var updatedShareButtonBackground: UIImage? - var updatedIconImage: UIImage? - if isReplies { - updatedShareButtonBackground = PresentationResourcesChat.chatFreeCommentButtonBackground(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) - updatedIconImage = PresentationResourcesChat.chatFreeCommentButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) - } else if message.id.peerId.isRepliesOrSavedMessages(accountPeerId: account.peerId) { - updatedShareButtonBackground = graphics.chatBubbleNavigateButtonImage - } else { - updatedShareButtonBackground = graphics.chatBubbleShareButtonImage - } - self.backgroundNode.image = updatedShareButtonBackground - self.iconNode.image = updatedIconImage - } - var size = CGSize(width: 30.0, height: 30.0) - var offsetIcon = false - if isReplies, replyCount > 0 { - offsetIcon = true - - let textNode: ImmediateTextNode - if let current = self.textNode { - textNode = current - } else { - textNode = ImmediateTextNode() - self.textNode = textNode - self.addSubnode(textNode) - } - - let textColor = bubbleVariableColor(variableColor: presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: presentationData.theme.wallpaper) - - let countString: String - if replyCount >= 1000 * 1000 { - countString = "\(replyCount / 1000_000)M" - } else if replyCount >= 1000 { - countString = "\(replyCount / 1000)K" - } else { - countString = "\(replyCount)" - } - - textNode.attributedText = NSAttributedString(string: countString, font: Font.regular(11.0), textColor: textColor) - let textSize = textNode.updateLayout(CGSize(width: 100.0, height: 100.0)) - size.height += textSize.height - 1.0 - textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: size.height - textSize.height - 4.0), size: textSize) - } else if let textNode = self.textNode { - self.textNode = nil - textNode.removeFromSupernode() - } - self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size) - if let image = self.iconNode.image { - self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.width - image.size.width) / 2.0) - (offsetIcon ? 1.0 : 0.0)), size: image.size) - } - return size - } -} - class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { private let contextSourceNode: ContextExtractedContentContainingNode private let containerNode: ContextControllerSourceNode @@ -144,7 +49,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { private var selectionNode: ChatMessageSelectionNode? private var deliveryFailedNode: ChatMessageDeliveryFailedNode? - private var shareButtonNode: ChatMessageShareButton? + private var shareButtonNode: HighlightableButtonNode? var telegramFile: TelegramMediaFile? var emojiFile: TelegramMediaFile? @@ -561,21 +466,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } else if incoming { hasAvatar = true } - case let .replyThread(messageId, _, _): - if messageId.peerId != item.context.account.peerId { - if messageId.peerId.isGroupOrChannel && item.message.author != nil { - var isBroadcastChannel = false - if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { - isBroadcastChannel = true - } - - if !isBroadcastChannel { - hasAvatar = true - } - } - } else if incoming { - hasAvatar = true - } + /*case .group: + hasAvatar = true*/ } if hasAvatar { @@ -589,7 +481,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var needShareButton = false if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) { needShareButton = false - } else if item.message.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { + } else if item.message.id.peerId == item.context.account.peerId { for attribute in item.content.firstMessage.attributes { if let _ = attribute as? SourceReferenceMessageAttribute { needShareButton = true @@ -666,16 +558,11 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var edited = false var viewCount: Int? = nil - var dateReplies = 0 for attribute in item.message.attributes { if let _ = attribute as? EditedMessageAttribute, isEmoji { edited = true } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count - } else if let attribute = attribute as? ReplyThreadMessageAttribute { - if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { - dateReplies = Int(attribute.count) - } } } @@ -694,7 +581,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .minimal, reactionCount: dateReactionCount) - let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) + let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions) var viaBotApply: (TextNodeLayout, () -> TextNode)? var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)? @@ -742,16 +629,13 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - if case let .replyThread(replyThreadMessageId, _, _) = item.chatLocation, replyThreadMessageId == replyAttribute.messageId { - } else { - replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude)) - } + replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude)) } else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty { replyMarkup = attribute } } - if item.message.id.peerId != item.context.account.peerId && !item.message.id.peerId.isReplies { + if item.message.id.peerId != item.context.account.peerId { for attribute in item.message.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { if let sourcePeer = item.message.peers[attribute.messageId.peerId] { @@ -775,12 +659,30 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { replyBackgroundImage = graphics.chatFreeformContentAdditionalInfoBackgroundImage } - var updatedShareButtonNode: ChatMessageShareButton? + var updatedShareButtonBackground: UIImage? + + var updatedShareButtonNode: HighlightableButtonNode? if needShareButton { - if let currentShareButtonNode = currentShareButtonNode { + if currentShareButtonNode != nil { updatedShareButtonNode = currentShareButtonNode + if item.presentationData.theme !== currentItem?.presentationData.theme { + let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) + if item.message.id.peerId == item.context.account.peerId { + updatedShareButtonBackground = graphics.chatBubbleNavigateButtonImage + } else { + updatedShareButtonBackground = graphics.chatBubbleShareButtonImage + } + } } else { - let buttonNode = ChatMessageShareButton() + let buttonNode = HighlightableButtonNode() + let buttonIcon: UIImage? + let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) + if item.message.id.peerId == item.context.account.peerId { + buttonIcon = graphics.chatBubbleNavigateButtonImage + } else { + buttonIcon = graphics.chatBubbleShareButtonImage + } + buttonNode.setBackgroundImage(buttonIcon, for: [.normal]) updatedShareButtonNode = buttonNode } } @@ -921,13 +823,18 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { strongSelf.addSubnode(updatedShareButtonNode) updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside) } - let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, message: item.message, account: item.context.account) - updatedShareButtonNode.frame = CGRect(origin: CGPoint(x: updatedImageFrame.maxX + 8.0, y: updatedImageFrame.maxY - buttonSize.height - 4.0), size: buttonSize) + if let updatedShareButtonBackground = updatedShareButtonBackground { + strongSelf.shareButtonNode?.setBackgroundImage(updatedShareButtonBackground, for: [.normal]) + } } else if let shareButtonNode = strongSelf.shareButtonNode { shareButtonNode.removeFromSupernode() strongSelf.shareButtonNode = nil } + if let shareButtonNode = strongSelf.shareButtonNode { + shareButtonNode.frame = CGRect(origin: CGPoint(x: updatedImageFrame.maxX + 8.0, y: updatedImageFrame.maxY - 30.0), size: CGSize(width: 29.0, height: 29.0)) + } + dateAndStatusApply(false) strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: max(displayLeftInset, updatedImageFrame.maxX - dateAndStatusSize.width - 4.0), y: updatedImageFrame.maxY - dateAndStatusSize.height - 4.0), size: dateAndStatusSize) @@ -1130,7 +1037,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty { item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame) } else { - if !item.message.id.peerId.isReplies, let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { + if let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { if case .member = channel.participationStatus { } else { item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame) @@ -1246,16 +1153,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { @objc func shareButtonPressed() { if let item = self.item { - if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { - for attribute in item.message.attributes { - if let _ = attribute as? ReplyThreadMessageAttribute { - item.controllerInteraction.openMessageReplies(item.message.id) - return - } - } - } - - if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { + if item.content.firstMessage.id.peerId == item.context.account.peerId { for attribute in item.content.firstMessage.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, attribute.messageId) diff --git a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift index f1e12efd59..2b7e30a494 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift @@ -13,9 +13,6 @@ import TextFormat import AccountContext import UrlEscaping import PhotoResources -import WebsiteType -import ChatMessageInteractiveMediaBadge -import GalleryData private let buttonFont = Font.semibold(13.0) @@ -317,16 +314,11 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { edited = true } var viewCount: Int? - var dateReplies = 0 for attribute in message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count - } else if let attribute = attribute as? ReplyThreadMessageAttribute { - if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { - dateReplies = Int(attribute.count) - } } } @@ -531,7 +523,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { var additionalImageBadgeContent: ChatMessageInteractiveMediaBadgeContent? switch position { - case .linear(_, .None), .linear(_, .Neighbour(true, _)): + case .linear(_, .None): let imageMode = !((refineContentImageLayout == nil && refineContentFileLayout == nil && contentInstantVideoSizeAndApply == nil) || preferMediaBeforeText) statusInText = !imageMode @@ -572,7 +564,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } } - statusSizeAndApply = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies) + statusSizeAndApply = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions) } default: break diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift index 05783efe5d..03517922e4 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift @@ -6,7 +6,6 @@ import Postbox import TelegramCore import SyncCore import TelegramUIPreferences -import TelegramPresentationData import AccountContext enum ChatMessageBubbleContentBackgroundHiding { @@ -41,14 +40,9 @@ enum ChatMessageBubbleMergeStatus { } enum ChatMessageBubbleRelativePosition { - enum NeighbourType { - case media - case freeform - } - case None(ChatMessageBubbleMergeStatus) case BubbleNeighbour - case Neighbour(Bool, NeighbourType) + case Neighbour } enum ChatMessageBubbleContentMosaicNeighbor { diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 6827a33cd1..0a03a0f7de 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -29,61 +29,54 @@ enum InternalBubbleTapAction { case openContextMenu(tapMessage: Message, selectAll: Bool, subFrame: CGRect) } -private struct BubbleItemAttributes { - var isAttachment: Bool - var neighborType: ChatMessageBubbleRelativePosition.NeighbourType -} - -private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)] { - var result: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)] = [] +private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(Message, AnyClass, ChatMessageEntryAttributes)] { + var result: [(Message, AnyClass, ChatMessageEntryAttributes)] = [] var skipText = false var messageWithCaptionToAdd: (Message, ChatMessageEntryAttributes)? var isUnsupportedMedia = false - var isAction = false outer: for (message, itemAttributes) in item.content { for attribute in message.attributes { if let attribute = attribute as? RestrictedContentMessageAttribute, attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) != nil { - result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) + result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes)) break outer } } inner: for media in message.media { if let _ = media as? TelegramMediaImage { - result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media))) + result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes)) } else if let file = media as? TelegramMediaFile { - let isVideo = file.isVideo || (file.isAnimated && file.dimensions != nil) + var isVideo = file.isVideo || (file.isAnimated && file.dimensions != nil) if isVideo { - result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media))) + result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes)) } else { - result.append((message, ChatMessageFileBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) + result.append((message, ChatMessageFileBubbleContentNode.self, itemAttributes)) } } else if let action = media as? TelegramMediaAction { - isAction = true if case .phoneCall = action.action { - result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) + result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes)) } else { - result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) + result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes)) } } else if let _ = media as? TelegramMediaMap { - result.append((message, ChatMessageMapBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media))) + result.append((message, ChatMessageMapBubbleContentNode.self, itemAttributes)) } else if let _ = media as? TelegramMediaGame { skipText = true - result.append((message, ChatMessageGameBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) + result.append((message, ChatMessageGameBubbleContentNode.self, itemAttributes)) break inner } else if let _ = media as? TelegramMediaInvoice { skipText = true - result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) + result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes)) break inner } else if let _ = media as? TelegramMediaContact { - result.append((message, ChatMessageContactBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) + result.append((message, ChatMessageContactBubbleContentNode.self, itemAttributes)) } else if let _ = media as? TelegramMediaExpiredContent { result.removeAll() - result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) + result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes)) return result } else if let _ = media as? TelegramMediaPoll { - result.append((message, ChatMessagePollBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) + result.append((message, ChatMessagePollBubbleContentNode.self, itemAttributes)) } else if let _ = media as? TelegramMediaUnsupported { isUnsupportedMedia = true } @@ -100,7 +93,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [( messageWithCaptionToAdd = (message, itemAttributes) skipText = true } else { - result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) + result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes)) } } else { if case .group = item.content { @@ -112,62 +105,29 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [( inner: for media in message.media { if let webpage = media as? TelegramMediaWebpage { if case .Loaded = webpage.content { - result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) + result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes)) } break inner } } if isUnsupportedMedia { - result.append((message, ChatMessageUnsupportedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) + result.append((message, ChatMessageUnsupportedBubbleContentNode.self, itemAttributes)) } } if let (messageWithCaptionToAdd, itemAttributes) = messageWithCaptionToAdd { - result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) + result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes)) } if let additionalContent = item.additionalContent { switch additionalContent { case let .eventLogPreviousMessage(previousMessage): - result.append((previousMessage, ChatMessageEventLogPreviousMessageContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) + result.append((previousMessage, ChatMessageEventLogPreviousMessageContentNode.self, ChatMessageEntryAttributes())) case let .eventLogPreviousDescription(previousMessage): - result.append((previousMessage, ChatMessageEventLogPreviousDescriptionContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) + result.append((previousMessage, ChatMessageEventLogPreviousDescriptionContentNode.self, ChatMessageEntryAttributes())) case let .eventLogPreviousLink(previousMessage): - result.append((previousMessage, ChatMessageEventLogPreviousLinkContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) - } - } - - let firstMessage = item.content.firstMessage - if !isAction && !Namespaces.Message.allScheduled.contains(firstMessage.id.namespace) { - var hasDiscussion = false - if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { - hasDiscussion = true - } - - if hasDiscussion { - var canComment = false - if firstMessage.id.namespace == Namespaces.Message.Local { - canComment = true - } else { - for attribute in firstMessage.attributes { - if let attribute = attribute as? ReplyThreadMessageAttribute, let commentsPeerId = attribute.commentsPeerId { - switch item.associatedData.channelDiscussionGroup { - case .unknown: - canComment = true - case let .known(groupId): - canComment = groupId == commentsPeerId - } - break - } - } - } - - if canComment { - result.append((firstMessage, ChatMessageCommentFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform))) - } - } else if firstMessage.id.peerId.isReplies { - result.append((firstMessage, ChatMessageCommentFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform))) + result.append((previousMessage, ChatMessageEventLogPreviousLinkContentNode.self, ChatMessageEntryAttributes())) } } @@ -792,7 +752,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode), replyInfoLayout: (ChatPresentationData, PresentationStrings, AccountContext, ChatMessageReplyInfoType, Message, CGSize) -> (CGSize, () -> ChatMessageReplyInfoNode), actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (Bool) -> ChatMessageActionButtonsNode)), - mosaicStatusLayout: (AccountContext, ChatPresentationData, Bool, Int?, String, ChatMessageDateAndStatusType, CGSize, [MessageReaction], Int) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode), + mosaicStatusLayout: (AccountContext, ChatPresentationData, Bool, Int?, String, ChatMessageDateAndStatusType, CGSize, [MessageReaction]) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode), currentShareButtonNode: HighlightableButtonNode?, layoutConstants: ChatMessageItemLayoutConstants, currentItem: ChatMessageItem?, @@ -806,6 +766,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let nameFont = Font.medium(fontSize) let inlineBotPrefixFont = Font.regular(fontSize) + let inlineBotNameFont = nameFont let baseWidth = params.width - params.leftInset - params.rightInset @@ -824,7 +785,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode var isCrosspostFromChannel = false if let _ = sourceReference { - if !firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { + if firstMessage.id.peerId != item.context.account.peerId { isCrosspostFromChannel = true } } @@ -837,61 +798,46 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode var hasAvatar = false var allowFullWidth = false - let chatLocationPeerId: PeerId switch item.chatLocation { - case let .peer(peerId): - chatLocationPeerId = peerId - case let .replyThread(messageId, _, _): - chatLocationPeerId = messageId.peerId - } - - do { - let peerId = chatLocationPeerId - if item.message.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { - if let forwardInfo = item.content.firstMessage.forwardInfo { - ignoreForward = true - effectiveAuthor = forwardInfo.author - if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { - effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: Int32(clamping: authorSignature.persistentHashValue)), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: UserInfoFlags()) + case let .peer(peerId): + if item.message.id.peerId == item.context.account.peerId { + if let forwardInfo = item.content.firstMessage.forwardInfo { + ignoreForward = true + effectiveAuthor = forwardInfo.author + if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { + effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: Int32(clamping: authorSignature.persistentHashValue)), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: UserInfoFlags()) + } + } + displayAuthorInfo = !mergedTop.merged && incoming && effectiveAuthor != nil + } else if isCrosspostFromChannel, let sourceReference = sourceReference, let source = firstMessage.peers[sourceReference.messageId.peerId] { + if firstMessage.forwardInfo?.author?.id == source.id { + ignoreForward = true + } + effectiveAuthor = source + displayAuthorInfo = !mergedTop.merged && incoming && effectiveAuthor != nil + } else { + effectiveAuthor = firstMessage.author + displayAuthorInfo = !mergedTop.merged && incoming && peerId.isGroupOrChannel && effectiveAuthor != nil + if let forwardInfo = firstMessage.forwardInfo, forwardInfo.psaType != nil { + displayAuthorInfo = false } } - displayAuthorInfo = !mergedTop.merged && incoming && effectiveAuthor != nil - } else if isCrosspostFromChannel, let sourceReference = sourceReference, let source = firstMessage.peers[sourceReference.messageId.peerId] { - if firstMessage.forwardInfo?.author?.id == source.id { - ignoreForward = true - } - effectiveAuthor = source - displayAuthorInfo = !mergedTop.merged && incoming && effectiveAuthor != nil - } else { - effectiveAuthor = firstMessage.author - - var allowAuthor = incoming - - if let author = firstMessage.author, author is TelegramChannel, author.id == firstMessage.id.peerId, !incoming { - allowAuthor = true - } - - displayAuthorInfo = !mergedTop.merged && allowAuthor && peerId.isGroupOrChannel && effectiveAuthor != nil - if let forwardInfo = firstMessage.forwardInfo, forwardInfo.psaType != nil { - displayAuthorInfo = false - } - } - - if !peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { - if peerId.isGroupOrChannel && effectiveAuthor != nil { - var isBroadcastChannel = false - if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info { - isBroadcastChannel = true - allowFullWidth = true - } - - if !isBroadcastChannel { - hasAvatar = item.content.firstMessage.effectivelyIncoming(item.context.account.peerId) + + if peerId != item.context.account.peerId { + if peerId.isGroupOrChannel && effectiveAuthor != nil { + var isBroadcastChannel = false + if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info { + isBroadcastChannel = true + allowFullWidth = true + } + + if !isBroadcastChannel { + hasAvatar = item.content.firstMessage.effectivelyIncoming(item.context.account.peerId) + } } + } else if incoming { + hasAvatar = true } - } else if incoming { - hasAvatar = true - } } if let forwardInfo = item.content.firstMessage.forwardInfo, forwardInfo.source == nil, forwardInfo.author?.id.namespace == Namespaces.Peer.CloudUser { @@ -914,8 +860,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode if let _ = sourceReference { needShareButton = true } - } else if item.message.id.peerId.isReplies { - needShareButton = false } else if item.message.effectivelyIncoming(item.context.account.peerId) { if let _ = sourceReference { needShareButton = true @@ -986,22 +930,22 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset) - var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, BubbleItemAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = [] + var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = [] var addedContentNodes: [(Message, ChatMessageBubbleContentNode)]? let contentNodeMessagesAndClasses = contentNodeMessagesAndClassesForItem(item) - for (contentNodeMessage, contentNodeClass, attributes, bubbleAttributes) in contentNodeMessagesAndClasses { + for (contentNodeMessage, contentNodeClass, attributes) in contentNodeMessagesAndClasses { var found = false for (currentMessage, currentClass, supportsMosaic, currentLayout) in currentContentClassesPropertiesAndLayouts { if currentClass == contentNodeClass && currentMessage.stableId == contentNodeMessage.stableId { - contentPropertiesAndPrepareLayouts.append((contentNodeMessage, supportsMosaic, attributes, bubbleAttributes, currentLayout)) + contentPropertiesAndPrepareLayouts.append((contentNodeMessage, supportsMosaic, attributes, currentLayout)) found = true break } } if !found { let contentNode = (contentNodeClass as! ChatMessageBubbleContentNode.Type).init() - contentPropertiesAndPrepareLayouts.append((contentNodeMessage, contentNode.supportsMosaic, attributes, bubbleAttributes, contentNode.asyncLayoutContent())) + contentPropertiesAndPrepareLayouts.append((contentNodeMessage, contentNode.supportsMosaic, attributes, contentNode.asyncLayoutContent())) if addedContentNodes == nil { addedContentNodes = [] } @@ -1044,10 +988,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode inlineBotNameString = attribute.title } } else if let attribute = attribute as? ReplyMessageAttribute { - if case let .replyThread(replyThreadMessageId, _, _) = item.chatLocation, replyThreadMessageId == attribute.messageId { - } else { - replyMessage = firstMessage.associatedMessages[attribute.messageId] - } + replyMessage = firstMessage.associatedMessages[attribute.messageId] } else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty && !isPreview { replyMarkup = attribute } @@ -1057,7 +998,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode inlineBotNameString = nil } - var contentPropertiesAndLayouts: [(CGSize?, ChatMessageBubbleContentProperties, ChatMessageBubblePreparePosition, BubbleItemAttributes, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void)))] = [] + var contentPropertiesAndLayouts: [(CGSize?, ChatMessageBubbleContentProperties, ChatMessageBubblePreparePosition, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void)))] = [] let topNodeMergeStatus: ChatMessageBubbleMergeStatus = mergedTop.merged ? (incoming ? .Left : .Right) : .None(incoming ? .Incoming : .Outgoing) let bottomNodeMergeStatus: ChatMessageBubbleMergeStatus = mergedBottom.merged ? (incoming ? .Left : .Right) : .None(incoming ? .Incoming : .Outgoing) @@ -1105,21 +1046,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } var index = 0 - for (message, _, attributes, bubbleAttributes, prepareLayout) in contentPropertiesAndPrepareLayouts { + for (message, _, attributes, prepareLayout) in contentPropertiesAndPrepareLayouts { let topPosition: ChatMessageBubbleRelativePosition let bottomPosition: ChatMessageBubbleRelativePosition - var topBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform) - var bottomBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform) - if index != 0 { - topBubbleAttributes = contentPropertiesAndPrepareLayouts[index - 1].3 - } - if index != contentPropertiesAndPrepareLayouts.count - 1 { - bottomBubbleAttributes = contentPropertiesAndPrepareLayouts[index + 1].3 - } - - topPosition = .Neighbour(topBubbleAttributes.isAttachment, topBubbleAttributes.neighborType) - bottomPosition = .Neighbour(bottomBubbleAttributes.isAttachment, bottomBubbleAttributes.neighborType) + topPosition = .Neighbour + bottomPosition = .Neighbour let prepareContentPosition: ChatMessageBubblePreparePosition if let mosaicRange = mosaicRange, mosaicRange.contains(index) { @@ -1128,8 +1060,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let refinedBottomPosition: ChatMessageBubbleRelativePosition if index == contentPropertiesAndPrepareLayouts.count - 1 { refinedBottomPosition = .None(.Left) - } else if index == contentPropertiesAndPrepareLayouts.count - 2 && contentPropertiesAndPrepareLayouts[contentPropertiesAndPrepareLayouts.count - 1].3.isAttachment { - refinedBottomPosition = .None(.Left) } else { refinedBottomPosition = bottomPosition } @@ -1161,7 +1091,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let (properties, unboundSize, maxNodeWidth, nodeLayout) = prepareLayout(contentItem, layoutConstants, prepareContentPosition, itemSelection, CGSize(width: maximumContentWidth, height: CGFloat.greatestFiniteMagnitude)) maximumNodeWidth = min(maximumNodeWidth, maxNodeWidth) - contentPropertiesAndLayouts.append((unboundSize, properties, prepareContentPosition, bubbleAttributes, nodeLayout)) + contentPropertiesAndLayouts.append((unboundSize, properties, prepareContentPosition, nodeLayout)) switch properties.hidesBackground { case .never: @@ -1192,10 +1122,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } else { if inlineBotNameString == nil && (ignoreForward || firstMessage.forwardInfo == nil) && replyMessage == nil { if let first = contentPropertiesAndLayouts.first, first.1.hidesSimpleAuthorHeader { - if let author = firstMessage.author as? TelegramChannel, case .group = author.info, author.id == firstMessage.id.peerId, !incoming { - } else { - initialDisplayHeader = false - } + initialDisplayHeader = false } } } @@ -1206,12 +1133,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode authorNameColor = chatMessagePeerIdColors[Int(peer.id.id % 7)] } else if let effectiveAuthor = effectiveAuthor { authorNameString = effectiveAuthor.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) - - if incoming { - authorNameColor = chatMessagePeerIdColors[Int(effectiveAuthor.id.id % 7)] - } else { - authorNameColor = item.presentationData.theme.theme.chat.message.outgoing.accentTextColor - } + authorNameColor = chatMessagePeerIdColors[Int(effectiveAuthor.id.id % 7)] var isScam = effectiveAuthor.isScam if case let .peer(peerId) = item.chatLocation, let authorPeerId = item.message.author?.id, authorPeerId == peerId { @@ -1255,7 +1177,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let firstNodeTopPosition: ChatMessageBubbleRelativePosition if displayHeader { - firstNodeTopPosition = .Neighbour(false, .freeform) + firstNodeTopPosition = .Neighbour } else { firstNodeTopPosition = .None(topNodeMergeStatus) } @@ -1276,7 +1198,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode maximumNodeWidth = size.width - if mosaicRange.upperBound == contentPropertiesAndLayouts.count || contentPropertiesAndLayouts[contentPropertiesAndLayouts.count - 1].3.isAttachment { + if mosaicRange.upperBound == contentPropertiesAndLayouts.count { let message = item.content.firstMessage var edited = false @@ -1284,16 +1206,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode edited = true } var viewCount: Int? - var dateReplies = 0 for attribute in message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count - } else if let attribute = attribute as? ReplyThreadMessageAttribute { - if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { - dateReplies = Int(attribute.count) - } } } @@ -1325,7 +1242,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } } - mosaicStatusSizeAndApply = mosaicStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) + mosaicStatusSizeAndApply = mosaicStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude), dateReactions) } } @@ -1474,7 +1391,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode findRemoved: for i in 0 ..< currentContentClassesPropertiesAndLayouts.count { let currentMessage = currentContentClassesPropertiesAndLayouts[i].0 let currentClass: AnyClass = currentContentClassesPropertiesAndLayouts[i].1 - for (contentNodeMessage, contentNodeClass, _, _) in contentNodeMessagesAndClasses { + for (contentNodeMessage, contentNodeClass, _) in contentNodeMessagesAndClasses { if currentClass == contentNodeClass && currentMessage.stableId == contentNodeMessage.stableId { continue findRemoved } @@ -1498,7 +1415,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } for i in 0 ..< contentPropertiesAndLayouts.count { - let (_, contentNodeProperties, preparePosition, isAttachment, contentNodeLayout) = contentPropertiesAndLayouts[i] + let (_, contentNodeProperties, preparePosition, contentNodeLayout) = contentPropertiesAndLayouts[i] if let mosaicRange = mosaicRange, mosaicRange.contains(i), let (framesAndPositions, size) = calculatedGroupFramesAndSize { let mosaicIndex = i - mosaicRange.lowerBound @@ -1549,7 +1466,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode if mosaicRange.upperBound - 1 == contentNodeCount - 1 { lastMosaicBottomPosition = lastNodeTopPosition } else { - lastMosaicBottomPosition = .Neighbour(false, .freeform) + lastMosaicBottomPosition = .Neighbour } if position.contains(.bottom), case .Neighbour = lastMosaicBottomPosition { @@ -1611,32 +1528,23 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode case .linear: let topPosition: ChatMessageBubbleRelativePosition let bottomPosition: ChatMessageBubbleRelativePosition - - var topBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform) - var bottomBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform) - if i != 0 { - topBubbleAttributes = contentPropertiesAndLayouts[i - 1].3 - } - if i != contentPropertiesAndLayouts.count - 1 { - bottomBubbleAttributes = contentPropertiesAndLayouts[i + 1].3 - } if i == 0 { topPosition = firstNodeTopPosition } else { - topPosition = .Neighbour(topBubbleAttributes.isAttachment, topBubbleAttributes.neighborType) + topPosition = .Neighbour } if i == contentNodeCount - 1 { bottomPosition = lastNodeTopPosition } else { - bottomPosition = .Neighbour(bottomBubbleAttributes.isAttachment, bottomBubbleAttributes.neighborType) + bottomPosition = .Neighbour } contentPosition = .linear(top: topPosition, bottom: bottomPosition) case .mosaic: assertionFailure() - contentPosition = .linear(top: .Neighbour(false, .freeform), bottom: .Neighbour(false, .freeform)) + contentPosition = .linear(top: .Neighbour, bottom: .Neighbour) } let (contentNodeWidth, contentNodeFinalize) = contentNodeLayout(CGSize(width: maximumNodeWidth, height: CGFloat.greatestFiniteMagnitude), contentPosition) #if DEBUG @@ -1742,7 +1650,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode updatedShareButtonNode = currentShareButtonNode if item.presentationData.theme !== currentItem?.presentationData.theme { let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) - if item.message.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { + if item.message.id.peerId == item.context.account.peerId { updatedShareButtonBackground = graphics.chatBubbleNavigateButtonImage } else { updatedShareButtonBackground = graphics.chatBubbleShareButtonImage @@ -1752,7 +1660,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let buttonNode = HighlightableButtonNode() let buttonIcon: UIImage? let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) - if item.message.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { + if item.message.id.peerId == item.context.account.peerId { buttonIcon = graphics.chatBubbleNavigateButtonImage } else { buttonIcon = graphics.chatBubbleShareButtonImage @@ -1849,7 +1757,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode replyInfoOriginY: CGFloat, removedContentNodeIndices: [Int]?, addedContentNodes: [(Message, ChatMessageBubbleContentNode)]?, - contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)], + contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes)], contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, (ListViewItemUpdateAnimation, Bool) -> Void)], mosaicStatusOrigin: CGPoint?, mosaicStatusSizeAndApply: (CGSize, (Bool) -> ChatMessageDateAndStatusNode)?, @@ -2056,7 +1964,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } var sortedContentNodes: [ChatMessageBubbleContentNode] = [] - outer: for (message, nodeClass, _, _) in contentNodeMessagesAndClasses { + outer: for (message, nodeClass, _) in contentNodeMessagesAndClasses { if let addedContentNodes = addedContentNodes { for (contentNodeMessage, contentNode) in addedContentNodes { if type(of: contentNode) == nodeClass && contentNodeMessage.stableId == message.stableId { @@ -2117,7 +2025,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } if let mosaicStatusOrigin = mosaicStatusOrigin, let (size, apply) = mosaicStatusSizeAndApply { - let mosaicStatusNode = apply(transition.isAnimated) + let mosaicStatusNode = apply(false) if mosaicStatusNode !== strongSelf.mosaicStatusNode { strongSelf.mosaicStatusNode?.removeFromSupernode() strongSelf.mosaicStatusNode = mosaicStatusNode @@ -2490,12 +2398,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode var openPeerId = item.effectiveAuthorId ?? author.id var navigate: ChatControllerInteractionNavigateToPeer - if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { + if item.content.firstMessage.id.peerId == item.context.account.peerId { navigate = .chat(textInputState: nil, subject: nil, peekData: nil) - } else if openPeerId.namespace == Namespaces.Peer.CloudUser { - navigate = .info } else { - navigate = .chat(textInputState: nil, subject: nil, peekData: nil) + navigate = .info } for attribute in item.content.firstMessage.attributes { @@ -2508,10 +2414,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty { item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame) } else { - if item.message.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId), let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { + if item.message.id.peerId == item.context.account.peerId, let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { } else if case .member = channel.participationStatus { - } else if !item.message.id.peerId.isReplies { + } else { item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame) return } @@ -2564,7 +2470,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode if let channel = forwardInfo.author as? TelegramChannel, channel.username == nil { if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { } else if case .member = channel.participationStatus { - } else if !item.message.id.peerId.isReplies { + } else { item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, forwardInfoNode, nil) return } @@ -3075,7 +2981,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode @objc func shareButtonPressed() { if let item = self.item { - if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { + if item.content.firstMessage.id.peerId == item.context.account.peerId { for attribute in item.content.firstMessage.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, attribute.messageId) diff --git a/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift deleted file mode 100644 index 3b22bc1c83..0000000000 --- a/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift +++ /dev/null @@ -1,294 +0,0 @@ -import Foundation -import UIKit -import Postbox -import Display -import AsyncDisplayKit -import SwiftSignalKit -import TelegramCore -import SyncCore -import TelegramPresentationData - -final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { - private let separatorNode: ASDisplayNode - private let textNode: TextNode - private let iconNode: ASImageNode - private let arrowNode: ASImageNode - private let buttonNode: HighlightTrackingButtonNode - private let avatarsNode: MergedAvatarsNode - - required init() { - self.separatorNode = ASDisplayNode() - self.separatorNode.isUserInteractionEnabled = false - - self.textNode = TextNode() - self.textNode.isUserInteractionEnabled = false - self.textNode.contentMode = .topLeft - self.textNode.contentsScale = UIScreenScale - self.textNode.displaysAsynchronously = true - - self.iconNode = ASImageNode() - self.iconNode.displaysAsynchronously = false - self.iconNode.displayWithoutProcessing = true - self.iconNode.isUserInteractionEnabled = false - - self.arrowNode = ASImageNode() - self.arrowNode.displaysAsynchronously = false - self.arrowNode.displayWithoutProcessing = true - self.arrowNode.isUserInteractionEnabled = false - - self.avatarsNode = MergedAvatarsNode() - self.avatarsNode.isUserInteractionEnabled = false - - self.buttonNode = HighlightTrackingButtonNode() - - super.init() - - self.buttonNode.addSubnode(self.separatorNode) - self.buttonNode.addSubnode(self.textNode) - self.buttonNode.addSubnode(self.iconNode) - self.buttonNode.addSubnode(self.arrowNode) - self.buttonNode.addSubnode(self.avatarsNode) - self.addSubnode(self.buttonNode) - - self.buttonNode.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - let nodes: [ASDisplayNode] = [ - strongSelf.textNode, - strongSelf.iconNode, - strongSelf.avatarsNode, - strongSelf.arrowNode, - ] - for node in nodes { - if highlighted { - node.layer.removeAnimation(forKey: "opacity") - node.alpha = 0.4 - } else { - node.alpha = 1.0 - node.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - } - - self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc private func buttonPressed() { - guard let item = self.item else { - return - } - if item.message.id.peerId.isReplies { - for attribute in item.message.attributes { - if let attribute = attribute as? SourceReferenceMessageAttribute { - item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId) - break - } - } - } else { - item.controllerInteraction.openMessageReplies(item.message.id) - } - } - - override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) { - let textLayout = TextNode.asyncLayout(self.textNode) - - return { item, layoutConstants, preparePosition, _, constrainedSize in - let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) - - let displaySeparator: Bool - let topOffset: CGFloat - if case let .linear(top, _) = preparePosition, case .Neighbour(_, .media) = top { - displaySeparator = false - topOffset = 2.0 - } else { - displaySeparator = true - topOffset = 0.0 - } - - return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in - let incoming = item.message.effectivelyIncoming(item.context.account.peerId) - - let maxTextWidth = CGFloat.greatestFiniteMagnitude - - let horizontalInset = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right - - var dateReplies = 0 - var replyPeers: [Peer] = [] - for attribute in item.message.attributes { - if let attribute = attribute as? ReplyThreadMessageAttribute { - dateReplies = Int(attribute.count) - replyPeers = attribute.latestUsers.compactMap { peerId -> Peer? in - return item.message.peers[peerId] - } - } - } - - //TODO:localize - let rawText: String - - if item.message.id.peerId.isReplies { - rawText = "View Reply" - } else if dateReplies > 0 { - if dateReplies == 1 { - rawText = "1 Comment" - } else { - rawText = "\(dateReplies) Comments" - } - } else { - rawText = "Leave a Comment" - } - - let imageSize: CGFloat = 30.0 - let imageSpacing: CGFloat = 20.0 - - var textLeftInset: CGFloat = 0.0 - if replyPeers.isEmpty { - textLeftInset = 41.0 - } else { - textLeftInset = 15.0 + imageSize * min(1.0, CGFloat(replyPeers.count)) + (imageSpacing) * max(0.0, min(2.0, CGFloat(replyPeers.count - 1))) - } - - let textConstrainedSize = CGSize(width: min(maxTextWidth, constrainedSize.width - horizontalInset - textLeftInset - 28.0), height: constrainedSize.height) - - let attributedText: NSAttributedString - - let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing - - let textFont = item.presentationData.messageFont - - attributedText = NSAttributedString(string: rawText, font: textFont, textColor: messageTheme.accentTextColor) - - let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 5.0, right: 2.0) - - let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor)) - - var textFrame = CGRect(origin: CGPoint(x: -textInsets.left + textLeftInset, y: -textInsets.top + 5.0 + topOffset), size: textLayout.size) - var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: textFrame.height - textInsets.top - textInsets.bottom)) - - textFrame = textFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top - 5.0 + UIScreenPixel) - textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top) - - var suggestedBoundingWidth: CGFloat - suggestedBoundingWidth = textFrameWithoutInsets.width - suggestedBoundingWidth += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right + textLeftInset + 28.0 - - let iconImage: UIImage? - let iconOffset: CGPoint - if item.message.id.peerId.isReplies { - iconImage = PresentationResourcesChat.chatMessageRepliesIcon(item.presentationData.theme.theme, incoming: incoming) - iconOffset = CGPoint(x: -4.0, y: -4.0) - } else { - iconImage = PresentationResourcesChat.chatMessageCommentsIcon(item.presentationData.theme.theme, incoming: incoming) - iconOffset = CGPoint(x: 0.0, y: -1.0) - } - let arrowImage = PresentationResourcesChat.chatMessageCommentsArrowIcon(item.presentationData.theme.theme, incoming: incoming) - - return (suggestedBoundingWidth, { boundingWidth in - var boundingSize: CGSize - - boundingSize = textFrameWithoutInsets.size - boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right - boundingSize.height = 40.0 + topOffset - - return (boundingSize, { [weak self] animation, synchronousLoad in - if let strongSelf = self { - strongSelf.item = item - - let cachedLayout = strongSelf.textNode.cachedLayout - - if case .System = animation { - if let cachedLayout = cachedLayout { - if !cachedLayout.areLinesEqual(to: textLayout) { - if let textContents = strongSelf.textNode.contents { - let fadeNode = ASDisplayNode() - fadeNode.displaysAsynchronously = false - fadeNode.contents = textContents - fadeNode.frame = strongSelf.textNode.frame - fadeNode.isLayerBacked = true - strongSelf.addSubnode(fadeNode) - fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak fadeNode] _ in - fadeNode?.removeFromSupernode() - }) - strongSelf.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) - } - } - } - } - - strongSelf.textNode.displaysAsynchronously = !item.presentationData.isPreview - let _ = textApply() - - let adjustedTextFrame = textFrame - - strongSelf.textNode.frame = adjustedTextFrame - - if let iconImage = iconImage { - strongSelf.iconNode.image = iconImage - strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: 15.0 + iconOffset.x, y: 6.0 + iconOffset.y + topOffset), size: iconImage.size) - } - - if let arrowImage = arrowImage { - strongSelf.arrowNode.image = arrowImage - strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: boundingWidth - 33.0, y: 6.0 + topOffset), size: arrowImage.size) - } - - strongSelf.iconNode.isHidden = !replyPeers.isEmpty - - let avatarsFrame = CGRect(origin: CGPoint(x: 13.0, y: 3.0 + topOffset), size: CGSize(width: imageSize * 3.0, height: imageSize)) - strongSelf.avatarsNode.frame = avatarsFrame - strongSelf.avatarsNode.updateLayout(size: avatarsFrame.size) - strongSelf.avatarsNode.update(context: item.context, peers: replyPeers, synchronousLoad: synchronousLoad, imageSize: imageSize, imageSpacing: imageSpacing, borderWidth: 2.0 - UIScreenPixel) - - strongSelf.separatorNode.backgroundColor = messageTheme.polls.separator - strongSelf.separatorNode.isHidden = !displaySeparator - strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: layoutConstants.bubble.strokeInsets.left, y: -3.0), size: CGSize(width: boundingWidth - layoutConstants.bubble.strokeInsets.left - layoutConstants.bubble.strokeInsets.right, height: UIScreenPixel)) - - strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: boundingWidth, height: boundingSize.height)) - - strongSelf.buttonNode.isUserInteractionEnabled = item.message.id.namespace == Namespaces.Message.Cloud - strongSelf.buttonNode.alpha = item.message.id.namespace == Namespaces.Message.Cloud ? 1.0 : 0.5 - } - }) - }) - }) - } - } - - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - } - - override func animateAdded(_ currentTimestamp: Double, duration: Double) { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - } - - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { - self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) - } - - override func animateInsertionIntoBubble(_ duration: Double) { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - } - - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { - if self.buttonNode.frame.contains(point) { - return .ignore - } - return .none - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if self.buttonNode.isUserInteractionEnabled && self.buttonNode.frame.contains(point) { - return self.buttonNode.view - } - return nil - } -} - - - - diff --git a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift index 4f63c7c143..a740794c22 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift @@ -146,16 +146,11 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { edited = true } var viewCount: Int? - var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count - } else if let attribute = attribute as? ReplyThreadMessageAttribute { - if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { - dateReplies = Int(attribute.count) - } } } @@ -176,7 +171,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { let statusType: ChatMessageDateAndStatusType? switch position { - case .linear(_, .None), .linear(_, .Neighbour(true, _)): + case .linear(_, .None): if item.message.effectivelyIncoming(item.context.account.peerId) { statusType = .BubbleIncoming } else { @@ -196,7 +191,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift b/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift index 5086a30fbe..0d6f734525 100644 --- a/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift @@ -160,15 +160,12 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { private var reactionNodes: [StatusReactionNode] = [] private var reactionCountNode: TextNode? private var reactionButtonNode: HighlightTrackingButtonNode? - private var repliesIcon: ASImageNode? - private var replyCountNode: TextNode? private var type: ChatMessageDateAndStatusType? private var theme: ChatPresentationThemeData? private var layoutSize: CGSize? var openReactions: (() -> Void)? - var openReplies: (() -> Void)? override init() { self.dateNode = TextNode() @@ -180,7 +177,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { self.addSubnode(self.dateNode) } - func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction], _ replies: Int) -> (CGSize, (Bool) -> Void) { + func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction]) -> (CGSize, (Bool) -> Void) { let dateLayout = TextNode.asyncLayout(self.dateNode) var checkReadNode = self.checkReadNode @@ -190,17 +187,15 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { var currentBackgroundNode = self.backgroundNode var currentImpressionIcon = self.impressionIcon - var currentRepliesIcon = self.repliesIcon let currentType = self.type let currentTheme = self.theme let makeReactionCountLayout = TextNode.asyncLayout(self.reactionCountNode) - let makeReplyCountLayout = TextNode.asyncLayout(self.replyCountNode) let previousLayoutSize = self.layoutSize - return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replyCount in + return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions in let dateColor: UIColor var backgroundImage: UIImage? var outgoingStatus: ChatMessageDateAndStatusOutgoingType? @@ -211,7 +206,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { let clockFrameImage: UIImage? let clockMinImage: UIImage? var impressionImage: UIImage? - var repliesImage: UIImage? let themeUpdated = presentationData.theme != currentTheme || type != currentType @@ -232,9 +226,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.incomingDateAndStatusImpressionIcon } - if replyCount != 0 { - repliesImage = graphics.incomingDateAndStatusRepliesIcon - } case let .BubbleOutgoing(status): dateColor = presentationData.theme.theme.chat.message.outgoing.secondaryTextColor outgoingStatus = status @@ -246,9 +237,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.outgoingDateAndStatusImpressionIcon } - if replyCount != 0 { - repliesImage = graphics.outgoingDateAndStatusRepliesIcon - } case .ImageIncoming: dateColor = presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor backgroundImage = graphics.dateAndStatusMediaBackground @@ -260,9 +248,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.mediaImpressionIcon } - if replyCount != 0 { - repliesImage = graphics.mediaRepliesIcon - } case let .ImageOutgoing(status): dateColor = presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor outgoingStatus = status @@ -275,9 +260,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.mediaImpressionIcon } - if replyCount != 0 { - repliesImage = graphics.mediaRepliesIcon - } case .FreeIncoming: let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) dateColor = serviceColor.primaryText @@ -290,9 +272,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.freeImpressionIcon } - if replyCount != 0 { - repliesImage = graphics.freeRepliesIcon - } case let .FreeOutgoing(status): let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) dateColor = serviceColor.primaryText @@ -306,9 +285,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.freeImpressionIcon } - if replyCount != 0 { - repliesImage = graphics.freeRepliesIcon - } } var updatedDateText = dateText @@ -347,20 +323,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { currentImpressionIcon = nil } - var repliesIconSize = CGSize() - if let repliesImage = repliesImage { - if currentRepliesIcon == nil { - let iconNode = ASImageNode() - iconNode.isLayerBacked = true - iconNode.displayWithoutProcessing = true - iconNode.displaysAsynchronously = false - currentRepliesIcon = iconNode - } - repliesIconSize = repliesImage.size - } else { - currentRepliesIcon = nil - } - if let outgoingStatus = outgoingStatus { switch outgoingStatus { case .Sending: @@ -471,7 +433,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { let reactionSize: CGFloat = 14.0 var reactionCountLayoutAndApply: (TextNodeLayout, () -> TextNode)? - var replyCountLayoutAndApply: (TextNodeLayout, () -> TextNode)? let reactionSpacing: CGFloat = -4.0 let reactionTrailingSpacing: CGFloat = 4.0 @@ -497,22 +458,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { reactionInset += max(10.0, layoutAndApply.0.size.width) + 2.0 reactionCountLayoutAndApply = layoutAndApply } - - if replyCount > 0 { - let countString: String - if replyCount > 1000000 { - countString = "\(replyCount / 1000000)M" - } else if replyCount > 1000 { - countString = "\(replyCount / 1000)K" - } else { - countString = "\(replyCount)" - } - - let layoutAndApply = makeReplyCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0))) - reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0 - replyCountLayoutAndApply = layoutAndApply - } - leftInset += reactionInset let layoutSize = CGSize(width: leftInset + impressionWidth + date.size.width + statusWidth + backgroundInsets.left + backgroundInsets.right, height: date.size.height + backgroundInsets.top + backgroundInsets.bottom) @@ -565,6 +510,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { strongSelf.impressionIcon = nil } + strongSelf.dateNode.frame = CGRect(origin: CGPoint(x: leftInset + backgroundInsets.left + impressionWidth, y: backgroundInsets.top + 1.0 + offset), size: date.size) if let clockFrameNode = clockFrameNode { @@ -731,60 +677,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { } } - if let currentRepliesIcon = currentRepliesIcon { - currentRepliesIcon.displaysAsynchronously = !presentationData.isPreview - if currentRepliesIcon.image !== repliesImage { - currentRepliesIcon.image = repliesImage - } - if currentRepliesIcon.supernode == nil { - strongSelf.repliesIcon = currentRepliesIcon - strongSelf.addSubnode(currentRepliesIcon) - if animated { - currentRepliesIcon.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) - } - } - currentRepliesIcon.frame = CGRect(origin: CGPoint(x: reactionOffset - 2.0, y: backgroundInsets.top + offset + floor((date.size.height - repliesIconSize.height) / 2.0)), size: repliesIconSize) - reactionOffset += 9.0 - } else if let repliesIcon = strongSelf.repliesIcon { - strongSelf.repliesIcon = nil - if animated { - if let previousLayoutSize = previousLayoutSize { - repliesIcon.frame = repliesIcon.frame.offsetBy(dx: layoutSize.width - previousLayoutSize.width, dy: 0.0) - } - repliesIcon.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak repliesIcon] _ in - repliesIcon?.removeFromSupernode() - }) - } else { - repliesIcon.removeFromSupernode() - } - } - - if let (layout, apply) = replyCountLayoutAndApply { - let node = apply() - if strongSelf.replyCountNode !== node { - strongSelf.replyCountNode?.removeFromSupernode() - strongSelf.addSubnode(node) - strongSelf.replyCountNode = node - if animated { - node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) - } - } - node.frame = CGRect(origin: CGPoint(x: reactionOffset + 4.0, y: backgroundInsets.top + 1.0 + offset), size: layout.size) - reactionOffset += 4.0 + layout.size.width - } else if let replyCountNode = strongSelf.replyCountNode { - strongSelf.replyCountNode = nil - if animated { - if let previousLayoutSize = previousLayoutSize { - replyCountNode.frame = replyCountNode.frame.offsetBy(dx: layoutSize.width - previousLayoutSize.width, dy: 0.0) - } - replyCountNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak replyCountNode] _ in - replyCountNode?.removeFromSupernode() - }) - } else { - replyCountNode.removeFromSupernode() - } - } - /*if !strongSelf.reactionNodes.isEmpty { if strongSelf.reactionButtonNode == nil { let reactionButtonNode = HighlightTrackingButtonNode() @@ -817,17 +709,17 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { } } - static func asyncLayout(_ node: ChatMessageDateAndStatusNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction], _ replies: Int) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode) { + static func asyncLayout(_ node: ChatMessageDateAndStatusNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction]) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode) { let currentLayout = node?.asyncLayout() - return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies in + return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions in let resultNode: ChatMessageDateAndStatusNode let resultSizeAndApply: (CGSize, (Bool) -> Void) if let node = node, let currentLayout = currentLayout { resultNode = node - resultSizeAndApply = currentLayout(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies) + resultSizeAndApply = currentLayout(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions) } else { resultNode = ChatMessageDateAndStatusNode() - resultSizeAndApply = resultNode.asyncLayout()(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies) + resultSizeAndApply = resultNode.asyncLayout()(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions) } return (resultSizeAndApply.0, { animated in diff --git a/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift index dc0198b6de..d05a91106a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift @@ -69,7 +69,7 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { let incoming = item.message.effectivelyIncoming(item.context.account.peerId) let statusType: ChatMessageDateAndStatusType? switch preparePosition { - case .linear(_, .None), .linear(_, .Neighbour(true, _)): + case .linear(_, .None): if incoming { statusType = .BubbleIncoming } else { diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index f6889fe4c6..14cd68d6ef 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -26,7 +26,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { private var selectionNode: ChatMessageSelectionNode? private var deliveryFailedNode: ChatMessageDeliveryFailedNode? - private var shareButtonNode: ChatMessageShareButton? + private var shareButtonNode: HighlightableButtonNode? private var swipeToReplyNode: ChatMessageSwipeToReplyNode? private var swipeToReplyFeedback: HapticFeedback? @@ -178,29 +178,24 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { let avatarInset: CGFloat var hasAvatar = false - let messagePeerId: PeerId switch item.chatLocation { - case let .peer(peerId): - messagePeerId = peerId - case let .replyThread(messageId, _, _): - messagePeerId = messageId.peerId - } - - do { - if messagePeerId != item.context.account.peerId { - if messagePeerId.isGroupOrChannel && item.message.author != nil { - var isBroadcastChannel = false - if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { - isBroadcastChannel = true - } - - if !isBroadcastChannel { - hasAvatar = true + case let .peer(peerId): + if peerId != item.context.account.peerId { + if peerId.isGroupOrChannel && item.message.author != nil { + var isBroadcastChannel = false + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { + isBroadcastChannel = true + } + + if !isBroadcastChannel { + hasAvatar = true + } } + } else if incoming { + hasAvatar = true } - } else if incoming { - hasAvatar = true - } + /*case .group: + hasAvatar = true*/ } if hasAvatar { @@ -337,10 +332,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { } if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - if case let .replyThread(replyThreadMessageId, _, _) = item.chatLocation, replyThreadMessageId == replyAttribute.messageId { - } else { - replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) - } + replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) } else if let _ = attribute as? InlineBotMessageAttribute { } else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty { replyMarkup = attribute @@ -360,12 +352,28 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { var updatedShareButtonBackground: UIImage? - var updatedShareButtonNode: ChatMessageShareButton? + var updatedShareButtonNode: HighlightableButtonNode? if needShareButton { if currentShareButtonNode != nil { updatedShareButtonNode = currentShareButtonNode + if item.presentationData.theme !== currentItem?.presentationData.theme { + let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) + if item.message.id.peerId == item.context.account.peerId { + updatedShareButtonBackground = graphics.chatBubbleNavigateButtonImage + } else { + updatedShareButtonBackground = graphics.chatBubbleShareButtonImage + } + } } else { - let buttonNode = ChatMessageShareButton() + let buttonNode = HighlightableButtonNode() + let buttonIcon: UIImage? + let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) + if item.message.id.peerId == item.context.account.peerId { + buttonIcon = graphics.chatBubbleNavigateButtonImage + } else { + buttonIcon = graphics.chatBubbleShareButtonImage + } + buttonNode.setBackgroundImage(buttonIcon, for: [.normal]) updatedShareButtonNode = buttonNode } } @@ -467,13 +475,18 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { strongSelf.addSubnode(updatedShareButtonNode) updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside) } - let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, message: item.message, account: item.context.account) - updatedShareButtonNode.frame = CGRect(origin: CGPoint(x: videoFrame.maxX - 7.0, y: videoFrame.maxY - 24.0 - buttonSize.height), size: buttonSize) + if let updatedShareButtonBackground = updatedShareButtonBackground { + strongSelf.shareButtonNode?.setBackgroundImage(updatedShareButtonBackground, for: [.normal]) + } } else if let shareButtonNode = strongSelf.shareButtonNode { shareButtonNode.removeFromSupernode() strongSelf.shareButtonNode = nil } + if let shareButtonNode = strongSelf.shareButtonNode { + shareButtonNode.frame = CGRect(origin: CGPoint(x: videoFrame.maxX - 7.0, y: videoFrame.maxY - 54.0), size: CGSize(width: 29.0, height: 29.0)) + } + if let updatedReplyBackgroundNode = updatedReplyBackgroundNode { if strongSelf.replyBackgroundNode == nil { strongSelf.replyBackgroundNode = updatedReplyBackgroundNode @@ -674,7 +687,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty { item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame) } else { - if !item.message.id.peerId.isReplies, let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { + if let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { if case .member = channel.participationStatus { } else { item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame) @@ -703,7 +716,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { if let item = self.item, let forwardInfo = item.message.forwardInfo { let performAction: () -> Void = { if let sourceMessageId = forwardInfo.sourceMessageId { - if !item.message.id.peerId.isReplies, let channel = forwardInfo.author as? TelegramChannel, channel.username == nil { + if let channel = forwardInfo.author as? TelegramChannel, channel.username == nil { if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { } else if case .member = channel.participationStatus { } else { @@ -739,16 +752,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { @objc func shareButtonPressed() { if let item = self.item { - if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { - for attribute in item.message.attributes { - if let _ = attribute as? ReplyThreadMessageAttribute { - item.controllerInteraction.openMessageReplies(item.message.id) - return - } - } - } - - if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { + if item.content.firstMessage.id.peerId == item.context.account.peerId { for attribute in item.content.firstMessage.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, attribute.messageId) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index 2054b91a02..c2cf930192 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -13,7 +13,6 @@ import PhotoResources import TelegramStringFormatting import RadialStatusNode import SemanticStatusNode -import FileMediaResourceStatus private struct FetchControls { let fetch: () -> Void @@ -260,15 +259,15 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { |> map { resourceStatus, actualFetchStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in return (resourceStatus, actualFetchStatus) } - updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: false) + updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: context, file: file, message: message, isRecentActions: isRecentActions) } else { updatedStatusSignal = messageFileMediaResourceStatus(context: context, file: file, message: message, isRecentActions: isRecentActions) |> map { resourceStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in return (resourceStatus, nil) } - updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: false) + updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: context, file: file, message: message, isRecentActions: isRecentActions) } - updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: false) + updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions) } var statusSize: CGSize? @@ -294,16 +293,11 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { edited = true } var viewCount: Int? - var dateReplies = 0 for attribute in message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count - } else if let attribute = attribute as? ReplyThreadMessageAttribute { - if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { - dateReplies = Int(attribute.count) - } } } @@ -322,7 +316,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { let dateText = stringForMessageTimestampStatus(accountPeerId: context.account.peerId, message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings, reactionCount: dateReactionCount) - let (size, apply) = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, constrainedSize, dateReactions, dateReplies) + let (size, apply) = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, constrainedSize, dateReactions) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index eafec61d64..87c3688d60 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -12,7 +12,6 @@ import AccountContext import RadialStatusNode import PhotoResources import TelegramUniversalVideoContent -import FileMediaResourceStatus struct ChatMessageInstantVideoItemLayoutResult { let contentSize: CGSize @@ -251,16 +250,11 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } let sentViaBot = false var viewCount: Int? = nil - var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count - } else if let attribute = attribute as? ReplyThreadMessageAttribute { - if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { - dateReplies = Int(attribute.count) - } } } @@ -285,7 +279,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } else { maxDateAndStatusWidth = width - videoFrame.midX - 85.0 } - let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) + let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), dateReactions) var contentSize = imageSize var dateAndStatusOverflow = false @@ -630,7 +624,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } playbackStatusNode.frame = videoFrame.insetBy(dx: 1.5, dy: 1.5) - let status = messageFileMediaPlaybackStatus(context: item.context, file: file, message: item.message, isRecentActions: item.associatedData.isRecentActions, isGlobalSearch: false) + let status = messageFileMediaPlaybackStatus(context: item.context, file: file, message: item.message, isRecentActions: item.associatedData.isRecentActions) playbackStatusNode.status = status self.durationNode?.status = status |> map(Optional.init) diff --git a/submodules/ChatMessageInteractiveMediaBadge/Sources/ChatMessageInteractiveMediaBadge.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaBadge.swift similarity index 95% rename from submodules/ChatMessageInteractiveMediaBadge/Sources/ChatMessageInteractiveMediaBadge.swift rename to submodules/TelegramUI/Sources/ChatMessageInteractiveMediaBadge.swift index 1c8fd02e91..09ff15c32c 100644 --- a/submodules/ChatMessageInteractiveMediaBadge/Sources/ChatMessageInteractiveMediaBadge.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaBadge.swift @@ -7,21 +7,18 @@ import TextFormat import RadialStatusNode import AppBundle -private let font = Font.regular(11.0) -private let boldFont = Font.semibold(11.0) - -public enum ChatMessageInteractiveMediaDownloadState: Equatable { +enum ChatMessageInteractiveMediaDownloadState: Equatable { case remote case fetching(progress: Float?) case compactRemote case compactFetching(progress: Float) } -public enum ChatMessageInteractiveMediaBadgeContent: Equatable { +enum ChatMessageInteractiveMediaBadgeContent: Equatable { case text(inset: CGFloat, backgroundColor: UIColor, foregroundColor: UIColor, text: NSAttributedString) case mediaDownload(backgroundColor: UIColor, foregroundColor: UIColor, duration: String, size: String?, muted: Bool, active: Bool) - public static func ==(lhs: ChatMessageInteractiveMediaBadgeContent, rhs: ChatMessageInteractiveMediaBadgeContent) -> Bool { + static func ==(lhs: ChatMessageInteractiveMediaBadgeContent, rhs: ChatMessageInteractiveMediaBadgeContent) -> Bool { switch lhs { case let .text(lhsInset, lhsBackgroundColor, lhsForegroundColor, lhsText): if case let .text(rhsInset, rhsBackgroundColor, rhsForegroundColor, rhsText) = rhs, lhsInset.isEqual(to: rhsInset), lhsBackgroundColor.isEqual(rhsBackgroundColor), lhsForegroundColor.isEqual(rhsForegroundColor), lhsText.isEqual(to: rhsText) { @@ -39,9 +36,12 @@ public enum ChatMessageInteractiveMediaBadgeContent: Equatable { } } -public final class ChatMessageInteractiveMediaBadge: ASDisplayNode { +private let font = Font.regular(11.0) +private let boldFont = Font.semibold(11.0) + +final class ChatMessageInteractiveMediaBadge: ASDisplayNode { private var content: ChatMessageInteractiveMediaBadgeContent? - public var pressed: (() -> Void)? + var pressed: (() -> Void)? private var mediaDownloadState: ChatMessageInteractiveMediaDownloadState? @@ -56,7 +56,7 @@ public final class ChatMessageInteractiveMediaBadge: ASDisplayNode { private var iconNode: ASImageNode? private var mediaDownloadStatusNode: RadialStatusNode? - override public init() { + override init() { self.backgroundNode = ASImageNode() self.backgroundNode.clipsToBounds = true self.durationNode = ASTextNode() @@ -68,7 +68,7 @@ public final class ChatMessageInteractiveMediaBadge: ASDisplayNode { self.backgroundNode.addSubnode(self.durationNode) } - override public func didLoad() { + override func didLoad() { super.didLoad() self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) @@ -87,7 +87,7 @@ public final class ChatMessageInteractiveMediaBadge: ASDisplayNode { return self.measureNode.measure(CGSize(width: 240.0, height: 160.0)).width } - public func update(theme: PresentationTheme?, content: ChatMessageInteractiveMediaBadgeContent?, mediaDownloadState: ChatMessageInteractiveMediaDownloadState?, alignment: NSTextAlignment = .left, animated: Bool, badgeAnimated: Bool = true) { + func update(theme: PresentationTheme?, content: ChatMessageInteractiveMediaBadgeContent?, mediaDownloadState: ChatMessageInteractiveMediaDownloadState?, alignment: NSTextAlignment = .left, animated: Bool, badgeAnimated: Bool = true) { var transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate let previousContentSize = self.previousContentSize @@ -297,7 +297,7 @@ public final class ChatMessageInteractiveMediaBadge: ASDisplayNode { } } - override public func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return self.backgroundNode.frame.contains(point) } } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift index b99d2a3502..2b00d06405 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift @@ -21,7 +21,6 @@ import AnimatedStickerNode import TelegramAnimatedStickerNode import LocalMediaResources import WallpaperResources -import ChatMessageInteractiveMediaBadge private struct FetchControls { let fetch: (Bool) -> Void diff --git a/submodules/TelegramUI/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Sources/ChatMessageItem.swift index bb3ffe9407..ad139684c6 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItem.swift @@ -119,27 +119,23 @@ private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs } } - if lhs.id.peerId.isRepliesOrSavedMessages(accountPeerId: accountPeerId) { + if lhs.id.peerId == accountPeerId { if let forwardInfo = lhs.forwardInfo { lhsEffectiveAuthor = forwardInfo.author } } - if rhs.id.peerId.isRepliesOrSavedMessages(accountPeerId: accountPeerId) { + if rhs.id.peerId == accountPeerId { if let forwardInfo = rhs.forwardInfo { rhsEffectiveAuthor = forwardInfo.author } } var sameAuthor = false - if lhsEffectiveAuthor?.id == rhsEffectiveAuthor?.id && lhs.effectivelyIncoming(accountPeerId) == rhs.effectivelyIncoming(accountPeerId) { + if lhsEffectiveAuthor?.id == rhsEffectiveAuthor?.id { sameAuthor = true } if abs(lhs.timestamp - rhs.timestamp) < Int32(10 * 60) && sameAuthor { - if let channel = lhs.peers[lhs.id.peerId] as? TelegramChannel, case .group = channel.info, lhsEffectiveAuthor?.id == channel.id, !lhs.effectivelyIncoming(accountPeerId) { - return .none - } - var upperStyle: Int32 = ChatMessageMerge.fullyMerged.rawValue var lowerStyle: Int32 = ChatMessageMerge.fullyMerged.rawValue for media in lhs.media { @@ -180,8 +176,6 @@ func chatItemsHaveCommonDateHeader(_ lhs: ListViewItem, _ rhs: ListViewItem?) - lhsHeader = nil } else if let lhs = lhs as? ChatUnreadItem { lhsHeader = lhs.header - } else if let lhs = lhs as? ChatReplyCountItem { - lhsHeader = lhs.header } else { lhsHeader = nil } @@ -193,8 +187,6 @@ func chatItemsHaveCommonDateHeader(_ lhs: ListViewItem, _ rhs: ListViewItem?) - rhsHeader = nil } else if let rhs = rhs as? ChatUnreadItem { rhsHeader = rhs.header - } else if let rhs = rhs as? ChatReplyCountItem { - rhsHeader = rhs.header } else { rhsHeader = nil } @@ -228,6 +220,51 @@ enum ChatMessageMerge: Int32 { } } +public final class ChatMessageItemAssociatedData: Equatable { + let automaticDownloadPeerType: MediaAutoDownloadPeerType + let automaticDownloadNetworkType: MediaAutoDownloadNetworkType + let isRecentActions: Bool + let isScheduledMessages: Bool + let contactsPeerIds: Set + let animatedEmojiStickers: [String: [StickerPackItem]] + let forcedResourceStatus: FileMediaResourceStatus? + + init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, isScheduledMessages: Bool = false, contactsPeerIds: Set = Set(), animatedEmojiStickers: [String: [StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil) { + self.automaticDownloadPeerType = automaticDownloadPeerType + self.automaticDownloadNetworkType = automaticDownloadNetworkType + self.isRecentActions = isRecentActions + self.isScheduledMessages = isScheduledMessages + self.contactsPeerIds = contactsPeerIds + self.animatedEmojiStickers = animatedEmojiStickers + self.forcedResourceStatus = forcedResourceStatus + } + + public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool { + if lhs.automaticDownloadPeerType != rhs.automaticDownloadPeerType { + return false + } + if lhs.automaticDownloadNetworkType != rhs.automaticDownloadNetworkType { + return false + } + if lhs.isRecentActions != rhs.isRecentActions { + return false + } + if lhs.isScheduledMessages != rhs.isScheduledMessages { + return false + } + if lhs.contactsPeerIds != rhs.contactsPeerIds { + return false + } + if lhs.animatedEmojiStickers != rhs.animatedEmojiStickers { + return false + } + if lhs.forcedResourceStatus != rhs.forcedResourceStatus { + return false + } + return true + } +} + public final class ChatMessageItem: ListViewItem, CustomStringConvertible { let presentationData: ChatPresentationData let context: AccountContext @@ -276,34 +313,29 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { var effectiveAuthor: Peer? let displayAuthorInfo: Bool - let messagePeerId: PeerId switch chatLocation { - case let .peer(peerId): - messagePeerId = peerId - case let .replyThread(messageId, _, _): - messagePeerId = messageId.peerId - } - - do { - let peerId = messagePeerId - if peerId.isRepliesOrSavedMessages(accountPeerId: context.account.peerId) { - if let forwardInfo = content.firstMessage.forwardInfo { - effectiveAuthor = forwardInfo.author - if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { - effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: Int32(clamping: authorSignature.persistentHashValue)), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: UserInfoFlags()) + case let .peer(peerId): + if peerId == context.account.peerId { + if let forwardInfo = content.firstMessage.forwardInfo { + effectiveAuthor = forwardInfo.author + if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { + effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: Int32(clamping: authorSignature.persistentHashValue)), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: UserInfoFlags()) + } } + displayAuthorInfo = incoming && effectiveAuthor != nil + } else { + effectiveAuthor = content.firstMessage.author + for attribute in content.firstMessage.attributes { + if let attribute = attribute as? SourceReferenceMessageAttribute { + effectiveAuthor = content.firstMessage.peers[attribute.messageId.peerId] + break + } + } + displayAuthorInfo = incoming && peerId.isGroupOrChannel && effectiveAuthor != nil } - displayAuthorInfo = incoming && effectiveAuthor != nil - } else { + /*case .group: effectiveAuthor = content.firstMessage.author - for attribute in content.firstMessage.attributes { - if let attribute = attribute as? SourceReferenceMessageAttribute { - effectiveAuthor = content.firstMessage.peers[attribute.messageId.peerId] - break - } - } - displayAuthorInfo = incoming && peerId.isGroupOrChannel && effectiveAuthor != nil - } + displayAuthorInfo = incoming && effectiveAuthor != nil*/ } self.effectiveAuthorId = effectiveAuthor?.id @@ -447,10 +479,6 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { if bottom.header.id != self.header.id { dateAtBottom = true } - } else if let bottom = bottom as? ChatReplyCountItem { - if bottom.header.id != self.header.id { - dateAtBottom = true - } } else if let _ = bottom as? ChatHoleItem { dateAtBottom = true } else { diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Sources/ChatMessageItemView.swift index d0a8422e62..f114d2af30 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItemView.swift @@ -9,7 +9,6 @@ import AccountContext import LocalizedPeerData import ContextUI import ChatListUI -import TelegramPresentationData struct ChatMessageItemWidthFill { var compactInset: CGFloat diff --git a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift index 6440354556..192883b28f 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift @@ -157,7 +157,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { if activeLiveBroadcastingTimeout != nil || selectedMedia?.venue != nil { var relativePosition = position if case let .linear(top, _) = position { - relativePosition = .linear(top: top, bottom: .Neighbour(false, .freeform)) + relativePosition = .linear(top: top, bottom: ChatMessageBubbleRelativePosition.Neighbour) } imageCorners = chatMessageBubbleImageContentCorners(relativeContentPosition: relativePosition, normalRadius: layoutConstants.image.defaultCornerRadius, mergedRadius: layoutConstants.image.mergedCornerRadius, mergedWithAnotherContentRadius: layoutConstants.image.contentMergedCornerRadius, layoutConstants: layoutConstants, chatPresentationData: item.presentationData) @@ -176,16 +176,11 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { edited = true } var viewCount: Int? - var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count - } else if let attribute = attribute as? ReplyThreadMessageAttribute { - if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { - dateReplies = Int(attribute.count) - } } } @@ -212,7 +207,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { let statusType: ChatMessageDateAndStatusType? switch position { - case .linear(_, .None), .linear(_, .Neighbour(true, _)): + case .linear(_, .None): if selectedMedia?.venue != nil || activeLiveBroadcastingTimeout != nil { if item.message.effectivelyIncoming(item.context.account.peerId) { statusType = .BubbleIncoming @@ -246,7 +241,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift index ebaf889c65..577556c523 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift @@ -174,7 +174,6 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { edited = true } var viewCount: Int? - var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { if case .mosaic = preparePosition { @@ -183,10 +182,6 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count - } else if let attribute = attribute as? ReplyThreadMessageAttribute { - if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { - dateReplies = Int(attribute.count) - } } } @@ -207,7 +202,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { let statusType: ChatMessageDateAndStatusType? switch position { - case .linear(_, .None), .linear(_, .Neighbour(true, _)): + case .linear(_, .None): if item.message.effectivelyIncoming(item.context.account.peerId) { statusType = .ImageIncoming } else { @@ -231,7 +226,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: imageSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: imageSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude), dateReactions) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift index 721e616c3d..bb6149c63f 100644 --- a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift @@ -1024,16 +1024,11 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { edited = true } var viewCount: Int? - var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count - } else if let attribute = attribute as? ReplyThreadMessageAttribute { - if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { - dateReplies = Int(attribute.count) - } } } @@ -1054,7 +1049,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { let statusType: ChatMessageDateAndStatusType? switch position { - case .linear(_, .None), .linear(_, .Neighbour(true, _)): + case .linear(_, .None): if incoming { statusType = .BubbleIncoming } else { @@ -1074,7 +1069,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions) statusSize = size statusApply = apply } @@ -1514,10 +1509,10 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { timerTransition.updateAlpha(node: strongSelf.solutionButtonNode, alpha: 0.0) } - let avatarsFrame = CGRect(origin: CGPoint(x: typeFrame.maxX + 6.0, y: typeFrame.minY + floor((typeFrame.height - defaultMergedImageSize) / 2.0)), size: CGSize(width: defaultMergedImageSize + defaultMergedImageSpacing * 2.0, height: defaultMergedImageSize)) + let avatarsFrame = CGRect(origin: CGPoint(x: typeFrame.maxX + 6.0, y: typeFrame.minY + floor((typeFrame.height - mergedImageSize) / 2.0)), size: CGSize(width: mergedImageSize + mergedImageSpacing * 2.0, height: mergedImageSize)) strongSelf.avatarsNode.frame = avatarsFrame strongSelf.avatarsNode.updateLayout(size: avatarsFrame.size) - strongSelf.avatarsNode.update(context: item.context, peers: avatarPeers, synchronousLoad: synchronousLoad, imageSize: defaultMergedImageSize, imageSpacing: defaultMergedImageSpacing, borderWidth: defaultBorderWidth) + strongSelf.avatarsNode.update(context: item.context, peers: avatarPeers, synchronousLoad: synchronousLoad) strongSelf.avatarsNode.isHidden = isBotChat let alphaTransition: ContainedViewLayoutTransition if animation.isAnimated { @@ -1792,33 +1787,23 @@ private extension PeerAvatarReference { private final class MergedAvatarsNodeArguments: NSObject { let peers: [PeerAvatarReference] let images: [PeerId: UIImage] - let imageSize: CGFloat - let imageSpacing: CGFloat - let borderWidth: CGFloat - init(peers: [PeerAvatarReference], images: [PeerId: UIImage], imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat) { + init(peers: [PeerAvatarReference], images: [PeerId: UIImage]) { self.peers = peers self.images = images - self.imageSize = imageSize - self.imageSpacing = imageSpacing - self.borderWidth = borderWidth } } -private let defaultMergedImageSize: CGFloat = 16.0 -private let defaultMergedImageSpacing: CGFloat = 15.0 -private let defaultBorderWidth: CGFloat = 1.0 +private let mergedImageSize: CGFloat = 16.0 +private let mergedImageSpacing: CGFloat = 15.0 private let avatarFont = avatarPlaceholderFont(size: 8.0) -final class MergedAvatarsNode: ASDisplayNode { +private final class MergedAvatarsNode: ASDisplayNode { private var peers: [PeerAvatarReference] = [] private var images: [PeerId: UIImage] = [:] private var disposables: [PeerId: Disposable] = [:] private let buttonNode: HighlightTrackingButtonNode - private var imageSize: CGFloat = defaultMergedImageSize - private var imageSpacing: CGFloat = defaultMergedImageSpacing - private var borderWidthValue: CGFloat = defaultBorderWidth var pressed: (() -> Void)? @@ -1847,13 +1832,10 @@ final class MergedAvatarsNode: ASDisplayNode { self.buttonNode.frame = CGRect(origin: CGPoint(), size: size) } - func update(context: AccountContext, peers: [Peer], synchronousLoad: Bool, imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat) { - self.imageSize = imageSize - self.imageSpacing = imageSpacing - self.borderWidthValue = borderWidth + func update(context: AccountContext, peers: [Peer], synchronousLoad: Bool) { var filteredPeers = peers.map(PeerAvatarReference.init) if filteredPeers.count > 3 { - filteredPeers = filteredPeers.dropLast(filteredPeers.count - 3) + let _ = filteredPeers.dropLast(filteredPeers.count - 3) } if filteredPeers != self.peers { self.peers = filteredPeers @@ -1888,7 +1870,7 @@ final class MergedAvatarsNode: ASDisplayNode { switch peer { case let .image(peerReference, representation): if self.disposables[peer.peerId] == nil { - if let signal = peerAvatarImage(account: context.account, peerReference: peerReference, authorOfMessage: nil, representation: representation, displayDimensions: CGSize(width: imageSize, height: imageSize), synchronousLoad: synchronousLoad) { + if let signal = peerAvatarImage(account: context.account, peerReference: peerReference, authorOfMessage: nil, representation: representation, displayDimensions: CGSize(width: mergedImageSize, height: mergedImageSize), synchronousLoad: synchronousLoad) { let disposable = (signal |> deliverOnMainQueue).start(next: { [weak self] imageVersions in guard let strongSelf = self else { @@ -1912,7 +1894,7 @@ final class MergedAvatarsNode: ASDisplayNode { } override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol { - return MergedAvatarsNodeArguments(peers: self.peers, images: self.images, imageSize: self.imageSize, imageSpacing: self.imageSpacing, borderWidth: self.borderWidthValue) + return MergedAvatarsNodeArguments(peers: self.peers, images: self.images) } @objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { @@ -1930,16 +1912,13 @@ final class MergedAvatarsNode: ASDisplayNode { return } - let mergedImageSize = parameters.imageSize - let mergedImageSpacing = parameters.imageSpacing + context.setBlendMode(.copy) var currentX = mergedImageSize + mergedImageSpacing * CGFloat(parameters.peers.count - 1) - mergedImageSize for i in (0 ..< parameters.peers.count).reversed() { let imageRect = CGRect(origin: CGPoint(x: currentX, y: 0.0), size: CGSize(width: mergedImageSize, height: mergedImageSize)) - context.setBlendMode(.copy) context.setFillColor(UIColor.clear.cgColor) - context.fillEllipse(in: imageRect.insetBy(dx: -parameters.borderWidth, dy: -parameters.borderWidth)) - context.setBlendMode(.normal) + context.fillEllipse(in: imageRect.insetBy(dx: -1.0, dy: -1.0)) context.saveGState() switch parameters.peers[i] { @@ -1947,7 +1926,7 @@ final class MergedAvatarsNode: ASDisplayNode { context.translateBy(x: currentX, y: 0.0) drawPeerAvatarLetters(context: context, size: CGSize(width: mergedImageSize, height: mergedImageSize), font: avatarFont, letters: letters, peerId: peerId) context.translateBy(x: -currentX, y: 0.0) - case .image: + case let .image(reference): if let image = parameters.images[parameters.peers[i].peerId] { context.translateBy(x: imageRect.midX, y: imageRect.midY) context.scaleBy(x: 1.0, y: -1.0) diff --git a/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift index b10af32878..c7ec00047c 100644 --- a/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift @@ -53,7 +53,6 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { } var viewCount: Int? var rawText = "" - var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden @@ -61,10 +60,6 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { viewCount = attribute.count } else if let attribute = attribute as? RestrictedContentMessageAttribute { rawText = attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) ?? "" - } else if let attribute = attribute as? ReplyThreadMessageAttribute { - if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { - dateReplies = Int(attribute.count) - } } } @@ -85,7 +80,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { let statusType: ChatMessageDateAndStatusType? switch position { - case .linear(_, .None), .linear(_, .Neighbour(true, _)): + case .linear(_, .None): if incoming { statusType = .BubbleIncoming } else { @@ -105,7 +100,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index 06979ee0ad..3d4d47aeba 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -28,7 +28,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { private var selectionNode: ChatMessageSelectionNode? private var deliveryFailedNode: ChatMessageDeliveryFailedNode? - private var shareButtonNode: ChatMessageShareButton? + private var shareButtonNode: HighlightableButtonNode? var telegramFile: TelegramMediaFile? private let fetchDisposable = MetaDisposable() @@ -249,21 +249,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } else if incoming { hasAvatar = true } - case let .replyThread(messageId, _, _): - if messageId.peerId != item.context.account.peerId { - if messageId.peerId.isGroupOrChannel && item.message.author != nil { - var isBroadcastChannel = false - if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { - isBroadcastChannel = true - } - - if !isBroadcastChannel { - hasAvatar = true - } - } - } else if incoming { - hasAvatar = true - } + /*case .group: + hasAvatar = true*/ } if hasAvatar { @@ -350,16 +337,11 @@ class ChatMessageStickerItemNode: ChatMessageItemView { var edited = false var viewCount: Int? = nil - var dateReplies = 0 for attribute in item.message.attributes { if let _ = attribute as? EditedMessageAttribute, isEmoji { edited = true } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count - } else if let attribute = attribute as? ReplyThreadMessageAttribute { - if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { - dateReplies = Int(attribute.count) - } } } @@ -378,7 +360,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular, reactionCount: dateReactionCount) - let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) + let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions) var viaBotApply: (TextNodeLayout, () -> TextNode)? var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)? @@ -411,16 +393,13 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - if case let .replyThread(replyThreadMessageId, _, _) = item.chatLocation, replyThreadMessageId == replyAttribute.messageId { - } else { - replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) - } + replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) } else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty { replyMarkup = attribute } } - if item.message.id.peerId != item.context.account.peerId && !item.message.id.peerId.isReplies{ + if item.message.id.peerId != item.context.account.peerId { for attribute in item.message.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { if let sourcePeer = item.message.peers[attribute.messageId.peerId] { @@ -444,12 +423,30 @@ class ChatMessageStickerItemNode: ChatMessageItemView { replyBackgroundImage = graphics.chatFreeformContentAdditionalInfoBackgroundImage } - var updatedShareButtonNode: ChatMessageShareButton? + var updatedShareButtonBackground: UIImage? + + var updatedShareButtonNode: HighlightableButtonNode? if needShareButton { if currentShareButtonNode != nil { updatedShareButtonNode = currentShareButtonNode + if item.presentationData.theme !== currentItem?.presentationData.theme { + let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) + if item.message.id.peerId == item.context.account.peerId { + updatedShareButtonBackground = graphics.chatBubbleNavigateButtonImage + } else { + updatedShareButtonBackground = graphics.chatBubbleShareButtonImage + } + } } else { - let buttonNode = ChatMessageShareButton() + let buttonNode = HighlightableButtonNode() + let buttonIcon: UIImage? + let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) + if item.message.id.peerId == item.context.account.peerId { + buttonIcon = graphics.chatBubbleNavigateButtonImage + } else { + buttonIcon = graphics.chatBubbleShareButtonImage + } + buttonNode.setBackgroundImage(buttonIcon, for: [.normal]) updatedShareButtonNode = buttonNode } } @@ -515,17 +512,22 @@ class ChatMessageStickerItemNode: ChatMessageItemView { strongSelf.addSubnode(updatedShareButtonNode) updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside) } - let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, message: item.message, account: item.context.account) - var shareButtonFrame = CGRect(origin: CGPoint(x: updatedImageFrame.maxX + 6.0, y: updatedImageFrame.maxY - 10.0 - buttonSize.height - 4.0), size: buttonSize) - if isEmoji && incoming { - shareButtonFrame.origin.x = dateAndStatusFrame.maxX + 8.0 + if let updatedShareButtonBackground = updatedShareButtonBackground { + strongSelf.shareButtonNode?.setBackgroundImage(updatedShareButtonBackground, for: [.normal]) } - transition.updateFrame(node: updatedShareButtonNode, frame: shareButtonFrame) } else if let shareButtonNode = strongSelf.shareButtonNode { shareButtonNode.removeFromSupernode() strongSelf.shareButtonNode = nil } + if let shareButtonNode = strongSelf.shareButtonNode { + var shareButtonFrame = CGRect(origin: CGPoint(x: updatedImageFrame.maxX + 8.0, y: updatedImageFrame.maxY - 30.0 - 10.0), size: CGSize(width: 29.0, height: 29.0)) + if isEmoji && incoming { + shareButtonFrame.origin.x = dateAndStatusFrame.maxX + 8.0 + } + transition.updateFrame(node: shareButtonNode, frame: shareButtonFrame) + } + if let updatedReplyBackgroundNode = updatedReplyBackgroundNode { if strongSelf.replyBackgroundNode == nil { strongSelf.replyBackgroundNode = updatedReplyBackgroundNode @@ -703,7 +705,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty { item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame) } else { - if !item.message.id.peerId.isReplies, let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { + if let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { if case .member = channel.participationStatus { } else { item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame) @@ -772,16 +774,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { @objc func shareButtonPressed() { if let item = self.item { - if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { - for attribute in item.message.attributes { - if let _ = attribute as? ReplyThreadMessageAttribute { - item.controllerInteraction.openMessageReplies(item.message.id) - return - } - } - } - - if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { + if item.content.firstMessage.id.peerId == item.context.account.peerId { for attribute in item.content.firstMessage.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, attribute.messageId) diff --git a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift index f21468cb52..b36dc9ba26 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift @@ -109,16 +109,11 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { edited = true } var viewCount: Int? - var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count - } else if let attribute = attribute as? ReplyThreadMessageAttribute { - if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { - dateReplies = Int(attribute.count) - } } } @@ -138,38 +133,28 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, reactionCount: dateReactionCount) let statusType: ChatMessageDateAndStatusType? - var displayStatus = false switch position { - case let .linear(_, neighbor): - if case .None = neighbor { - displayStatus = true - } else if case .Neighbour(true, _) = neighbor { - displayStatus = true - } - default: - break - } - if displayStatus { - if incoming { - statusType = .BubbleIncoming - } else { - if message.flags.contains(.Failed) { - statusType = .BubbleOutgoing(.Failed) - } else if (message.flags.isSending && !message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil { - statusType = .BubbleOutgoing(.Sending) + case .linear(_, .None): + if incoming { + statusType = .BubbleIncoming } else { - statusType = .BubbleOutgoing(.Sent(read: item.read)) + if message.flags.contains(.Failed) { + statusType = .BubbleOutgoing(.Failed) + } else if (message.flags.isSending && !message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil { + statusType = .BubbleOutgoing(.Sending) + } else { + statusType = .BubbleOutgoing(.Sent(read: item.read)) + } } - } - } else { - statusType = nil + default: + statusType = nil } var statusSize: CGSize? var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift index bb141ab5c2..4ad043b1a8 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -12,7 +12,83 @@ import AccountContext import WebsiteType import InstantPageUI import UrlHandling -import GalleryData + +enum InstantPageType { + case generic + case album +} + +func instantPageType(of webpage: TelegramMediaWebpageLoadedContent) -> InstantPageType { + if let type = webpage.type, type == "telegram_album" { + return .album + } + + switch websiteType(of: webpage.websiteName) { + case .instagram, .twitter: + return .album + default: + return .generic + } +} + +func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage, galleryMedia: Media) -> [InstantPageGalleryEntry] { + var result: [InstantPageGalleryEntry] = [] + var counter: Int = 0 + + for block in page.blocks { + result.append(contentsOf: instantPageBlockMedia(pageId: webpageId, block: block, media: page.media, counter: &counter)) + } + + var found = false + for item in result { + if item.media.media.id == galleryMedia.id { + found = true + break + } + } + + if !found { + 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, credit: item.credit, location: InstantPageGalleryEntryLocation(position: Int32(i), totalCount: Int32(result.count))) + } + return result +} + +private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, media: [MediaId: Media], counter: inout Int) -> [InstantPageGalleryEntry] { + 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, 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, _, _): + if let m = media[id] { + 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 .collage(items, _): + var result: [InstantPageGalleryEntry] = [] + for item in items { + result.append(contentsOf: instantPageBlockMedia(pageId: pageId, block: item, media: media, counter: &counter)) + } + return result + case let .slideshow(items, _): + var result: [InstantPageGalleryEntry] = [] + for item in items { + result.append(contentsOf: instantPageBlockMedia(pageId: pageId, block: item, media: media, counter: &counter)) + } + return result + default: + break + } + return [] +} private let titleFont: UIFont = Font.semibold(15.0) diff --git a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift index 81eed2ea55..40598db1b3 100644 --- a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift @@ -72,7 +72,7 @@ final class ChatPanelInterfaceInteraction { let openSearchResults: () -> Void let openCalendarSearch: () -> Void let toggleMembersSearch: (Bool) -> Void - let navigateToMessage: (MessageId, Bool) -> Void + let navigateToMessage: (MessageId) -> Void let navigateToChat: (PeerId) -> Void let navigateToProfile: (PeerId) -> Void let openPeerInfo: () -> Void @@ -120,7 +120,6 @@ final class ChatPanelInterfaceInteraction { let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void let openPeersNearby: () -> Void let unarchivePeer: () -> Void - let viewReplies: (MessageId?, ChatReplyThreadMessage) -> Void let statuses: ChatPanelInterfaceInteractionStatuses? init( @@ -146,7 +145,7 @@ final class ChatPanelInterfaceInteraction { navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, - navigateToMessage: @escaping (MessageId, Bool) -> Void, + navigateToMessage: @escaping (MessageId) -> Void, navigateToChat: @escaping (PeerId) -> Void, navigateToProfile: @escaping (PeerId) -> Void, openPeerInfo: @escaping () -> Void, @@ -194,7 +193,6 @@ final class ChatPanelInterfaceInteraction { openPeersNearby: @escaping () -> Void, displaySearchResultsTooltip: @escaping (ASDisplayNode, CGRect) -> Void, unarchivePeer: @escaping () -> Void, - viewReplies: @escaping (MessageId?, ChatReplyThreadMessage) -> Void, statuses: ChatPanelInterfaceInteractionStatuses? ) { self.setupReplyMessage = setupReplyMessage @@ -267,7 +265,6 @@ final class ChatPanelInterfaceInteraction { self.openPeersNearby = openPeersNearby self.displaySearchResultsTooltip = displaySearchResultsTooltip self.unarchivePeer = unarchivePeer - self.viewReplies = viewReplies self.statuses = statuses } } diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index d400751579..960a17e718 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -28,8 +28,6 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { private var currentMessage: Message? private var previousMediaReference: AnyMediaReference? - private var isReplyThread: Bool = false - private let fetchDisposable = MetaDisposable() private let queue = Queue() @@ -117,16 +115,6 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.separatorNode.backgroundColor = interfaceState.theme.chat.historyNavigation.strokeColor } - let isReplyThread: Bool - if case .replyThread = interfaceState.chatLocation { - isReplyThread = true - } else { - isReplyThread = false - } - self.isReplyThread = isReplyThread - - self.closeButton.isHidden = isReplyThread - var messageUpdated = false if let currentMessage = self.currentMessage, let pinnedMessage = interfaceState.pinnedMessage { if currentMessage.id != pinnedMessage.id || currentMessage.stableVersion != pinnedMessage.stableVersion { @@ -141,7 +129,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.currentMessage = interfaceState.pinnedMessage if let currentMessage = currentMessage, let currentLayout = self.currentLayout { - self.enqueueTransition(width: currentLayout.0, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil, isReplyThread: isReplyThread) + self.enqueueTransition(width: currentLayout.0, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil) } } @@ -160,14 +148,14 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.currentLayout = (width, leftInset, rightInset) if let currentMessage = self.currentMessage { - self.enqueueTransition(width: width, leftInset: leftInset, rightInset: rightInset, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: interfaceState.accountPeerId, firstTime: true, isReplyThread: isReplyThread) + self.enqueueTransition(width: width, leftInset: leftInset, rightInset: rightInset, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: interfaceState.accountPeerId, firstTime: true) } } return panelHeight } - private func enqueueTransition(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, message: Message, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId, firstTime: Bool, isReplyThread: Bool) { + private func enqueueTransition(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, message: Message, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId, firstTime: Bool) { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTextLayout = TextNode.asyncLayout(self.textNode) let imageNodeLayout = self.imageNode.asyncLayout() @@ -216,14 +204,6 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { } } - if isReplyThread { - if let author = message.effectiveAuthor { - titleString = author.displayTitle(strings: strings, displayOrder: nameDisplayOrder) - } else { - titleString = "" - } - } - var applyImage: (() -> Void)? if let imageDimensions = imageDimensions { let boundingSize = CGSize(width: 35.0, height: 35.0) @@ -298,15 +278,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { @objc func tapped() { if let interfaceInteraction = self.interfaceInteraction, let message = self.currentMessage { - if self.isReplyThread { - if let sourceReference = message.sourceReference { - interfaceInteraction.navigateToMessage(sourceReference.messageId, true) - } else { - interfaceInteraction.navigateToMessage(message.id, false) - } - } else { - interfaceInteraction.navigateToMessage(message.id, false) - } + interfaceInteraction.navigateToMessage(message.id) } } diff --git a/submodules/TelegramUI/Sources/ChatPresentationData.swift b/submodules/TelegramUI/Sources/ChatPresentationData.swift new file mode 100644 index 0000000000..253882e904 --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatPresentationData.swift @@ -0,0 +1,65 @@ +import Foundation +import UIKit +import Display +import TelegramCore +import SyncCore +import TelegramPresentationData +import TelegramUIPreferences + +public final class ChatPresentationThemeData: Equatable { + public let theme: PresentationTheme + public let wallpaper: TelegramWallpaper + + public init(theme: PresentationTheme, wallpaper: TelegramWallpaper) { + self.theme = theme + self.wallpaper = wallpaper + } + + public static func ==(lhs: ChatPresentationThemeData, rhs: ChatPresentationThemeData) -> Bool { + return lhs.theme === rhs.theme && lhs.wallpaper == rhs.wallpaper + } +} + +public final class ChatPresentationData { + let theme: ChatPresentationThemeData + let fontSize: PresentationFontSize + let strings: PresentationStrings + let dateTimeFormat: PresentationDateTimeFormat + let nameDisplayOrder: PresentationPersonNameOrder + let disableAnimations: Bool + let largeEmoji: Bool + let chatBubbleCorners: PresentationChatBubbleCorners + let animatedEmojiScale: CGFloat + let isPreview: Bool + + let messageFont: UIFont + let messageEmojiFont: UIFont + let messageBoldFont: UIFont + let messageItalicFont: UIFont + let messageBoldItalicFont: UIFont + let messageFixedFont: UIFont + let messageBlockQuoteFont: UIFont + + init(theme: ChatPresentationThemeData, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool, largeEmoji: Bool, chatBubbleCorners: PresentationChatBubbleCorners, animatedEmojiScale: CGFloat = 1.0, isPreview: Bool = false) { + self.theme = theme + self.fontSize = fontSize + self.strings = strings + self.dateTimeFormat = dateTimeFormat + self.nameDisplayOrder = nameDisplayOrder + self.disableAnimations = disableAnimations + self.chatBubbleCorners = chatBubbleCorners + self.largeEmoji = largeEmoji + self.isPreview = isPreview + + let baseFontSize = fontSize.baseDisplaySize + self.messageFont = Font.regular(baseFontSize) + self.messageEmojiFont = Font.regular(53.0) + self.messageBoldFont = Font.bold(baseFontSize) + self.messageItalicFont = Font.italic(baseFontSize) + self.messageBoldItalicFont = Font.semiboldItalic(baseFontSize) + self.messageFixedFont = Font.monospace(baseFontSize) + self.messageBlockQuoteFont = Font.regular(baseFontSize - 1.0) + + self.animatedEmojiScale = animatedEmojiScale + } +} diff --git a/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift index 1be0b8f25f..5f66288675 100644 --- a/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift @@ -6,7 +6,6 @@ import SyncCore import TelegramPresentationData import TelegramUIPreferences import AccountContext -import ChatInterfaceState enum ChatPresentationInputQueryKind: Int32 { case emoji diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift index 763875bfce..2be30eb2bf 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift @@ -75,7 +75,7 @@ final class ChatRecentActionsController: TelegramBaseController { }, navigateMessageSearch: { _ in }, openCalendarSearch: { }, toggleMembersSearch: { _ in - }, navigateToMessage: { _, _ in + }, navigateToMessage: { _ in }, navigateToChat: { _ in }, navigateToProfile: { _ in }, openPeerInfo: { @@ -125,7 +125,7 @@ final class ChatRecentActionsController: TelegramBaseController { }, openScheduledMessages: { }, openPeersNearby: { }, displaySearchResultsTooltip: { _, _ in - }, unarchivePeer: {}, viewReplies: { _, _ in }, statuses: nil) + }, unarchivePeer: {}, statuses: nil) self.navigationItem.titleView = self.titleView diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index 9f371b3aeb..501b521a41 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -157,7 +157,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { break } } - return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: true, reverseMessageGalleryOrder: false, navigationController: navigationController, dismissInput: { + return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, message: message, standalone: true, reverseMessageGalleryOrder: false, navigationController: navigationController, dismissInput: { //self?.chatDisplayNode.dismissInput() }, present: { c, a in self?.presentController(c, a) @@ -451,7 +451,6 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in - }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, @@ -816,10 +815,6 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { if let navigationController = strongSelf.getNavigationController() { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: .message(messageId))) } - case let .replyThreadMessage(replyThreadMessageId, isChannelPost, maxReadMessageId, messageId): - if let navigationController = strongSelf.getNavigationController() { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(threadMessageId: replyThreadMessageId, isChannelPost: isChannelPost, maxReadMessageId: maxReadMessageId), subject: .message(messageId))) - } case let .stickerPack(name): let packReference: StickerPackReference = .name(name) strongSelf.presentController(StickerPackScreen(context: strongSelf.context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.getNavigationController()), nil) diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift index e070ab91db..72086329da 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift @@ -118,7 +118,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { peers[peer.id] = peer let action = TelegramMediaActionType.titleUpdated(title: new) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .changeAbout(prev, new): var peers = SimpleDictionary() @@ -149,14 +149,14 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: let peers = SimpleDictionary() let attributes: [MessageAttribute] = [] - let prevMessage = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prev, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let prevMessage = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prev, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: new, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: new, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()), additionalContent: !prev.isEmpty ? .eventLogPreviousDescription(prevMessage) : nil) } case let .changeUsername(prev, new): @@ -187,7 +187,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) } let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: var previousAttributes: [MessageAttribute] = [] @@ -205,8 +205,8 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { attributes.append(TextEntitiesMessageAttribute(entities: [MessageTextEntity(range: 0 ..< text.count, type: .Italic)])) } - let prevMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prevText, attributes: previousAttributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let prevMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prevText, attributes: previousAttributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) } case let .changePhoto(_, new): @@ -225,7 +225,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.photoUpdated(image: photo) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .toggleInvites(value): var peers = SimpleDictionary() @@ -252,7 +252,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .toggleSignatures(value): var peers = SimpleDictionary() @@ -279,7 +279,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .updatePinned(message): switch self.id.contentIndex { @@ -303,7 +303,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: if let message = message { @@ -321,7 +321,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } else { var peers = SimpleDictionary() @@ -343,7 +343,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } } @@ -388,7 +388,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: var peers = SimpleDictionary() @@ -405,7 +405,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes()), additionalContent: !prev.text.isEmpty || !message.text.isEmpty ? .eventLogPreviousMessage(filterOriginalMessageFlags(prev)) : nil) } case let .deleteMessage(message): @@ -431,7 +431,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: var peers = SimpleDictionary() @@ -455,7 +455,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } case .participantJoin, .participantLeave: @@ -473,7 +473,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } else { action = TelegramMediaActionType.removedMembers(peerIds: [self.entry.event.peerId]) } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .participantInvite(participant): var peers = SimpleDictionary() @@ -490,7 +490,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action: TelegramMediaActionType action = TelegramMediaActionType.addedMembers(peerIds: [participant.peer.id]) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .participantToggleBan(prev, new): var peers = SimpleDictionary() @@ -620,7 +620,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .participantToggleAdmin(prev, new): var peers = SimpleDictionary() @@ -649,7 +649,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } else { var appendedRightsHeader = false - if case let .creator(_, _, prevRank) = prev.participant, case let .creator(_, _, newRank) = new.participant, prevRank != newRank { + if case let .creator(_, prevRank) = prev.participant, case let .creator(_, newRank) = new.participant, prevRank != newRank { appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRankName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), newRank ?? "") : self.presentationData.strings.Channel_AdminLog_MessageRankUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!, newRank ?? ""), generateEntities: { index in var result: [MessageTextEntityType] = [] if index == 0 { @@ -756,7 +756,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .changeStickerPack(_, new): var peers = SimpleDictionary() @@ -785,7 +785,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .togglePreHistoryHidden(value): var peers = SimpleDictionary() @@ -815,7 +815,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .updateDefaultBannedRights(prev, new): var peers = SimpleDictionary() @@ -873,7 +873,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .pollStopped(message): switch self.id.contentIndex { @@ -901,7 +901,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: var peers = SimpleDictionary() @@ -918,7 +918,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.author, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.author, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes()), additionalContent: nil) } case let .linkedPeerUpdated(previous, updated): @@ -974,7 +974,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .changeGeoLocation(_, updated): var peers = SimpleDictionary() @@ -996,12 +996,12 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let mediaMap = TelegramMediaMap(latitude: updated.latitude, longitude: updated.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: [], media: [mediaMap], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: [], media: [mediaMap], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } else { let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } case let .updateSlowmode(_, newValue): @@ -1032,7 +1032,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } } diff --git a/submodules/TelegramUI/Sources/ChatReplyCountItem.swift b/submodules/TelegramUI/Sources/ChatReplyCountItem.swift deleted file mode 100644 index c6208f1e50..0000000000 --- a/submodules/TelegramUI/Sources/ChatReplyCountItem.swift +++ /dev/null @@ -1,176 +0,0 @@ -import Foundation -import UIKit -import Postbox -import AsyncDisplayKit -import Display -import SwiftSignalKit -import TelegramPresentationData -import AccountContext - -private let titleFont = UIFont.systemFont(ofSize: 13.0) - -class ChatReplyCountItem: ListViewItem { - let index: MessageIndex - let isComments: Bool - let count: Int - let presentationData: ChatPresentationData - let header: ChatMessageDateHeader - - init(index: MessageIndex, isComments: Bool, count: Int, presentationData: ChatPresentationData, context: AccountContext) { - self.index = index - self.isComments = isComments - self.count = count - self.presentationData = presentationData - self.header = ChatMessageDateHeader(timestamp: index.timestamp, scheduled: false, presentationData: presentationData, context: context) - } - - func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { - async { - let node = ChatReplyCountItemNode() - node.layoutForParams(params, item: self, previousItem: previousItem, nextItem: nextItem) - Queue.mainQueue().async { - completion(node, { - return (nil, { _ in }) - }) - } - } - } - - public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { - Queue.mainQueue().async { - if let nodeValue = node() as? ChatReplyCountItemNode { - let nodeLayout = nodeValue.asyncLayout() - - async { - let dateAtBottom = !chatItemsHaveCommonDateHeader(self, nextItem) - - let (layout, apply) = nodeLayout(self, params, dateAtBottom) - Queue.mainQueue().async { - completion(layout, { _ in - apply() - }) - } - } - } else { - assertionFailure() - } - } - } -} - -class ChatReplyCountItemNode: ListViewItemNode { - var item: ChatReplyCountItem? - let backgroundNode: ASImageNode - let labelNode: TextNode - - private var theme: ChatPresentationThemeData? - - private let layoutConstants = ChatMessageItemLayoutConstants.default - - init() { - self.backgroundNode = ASImageNode() - self.backgroundNode.isLayerBacked = true - self.backgroundNode.displayWithoutProcessing = true - - self.labelNode = TextNode() - self.labelNode.isUserInteractionEnabled = false - - super.init(layerBacked: false, dynamicBounce: true, rotated: true) - - self.addSubnode(self.backgroundNode) - - self.addSubnode(self.labelNode) - - self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) - - self.scrollPositioningInsets = UIEdgeInsets(top: 5.0, left: 0.0, bottom: 6.0, right: 0.0) - self.canBeUsedAsScrollToItemAnchor = false - } - - override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { - super.animateInsertion(currentTimestamp, duration: duration, short: short) - - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - - override func animateAdded(_ currentTimestamp: Double, duration: Double) { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - - override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { - if let item = item as? ChatReplyCountItem { - let dateAtBottom = !chatItemsHaveCommonDateHeader(item, nextItem) - let (layout, apply) = self.asyncLayout()(item, params, dateAtBottom) - apply() - self.contentSize = layout.contentSize - self.insets = layout.insets - } - } - - func asyncLayout() -> (_ item: ChatReplyCountItem, _ params: ListViewItemLayoutParams, _ dateAtBottom: Bool) -> (ListViewItemNodeLayout, () -> Void) { - let labelLayout = TextNode.asyncLayout(self.labelNode) - let layoutConstants = self.layoutConstants - let currentTheme = self.theme - - return { item, params, dateAtBottom in - var updatedBackgroundImage: UIImage? - if currentTheme != item.presentationData.theme { - updatedBackgroundImage = PresentationResourcesChat.chatUnreadBarBackgroundImage(item.presentationData.theme.theme) - } - - let text: String - //TODO:localize - if item.isComments { - if item.count == 0 { - text = "Comments" - } else if item.count == 1 { - text = "1 Comment" - } else { - text = "\(item.count) Comments" - } - } else { - if item.count == 0 { - text = "Replies" - } else if item.count == 1 { - text = "1 Reply" - } else { - text = "\(item.count) Replies" - } - } - - let (size, apply) = labelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: text, font: titleFont, textColor: item.presentationData.theme.theme.chat.serviceMessage.unreadBarTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - - let backgroundSize = CGSize(width: params.width, height: 25.0) - - return (ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 25.0), insets: UIEdgeInsets(top: 6.0 + (dateAtBottom ? layoutConstants.timestampHeaderHeight : 0.0), left: 0.0, bottom: 5.0, right: 0.0)), { [weak self] in - if let strongSelf = self { - strongSelf.item = item - strongSelf.theme = item.presentationData.theme - - if let updatedBackgroundImage = updatedBackgroundImage { - strongSelf.backgroundNode.image = updatedBackgroundImage - } - - let _ = apply() - - strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backgroundSize) - strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - size.size.width) / 2.0), y: floorToScreenPixels((backgroundSize.height - size.size.height) / 2.0)), size: size.size) - } - }) - } - } - - override public func header() -> ListViewItemHeader? { - if let item = self.item { - return item.header - } else { - return nil - } - } - - override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { - super.animateRemoved(currentTimestamp, duration: duration) - - self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) - } -} diff --git a/submodules/TelegramUI/Sources/ChatScheduleTimeControllerNode.swift b/submodules/TelegramUI/Sources/ChatScheduleTimeControllerNode.swift index e9082e6f4d..d3b4346f7f 100644 --- a/submodules/TelegramUI/Sources/ChatScheduleTimeControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatScheduleTimeControllerNode.swift @@ -8,6 +8,7 @@ import SyncCore import TelegramPresentationData import TelegramStringFormatting import AccountContext +import ShareController import SolidRoundedButtonNode import PresentationDataUtils diff --git a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift index 2038a12862..8cbe14652a 100644 --- a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift @@ -189,10 +189,6 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode { canSearchMembers = false } } - if case .replyThread = interfaceState.chatLocation { - self.calendarButton.isHidden = true - canSearchMembers = false - } self.membersButton.isHidden = (!(interfaceState.search?.query.isEmpty ?? true)) || self.displayActivity || !canSearchMembers let resultsEnabled = (resultCount ?? 0) > 0 diff --git a/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift b/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift index e8f3da3c41..420496db61 100644 --- a/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift @@ -9,7 +9,6 @@ import TelegramPresentationData import SearchBarNode import LocalizedPeerData import SwiftSignalKit -import AccountContext private let searchBarFont = Font.regular(17.0) @@ -32,8 +31,10 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode { self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern) let placeholderText: String switch chatLocation { - case .peer, .replyThread: + case .peer: placeholderText = strings.Conversation_SearchPlaceholder + /*case .group: + placeholderText = "Search this feed"*/ } self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) @@ -54,10 +55,6 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode { self?.interaction.toggleMembersSearch(false) } - self.searchBar.clearTokens = { [weak self] in - self?.interaction.toggleMembersSearch(false) - } - if let statuses = interaction.statuses { self.searchingActivityDisposable = (statuses.searching |> deliverOnMainQueue).start(next: { [weak self] value in @@ -94,21 +91,23 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode { switch search.domain { case .everything: - self.searchBar.tokens = [] self.searchBar.prefixString = nil let placeholderText: String switch self.chatLocation { - case .peer, .replyThread: + case .peer: placeholderText = self.strings.Conversation_SearchPlaceholder + /*case .group: + placeholderText = "Search this feed"*/ } self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) case .members: - self.searchBar.tokens = [] self.searchBar.prefixString = NSAttributedString(string: strings.Conversation_SearchByName_Prefix, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputTextColor) self.searchBar.placeholderString = nil case let .member(peer): - self.searchBar.tokens = [SearchBarToken(id: peer.id, icon: UIImage(bundleImageName: "Chat List/Search/User"), title: peer.compactDisplayTitle)] - self.searchBar.prefixString = nil + let prefixString = NSMutableAttributedString() + prefixString.append(NSAttributedString(string: self.strings.Conversation_SearchByName_Prefix, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputTextColor)) + prefixString.append(NSAttributedString(string: "\(peer.compactDisplayTitle) ", font: searchBarFont, textColor: theme.rootController.navigationSearchBar.accentColor)) + self.searchBar.prefixString = prefixString self.searchBar.placeholderString = nil } diff --git a/submodules/TelegramUI/Sources/ChatSendMessageActionSheetController.swift b/submodules/TelegramUI/Sources/ChatSendMessageActionSheetController.swift index 0123da2cd2..4b420cf10f 100644 --- a/submodules/TelegramUI/Sources/ChatSendMessageActionSheetController.swift +++ b/submodules/TelegramUI/Sources/ChatSendMessageActionSheetController.swift @@ -69,11 +69,9 @@ final class ChatSendMessageActionSheetController: ViewController { var reminders = false var isSecret = false - var canSchedule = false if case let .peer(peerId) = self.interfaceState.chatLocation { reminders = peerId == context.account.peerId isSecret = peerId.namespace == Namespaces.Peer.SecretChat - canSchedule = !isSecret } self.displayNode = ChatSendMessageActionSheetControllerNode(context: self.context, reminders: reminders, gesture: gesture, sendButtonFrame: self.sendButtonFrame, textInputNode: self.textInputNode, forwardedCount: forwardedCount, send: { [weak self] in @@ -82,7 +80,7 @@ final class ChatSendMessageActionSheetController: ViewController { }, sendSilently: { [weak self] in self?.controllerInteraction?.sendCurrentMessage(true) self?.dismiss(cancel: false) - }, schedule: !canSchedule ? nil : { [weak self] in + }, schedule: isSecret ? nil : { [weak self] in self?.controllerInteraction?.scheduleCurrentMessage() self?.dismiss(cancel: false) }, cancel: { [weak self] in diff --git a/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift b/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift index 24283d889e..fa8a5881a7 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift @@ -8,11 +8,15 @@ import SwiftSignalKit import TelegramPresentationData import LegacyComponents import AccountContext -import ChatInterfaceState private let offsetThreshold: CGFloat = 10.0 private let dismissOffsetThreshold: CGFloat = 70.0 +enum ChatTextInputMediaRecordingButtonMode: Int32 { + case audio = 0 + case video = 1 +} + private func findTargetView(_ view: UIView, point: CGPoint) -> UIView? { if view.bounds.contains(point) && view.tag == 0x01f2bca { return view diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 8988126ecb..5a21dcb5f2 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -790,15 +790,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } else { placeholder = interfaceState.strings.Conversation_InputTextBroadcastPlaceholder } - } else if let channel = peer as? TelegramChannel, case .group = channel.info, channel.hasPermission(.canBeAnonymous) { - placeholder = interfaceState.strings.Conversation_InputTextAnonymousPlaceholder - } else if case let .replyThread(_, isChannelPost, _) = interfaceState.chatLocation { - //TODO:localize - if isChannelPost { - placeholder = "Comment" - } else { - placeholder = "Reply" - } } else { placeholder = interfaceState.strings.Conversation_InputTextPlaceholder } diff --git a/submodules/TelegramUI/Sources/ChatTitleView.swift b/submodules/TelegramUI/Sources/ChatTitleView.swift index dd226e10fe..8228544ff7 100644 --- a/submodules/TelegramUI/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Sources/ChatTitleView.swift @@ -18,13 +18,7 @@ import PhoneNumberFormat import ChatTitleActivityNode enum ChatTitleContent { - enum ReplyThreadType { - case replies - case comments - } - case peer(peerView: PeerView, onlineMemberCount: Int32?, isScheduledMessages: Bool) - case replyThread(type: ReplyThreadType, text: String) case group([Peer]) case custom(String) } @@ -107,12 +101,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView { var isEnabled = true switch titleContent { case let .peer(peerView, _, isScheduledMessages): - if peerView.peerId.isReplies { - //TODO:localize - let typeText: String = "Replies" - string = NSAttributedString(string: typeText, font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor) - isEnabled = false - } else if isScheduledMessages { + if isScheduledMessages { if peerView.peerId == self.account.peerId { string = NSAttributedString(string: self.strings.ScheduledMessages_RemindersTitle, font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor) } else { @@ -141,22 +130,6 @@ final class ChatTitleView: UIView, NavigationBarTitleView { } } } - case let .replyThread(type, text): - //TODO:localize - let typeText: String - if !text.isEmpty { - typeText = text - } else { - switch type { - case .comments: - typeText = "Comments" - case .replies: - typeText = "Replies" - } - } - - string = NSAttributedString(string: typeText, font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor) - isEnabled = false case .group: string = NSAttributedString(string: "Feed", font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor) case let .custom(text): @@ -196,7 +169,6 @@ final class ChatTitleView: UIView, NavigationBarTitleView { self.setNeedsLayout() } self.isUserInteractionEnabled = isEnabled - self.button.isUserInteractionEnabled = isEnabled self.updateStatus() } } @@ -208,7 +180,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView { switch titleContent { case let .peer(peerView, _, isScheduledMessages): if let peer = peerViewMainPeer(peerView) { - if peer.id == self.account.peerId || isScheduledMessages || peer.id.isReplies { + if peer.id == self.account.peerId || isScheduledMessages { inputActivitiesAllowed = false } } @@ -297,7 +269,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView { case let .peer(peerView, onlineMemberCount, isScheduledMessages): if let peer = peerViewMainPeer(peerView) { let servicePeer = isServicePeer(peer) - if peer.id == self.account.peerId || isScheduledMessages || peer.id.isReplies { + if peer.id == self.account.peerId || isScheduledMessages { let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor) state = .info(string, .generic) } else if let user = peer as? TelegramUser { @@ -629,9 +601,6 @@ final class ChatTitleView: UIView, NavigationBarTitleView { } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if !self.isUserInteractionEnabled { - return nil - } if self.button.frame.contains(point) { return self.button.view } diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift index 2d18b17a73..700919df2b 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift @@ -18,8 +18,6 @@ import CounterContollerTitleView private func peerTokenTitle(accountPeerId: PeerId, peer: Peer, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) -> String { if peer.id == accountPeerId { return strings.DialogList_SavedMessages - } else if peer.id.isReplies { - return strings.DialogList_Replies } else { return peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder) } diff --git a/submodules/TelegramUI/Sources/DeclareEncodables.swift b/submodules/TelegramUI/Sources/DeclareEncodables.swift index c469aa47f1..01c3ef9546 100644 --- a/submodules/TelegramUI/Sources/DeclareEncodables.swift +++ b/submodules/TelegramUI/Sources/DeclareEncodables.swift @@ -9,9 +9,7 @@ import WebSearchUI import InstantPageCache import SettingsUI import WallpaperResources -import MediaResources import LocationUI -import ChatInterfaceState private var telegramUIDeclaredEncodables: Void = { declareEncodable(InAppNotificationSettings.self, f: { InAppNotificationSettings(decoder: $0) }) diff --git a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift index 5bbb1c44de..31a2758560 100644 --- a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift +++ b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift @@ -144,7 +144,6 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in - }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, diff --git a/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift index 87c6738995..a849ec4517 100644 --- a/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift @@ -347,7 +347,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode { @objc func contentTap(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state, let message = self.currentMessage { - self.interfaceInteraction?.navigateToMessage(message.id, false) + self.interfaceInteraction?.navigateToMessage(message.id) } } } diff --git a/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift b/submodules/TelegramUI/Sources/FileMediaResourceStatus.swift similarity index 80% rename from submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift rename to submodules/TelegramUI/Sources/FileMediaResourceStatus.swift index 30b7e823eb..66f8e5198d 100644 --- a/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift +++ b/submodules/TelegramUI/Sources/FileMediaResourceStatus.swift @@ -7,12 +7,12 @@ import SwiftSignalKit import UniversalMediaPlayer import AccountContext -private func internalMessageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> Signal { +private func internalMessageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool) -> Signal { guard let playerType = peerMessageMediaPlayerType(message) else { return .single(nil) } - if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) { + if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions) { return context.sharedContext.mediaManager.filteredPlaylistState(accountId: context.account.id, playlistId: playlistId, itemId: itemId, type: playerType) |> mapToSignal { state -> Signal in return .single(state?.status) @@ -22,31 +22,31 @@ private func internalMessageFileMediaPlaybackStatus(context: AccountContext, fil } } -public func messageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> Signal { +func messageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool) -> Signal { var duration = 0.0 if let value = file.duration { duration = Double(value) } let defaultStatus = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) - return internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) |> map { status in + return internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions) |> map { status in return status ?? defaultStatus } } -public func messageFileMediaPlaybackAudioLevelEvents(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> Signal { +func messageFileMediaPlaybackAudioLevelEvents(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool) -> Signal { guard let playerType = peerMessageMediaPlayerType(message) else { return .never() } - if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) { + if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions) { return context.sharedContext.mediaManager.filteredPlayerAudioLevelEvents(accountId: context.account.id, playlistId: playlistId, itemId: itemId, type: playerType) } else { return .never() } } -public func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false) -> Signal { - let playbackStatus = internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) |> map { status -> MediaPlayerPlaybackStatus? in +func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false) -> Signal { + let playbackStatus = internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions) |> map { status -> MediaPlayerPlaybackStatus? in return status?.status } diff --git a/submodules/TelegramUI/Sources/GridMessageItem.swift b/submodules/TelegramUI/Sources/GridMessageItem.swift index 9d60e67869..46b058964b 100644 --- a/submodules/TelegramUI/Sources/GridMessageItem.swift +++ b/submodules/TelegramUI/Sources/GridMessageItem.swift @@ -14,7 +14,6 @@ import RadialStatusNode import PhotoResources import GridMessageSelectionNode import ContextUI -import ChatMessageInteractiveMediaBadge private func mediaForMessage(_ message: Message) -> Media? { for media in message.media { diff --git a/submodules/ListMessageItem/Sources/ListMessageDateHeader.swift b/submodules/TelegramUI/Sources/ListMessageDateHeader.swift similarity index 89% rename from submodules/ListMessageItem/Sources/ListMessageDateHeader.swift rename to submodules/TelegramUI/Sources/ListMessageDateHeader.swift index 8205218905..67f3523968 100644 --- a/submodules/ListMessageItem/Sources/ListMessageDateHeader.swift +++ b/submodules/TelegramUI/Sources/ListMessageDateHeader.swift @@ -16,7 +16,7 @@ private let timezoneOffset: Int32 = { return Int32(timeinfoNow.tm_gmtoff) }() -public func listMessageDateHeaderId(timestamp: Int32) -> Int64 { +func listMessageDateHeaderId(timestamp: Int32) -> Int64 { let unclippedValue: Int64 = min(Int64(Int32.max), Int64(timestamp) + Int64(timezoneOffset)) var time: time_t = time_t(Int32(clamping: unclippedValue)) @@ -28,7 +28,7 @@ public func listMessageDateHeaderId(timestamp: Int32) -> Int64 { return Int64(roundedTimestamp) } -public func listMessageDateHeaderInfo(timestamp: Int32) -> (year: Int32, month: Int32) { +func listMessageDateHeaderInfo(timestamp: Int32) -> (year: Int32, month: Int32) { var time: time_t = time_t(timestamp + timezoneOffset) var timeinfo: tm = tm() localtime_r(&time, &timeinfo) @@ -76,7 +76,7 @@ final class ListMessageDateHeader: ListViewItemHeader { } } -public final class ListMessageDateHeaderNode: ListViewItemHeaderNode { +final class ListMessageDateHeaderNode: ListViewItemHeaderNode { var theme: PresentationTheme var strings: PresentationStrings let headerNode: ListSectionHeaderNode @@ -99,7 +99,7 @@ public final class ListMessageDateHeaderNode: ListViewItemHeaderNode { self.headerNode.title = stringForMonth(strings: strings, month: month, ofYear: year).uppercased() } - public func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { self.theme = theme self.headerNode.updateTheme(theme: theme) @@ -109,7 +109,7 @@ public final class ListMessageDateHeaderNode: ListViewItemHeaderNode { self.setNeedsLayout() } - override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: size.height + UIScreenPixel)) self.headerNode.frame = headerFrame self.headerNode.updateLayout(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset) diff --git a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift b/submodules/TelegramUI/Sources/ListMessageFileItemNode.swift similarity index 81% rename from submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift rename to submodules/TelegramUI/Sources/ListMessageFileItemNode.swift index e625ab6971..fd4ea3088e 100644 --- a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift +++ b/submodules/TelegramUI/Sources/ListMessageFileItemNode.swift @@ -18,7 +18,6 @@ import PhotoResources import MusicAlbumArtResources import UniversalMediaPlayer import ContextUI -import FileMediaResourceStatus private let extensionImageCache = Atomic<[UInt32: UIImage]>(value: [:]) @@ -89,36 +88,6 @@ private func extensionImage(fileExtension: String?) -> UIImage? { } private let extensionFont = Font.with(size: 15.0, design: .round, traits: [.bold]) -func fullAuthorString(for item: ListMessageItem) -> String { - var authorString = "" - if let author = item.message.author, [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(item.message.id.peerId.namespace) { - var authorName = "" - if author.id == item.context.account.peerId { - authorName = item.presentationData.strings.DialogList_You - } else { - authorName = author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) - } - if let peer = item.message.peers[item.message.id.peerId], author.id != peer.id { - authorString = "\(authorName) → \(peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder))" - } else { - authorString = authorName - } - } else if let peer = item.message.peers[item.message.id.peerId] { - if item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel { - authorString = peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) - } else { - if item.message.id.peerId == item.context.account.peerId { - authorString = item.presentationData.strings.DialogList_SavedMessages - } else if item.message.flags.contains(.Incoming) { - authorString = peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) - } else { - authorString = "\(item.presentationData.strings.DialogList_You) → \(peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder))" - } - } - } - return authorString -} - private struct FetchControls { let fetch: () -> Void let cancel: () -> Void @@ -153,7 +122,7 @@ private enum FileIconImage: Equatable { } } -public final class ListMessageFileItemNode: ListMessageNode { +final class ListMessageFileItemNode: ListMessageNode { private let contextSourceNode: ContextExtractedContentContainingNode private let containerNode: ContextControllerSourceNode private let extractedBackgroundImageNode: ASImageNode @@ -171,7 +140,6 @@ public final class ListMessageFileItemNode: ListMessageNode { private let titleNode: TextNode private let descriptionNode: TextNode private let descriptionProgressNode: ImmediateTextNode - private let dateNode: TextNode private let extensionIconNode: ASImageNode private let extensionIconText: TextNode @@ -227,9 +195,6 @@ public final class ListMessageFileItemNode: ListMessageNode { self.descriptionProgressNode.isUserInteractionEnabled = false self.descriptionProgressNode.maximumNumberOfLines = 1 - self.dateNode = TextNode() - self.dateNode.isUserInteractionEnabled = false - self.extensionIconNode = ASImageNode() self.extensionIconNode.isLayerBacked = true self.extensionIconNode.displaysAsynchronously = false @@ -263,7 +228,6 @@ public final class ListMessageFileItemNode: ListMessageNode { self.offsetContainerNode.addSubnode(self.titleNode) self.offsetContainerNode.addSubnode(self.descriptionNode) self.offsetContainerNode.addSubnode(self.descriptionProgressNode) - self.offsetContainerNode.addSubnode(self.dateNode) self.offsetContainerNode.addSubnode(self.extensionIconNode) self.offsetContainerNode.addSubnode(self.extensionIconText) self.offsetContainerNode.addSubnode(self.iconStatusNode) @@ -273,7 +237,7 @@ public final class ListMessageFileItemNode: ListMessageNode { return } - item.interaction.openMessageContextMenu(item.message, false, strongSelf.contextSourceNode, strongSelf.contextSourceNode.bounds, gesture) + item.controllerInteraction.openMessageContextMenu(item.message, false, strongSelf.contextSourceNode, strongSelf.contextSourceNode.bounds, gesture) } self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in @@ -282,7 +246,7 @@ public final class ListMessageFileItemNode: ListMessageNode { } if isExtracted { - strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.presentationData.theme.theme.list.plainBackgroundColor) + strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.theme.list.plainBackgroundColor) } if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect { @@ -296,7 +260,6 @@ public final class ListMessageFileItemNode: ListMessageNode { self?.extractedBackgroundImageNode.image = nil } }) - transition.updateAlpha(node: strongSelf.dateNode, alpha: isExtracted ? 0.0 : 1.0) } } @@ -331,15 +294,14 @@ public final class ListMessageFileItemNode: ListMessageNode { self.addTransitionOffsetAnimation(0.0, duration: duration, beginAt: currentTimestamp) } - override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) } - override public func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) { + override func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) { let titleNodeMakeLayout = TextNode.asyncLayout(self.titleNode) let descriptionNodeMakeLayout = TextNode.asyncLayout(self.descriptionNode) let extensionIconTextMakeLayout = TextNode.asyncLayout(self.extensionIconText) - let dateNodeMakeLayout = TextNode.asyncLayout(self.dateNode) let iconImageLayout = self.iconImageNode.asyncLayout() let currentMedia = self.currentMedia @@ -353,14 +315,13 @@ public final class ListMessageFileItemNode: ListMessageNode { return { [weak self] item, params, _, _, dateHeaderAtBottom in var updatedTheme: PresentationTheme? - if currentItem?.presentationData.theme.theme !== item.presentationData.theme.theme { - updatedTheme = item.presentationData.theme.theme + if currentItem?.theme !== item.theme { + updatedTheme = item.theme } - let titleFont = Font.semibold(floor(item.presentationData.fontSize.baseDisplaySize * 16.0 / 17.0)) - let audioTitleFont = Font.semibold(floor(item.presentationData.fontSize.baseDisplaySize * 16.0 / 17.0)) - let descriptionFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)) - let dateFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) + let titleFont = Font.semibold(floor(item.fontSize.baseDisplaySize * 16.0 / 17.0)) + let audioTitleFont = Font.semibold(floor(item.fontSize.baseDisplaySize * 16.0 / 17.0)) + let descriptionFont = Font.regular(floor(item.fontSize.baseDisplaySize * 14.0 / 17.0)) var leftInset: CGFloat = 65.0 + params.leftInset let rightInset: CGFloat = 8.0 + params.rightInset @@ -368,7 +329,7 @@ public final class ListMessageFileItemNode: ListMessageNode { var leftOffset: CGFloat = 0.0 var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)? if case let .selectable(selected) = item.selection { - let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.theme.list.itemCheckColors.fillColor, item.presentationData.theme.theme.list.itemCheckColors.foregroundColor, selected, false) + let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected, false) selectionNodeWidthAndApply = (selectionWidth, selectionApply) leftOffset += selectionWidth } @@ -402,85 +363,61 @@ public final class ListMessageFileItemNode: ListMessageNode { isAudio = true isVoice = voice - titleText = NSAttributedString(string: title ?? (file.fileName ?? "Unknown Track"), font: audioTitleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor) + titleText = NSAttributedString(string: title ?? (file.fileName ?? "Unknown Track"), font: audioTitleFont, textColor: item.theme.list.itemPrimaryTextColor) - var descriptionString: String + let descriptionString: String if let performer = performer { - if item.isGlobalSearchResult { - descriptionString = performer - } else { - descriptionString = "\(stringForDuration(Int32(duration))) • \(performer)" - } + descriptionString = "\(stringForDuration(Int32(duration))) • \(performer)" } else if let size = file.size { - descriptionString = dataSizeString(size, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator) + descriptionString = dataSizeString(size, decimalSeparator: item.dateTimeFormat.decimalSeparator) } else { descriptionString = "" } - if item.isGlobalSearchResult { - let authorString = fullAuthorString(for: item) - if descriptionString.isEmpty { - descriptionString = authorString - } else { - descriptionString = "\(descriptionString) • \(authorString)" - } - } - - descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor) + descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor) if !voice { iconImage = .albumArt(file, SharedMediaPlaybackAlbumArt(thumbnailResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: true), fullSizeResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: false))) } else { - titleText = NSAttributedString(string: " ", font: audioTitleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor) - descriptionText = NSAttributedString(string: item.message.author?.displayTitle(strings: item.presentationData.strings, displayOrder: .firstLast) ?? " ", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor) + titleText = NSAttributedString(string: " ", font: audioTitleFont, textColor: item.theme.list.itemPrimaryTextColor) + descriptionText = NSAttributedString(string: item.message.author?.displayTitle(strings: item.strings, displayOrder: .firstLast) ?? " ", font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor) } } } if isInstantVideo || isVoice { - var authorName: String + let authorName: String if let author = message.forwardInfo?.author { if author.id == item.context.account.peerId { - authorName = item.presentationData.strings.DialogList_You + authorName = item.strings.DialogList_You } else { - authorName = author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) + authorName = author.displayTitle(strings: item.strings, displayOrder: .firstLast) } } else if let signature = message.forwardInfo?.authorSignature { authorName = signature } else if let author = message.author { if author.id == item.context.account.peerId { - authorName = item.presentationData.strings.DialogList_You + authorName = item.strings.DialogList_You } else { - authorName = author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) + authorName = author.displayTitle(strings: item.strings, displayOrder: .firstLast) } } else { authorName = " " } - - if item.isGlobalSearchResult { - authorName = fullAuthorString(for: item) - } - - titleText = NSAttributedString(string: authorName, font: audioTitleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor) - let dateString = stringForFullDate(timestamp: item.message.timestamp, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat) - var descriptionString: String = "" + titleText = NSAttributedString(string: authorName, font: audioTitleFont, textColor: item.theme.list.itemPrimaryTextColor) + let dateString = stringForFullDate(timestamp: item.message.timestamp, strings: item.strings, dateTimeFormat: item.dateTimeFormat) + let descriptionString: String if let duration = file.duration { - if item.isGlobalSearchResult { - descriptionString = stringForDuration(Int32(duration)) - } else { - descriptionString = "\(stringForDuration(Int32(duration))) • \(dateString)" - } + descriptionString = "\(stringForDuration(Int32(duration))) • \(dateString)" } else { - if !item.isGlobalSearchResult { - descriptionString = dateString - } + descriptionString = dateString } - descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor) + descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor) iconImage = .roundVideo(file) } else if !isAudio { let fileName: String = file.fileName ?? "" - titleText = NSAttributedString(string: fileName, font: titleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor) + titleText = NSAttributedString(string: fileName, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor) var fileExtension: String? if let range = fileName.range(of: ".", options: [.backwards]) { @@ -495,31 +432,16 @@ public final class ListMessageFileItemNode: ListMessageNode { iconImage = .imageRepresentation(file, representation) } - let dateString = stringForFullDate(timestamp: item.message.timestamp, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat) + let dateString = stringForFullDate(timestamp: item.message.timestamp, strings: item.strings, dateTimeFormat: item.dateTimeFormat) - var descriptionString: String = "" + let descriptionString: String if let size = file.size { - if item.isGlobalSearchResult { - descriptionString = (dataSizeString(size, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)) - } else { - descriptionString = "\(dataSizeString(size, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)) • \(dateString)" - } + descriptionString = "\(dataSizeString(size, decimalSeparator: item.dateTimeFormat.decimalSeparator)) • \(dateString)" } else { - if !item.isGlobalSearchResult { - descriptionString = "\(dateString)" - } - } - - if item.isGlobalSearchResult { - let authorString = fullAuthorString(for: item) - if descriptionString.isEmpty { - descriptionString = authorString - } else { - descriptionString = "\(descriptionString) • \(authorString)" - } + descriptionString = "\(dateString)" } - descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor) + descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor) } break @@ -556,7 +478,7 @@ public final class ListMessageFileItemNode: ListMessageNode { } if statusUpdated { - updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult) + updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false, isSharedMedia: true) if isAudio || isInstantVideo { if let currentUpdatedStatusSignal = updatedStatusSignal { @@ -572,20 +494,14 @@ public final class ListMessageFileItemNode: ListMessageNode { } } if isVoice { - updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false, isGlobalSearch: item.isGlobalSearchResult) + updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false) } } } - let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - let dateText = stringForRelativeTimestamp(strings: item.presentationData.strings, relativeTimestamp: item.message.timestamp, relativeTo: timestamp, dateTimeFormat: item.presentationData.dateTimeFormat) - let dateAttributedString = NSAttributedString(string: dateText, font: dateFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor) + let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(TextNodeLayoutArguments(attributedString: titleText, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 40.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (dateNodeLayout, dateNodeApply) = dateNodeMakeLayout(TextNodeLayoutArguments(attributedString: dateAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 12.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - - let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(TextNodeLayoutArguments(attributedString: titleText, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - leftOffset - rightInset - dateNodeLayout.size.width - 4.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - - let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 12.0 - 40.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (extensionTextLayout, extensionTextApply) = extensionIconTextMakeLayout(TextNodeLayoutArguments(attributedString: extensionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 38.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -595,17 +511,17 @@ public final class ListMessageFileItemNode: ListMessageNode { case let .imageRepresentation(_, representation): let iconSize = CGSize(width: 40.0, height: 40.0) let imageCorners = ImageCorners(radius: 6.0) - let arguments = TransformImageArguments(corners: imageCorners, imageSize: representation.dimensions.cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.presentationData.theme.theme.list.mediaPlaceholderColor) + let arguments = TransformImageArguments(corners: imageCorners, imageSize: representation.dimensions.cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor) iconImageApply = iconImageLayout(arguments) case .albumArt: let iconSize = CGSize(width: 40.0, height: 40.0) let imageCorners = ImageCorners(radius: iconSize.width / 2.0) - let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.presentationData.theme.theme.list.mediaPlaceholderColor) + let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor) iconImageApply = iconImageLayout(arguments) case let .roundVideo(file): let iconSize = CGSize(width: 40.0, height: 40.0) let imageCorners = ImageCorners(radius: iconSize.width / 2.0) - let arguments = TransformImageArguments(corners: imageCorners, imageSize: (file.dimensions ?? PixelDimensions(width: 320, height: 320)).cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.presentationData.theme.theme.list.mediaPlaceholderColor) + let arguments = TransformImageArguments(corners: imageCorners, imageSize: (file.dimensions ?? PixelDimensions(width: 320, height: 320)).cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor) iconImageApply = iconImageLayout(arguments) } } @@ -616,7 +532,7 @@ public final class ListMessageFileItemNode: ListMessageNode { case let .imageRepresentation(file, representation): updateIconImageSignal = chatWebpageSnippetFile(account: item.context.account, fileReference: .message(message: MessageReference(message), media: file), representation: representation) case let .albumArt(file, albumArt): - updateIconImageSignal = playerAlbumArt(postbox: item.context.account.postbox, fileReference: .message(message: MessageReference(message), media: file), albumArt: albumArt, thumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3), emptyColor: item.presentationData.theme.theme.list.itemAccentColor) + updateIconImageSignal = playerAlbumArt(postbox: item.context.account.postbox, fileReference: .message(message: MessageReference(message), media: file), albumArt: albumArt, thumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3), emptyColor: item.theme.list.itemAccentColor) case let .roundVideo(file): updateIconImageSignal = mediaGridMessageVideo(postbox: item.context.account.postbox, videoReference: FileMediaReference.message(message: MessageReference(message), media: file), autoFetchFullSizeThumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3)) } @@ -667,9 +583,9 @@ public final class ListMessageFileItemNode: ListMessageNode { strongSelf.currentLeftOffset = leftOffset if let _ = updatedTheme { - strongSelf.separatorNode.backgroundColor = item.presentationData.theme.theme.list.itemPlainSeparatorColor - strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.theme.list.itemHighlightedBackgroundColor - strongSelf.linearProgressNode?.updateTheme(theme: item.presentationData.theme.theme) + strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor + strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor + strongSelf.linearProgressNode?.updateTheme(theme: item.theme) } if let (selectionWidth, selectionApply) = selectionNodeWidthAndApply { @@ -716,10 +632,6 @@ public final class ListMessageFileItemNode: ListMessageNode { transition.updateFrame(node: strongSelf.descriptionNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset, y: strongSelf.titleNode.frame.maxY + 1.0), size: descriptionNodeLayout.size)) let _ = descriptionNodeApply() - let _ = dateNodeApply() - transition.updateFrame(node: strongSelf.dateNode, frame: CGRect(origin: CGPoint(x: params.width - rightInset - dateNodeLayout.size.width, y: 11.0), size: dateNodeLayout.size)) - strongSelf.dateNode.isHidden = !item.isGlobalSearchResult - let iconFrame: CGRect if isAudio { let iconSize = CGSize(width: 40.0, height: 40.0) @@ -820,8 +732,8 @@ public final class ListMessageFileItemNode: ListMessageNode { var iconStatusForegroundColor: UIColor = .white if isVoice { - iconStatusBackgroundColor = item.presentationData.theme.theme.list.itemAccentColor - iconStatusForegroundColor = item.presentationData.theme.theme.list.itemCheckColors.foregroundColor + iconStatusBackgroundColor = item.theme.list.itemAccentColor + iconStatusForegroundColor = item.theme.list.itemCheckColors.foregroundColor } if !isAudio && !isInstantVideo { @@ -855,7 +767,7 @@ public final class ListMessageFileItemNode: ListMessageNode { self.iconStatusNode.transitionToState(iconStatusState) } - override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { + override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { super.setHighlighted(highlighted, at: point, animated: animated) if highlighted, let item = self.item, case .none = item.selection { @@ -881,7 +793,7 @@ public final class ListMessageFileItemNode: ListMessageNode { } } - override public func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if let item = self.item, item.message.id == id, self.iconImageNode.supernode != nil { let iconImageNode = self.iconImageNode return (self.iconImageNode, self.iconImageNode.bounds, { [weak iconImageNode] in @@ -891,15 +803,15 @@ public final class ListMessageFileItemNode: ListMessageNode { return nil } - override public func updateHiddenMedia() { - if let interaction = self.interaction, let item = self.item, interaction.getHiddenMedia()[item.message.id] != nil { + override func updateHiddenMedia() { + if let controllerInteraction = self.controllerInteraction, let item = self.item, controllerInteraction.hiddenMedia[item.message.id] != nil { self.iconImageNode.isHidden = true } else { self.iconImageNode.isHidden = false } } - override public func updateSelectionState(animated: Bool) { + override func updateSelectionState(animated: Bool) { } private func updateProgressFrame(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { @@ -919,7 +831,7 @@ public final class ListMessageFileItemNode: ListMessageNode { switch fetchStatus { case let .Fetching(_, progress): if let file = self.currentMedia as? TelegramMediaFile, let size = file.size { - downloadingString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)) / \(dataSizeString(size, forceDecimal: true, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator))" + downloadingString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, decimalSeparator: item.dateTimeFormat.decimalSeparator)) / \(dataSizeString(size, forceDecimal: true, decimalSeparator: item.dateTimeFormat.decimalSeparator))" } descriptionOffset = 14.0 case .Remote: @@ -937,7 +849,7 @@ public final class ListMessageFileItemNode: ListMessageNode { linearProgressNode = current } else { linearProgressNode = LinearProgressNode() - linearProgressNode.updateTheme(theme: item.presentationData.theme.theme) + linearProgressNode.updateTheme(theme: item.theme) self.linearProgressNode = linearProgressNode self.addSubnode(linearProgressNode) } @@ -947,7 +859,7 @@ public final class ListMessageFileItemNode: ListMessageNode { if self.downloadStatusIconNode.supernode == nil { self.offsetContainerNode.addSubnode(self.downloadStatusIconNode) } - self.downloadStatusIconNode.image = PresentationResourcesChat.sharedMediaFileDownloadPauseIcon(item.presentationData.theme.theme) + self.downloadStatusIconNode.image = PresentationResourcesChat.sharedMediaFileDownloadPauseIcon(item.theme) case .Local: if let linearProgressNode = self.linearProgressNode { self.linearProgressNode = nil @@ -971,7 +883,7 @@ public final class ListMessageFileItemNode: ListMessageNode { if self.downloadStatusIconNode.supernode == nil { self.offsetContainerNode.addSubnode(self.downloadStatusIconNode) } - self.downloadStatusIconNode.image = PresentationResourcesChat.sharedMediaFileDownloadStartIcon(item.presentationData.theme.theme) + self.downloadStatusIconNode.image = PresentationResourcesChat.sharedMediaFileDownloadStartIcon(item.theme) } } else { if let linearProgressNode = self.linearProgressNode { @@ -999,8 +911,8 @@ public final class ListMessageFileItemNode: ListMessageNode { self.descriptionProgressNode.isHidden = true self.descriptionNode.isHidden = false } - let descriptionFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 13.0 / 17.0)) - self.descriptionProgressNode.attributedText = NSAttributedString(string: downloadingString ?? "", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor) + let descriptionFont = Font.regular(floor(item.fontSize.baseDisplaySize * 13.0 / 17.0)) + self.descriptionProgressNode.attributedText = NSAttributedString(string: downloadingString ?? "", font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor) let descriptionSize = self.descriptionProgressNode.updateLayout(CGSize(width: size.width - 14.0, height: size.height)) transition.updateFrame(node: self.descriptionProgressNode, frame: CGRect(origin: self.descriptionNode.frame.origin, size: descriptionSize)) @@ -1024,8 +936,8 @@ public final class ListMessageFileItemNode: ListMessageNode { fetch() } case .Local: - if let item = self.item, let interaction = self.interaction { - let _ = interaction.openMessage(item.message, .default) + if let item = self.item, let controllerInteraction = self.controllerInteraction { + let _ = controllerInteraction.openMessage(item.message, .default) } } case .playbackStatus: @@ -1036,11 +948,11 @@ public final class ListMessageFileItemNode: ListMessageNode { } } - override public func header() -> ListViewItemHeader? { + override func header() -> ListViewItemHeader? { return self.item?.header } - override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let item = self.item, case .selectable = item.selection { if self.bounds.contains(point) { return self.view diff --git a/submodules/ListMessageItem/Sources/ListMessageHoleItem.swift b/submodules/TelegramUI/Sources/ListMessageHoleItem.swift similarity index 100% rename from submodules/ListMessageItem/Sources/ListMessageHoleItem.swift rename to submodules/TelegramUI/Sources/ListMessageHoleItem.swift diff --git a/submodules/ListMessageItem/Sources/ListMessageItem.swift b/submodules/TelegramUI/Sources/ListMessageItem.swift similarity index 61% rename from submodules/ListMessageItem/Sources/ListMessageItem.swift rename to submodules/TelegramUI/Sources/ListMessageItem.swift index dfd0e4ef98..b3264a7cad 100644 --- a/submodules/ListMessageItem/Sources/ListMessageItem.swift +++ b/submodules/TelegramUI/Sources/ListMessageItem.swift @@ -10,73 +10,51 @@ import TelegramPresentationData import AccountContext import TelegramUIPreferences -public final class ListMessageItemInteraction { - let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool - let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void - let toggleMessagesSelection: ([MessageId], Bool) -> Void - let openUrl: (String, Bool, Bool?, Message?) -> Void - let openInstantPage: (Message, ChatMessageItemAssociatedData?) -> Void - let longTap: (ChatControllerInteractionLongTapAction, Message?) -> Void - let getHiddenMedia: () -> [MessageId: [Media]] - - public init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, getHiddenMedia: @escaping () -> [MessageId: [Media]]) { - self.openMessage = openMessage - self.openMessageContextMenu = openMessageContextMenu - self.toggleMessagesSelection = toggleMessagesSelection - self.openUrl = openUrl - self.openInstantPage = openInstantPage - self.longTap = longTap - self.getHiddenMedia = getHiddenMedia - } -} - -public final class ListMessageItem: ListViewItem { - let presentationData: ChatPresentationData +final class ListMessageItem: ListViewItem { + let theme: PresentationTheme + let strings: PresentationStrings + let fontSize: PresentationFontSize + let dateTimeFormat: PresentationDateTimeFormat let context: AccountContext let chatLocation: ChatLocation - let interaction: ListMessageItemInteraction + let controllerInteraction: ChatControllerInteraction let message: Message let selection: ChatHistoryMessageSelection - let hintIsLink: Bool - let isGlobalSearchResult: Bool - let header: ListViewItemHeader? + let header: ListMessageDateHeader? - public let selectable: Bool = true + let selectable: Bool = true - public init(presentationData: ChatPresentationData, context: AccountContext, chatLocation: ChatLocation, interaction: ListMessageItemInteraction, message: Message, selection: ChatHistoryMessageSelection, displayHeader: Bool, customHeader: ListViewItemHeader? = nil, hintIsLink: Bool = false, isGlobalSearchResult: Bool = false) { - self.presentationData = presentationData + public init(theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, context: AccountContext, chatLocation: ChatLocation, controllerInteraction: ChatControllerInteraction, message: Message, selection: ChatHistoryMessageSelection, displayHeader: Bool) { + self.theme = theme + self.strings = strings + self.fontSize = fontSize + self.dateTimeFormat = dateTimeFormat self.context = context self.chatLocation = chatLocation - self.interaction = interaction + self.controllerInteraction = controllerInteraction self.message = message - if let header = customHeader { - self.header = header - } else if displayHeader { - self.header = ListMessageDateHeader(timestamp: message.timestamp, theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize) + if displayHeader { + self.header = ListMessageDateHeader(timestamp: message.timestamp, theme: theme, strings: strings, fontSize: fontSize) } else { self.header = nil } self.selection = selection - self.hintIsLink = hintIsLink - self.isGlobalSearchResult = isGlobalSearchResult } public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { var viewClassName: AnyClass = ListMessageSnippetItemNode.self - if !self.hintIsLink { - for media in self.message.media { - if let _ = media as? TelegramMediaFile { - viewClassName = ListMessageFileItemNode.self - break - } + for media in message.media { + if let _ = media as? TelegramMediaFile { + viewClassName = ListMessageFileItemNode.self + break } } let configure = { () -> Void in let node = (viewClassName as! ListMessageNode.Type).init() - node.interaction = self.interaction + node.controllerInteraction = self.controllerInteraction node.setupItem(self) let nodeLayout = node.asyncLayout() @@ -128,11 +106,11 @@ public final class ListMessageItem: ListViewItem { } } - public func selected(listView: ListView) { + func selected(listView: ListView) { listView.clearHighlightAnimated(true) if case let .selectable(selected) = self.selection { - self.interaction.toggleMessagesSelection([self.message.id], !selected) + self.controllerInteraction.toggleMessagesSelection([self.message.id], !selected) } else { listView.forEachItemNode { itemNode in if let itemNode = itemNode as? ListMessageFileItemNode { diff --git a/submodules/ListMessageItem/Sources/ListMessageNode.swift b/submodules/TelegramUI/Sources/ListMessageNode.swift similarity index 56% rename from submodules/ListMessageItem/Sources/ListMessageNode.swift rename to submodules/TelegramUI/Sources/ListMessageNode.swift index 7c3cea93b0..f110ca203e 100644 --- a/submodules/ListMessageItem/Sources/ListMessageNode.swift +++ b/submodules/TelegramUI/Sources/ListMessageNode.swift @@ -3,11 +3,10 @@ import UIKit import Display import AsyncDisplayKit import Postbox -import AccountContext -public class ListMessageNode: ListViewItemNode { +class ListMessageNode: ListViewItemNode { var item: ListMessageItem? - var interaction: ListMessageItemInteraction? + var controllerInteraction: ChatControllerInteraction? required init() { super.init(layerBacked: false, dynamicBounce: false) @@ -20,7 +19,7 @@ public class ListMessageNode: ListViewItemNode { override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { } - public func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) { + func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) { return { _, params, _, _, _ in return (ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 1.0), insets: UIEdgeInsets()), { _ in @@ -28,13 +27,13 @@ public class ListMessageNode: ListViewItemNode { } } - public func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } - public func updateHiddenMedia() { + func updateHiddenMedia() { } - public func updateSelectionState(animated: Bool) { + func updateSelectionState(animated: Bool) { } } diff --git a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift b/submodules/TelegramUI/Sources/ListMessageSnippetItemNode.swift similarity index 73% rename from submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift rename to submodules/TelegramUI/Sources/ListMessageSnippetItemNode.swift index 7d4706b13b..773aadf8af 100644 --- a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift +++ b/submodules/TelegramUI/Sources/ListMessageSnippetItemNode.swift @@ -13,15 +13,12 @@ import TextFormat import PhotoResources import WebsiteType import UrlHandling -import UrlWhitelist -import AccountContext -import TelegramStringFormatting private let iconFont = Font.with(size: 30.0, design: .round, traits: [.bold]) private let iconTextBackgroundImage = generateStretchableFilledCircleImage(radius: 6.0, color: UIColor(rgb: 0xFF9500)) -public final class ListMessageSnippetItemNode: ListMessageNode { +final class ListMessageSnippetItemNode: ListMessageNode { private let contextSourceNode: ContextExtractedContentContainingNode private let containerNode: ContextControllerSourceNode private let extractedBackgroundImageNode: ASImageNode @@ -37,11 +34,9 @@ public final class ListMessageSnippetItemNode: ListMessageNode { private let titleNode: TextNode private let descriptionNode: TextNode - private let dateNode: TextNode private let instantViewIconNode: ASImageNode private let linkNode: TextNode private var linkHighlightingNode: LinkHighlightingNode? - private let authorNode: TextNode private let iconTextBackgroundNode: ASImageNode private let iconTextNode: TextNode @@ -49,7 +44,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode { private var currentIconImageRepresentation: TelegramMediaImageRepresentation? private var currentMedia: Media? - public var currentPrimaryUrl: String? + var currentPrimaryUrl: String? private var currentIsInstantView: Bool? private var appliedItem: ListMessageItem? @@ -77,9 +72,6 @@ public final class ListMessageSnippetItemNode: ListMessageNode { self.descriptionNode = TextNode() self.descriptionNode.isUserInteractionEnabled = false - self.dateNode = TextNode() - self.dateNode.isUserInteractionEnabled = false - self.instantViewIconNode = ASImageNode() self.instantViewIconNode.isLayerBacked = true self.instantViewIconNode.displaysAsynchronously = false @@ -98,9 +90,6 @@ public final class ListMessageSnippetItemNode: ListMessageNode { self.iconImageNode = TransformImageNode() self.iconImageNode.displaysAsynchronously = false - self.authorNode = TextNode() - self.authorNode.isUserInteractionEnabled = false - super.init() self.addSubnode(self.separatorNode) @@ -113,18 +102,16 @@ public final class ListMessageSnippetItemNode: ListMessageNode { self.contextSourceNode.contentNode.addSubnode(self.offsetContainerNode) self.offsetContainerNode.addSubnode(self.titleNode) self.offsetContainerNode.addSubnode(self.descriptionNode) - self.offsetContainerNode.addSubnode(self.dateNode) self.offsetContainerNode.addSubnode(self.linkNode) self.offsetContainerNode.addSubnode(self.instantViewIconNode) self.offsetContainerNode.addSubnode(self.iconImageNode) - self.offsetContainerNode.addSubnode(self.authorNode) self.containerNode.activated = { [weak self] gesture, _ in guard let strongSelf = self, let item = strongSelf.item else { return } - item.interaction.openMessageContextMenu(item.message, false, strongSelf.contextSourceNode, strongSelf.contextSourceNode.bounds, gesture) + item.controllerInteraction.openMessageContextMenu(item.message, false, strongSelf.contextSourceNode, strongSelf.contextSourceNode.bounds, gesture) } self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in @@ -133,7 +120,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode { } if isExtracted { - strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.presentationData.theme.theme.list.plainBackgroundColor) + strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.theme.list.plainBackgroundColor) } if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect { @@ -148,7 +135,6 @@ public final class ListMessageSnippetItemNode: ListMessageNode { self?.extractedBackgroundImageNode.image = nil } }) - transition.updateAlpha(node: strongSelf.dateNode, alpha: isExtracted ? 0.0 : 1.0) } } @@ -156,7 +142,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode { fatalError("init(coder:) has not been implemented") } - override public func didLoad() { + override func didLoad() { super.didLoad() let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) @@ -196,19 +182,17 @@ public final class ListMessageSnippetItemNode: ListMessageNode { self.addTransitionOffsetAnimation(0.0, duration: duration, beginAt: currentTimestamp) } - override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) } - override public func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) { + override func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) { let titleNodeMakeLayout = TextNode.asyncLayout(self.titleNode) let descriptionNodeMakeLayout = TextNode.asyncLayout(self.descriptionNode) let linkNodeMakeLayout = TextNode.asyncLayout(self.linkNode) - let dateNodeMakeLayout = TextNode.asyncLayout(self.dateNode) let iconTextMakeLayout = TextNode.asyncLayout(self.iconTextNode) let iconImageLayout = self.iconImageNode.asyncLayout() - let authorNodeMakeLayout = TextNode.asyncLayout(self.authorNode) - + let currentIconImageRepresentation = self.currentIconImageRepresentation let currentItem = self.appliedItem @@ -218,21 +202,19 @@ public final class ListMessageSnippetItemNode: ListMessageNode { return { [weak self] item, params, _, _, dateHeaderAtBottom in var updatedTheme: PresentationTheme? - if currentItem?.presentationData.theme.theme !== item.presentationData.theme.theme { - updatedTheme = item.presentationData.theme.theme + if currentItem?.theme !== item.theme { + updatedTheme = item.theme } - let titleFont = Font.semibold(floor(item.presentationData.fontSize.baseDisplaySize * 16.0 / 17.0)) - let descriptionFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)) - let dateFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) - let authorFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)) + let titleFont = Font.semibold(floor(item.fontSize.baseDisplaySize * 16.0 / 17.0)) + let descriptionFont = Font.regular(floor(item.fontSize.baseDisplaySize * 14.0 / 17.0)) let leftInset: CGFloat = 65.0 + params.leftInset var leftOffset: CGFloat = 0.0 var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)? if case let .selectable(selected) = item.selection { - let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.theme.list.itemCheckColors.fillColor, item.presentationData.theme.theme.list.itemCheckColors.foregroundColor, selected, false) + let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected, false) selectionNodeWidthAndApply = (selectionWidth, selectionApply) leftOffset += selectionWidth } @@ -273,7 +255,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode { iconText = NSAttributedString(string: host[.. nsString.length { - range.location = max(0, nsString.length - range.length) - range.length = nsString.length - range.location - } - let tempTitleString = (nsString.substring(with: range) as String).trimmingCharacters(in: .whitespacesAndNewlines) - - var (urlString, concealed) = parseUrl(url: url, wasConcealed: false) - let rawUrlString = urlString - var parsedUrl = URL(string: urlString) - if parsedUrl == nil || parsedUrl!.host == nil || parsedUrl!.host!.isEmpty { - urlString = "http://" + urlString - parsedUrl = URL(string: urlString) - } - let host: String? = concealed ? urlString : parsedUrl?.host - if let url = parsedUrl, let host = host { - primaryUrl = urlString - title = NSAttributedString(string: tempTitleString as String, font: titleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor) - if url.path.hasPrefix("/addstickers/") { - iconText = NSAttributedString(string: "S", font: iconFont, textColor: UIColor.white) - } else { - iconText = NSAttributedString(string: host[.. (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if let item = self.item, item.message.id == id, self.iconImageNode.supernode != nil { let iconImageNode = self.iconImageNode return (self.iconImageNode, self.iconImageNode.bounds, { [weak iconImageNode] in @@ -667,15 +564,15 @@ public final class ListMessageSnippetItemNode: ListMessageNode { return nil } - override public func updateHiddenMedia() { - if let interaction = self.interaction, let item = self.item, interaction.getHiddenMedia()[item.message.id] != nil { + override func updateHiddenMedia() { + if let controllerInteraction = self.controllerInteraction, let item = self.item, controllerInteraction.hiddenMedia[item.message.id] != nil { self.iconImageNode.isHidden = true } else { self.iconImageNode.isHidden = false } } - override public func updateSelectionState(animated: Bool) { + override func updateSelectionState(animated: Bool) { } func activateMedia() { @@ -683,28 +580,30 @@ public final class ListMessageSnippetItemNode: ListMessageNode { if let webpage = self.currentMedia as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { if content.instantPage != nil { if websiteType(of: content.websiteName) == .instagram { - if !item.interaction.openMessage(item.message, .default) { - item.interaction.openInstantPage(item.message, nil) + if !item.controllerInteraction.openMessage(item.message, .default) { + item.controllerInteraction.openInstantPage(item.message, nil) } } else { - item.interaction.openInstantPage(item.message, nil) + item.controllerInteraction.openInstantPage(item.message, nil) } } else { - if isTelegramMeLink(content.url) || !item.interaction.openMessage(item.message, .link) { - item.interaction.openUrl(currentPrimaryUrl, false, false, nil) + if isTelegramMeLink(content.url) || !item.controllerInteraction.openMessage(item.message, .link) { + item.controllerInteraction.openUrl(currentPrimaryUrl, false, false, nil) } } } else { - item.interaction.openUrl(currentPrimaryUrl, false, false, nil) + if !item.controllerInteraction.openMessage(item.message, .default) { + item.controllerInteraction.openUrl(currentPrimaryUrl, false, false, nil) + } } } } - override public func header() -> ListViewItemHeader? { + override func header() -> ListViewItemHeader? { return self.item?.header } - override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let item = self.item, case .selectable = item.selection { if self.bounds.contains(point) { return self.view @@ -741,13 +640,13 @@ public final class ListMessageSnippetItemNode: ListMessageNode { case .tap, .longTap: if let item = self.item, let url = self.urlAtPoint(location) { if case .longTap = gesture { - item.interaction.longTap(ChatControllerInteractionLongTapAction.url(url), item.message) + item.controllerInteraction.longTap(ChatControllerInteractionLongTapAction.url(url), item.message) } else if url == self.currentPrimaryUrl { - if !item.interaction.openMessage(item.message, .default) { - item.interaction.openUrl(url, false, false, nil) + if !item.controllerInteraction.openMessage(item.message, .default) { + item.controllerInteraction.openUrl(url, false, false, nil) } } else { - item.interaction.openUrl(url, false, true, nil) + item.controllerInteraction.openUrl(url, false, true, nil) } } case .hold, .doubleTap: @@ -784,7 +683,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode { if let current = self.linkHighlightingNode { linkHighlightingNode = current } else { - linkHighlightingNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor) + linkHighlightingNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.theme.chat.message.incoming.linkHighlightColor : item.theme.chat.message.outgoing.linkHighlightColor) self.linkHighlightingNode = linkHighlightingNode self.offsetContainerNode.insertSubnode(linkHighlightingNode, belowSubnode: self.linkNode) } diff --git a/submodules/TelegramUI/Sources/ManageSharedAccountInfo.swift b/submodules/TelegramUI/Sources/ManageSharedAccountInfo.swift index 396a494187..49ee068a3a 100644 --- a/submodules/TelegramUI/Sources/ManageSharedAccountInfo.swift +++ b/submodules/TelegramUI/Sources/ManageSharedAccountInfo.swift @@ -22,7 +22,7 @@ private func accountInfo(account: Account) -> Signal var datacenters: [Int32: AccountDatacenterInfo] = [:] for nId in context.knownDatacenterIds() { if let id = nId as? Int { - if let authInfo = context.authInfoForDatacenter(withId: id, selector: .persistent), let authKey = authInfo.authKey { + if let authInfo = context.authInfoForDatacenter(withId: id), let authKey = authInfo.authKey { let transportScheme = context.chooseTransportSchemeForConnection(toDatacenterId: id, schemes: context.transportSchemesForDatacenter(withId: id, media: true, enforceMedia: false, isProxy: false)) var addressList: [AccountDatacenterAddress] = [] if let transportScheme = transportScheme, let address = transportScheme.address, let host = address.host { diff --git a/submodules/TelegramUI/Sources/MediaManager.swift b/submodules/TelegramUI/Sources/MediaManager.swift index 988498fede..24ab8e3734 100644 --- a/submodules/TelegramUI/Sources/MediaManager.swift +++ b/submodules/TelegramUI/Sources/MediaManager.swift @@ -13,7 +13,6 @@ import TelegramUIPreferences import AccountContext import TelegramUniversalVideoContent import DeviceProximity -import MediaResources enum SharedMediaPlayerGroup: Int { case music = 0 diff --git a/submodules/MediaResources/Sources/MediaPlaybackStoredState.swift b/submodules/TelegramUI/Sources/MediaPlaybackStoredState.swift similarity index 100% rename from submodules/MediaResources/Sources/MediaPlaybackStoredState.swift rename to submodules/TelegramUI/Sources/MediaPlaybackStoredState.swift diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 04c124c03a..83b2486125 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -125,10 +125,6 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam if message.id.peerId == peerId { return true } - case let .replyThread(messageId, _, _): - if message.id.peerId == messageId.peerId { - return true - } } } } diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index 4458faf3be..fed47ab635 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -21,11 +21,229 @@ import AlertUI import PresentationDataUtils import ShareController import UndoUI -import WebsiteType -import GalleryData + +private enum ChatMessageGalleryControllerData { + case url(String) + case pass(TelegramMediaFile) + case instantPage(InstantPageGalleryController, Int, Media) + case map(TelegramMediaMap) + case stickerPack(StickerPackReference) + case audio(TelegramMediaFile) + case document(TelegramMediaFile, Bool) + case gallery(Signal) + case secretGallery(SecretMediaPreviewController) + case chatAvatars(AvatarGalleryController, Media) + case theme(TelegramMediaFile) + case other(Media) +} + +private func chatMessageGalleryControllerData(context: AccountContext, message: Message, navigationController: NavigationController?, standalone: Bool, reverseMessageGalleryOrder: Bool, mode: ChatControllerInteractionOpenMessageMode, synchronousLoad: Bool, actionInteraction: GalleryControllerActionInteraction?) -> ChatMessageGalleryControllerData? { + var galleryMedia: Media? + var otherMedia: Media? + var instantPageMedia: (TelegramMediaWebpage, [InstantPageGalleryEntry])? + for media in message.media { + if let action = media as? TelegramMediaAction { + switch action.action { + case let .photoUpdated(image): + if let peer = messageMainPeer(message), let image = image { + let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer, message.timestamp, nil, message.id, image.immediateThumbnailData, "action")]) + let galleryController = AvatarGalleryController(context: context, peer: peer, sourceCorners: .roundRect(15.5), remoteEntries: promise, skipInitial: true, replaceRootController: { controller, ready in + + }) + return .chatAvatars(galleryController, image) + } + default: + break + } + } else if let file = media as? TelegramMediaFile { + galleryMedia = file + } else if let image = media as? TelegramMediaImage { + galleryMedia = image + } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { + if let file = content.file { + galleryMedia = file + } else if let image = content.image { + if case .link = mode { + } else if ["photo", "document", "video", "gif", "telegram_album"].contains(content.type) { + galleryMedia = image + } + } + + if let instantPage = content.instantPage, let galleryMedia = galleryMedia { + switch instantPageType(of: content) { + case .album: + let medias = instantPageGalleryMedia(webpageId: webpage.webpageId, page: instantPage, galleryMedia: galleryMedia) + if medias.count > 1 { + instantPageMedia = (webpage, medias) + } + default: + break + } + } + } else if let mapMedia = media as? TelegramMediaMap { + galleryMedia = mapMedia + } else if let contactMedia = media as? TelegramMediaContact { + otherMedia = contactMedia + } + } + + var stream = false + var autoplayingVideo = false + var landscape = false + var timecode: Double? = nil + + switch mode { + case .stream: + stream = true + case .automaticPlayback: + autoplayingVideo = true + case .landscape: + autoplayingVideo = true + landscape = true + case let .timecode(time): + timecode = time + default: + break + } + + if let (webPage, instantPageMedia) = instantPageMedia, let galleryMedia = galleryMedia { + var centralIndex: Int = 0 + for i in 0 ..< instantPageMedia.count { + if instantPageMedia[i].media.media.id == galleryMedia.id { + centralIndex = i + break + } + } + + let gallery = InstantPageGalleryController(context: context, webPage: webPage, message: message, entries: instantPageMedia, centralIndex: centralIndex, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, replaceRootController: { [weak navigationController] controller, ready in + if let navigationController = navigationController { + navigationController.replaceTopController(controller, animated: false, ready: ready) + } + }, baseNavigationController: navigationController) + return .instantPage(gallery, centralIndex, galleryMedia) + } else if let galleryMedia = galleryMedia { + if let mapMedia = galleryMedia as? TelegramMediaMap { + return .map(mapMedia) + } else if let file = galleryMedia as? TelegramMediaFile, (file.isSticker || file.isAnimatedSticker) { + for attribute in file.attributes { + if case let .Sticker(_, reference, _) = attribute { + if let reference = reference { + return .stickerPack(reference) + } + break + } + } + } else if let file = galleryMedia as? TelegramMediaFile, file.isAnimatedSticker { + return nil + } else if let file = galleryMedia as? TelegramMediaFile, file.isMusic || file.isVoice || file.isInstantVideo { + return .audio(file) + } else if let file = galleryMedia as? TelegramMediaFile, file.mimeType == "application/vnd.apple.pkpass" || (file.fileName != nil && file.fileName!.lowercased().hasSuffix(".pkpass")) { + return .pass(file) + } else { + if let file = galleryMedia as? TelegramMediaFile { + if let fileName = file.fileName { + let ext = (fileName as NSString).pathExtension.lowercased() + if ext == "tgios-theme" { + return .theme(file) + } else if ext == "wav" || ext == "opus" { + return .audio(file) + } else if ext == "json", let fileSize = file.size, fileSize < 1024 * 1024 { + if let path = context.account.postbox.mediaBox.completedResourcePath(file.resource), let composition = LOTComposition(filePath: path), composition.timeDuration > 0.0 { + let gallery = GalleryController(context: context, source: .peerMessagesAtId(message.id), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in + navigationController?.replaceTopController(controller, animated: false, ready: ready) + }, baseNavigationController: navigationController, actionInteraction: actionInteraction) + return .gallery(.single(gallery)) + } + } + + if ext == "mkv" { + return .document(file, true) + } + } + + if internalDocumentItemSupportsMimeType(file.mimeType, fileName: file.fileName ?? "file") { + let gallery = GalleryController(context: context, source: .peerMessagesAtId(message.id), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in + navigationController?.replaceTopController(controller, animated: false, ready: ready) + }, baseNavigationController: navigationController, actionInteraction: actionInteraction) + return .gallery(.single(gallery)) + } + + if !file.isVideo { + return .document(file, false) + } + } + + if message.containsSecretMedia { + let gallery = SecretMediaPreviewController(context: context, messageId: message.id) + return .secretGallery(gallery) + } else { + let startTimecode: Signal + if let timecode = timecode { + startTimecode = .single(timecode) + } else { + startTimecode = mediaPlaybackStoredState(postbox: context.account.postbox, messageId: message.id) + |> map { state in + return state?.timestamp + } + } + + return .gallery(startTimecode + |> deliverOnMainQueue + |> map { timecode in + let gallery = GalleryController(context: context, source: standalone ? .standaloneMessage(message) : .peerMessagesAtId(message.id), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in + navigationController?.replaceTopController(controller, animated: false, ready: ready) + }, baseNavigationController: navigationController, actionInteraction: actionInteraction) + gallery.temporaryDoNotWaitForReady = autoplayingVideo + return gallery + }) + } + } + } + if let otherMedia = otherMedia { + return .other(otherMedia) + } else { + return nil + } +} + +enum ChatMessagePreviewControllerData { + case instantPage(InstantPageGalleryController, Int, Media) + case gallery(GalleryController) +} + +func chatMessagePreviewControllerData(context: AccountContext, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> ChatMessagePreviewControllerData? { + if let mediaData = chatMessageGalleryControllerData(context: context, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, synchronousLoad: true, actionInteraction: nil) { + switch mediaData { + case let .gallery(gallery): + break + case let .instantPage(gallery, centralIndex, galleryMedia): + return .instantPage(gallery, centralIndex, galleryMedia) + default: + break + } + } + return nil +} + +func chatMediaListPreviewControllerData(context: AccountContext, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> Signal { + if let mediaData = chatMessageGalleryControllerData(context: context, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, synchronousLoad: true, actionInteraction: nil) { + switch mediaData { + case let .gallery(gallery): + return gallery + |> map { gallery in + return .gallery(gallery) + } + case let .instantPage(gallery, centralIndex, galleryMedia): + return .single(.instantPage(gallery, centralIndex, galleryMedia)) + default: + break + } + } + return .single(nil) +} func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { - if let mediaData = chatMessageGalleryControllerData(context: params.context, chatLocation: params.chatLocation, chatLocationContextHolder: params.chatLocationContextHolder, message: params.message, navigationController: params.navigationController, standalone: params.standalone, reverseMessageGalleryOrder: params.reverseMessageGalleryOrder, mode: params.mode, source: params.gallerySource, synchronousLoad: false, actionInteraction: params.actionInteraction) { + if let mediaData = chatMessageGalleryControllerData(context: params.context, message: params.message, navigationController: params.navigationController, standalone: params.standalone, reverseMessageGalleryOrder: params.reverseMessageGalleryOrder, mode: params.mode, synchronousLoad: false, actionInteraction: params.actionInteraction) { switch mediaData { case let .url(url): params.openUrl(url) @@ -133,19 +351,15 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { control = .seek(time) } if (file.isVoice || file.isInstantVideo) && params.message.tags.contains(.voiceOrInstantVideo) { - if let playlistLocation = params.playlistLocation { - location = playlistLocation - } else if params.standalone { + if params.standalone { location = .recentActions(params.message) } else { location = .messages(peerId: params.message.id.peerId, tagMask: .voiceOrInstantVideo, at: params.message.id) } playerType = .voice } else if file.isMusic && params.message.tags.contains(.music) { - if let playlistLocation = params.playlistLocation { - location = playlistLocation - } else if params.standalone { - location = .recentActions(params.message) + if params.standalone { + location = .recentActions(params.message) } else { location = .messages(peerId: params.message.id.peerId, tagMask: .music, at: params.message.id) } @@ -241,9 +455,52 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { } func openChatInstantPage(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?, navigationController: NavigationController) { - if let (webpage, anchor) = instantPageAndAnchor(message: message) { - let pageController = InstantPageController(context: context, webPage: webpage, sourcePeerType: sourcePeerType ?? .channel, anchor: anchor) - navigationController.pushViewController(pageController) + for media in message.media { + if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { + if let _ = content.instantPage { + var textUrl: String? + if let pageUrl = URL(string: content.url) { + inner: for attribute in message.attributes { + if let attribute = attribute as? TextEntitiesMessageAttribute { + for entity in attribute.entities { + switch entity.type { + case let .TextUrl(url): + if let parsedUrl = URL(string: url) { + if pageUrl.scheme == parsedUrl.scheme && pageUrl.host == parsedUrl.host && pageUrl.path == parsedUrl.path { + textUrl = url + } + } + case .Url: + let nsText = message.text as NSString + var entityRange = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) + if entityRange.location + entityRange.length > nsText.length { + entityRange.location = max(0, nsText.length - entityRange.length) + entityRange.length = nsText.length - entityRange.location + } + let url = nsText.substring(with: entityRange) + if let parsedUrl = URL(string: url) { + if pageUrl.scheme == parsedUrl.scheme && pageUrl.host == parsedUrl.host && pageUrl.path == parsedUrl.path { + textUrl = url + } + } + default: + break + } + } + break inner + } + } + } + var anchor: String? + if let textUrl = textUrl, let anchorRange = textUrl.range(of: "#") { + anchor = String(textUrl[anchorRange.upperBound...]) + } + + let pageController = InstantPageController(context: context, webPage: webpage, sourcePeerType: sourcePeerType ?? .channel, anchor: anchor) + navigationController.pushViewController(pageController) + } + break + } } } diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index fe62d3d07b..22df999139 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -20,7 +20,6 @@ import LanguageLinkPreviewUI import SettingsUI import UrlHandling import ShareController -import ChatInterfaceState private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer { if case .default = navigation { @@ -90,10 +89,6 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur navigationController?.pushViewController(controller) case let .channelMessage(peerId, messageId): openPeer(peerId, .chat(textInputState: nil, subject: .message(messageId), peekData: nil)) - case let .replyThreadMessage(replyThreadMessageId, isChannelPost, maxReadMessageId, messageId): - if let navigationController = navigationController { - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .replyThread(threadMessageId: replyThreadMessageId, isChannelPost: isChannelPost, maxReadMessageId: maxReadMessageId), subject: .message(messageId))) - } case let .stickerPack(name): dismissInput() if false { diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift index 9581fbc8ff..d1f106392e 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift @@ -15,7 +15,6 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer let type: MediaManagerPlayerType let initialMessageId: MessageId let initialOrder: MusicPlaybackSettingsOrder - let isGlobalSearch: Bool private weak var parentNavigationController: NavigationController? @@ -27,13 +26,12 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer private var accountInUseDisposable: Disposable? - init(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, isGlobalSearch: Bool = false, parentNavigationController: NavigationController?) { + init(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, parentNavigationController: NavigationController?) { self.context = context self.peerId = peerId self.type = type self.initialMessageId = initialMessageId self.initialOrder = initialOrder - self.isGlobalSearch = isGlobalSearch self.parentNavigationController = parentNavigationController super.init(navigationBarPresentationData: nil) @@ -54,7 +52,7 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer } override public func loadDisplayNode() { - self.displayNode = OverlayAudioPlayerControllerNode(context: self.context, peerId: self.peerId, type: self.type, initialMessageId: self.initialMessageId, initialOrder: self.initialOrder, isGlobalSearch: self.isGlobalSearch, requestDismiss: { [weak self] in + self.displayNode = OverlayAudioPlayerControllerNode(context: self.context, peerId: self.peerId, type: self.type, initialMessageId: self.initialMessageId, initialOrder: self.initialOrder, requestDismiss: { [weak self] in self?.dismiss() }, requestShare: { [weak self] messageId in if let strongSelf = self { diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift similarity index 94% rename from submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift rename to submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift index 4d091f74d2..dbdaa4786e 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift @@ -20,7 +20,6 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu private let type: MediaManagerPlayerType private let requestDismiss: () -> Void private let requestShare: (MessageId) -> Void - private let isGlobalSearch: Bool private let controllerInteraction: ChatControllerInteraction @@ -41,14 +40,13 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu private var presentationDataDisposable: Disposable? private let replacementHistoryNodeReadyDisposable = MetaDisposable() - init(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, isGlobalSearch: Bool, requestDismiss: @escaping () -> Void, requestShare: @escaping (MessageId) -> Void) { + init(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, requestDismiss: @escaping () -> Void, requestShare: @escaping (MessageId) -> Void) { self.context = context self.peerId = peerId self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.type = type self.requestDismiss = requestDismiss self.requestShare = requestShare - self.isGlobalSearch = isGlobalSearch if case .regular = initialOrder { self.currentIsReversed = false @@ -133,7 +131,6 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in - }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false)) @@ -162,9 +159,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu tagMask = .voiceOrInstantVideo } - let chatLocationContextHolder = Atomic(value: nil) - - self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(initialMessageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: isGlobalSearch)) + self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: tagMask, subject: .message(initialMessageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: currentIsReversed, displayHeaders: .none)) super.init() @@ -242,7 +237,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu openMessageImpl = { [weak self] id in if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.historyNode.messageInCurrentHistoryView(id) { - return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in })) + return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in })) } return false } @@ -494,8 +489,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu tagMask = .voiceOrInstantVideo } - let chatLocationContextHolder = Atomic(value: nil) - let historyNode = ChatHistoryListNode(context: self.context, chatLocation: .peer(self.peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(messageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch)) + let historyNode = ChatHistoryListNode(context: self.context, chatLocation: .peer(self.peerId), tagMask: tagMask, subject: .message(messageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none)) historyNode.preloadPages = true historyNode.stackFromBottom = true historyNode.updateFloatingHeaderOffset = { [weak self] offset, _ in diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift index 8bc01d9a22..414c34cc5c 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -13,7 +13,6 @@ import TelegramUIPreferences import UniversalMediaPlayer import TelegramBaseController import OverlayStatusController -import ListMessageItem private final class PassthroughContainerNode: ASDisplayNode { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { @@ -71,8 +70,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { self.selectedMessages = chatControllerInteraction.selectionState.flatMap { $0.selectedIds } self.selectedMessagesPromise.set(.single(self.selectedMessages)) - let chatLocationContextHolder = Atomic(value: nil) - self.listNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(search: false, reversed: false, displayHeaders: .allButLast, hintLinks: tagMask == .webPage, isGlobalSearch: false)) + self.listNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: tagMask, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(search: false, reversed: false, displayHeaders: .allButLast)) self.listNode.defaultToSynchronousTransactionWhileScrolling = true if tagMask == .music { @@ -269,7 +267,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { } if let id = state.id as? PeerMessagesMediaPlaylistItemId { if type == .music { - let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), context: strongSelf.context, chatLocation: .peer(id.messageId.peerId), chatLocationContextHolder: Atomic(value: nil), tagMask: MessageTags.music) + let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), account: account, chatLocation: .peer(id.messageId.peerId), tagMask: MessageTags.music) var cancelImpl: (() -> Void)? let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } @@ -306,7 +304,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { } else { controllerContext = strongSelf.context.sharedContext.makeTempAccountContext(account: account) } - let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, peerId: id.messageId.peerId, type: type, initialMessageId: id.messageId, initialOrder: order, isGlobalSearch: false, parentNavigationController: strongSelf.chatControllerInteraction.navigationController()) + let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, peerId: id.messageId.peerId, type: type, initialMessageId: id.messageId, initialOrder: order, parentNavigationController: strongSelf.chatControllerInteraction.navigationController()) strongSelf.view.window?.endEditing(true) strongSelf.chatControllerInteraction.presentController(controller, nil) } else if index.1 { diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift index 5a905bda94..a779b60417 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift @@ -12,8 +12,6 @@ import RadialStatusNode import TelegramStringFormatting import GridMessageSelectionNode import UniversalMediaPlayer -import ListMessageItem -import ChatMessageInteractiveMediaBadge private final class FrameSequenceThumbnailNode: ASDisplayNode { private let context: AccountContext diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index 7f4aa1e359..83ec96e376 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -638,7 +638,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen } var discussionPeer: Peer? - if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { + if let linkedDiscussionPeerId = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { discussionPeer = peer } @@ -767,7 +767,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen } var discussionPeer: Peer? - if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { + if let linkedDiscussionPeerId = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { discussionPeer = peer } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 8ff6a0bbde..149defb74d 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -2851,8 +2851,6 @@ final class PeerInfoHeaderNode: ASDisplayNode { if let peer = peer { if peer.id == self.context.account.peerId && !self.isSettings { titleString = NSAttributedString(string: presentationData.strings.Conversation_SavedMessages, font: Font.medium(24.0), textColor: presentationData.theme.list.itemPrimaryTextColor) - } else if peer.id == self.context.account.peerId && !self.isSettings { - titleString = NSAttributedString(string: presentationData.strings.DialogList_Replies, font: Font.medium(24.0), textColor: presentationData.theme.list.itemPrimaryTextColor) } else { titleString = NSAttributedString(string: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.medium(24.0), textColor: presentationData.theme.list.itemPrimaryTextColor) } @@ -3301,14 +3299,14 @@ final class PeerInfoHeaderNode: ASDisplayNode { if buttonKeys.count > 3 { if self.isOpenedFromChat { switch buttonKey { - case .message, .search, .mute: + case .message, .search, .videoCall: hiddenWhileExpanded = true default: hiddenWhileExpanded = false } } else { switch buttonKey { - case .mute, .search, .mute: + case .mute, .search, .videoCall: hiddenWhileExpanded = true default: hiddenWhileExpanded = false diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 67ccc1a56d..b8f58c38a2 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -49,9 +49,6 @@ import LegacyMediaPickerUI import TelegramNotices import SaveToCameraRoll import PeerInfoUI -import ListMessageItem -import GalleryData -import ChatInterfaceState protocol PeerInfoScreenItem: class { var id: AnyHashable { get } @@ -380,7 +377,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, navigateMessageSearch: { _ in }, openCalendarSearch: { }, toggleMembersSearch: { _ in - }, navigateToMessage: { _, _ in + }, navigateToMessage: { _ in }, navigateToChat: { _ in }, navigateToProfile: { _ in }, openPeerInfo: { @@ -430,7 +427,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, openScheduledMessages: { }, openPeersNearby: { }, displaySearchResultsTooltip: { _, _ in - }, unarchivePeer: {}, viewReplies: { _, _ in }, statuses: nil) + }, unarchivePeer: {}, statuses: nil) self.selectionPanel.interfaceInteraction = interfaceInteraction @@ -1684,7 +1681,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD return } - let _ = (chatMediaListPreviewControllerData(context: strongSelf.context, chatLocation: .peer(message.id.peerId), chatLocationContextHolder: Atomic(value: nil), message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongSelf.controller?.navigationController as? NavigationController) + let _ = (chatMediaListPreviewControllerData(context: strongSelf.context, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongSelf.controller?.navigationController as? NavigationController) |> deliverOnMainQueue).start(next: { previewData in guard let strongSelf = self else { gesture?.cancel() @@ -1956,7 +1953,6 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in - }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, @@ -2717,7 +2713,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } self.view.endEditing(true) - return self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, chatLocation: nil, chatLocationContextHolder: nil, message: galleryMessage, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { [weak self] in + return self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, message: galleryMessage, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { [weak self] in self?.view.endEditing(true) }, present: { [weak self] c, a in self?.controller?.present(c, in: .window(.root), with: a, blockInteraction: true) @@ -2826,7 +2822,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD }) } case .discussion: - if let cachedData = self.data?.cachedData as? CachedChannelData, case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId { + if let cachedData = self.data?.cachedData as? CachedChannelData, let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId { if let navigationController = controller.navigationController as? NavigationController { self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(linkedDiscussionPeerId))) } @@ -4872,7 +4868,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } }) } - }, resolvedFaqUrl: self.cachedFaq.get(), exceptionsList: .single(settings.notificationExceptions), archivedStickerPacks: .single(settings.archivedStickerPacks), privacySettings: .single(settings.privacySettings), hasWallet: .single(false), activeSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.0 }, webSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.2 }), cancel: { [weak self] in + }, exceptionsList: .single(settings.notificationExceptions), archivedStickerPacks: .single(settings.archivedStickerPacks), privacySettings: .single(settings.privacySettings), hasWallet: .single(false), activeSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.0 }, webSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.2 }), cancel: { [weak self] in self?.deactivateSearch() }) } diff --git a/submodules/TelegramUI/Sources/PeerMediaCollectionController.swift b/submodules/TelegramUI/Sources/PeerMediaCollectionController.swift new file mode 100644 index 0000000000..4fdb8667ec --- /dev/null +++ b/submodules/TelegramUI/Sources/PeerMediaCollectionController.swift @@ -0,0 +1,971 @@ +import Foundation +import UIKit +import Postbox +import SwiftSignalKit +import Display +import AsyncDisplayKit +import TelegramCore +import SyncCore +import SafariServices +import TelegramPresentationData +import TelegramUIPreferences +import TelegramBaseController +import OverlayStatusController +import AccountContext +import ShareController +import OpenInExternalAppUI +import PeerInfoUI +import ContextUI +import PresentationDataUtils +import LocalizedPeerData + +public class PeerMediaCollectionController: TelegramBaseController { + private var validLayout: ContainerViewLayout? + + private let context: AccountContext + private let peerId: PeerId + private let messageId: MessageId? + + private let peerDisposable = MetaDisposable() + private let navigationActionDisposable = MetaDisposable() + + private let messageIndexDisposable = MetaDisposable() + + private let _peerReady = Promise() + private var didSetPeerReady = false + private let peer = Promise(nil) + + private var interfaceState: PeerMediaCollectionInterfaceState + + private var rightNavigationButton: PeerMediaCollectionNavigationButton? + + private let galleryHiddenMesageAndMediaDisposable = MetaDisposable() + private var presentationDataDisposable:Disposable? + + private var controllerInteraction: ChatControllerInteraction? + private var interfaceInteraction: ChatPanelInterfaceInteraction? + + private let messageContextDisposable = MetaDisposable() + private var shareStatusDisposable: MetaDisposable? + + private var presentationData: PresentationData + + private var resolveUrlDisposable: MetaDisposable? + + public init(context: AccountContext, peerId: PeerId, messageId: MessageId? = nil) { + self.context = context + self.peerId = peerId + self.messageId = messageId + + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.interfaceState = PeerMediaCollectionInterfaceState(theme: self.presentationData.theme, strings: self.presentationData.strings) + + super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme).withUpdatedSeparatorColor(self.presentationData.theme.rootController.navigationBar.backgroundColor), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)), mediaAccessoryPanelVisibility: .specific(size: .compact), locationBroadcastPanelSource: .none) + + self.navigationPresentation = .modalInLargeLayout + + self.title = self.presentationData.strings.SharedMedia_TitleAll + + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style + + self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) + + self.ready.set(.never()) + + self.scrollToTop = { [weak self] in + if let strongSelf = self, strongSelf.isNodeLoaded { + strongSelf.mediaCollectionDisplayNode.historyNode.scrollToEndOfHistory() + } + } + + self.presentationDataDisposable = (context.sharedContext.presentationData + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + let previousTheme = strongSelf.presentationData.theme + let previousStrings = strongSelf.presentationData.strings + let previousChatWallpaper = strongSelf.presentationData.chatWallpaper + + strongSelf.presentationData = presentationData + + if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings || presentationData.chatWallpaper != previousChatWallpaper { + strongSelf.themeAndStringsUpdated() + } + } + }) + + let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message, mode in + if let strongSelf = self, strongSelf.isNodeLoaded, let galleryMessage = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id) { + guard let navigationController = strongSelf.navigationController as? NavigationController else { + return false + } + strongSelf.mediaCollectionDisplayNode.view.endEditing(true) + return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, message: galleryMessage.message, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { + self?.mediaCollectionDisplayNode.view.endEditing(true) + }, present: { c, a in + self?.present(c, in: .window(.root), with: a, blockInteraction: true) + }, transitionNode: { messageId, media in + if let strongSelf = self { + return strongSelf.mediaCollectionDisplayNode.transitionNodeForGallery(messageId: messageId, media: media) + } + return nil + }, addToTransitionSurface: { view in + if let strongSelf = self { + var belowSubview: UIView? + if let historyNode = strongSelf.mediaCollectionDisplayNode.historyNode as? ChatHistoryGridNode { + if let lowestSectionNode = historyNode.lowestSectionNode() { + belowSubview = lowestSectionNode.view + } + } + strongSelf.mediaCollectionDisplayNode.historyNode + if let belowSubview = belowSubview { + strongSelf.mediaCollectionDisplayNode.historyNode.view.insertSubview(view, belowSubview: belowSubview) + } else { + strongSelf.mediaCollectionDisplayNode.historyNode.view.addSubview(view) + } + } + }, openUrl: { url in + self?.openUrl(url) + }, openPeer: { peer, navigation in + self?.controllerInteraction?.openPeer(peer.id, navigation, nil) + }, callPeer: { peerId, isVideo in + self?.controllerInteraction?.callPeer(peerId, isVideo) + }, enqueueMessage: { _ in + }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in })) + } + return false + }, openPeer: { [weak self] id, navigation, _ in + if let strongSelf = self, let id = id, let navigationController = strongSelf.navigationController as? NavigationController { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id))) + } + }, openPeerMention: { _ in + }, openMessageContextMenu: { [weak self] message, _, _, _, _ in + guard let strongSelf = self else { + return + } + (chatAvailableMessageActionsImpl(postbox: strongSelf.context.account.postbox, accountPeerId: strongSelf.context.account.peerId, messageIds: [message.id]) + |> deliverOnMainQueue).start(next: { actions in + var messageIds = Set() + messageIds.insert(message.id) + + if let strongSelf = self, strongSelf.isNodeLoaded { + if let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message { + let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) + var items: [ActionSheetButtonItem] = [] + + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.SharedMedia_ViewInChat, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), subject: .message(message.id))) + } + })) + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_ContextMenuForward, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + if let strongSelf = self { + strongSelf.forwardMessages(messageIds) + } + })) + if actions.options.contains(.deleteLocally) || actions.options.contains(.deleteGlobally) { + items.append( ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_ContextMenuDelete, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + if let strongSelf = self { + strongSelf.deleteMessages(messageIds) + } + })) + } + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + strongSelf.mediaCollectionDisplayNode.view.endEditing(true) + strongSelf.present(actionSheet, in: .window(.root)) + } + } + }) + }, openMessageContextActions: { [weak self] message, node, rect, gesture in + guard let strongSelf = self else { + gesture?.cancel() + return + } + + let _ = (chatMediaListPreviewControllerData(context: strongSelf.context, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongSelf.navigationController as? NavigationController) + |> deliverOnMainQueue).start(next: { previewData in + guard let strongSelf = self else { + gesture?.cancel() + return + } + if let previewData = previewData { + let context = strongSelf.context + let strings = strongSelf.presentationData.strings + let items = chatAvailableMessageActionsImpl(postbox: strongSelf.context.account.postbox, accountPeerId: strongSelf.context.account.peerId, messageIds: [message.id]) + |> map { actions -> [ContextMenuItem] in + var items: [ContextMenuItem] = [] + + items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in + c.dismiss(completion: { + if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), subject: .message(message.id))) + } + }) + }))) + + items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { c, f in + c.dismiss(completion: { + if let strongSelf = self { + strongSelf.forwardMessages([message.id]) + } + }) + }))) + + if actions.options.contains(.deleteLocally) || actions.options.contains(.deleteGlobally) { + items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { c, f in + c.setItems(context.account.postbox.transaction { transaction -> [ContextMenuItem] in + var items: [ContextMenuItem] = [] + let messageIds = [message.id] + + if let peer = transaction.getPeer(message.id.peerId) { + var personalPeerName: String? + var isChannel = false + if let user = peer as? TelegramUser { + personalPeerName = user.compactDisplayTitle + } else if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + isChannel = true + } + + if actions.options.contains(.deleteGlobally) { + let globalTitle: String + if isChannel { + globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe + } else if let personalPeerName = personalPeerName { + globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).0 + } else { + globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone + } + items.append(.action(ContextMenuActionItem(text: globalTitle, textColor: .destructive, icon: { _ in nil }, action: { c, f in + c.dismiss(completion: { + if let strongSelf = self { + strongSelf.updateInterfaceState(animated: true, { $0.withoutSelectionState() }) + let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forEveryone).start() + } + }) + }))) + } + + if actions.options.contains(.deleteLocally) { + var localOptionText = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe + if strongSelf.context.account.peerId == strongSelf.peerId { + if messageIds.count == 1 { + localOptionText = strongSelf.presentationData.strings.Conversation_Moderate_Delete + } else { + localOptionText = strongSelf.presentationData.strings.Conversation_DeleteManyMessages + } + } + items.append(.action(ContextMenuActionItem(text: localOptionText, textColor: .destructive, icon: { _ in nil }, action: { c, f in + c.dismiss(completion: { + if let strongSelf = self { + strongSelf.updateInterfaceState(animated: true, { $0.withoutSelectionState() }) + let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forLocalPeer).start() + } + }) + }))) + } + } + + return items + }) + }))) + } + + return items + } + + switch previewData { + case let .gallery(gallery): + gallery.setHintWillBePresentedInPreviewingContext(true) + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: items, reactionItems: [], gesture: gesture) + strongSelf.presentInGlobalOverlay(contextController) + case .instantPage: + break + } + } + }) + }, navigateToMessage: { [weak self] fromId, id in + if let strongSelf = self, strongSelf.isNodeLoaded { + if id.peerId == strongSelf.peerId { + var fromIndex: MessageIndex? + + if let message = strongSelf.mediaCollectionDisplayNode.historyNode.messageInCurrentHistoryView(fromId) { + fromIndex = message.index + } + + /*if let fromIndex = fromIndex { + if let message = strongSelf.mediaCollectionDisplayNode.historyNode.messageInCurrentHistoryView(id) { + strongSelf.mediaCollectionDisplayNode.historyNode.scrollToMessage(from: fromIndex, to: MessageIndex(message)) + } else { + strongSelf.messageIndexDisposable.set((strongSelf.account.postbox.messageIndexAtId(id) |> deliverOnMainQueue).start(next: { [weak strongSelf] index in + if let strongSelf = strongSelf, let index = index { + strongSelf.mediaCollectionDisplayNode.historyNode.scrollToMessage(from: fromIndex, to: index) + } + })) + } + }*/ + } else { + (strongSelf.navigationController as? NavigationController)?.pushViewController(ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(id.peerId), subject: .message(id))) + } + } + }, tapMessage: nil, clickThroughMessage: { [weak self] in + self?.view.endEditing(true) + }, toggleMessagesSelection: { [weak self] ids, value in + if let strongSelf = self, strongSelf.isNodeLoaded { + strongSelf.updateInterfaceState(animated: true, { $0.withToggledSelectedMessages(ids, value: value) }) + } + }, sendCurrentMessage: { _ in + }, sendMessage: { _ in + }, sendSticker: { _, _, _, _ in + return false + }, sendGif: { _, _, _ in + return false + }, sendBotContextResultAsGif: { _, _, _, _ in + return false + }, requestMessageActionCallback: { _, _, _, _ in + }, requestMessageActionUrlAuth: { _, _, _ in + }, activateSwitchInline: { _, _ in + }, openUrl: { [weak self] url, _, external, _ in + self?.openUrl(url, external: external ?? false) + }, shareCurrentLocation: { + }, shareAccountContact: { + }, sendBotCommand: { _, _ in + }, openInstantPage: { [weak self] message, associatedData in + if let strongSelf = self, strongSelf.isNodeLoaded, let navigationController = strongSelf.navigationController as? NavigationController, let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message { + openChatInstantPage(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController) + } + }, openWallpaper: { [weak self] message in + if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message { + openChatWallpaper(context: strongSelf.context, message: message, present: { [weak self] c, a in + self?.present(c, in: .window(.root), with: a, blockInteraction: true) + }) + } + }, openTheme: { _ in + }, openHashtag: { _, _ in + }, updateInputState: { _ in + }, updateInputMode: { _ in + }, openMessageShareMenu: { _ in + }, presentController: { _, _ in + }, navigationController: { + return nil + }, chatControllerNode: { + return nil + }, reactionContainerNode: { + return nil + }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in + }, longTap: { [weak self] content, _ in + if let strongSelf = self { + strongSelf.view.endEditing(true) + switch content { + case let .url(url): + let canOpenIn = availableOpenInOptions(context: strongSelf.context, item: .url(url: url)).count > 1 + let openText = canOpenIn ? strongSelf.presentationData.strings.Conversation_FileOpenIn : strongSelf.presentationData.strings.Conversation_LinkDialogOpen + let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) + actionSheet.setItemGroups([ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: url), + ActionSheetButtonItem(title: openText, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + if let strongSelf = self { + if canOpenIn { + let actionSheet = OpenInActionSheetController(context: strongSelf.context, item: .url(url: url), openUrl: { [weak self] url in + if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { + strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: strongSelf.presentationData, navigationController: navigationController, dismissInput: { + }) + } + }) + strongSelf.present(actionSheet, in: .window(.root)) + } else { + strongSelf.context.sharedContext.applicationBindings.openUrl(url) + } + } + }), + ActionSheetButtonItem(title: strongSelf.presentationData.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + UIPasteboard.general.string = url + }), + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + if let link = URL(string: url) { + let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil) + } + }) + ]), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + strongSelf.present(actionSheet, in: .window(.root)) + default: + break + } + } + }, openCheckoutOrReceipt: { _ in + }, openSearch: { [weak self] in + self?.activateSearch() + }, setupReply: { _ in + }, canSetupReply: { _ in + return .none + }, navigateToFirstDateMessage: { _ in + }, requestRedeliveryOfFailedMessages: { _ in + }, addContact: { _ in + }, rateCall: { _, _, _ in + }, requestSelectMessagePollOptions: { _, _ in + }, requestOpenMessagePollResults: { _, _ in + }, openAppStorePage: { + }, displayMessageTooltip: { _, _, _, _ in + }, seekToTimecode: { _, _, _ in + }, scheduleCurrentMessage: { + }, sendScheduledMessagesNow: { _ in + }, editScheduledMessagesTime: { _ in + }, performTextSelectionAction: { _, _, _ in + }, updateMessageLike: { _, _ in + }, openMessageReactions: { _ in + }, displaySwipeToReplyHint: { + }, dismissReplyMarkupMessage: { _ in + }, openMessagePollResults: { _, _ in + }, openPollCreation: { _ in + }, displayPollSolution: { _, _ in + }, displayPsa: { _, _ in + }, displayDiceTooltip: { _ in + }, animateDiceSuccess: { + }, greetingStickerNode: { + return nil + }, openPeerContextMenu: { _, _, _, _ in + }, requestMessageUpdate: { _ in + }, cancelInteractiveKeyboardGestures: { + }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, + pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false)) + + self.controllerInteraction = controllerInteraction + + self.interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { _, _ in + }, setupEditMessage: { _, _ in + }, beginMessageSelection: { _, _ in + }, deleteSelectedMessages: { [weak self] in + if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds { + strongSelf.deleteMessages(messageIds) + } + }, reportSelectedMessages: { [weak self] in + if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty { + strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in + self?.present(c, in: .window(.root), with: a) + }, push: { c in + self?.push(c) + }, completion: { _ in }), in: .window(.root)) + } + }, reportMessages: { _, _ in + }, deleteMessages: { _, _, f in + f(.default) + }, forwardSelectedMessages: { [weak self] in + if let strongSelf = self { + if let forwardMessageIdsSet = strongSelf.interfaceState.selectionState?.selectedIds { + strongSelf.forwardMessages(forwardMessageIdsSet) + } + } + }, forwardCurrentForwardMessages: { + }, forwardMessages: { _ in + }, shareSelectedMessages: { [weak self] in + if let strongSelf = self, let selectedIds = strongSelf.interfaceState.selectionState?.selectedIds, !selectedIds.isEmpty { + let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Message] in + var messages: [Message] = [] + for id in selectedIds { + if let message = transaction.getMessage(id) { + messages.append(message) + } + } + return messages + } |> deliverOnMainQueue).start(next: { messages in + if let strongSelf = self, !messages.isEmpty { + strongSelf.updateInterfaceState(animated: true, { + $0.withoutSelectionState() + }) + + let shareController = ShareController(context: strongSelf.context, subject: .messages(messages.sorted(by: { lhs, rhs in + return lhs.index < rhs.index + })), externalShare: true, immediateExternalShare: true) + strongSelf.present(shareController, in: .window(.root)) + } + }) + } + }, updateTextInputStateAndMode: { _ in + }, updateInputModeAndDismissedButtonKeyboardMessageId: { _ in + }, openStickers: { + }, editMessage: { + }, beginMessageSearch: { _, _ in + }, dismissMessageSearch: { + }, updateMessageSearch: { _ in + }, openSearchResults: { + }, navigateMessageSearch: { _ in + }, openCalendarSearch: { + }, toggleMembersSearch: { _ in + }, navigateToMessage: { _ in + }, navigateToChat: { _ in + }, navigateToProfile: { _ in + }, openPeerInfo: { + }, togglePeerNotifications: { + }, sendContextResult: { _, _, _, _ in + return false + }, sendBotCommand: { _, _ in + }, sendBotStart: { _ in + }, botSwitchChatWithPayload: { _, _ in + }, beginMediaRecording: { _ in + }, finishMediaRecording: { _ in + }, stopMediaRecording: { + }, lockMediaRecording: { + }, deleteRecordedMedia: { + }, sendRecordedMedia: { + }, displayRestrictedInfo: { _, _ in + }, displayVideoUnmuteTip: { _ in + }, switchMediaRecordingMode: { + }, setupMessageAutoremoveTimeout: { + }, sendSticker: { _, _, _ in + return false + }, unblockPeer: { + }, pinMessage: { _ in + }, unpinMessage: { + }, shareAccountContact: { + }, reportPeer: { + }, presentPeerContact: { + }, dismissReportPeer: { + }, deleteChat: { + }, beginCall: { _ in + }, toggleMessageStickerStarred: { _ in + }, presentController: { _, _ in + }, getNavigationController: { + return nil + }, presentGlobalOverlayController: { _, _ in + }, navigateFeed: { + }, openGrouping: { + }, toggleSilentPost: { + }, requestUnvoteInMessage: { _ in + }, requestStopPollInMessage: { _ in + }, updateInputLanguage: { _ in + }, unarchiveChat: { + }, openLinkEditing: { + }, reportPeerIrrelevantGeoLocation: { + }, displaySlowmodeTooltip: { _, _ in + }, displaySendMessageOptions: { _, _ in + }, openScheduledMessages: { + }, openPeersNearby: { + }, displaySearchResultsTooltip: { _, _ in + }, unarchivePeer: {}, statuses: nil) + + self.updateInterfaceState(animated: false, { return $0 }) + + self.peer.set(context.account.postbox.peerView(id: peerId) |> map { $0.peers[$0.peerId] }) + + self.peerDisposable.set((self.peer.get() + |> deliverOnMainQueue).start(next: { [weak self] peer in + if let strongSelf = self { + strongSelf.updateInterfaceState(animated: false, { return $0.withUpdatedPeer(peer) }) + if !strongSelf.didSetPeerReady { + strongSelf.didSetPeerReady = true + strongSelf._peerReady.set(.single(true)) + } + } + })) + } + + private func themeAndStringsUpdated() { + self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + // self.chatTitleView?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) + self.updateInterfaceState(animated: false, { state in + var state = state + state = state.updatedTheme(self.presentationData.theme) + state = state.updatedStrings(self.presentationData.strings) + return state + }) + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.messageIndexDisposable.dispose() + self.navigationActionDisposable.dispose() + self.galleryHiddenMesageAndMediaDisposable.dispose() + self.messageContextDisposable.dispose() + self.resolveUrlDisposable?.dispose() + self.presentationDataDisposable?.dispose() + } + + var mediaCollectionDisplayNode: PeerMediaCollectionControllerNode { + get { + return super.displayNode as! PeerMediaCollectionControllerNode + } + } + + override public func loadDisplayNode() { + self.displayNode = PeerMediaCollectionControllerNode(context: self.context, peerId: self.peerId, messageId: self.messageId, controllerInteraction: self.controllerInteraction!, interfaceInteraction: self.interfaceInteraction!, navigationBar: self.navigationBar, requestDeactivateSearch: { [weak self] in + self?.deactivateSearch() + }) + + let mediaManager = self.context.sharedContext.mediaManager + self.galleryHiddenMesageAndMediaDisposable.set(mediaManager.galleryHiddenMediaManager.hiddenIds().start(next: { [weak self] ids in + if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { + var messageIdAndMedia: [MessageId: [Media]] = [:] + + for id in ids { + if case let .chat(accountId, messageId, media) = id, accountId == strongSelf.context.account.id { + messageIdAndMedia[messageId] = [media] + } + } + + //if controllerInteraction.hiddenMedia != messageIdAndMedia { + controllerInteraction.hiddenMedia = messageIdAndMedia + + strongSelf.mediaCollectionDisplayNode.historyNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? GridMessageItemNode { + itemNode.updateHiddenMedia() + } else if let itemNode = itemNode as? ListMessageNode { + itemNode.updateHiddenMedia() + } + } + //} + } + })) + + self.ready.set(combineLatest(self.mediaCollectionDisplayNode.historyNode.historyState.get(), self._peerReady.get()) |> map { $1 }) + + self.mediaCollectionDisplayNode.requestLayout = { [weak self] transition in + self?.requestLayout(transition: transition) + } + + self.mediaCollectionDisplayNode.requestUpdateMediaCollectionInterfaceState = { [weak self] animated, f in + self?.updateInterfaceState(animated: animated, f) + } + + self.displayNodeDidLoad() + } + + override public func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.mediaCollectionDisplayNode.historyNode.preloadPages = true + } + + override public func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + self.mediaCollectionDisplayNode.clearHighlightAnimated(true) + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.validLayout = layout + + self.mediaCollectionDisplayNode.containerLayoutUpdated(layout, navigationBarHeightAndPrimaryHeight: (self.navigationHeight, self.primaryNavigationHeight), transition: transition, listViewTransaction: { updateSizeAndInsets in + self.mediaCollectionDisplayNode.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets) + }) + } + + func updateInterfaceState(animated: Bool = true, _ f: (PeerMediaCollectionInterfaceState) -> PeerMediaCollectionInterfaceState) { + let updatedInterfaceState = f(self.interfaceState) + + if self.isNodeLoaded { + self.mediaCollectionDisplayNode.updateMediaCollectionInterfaceState(updatedInterfaceState, animated: animated) + } + self.interfaceState = updatedInterfaceState + + if let button = rightNavigationButtonForPeerMediaCollectionInterfaceState(updatedInterfaceState, currentButton: self.rightNavigationButton, target: self, selector: #selector(self.rightNavigationButtonAction)) { + if self.rightNavigationButton != button { + self.navigationItem.setRightBarButton(button.buttonItem, animated: true) + } + self.rightNavigationButton = button + } else if let _ = self.rightNavigationButton { + self.navigationItem.setRightBarButton(nil, animated: true) + self.rightNavigationButton = nil + } + + if let controllerInteraction = self.controllerInteraction { + if updatedInterfaceState.selectionState != controllerInteraction.selectionState { + let animated = animated || controllerInteraction.selectionState == nil || updatedInterfaceState.selectionState == nil + controllerInteraction.selectionState = updatedInterfaceState.selectionState + self.mediaCollectionDisplayNode.historyNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? ChatMessageItemView { + itemNode.updateSelectionState(animated: animated) + } else if let itemNode = itemNode as? GridMessageItemNode { + itemNode.updateSelectionState(animated: animated) + } + } + + self.mediaCollectionDisplayNode.selectedMessages = updatedInterfaceState.selectionState?.selectedIds + view.disablesInteractiveTransitionGestureRecognizer = updatedInterfaceState.selectionState != nil && self.mediaCollectionDisplayNode.historyNode is ChatHistoryGridNode + } + } + } + + @objc func rightNavigationButtonAction() { + if let button = self.rightNavigationButton { + self.navigationButtonAction(button.action) + } + } + + private func navigationButtonAction(_ action: PeerMediaCollectionNavigationButtonAction) { + switch action { + case .cancelMessageSelection: + self.updateInterfaceState(animated: true, { $0.withoutSelectionState() }) + case .beginMessageSelection: + self.updateInterfaceState(animated: true, { $0.withSelectionState() }) + } + } + + private func activateSearch() { + if self.displayNavigationBar { + if let scrollToTop = self.scrollToTop { + scrollToTop() + } + self.mediaCollectionDisplayNode.activateSearch() + self.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring)) + } + } + + private func deactivateSearch() { + if !self.displayNavigationBar { + self.setDisplayNavigationBar(true, transition: .animated(duration: 0.5, curve: .spring)) + self.mediaCollectionDisplayNode.deactivateSearch() + } + } + + private func openUrl(_ url: String, external: Bool = false) { + let disposable: MetaDisposable + if let current = self.resolveUrlDisposable { + disposable = current + } else { + disposable = MetaDisposable() + self.resolveUrlDisposable = disposable + } + + let resolvedUrl: Signal + if external { + resolvedUrl = .single(.externalUrl(url)) + } else { + resolvedUrl = self.context.sharedContext.resolveUrl(account: self.context.account, url: url) + } + + disposable.set((resolvedUrl |> deliverOnMainQueue).start(next: { [weak self] result in + if let strongSelf = self { + strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.navigationController as? NavigationController, openPeer: { peerId, navigation in + if let strongSelf = self { + switch navigation { + case let .chat(_, subject, peekData): + if let navigationController = strongSelf.navigationController as? NavigationController { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject, keepStack: .always, peekData: peekData)) + } + case .info: + strongSelf.navigationActionDisposable.set((strongSelf.context.account.postbox.loadedPeerWithId(peerId) + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] peer in + if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { + (strongSelf.navigationController as? NavigationController)?.pushViewController(infoController) + } + } + })) + case let .withBotStartPayload(startPayload): + if let navigationController = strongSelf.navigationController as? NavigationController { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), botStart: startPayload)) + } + default: + break + } + } + }, sendFile: nil, + sendSticker: nil, + present: { c, a in + self?.present(c, in: .window(.root), with: a) + }, dismissInput: { + self?.view.endEditing(true) + }, contentContext: nil) + } + })) + } + + func forwardMessages(_ messageIds: Set) { + let currentMessages = (self.mediaCollectionDisplayNode.searchDisplayController?.contentNode as? ChatHistorySearchContainerNode)?.currentMessages + let _ = (self.context.account.postbox.transaction { transaction -> Void in + for id in messageIds { + if transaction.getMessage(id) == nil { + if let message = currentMessages?[id] { + storeMessageFromSearch(transaction: transaction, message: message) + } + } + } + } + |> deliverOnMainQueue).start(completed: { [weak self] in + guard let strongSelf = self else { + return + } + let forwardMessageIds = Array(messageIds).sorted() + + let controller = strongSelf.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: strongSelf.context, filter: [.onlyWriteable, .excludeDisabled])) + controller.peerSelected = { [weak controller] peerId in + if let strongSelf = self, let _ = controller { + if peerId == strongSelf.context.account.peerId { + strongSelf.updateInterfaceState(animated: false, { $0.withoutSelectionState() }) + + let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in + return .forward(source: id, grouping: .auto, attributes: []) + }) + |> deliverOnMainQueue).start(next: { [weak self] messageIds in + if let strongSelf = self { + let signals: [Signal] = messageIds.compactMap({ id -> Signal? in + guard let id = id else { + return nil + } + return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id) + |> mapToSignal { status, _ -> Signal in + if status != nil { + return .never() + } else { + return .single(true) + } + } + |> take(1) + }) + if strongSelf.shareStatusDisposable == nil { + strongSelf.shareStatusDisposable = MetaDisposable() + } + strongSelf.shareStatusDisposable?.set((combineLatest(signals) + |> deliverOnMainQueue).start(completed: { + guard let strongSelf = self else { + return + } + strongSelf.present(OverlayStatusController(theme: strongSelf.presentationData.theme, type: .success), in: .window(.root)) + })) + } + }) + if let strongController = controller { + strongController.dismiss() + } + } else { + let _ = (strongSelf.context.account.postbox.transaction({ transaction -> Void in + transaction.updatePeerChatInterfaceState(peerId, update: { currentState in + if let currentState = currentState as? ChatInterfaceState { + return currentState.withUpdatedForwardMessageIds(forwardMessageIds) + } else { + return ChatInterfaceState().withUpdatedForwardMessageIds(forwardMessageIds) + } + }) + }) |> deliverOnMainQueue).start(completed: { + if let strongSelf = self { + strongSelf.updateInterfaceState(animated: false, { $0.withoutSelectionState() }) + + let ready = Promise() + strongSelf.messageContextDisposable.set((ready.get() |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { _ in + if let strongController = controller { + strongController.dismiss() + } + })) + + (strongSelf.navigationController as? NavigationController)?.replaceTopController(ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(peerId)), animated: false, ready: ready) + } + }) + } + } + } + (strongSelf.navigationController as? NavigationController)?.pushViewController(controller) + }) + } + + func deleteMessages(_ messageIds: Set) { + if !messageIds.isEmpty { + self.messageContextDisposable.set((combineLatest(self.context.sharedContext.chatAvailableMessageActions(postbox: self.context.account.postbox, accountPeerId: self.context.account.peerId, messageIds: messageIds), self.peer.get() |> take(1)) |> deliverOnMainQueue).start(next: { [weak self] actions, peer in + if let strongSelf = self, let peer = peer, !actions.options.isEmpty { + let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) + var items: [ActionSheetItem] = [] + var personalPeerName: String? + var isChannel = false + if let user = peer as? TelegramUser { + personalPeerName = user.compactDisplayTitle + } else if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + isChannel = true + } + + if actions.options.contains(.deleteGlobally) { + let globalTitle: String + if isChannel { + globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe + } else if let personalPeerName = personalPeerName { + globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).0 + } else { + globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone + } + items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + if let strongSelf = self { + strongSelf.updateInterfaceState(animated: true, { $0.withoutSelectionState() }) + let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forEveryone).start() + } + })) + } + if actions.options.contains(.deleteLocally) { + var localOptionText = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe + if strongSelf.context.account.peerId == strongSelf.peerId { + if messageIds.count == 1 { + localOptionText = strongSelf.presentationData.strings.Conversation_Moderate_Delete + } else { + localOptionText = strongSelf.presentationData.strings.Conversation_DeleteManyMessages + } + } + items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + if let strongSelf = self { + strongSelf.updateInterfaceState(animated: true, { $0.withoutSelectionState() }) + let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forLocalPeer).start() + } + })) + } + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + strongSelf.present(actionSheet, in: .window(.root)) + } + })) + } + } +} + +private final class ContextControllerContentSourceImpl: ContextControllerContentSource { + let controller: ViewController + weak var sourceNode: ASDisplayNode? + + let navigationController: NavigationController? = nil + + let passthroughTouches: Bool = false + + init(controller: ViewController, sourceNode: ASDisplayNode?) { + self.controller = controller + self.sourceNode = sourceNode + } + + func transitionInfo() -> ContextControllerTakeControllerInfo? { + let sourceNode = self.sourceNode + return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in + if let sourceNode = sourceNode { + return (sourceNode, sourceNode.bounds) + } else { + return nil + } + }) + } + + func animatedIn() { + self.controller.didAppearInContextPreview() + } +} diff --git a/submodules/TelegramUI/Sources/PeerMediaCollectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerMediaCollectionControllerNode.swift new file mode 100644 index 0000000000..ac5bd47f2e --- /dev/null +++ b/submodules/TelegramUI/Sources/PeerMediaCollectionControllerNode.swift @@ -0,0 +1,546 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Postbox +import SwiftSignalKit +import Display +import TelegramCore +import SyncCore +import TelegramPresentationData +import AccountContext +import SearchBarNode +import SearchUI +import ChatListSearchItemNode + +struct PeerMediaCollectionMessageForGallery { + let message: Message + let fromSearchResults: Bool +} + +private func historyNodeImplForMode(_ mode: PeerMediaCollectionMode, context: AccountContext, theme: PresentationTheme, peerId: PeerId, messageId: MessageId?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>) -> ChatHistoryNode & ASDisplayNode { + switch mode { + case .photoOrVideo: + let node = ChatHistoryGridNode(context: context, peerId: peerId, messageId: messageId, tagMask: .photoOrVideo, controllerInteraction: controllerInteraction) + node.showVerticalScrollIndicator = true + if theme.list.plainBackgroundColor.argb == 0xffffffff { + node.indicatorStyle = .default + } else { + node.indicatorStyle = .white + } + return node + case .file: + let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .file, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false, displayHeaders: .all)) + node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor + node.didEndScrolling = { [weak node] in + guard let node = node else { + return + } + fixSearchableListNodeScrolling(node) + } + node.preloadPages = true + return node + case .music: + let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .music, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false, displayHeaders: .all)) + node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor + node.didEndScrolling = { [weak node] in + guard let node = node else { + return + } + fixSearchableListNodeScrolling(node) + } + node.preloadPages = true + return node + case .webpage: + let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .webPage, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false, displayHeaders: .all)) + node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor + node.didEndScrolling = { [weak node] in + guard let node = node else { + return + } + fixSearchableListNodeScrolling(node) + } + node.preloadPages = true + return node + } +} + +private func updateLoadNodeState(_ node: PeerMediaCollectionEmptyNode, _ loadState: ChatHistoryNodeLoadState?) { + if let loadState = loadState { + switch loadState { + case .messages: + node.isHidden = true + node.isLoading = false + case .empty: + node.isHidden = false + node.isLoading = false + case .loading: + node.isHidden = false + node.isLoading = true + } + } else { + node.isHidden = false + node.isLoading = true + } +} + +private func tagMaskForMode(_ mode: PeerMediaCollectionMode) -> MessageTags { + switch mode { + case .photoOrVideo: + return .photoOrVideo + case .file: + return .file + case .music: + return .music + case .webpage: + return .webPage + } +} + +class PeerMediaCollectionControllerNode: ASDisplayNode { + private let context: AccountContext + private let peerId: PeerId + private let controllerInteraction: ChatControllerInteraction + private let interfaceInteraction: ChatPanelInterfaceInteraction + private let navigationBar: NavigationBar? + + private let sectionsNode: PeerMediaCollectionSectionsNode + + private(set) var historyNode: ChatHistoryNode & ASDisplayNode + private var historyEmptyNode: PeerMediaCollectionEmptyNode + + private(set) var searchDisplayController: SearchDisplayController? + + private let candidateHistoryNodeReadyDisposable = MetaDisposable() + private var candidateHistoryNode: (ASDisplayNode, PeerMediaCollectionMode)? + + private var containerLayout: (ContainerViewLayout, CGFloat)? + + var requestLayout: (ContainedViewLayoutTransition) -> Void = { _ in } + var requestUpdateMediaCollectionInterfaceState: (Bool, (PeerMediaCollectionInterfaceState) -> PeerMediaCollectionInterfaceState) -> Void = { _, _ in } + let requestDeactivateSearch: () -> Void + + private var mediaCollectionInterfaceState: PeerMediaCollectionInterfaceState + + private let selectedMessagesPromise = Promise?>(nil) + var selectedMessages: Set? { + didSet { + if self.selectedMessages != oldValue { + self.selectedMessagesPromise.set(.single(self.selectedMessages)) + } + } + } + private var selectionPanel: ChatMessageSelectionInputPanelNode? + private var selectionPanelSeparatorNode: ASDisplayNode? + private var selectionPanelBackgroundNode: ASDisplayNode? + + private var chatPresentationInterfaceState: ChatPresentationInterfaceState + + private var presentationData: PresentationData + + init(context: AccountContext, peerId: PeerId, messageId: MessageId?, controllerInteraction: ChatControllerInteraction, interfaceInteraction: ChatPanelInterfaceInteraction, navigationBar: NavigationBar?, requestDeactivateSearch: @escaping () -> Void) { + self.context = context + self.peerId = peerId + self.controllerInteraction = controllerInteraction + self.interfaceInteraction = interfaceInteraction + self.navigationBar = navigationBar + + self.requestDeactivateSearch = requestDeactivateSearch + + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.mediaCollectionInterfaceState = PeerMediaCollectionInterfaceState(theme: self.presentationData.theme, strings: self.presentationData.strings) + + self.sectionsNode = PeerMediaCollectionSectionsNode(theme: self.presentationData.theme, strings: self.presentationData.strings) + + self.historyNode = historyNodeImplForMode(self.mediaCollectionInterfaceState.mode, context: context, theme: self.presentationData.theme, peerId: peerId, messageId: messageId, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) + self.historyEmptyNode = PeerMediaCollectionEmptyNode(mode: self.mediaCollectionInterfaceState.mode, theme: self.presentationData.theme, strings: self.presentationData.strings) + self.historyEmptyNode.isHidden = true + + self.chatPresentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: .defaultValue, fontSize: self.presentationData.listsFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: .standard(previewing: false), chatLocation: .peer(self.peerId), isScheduledMessages: false, peerNearbyData: nil) + + super.init() + + self.setViewBlock({ + return UITracingLayerView() + }) + + self.historyNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + + self.addSubnode(self.historyNode) + self.addSubnode(self.historyEmptyNode) + if let navigationBar = navigationBar { + self.addSubnode(navigationBar) + } + if let navigationBar = self.navigationBar { + self.insertSubnode(self.sectionsNode, aboveSubnode: navigationBar) + } else { + self.addSubnode(self.sectionsNode) + } + + self.sectionsNode.indexUpdated = { [weak self] index in + if let strongSelf = self { + let mode: PeerMediaCollectionMode + switch index { + case 0: + mode = .photoOrVideo + case 1: + mode = .file + case 2: + mode = .webpage + case 3: + mode = .music + default: + mode = .photoOrVideo + } + strongSelf.requestUpdateMediaCollectionInterfaceState(true, { $0.withMode(mode) }) + } + } + + updateLoadNodeState(self.historyEmptyNode, self.historyNode.loadState) + self.historyNode.setLoadStateUpdated { [weak self] loadState, _ in + if let strongSelf = self { + updateLoadNodeState(strongSelf.historyEmptyNode, loadState) + } + } + } + + deinit { + self.candidateHistoryNodeReadyDisposable.dispose() + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeightAndPrimaryHeight: (CGFloat, CGFloat), transition: ContainedViewLayoutTransition, listViewTransaction: (ListViewUpdateSizeAndInsets) -> Void) { + let navigationBarHeight = navigationBarHeightAndPrimaryHeight.0 + let primaryNavigationBarHeight = navigationBarHeightAndPrimaryHeight.1 + let navigationBarHeightDelta = (navigationBarHeight - primaryNavigationBarHeight) + + self.containerLayout = (layout, navigationBarHeight) + + var vanillaInsets = layout.insets(options: []) + vanillaInsets.top += navigationBarHeight + + var additionalInset: CGFloat = 0.0 + + if (navigationBarHeight - (layout.statusBarHeight ?? 0.0)).isLessThanOrEqualTo(44.0) { + } else { + additionalInset += 10.0 + } + + if let searchDisplayController = self.searchDisplayController { + searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) + if !searchDisplayController.isDeactivating { + vanillaInsets.top += (layout.statusBarHeight ?? 0.0) - navigationBarHeightDelta + } + } + + let sectionsHeight = self.sectionsNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalInset: additionalInset, transition: transition, interfaceState: self.mediaCollectionInterfaceState) + var sectionOffset: CGFloat = 0.0 + if primaryNavigationBarHeight.isZero { + sectionOffset = -sectionsHeight - navigationBarHeightDelta + } else { + //layout.statusBarHeight ?? 0.0 + //if navigationBarHeightAndPrimaryHeight.0 > navigationBarHeightAndPrimaryHeight.1 { + // sectionOffset += 1.0 + //}// + } + transition.updateFrame(node: self.sectionsNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight + sectionOffset), size: CGSize(width: layout.size.width, height: sectionsHeight))) + + var insets = vanillaInsets + if !primaryNavigationBarHeight.isZero { + insets.top += sectionsHeight + } + + if let inputHeight = layout.inputHeight { + insets.bottom += inputHeight + } + + if let selectionState = self.mediaCollectionInterfaceState.selectionState { + let interfaceState = self.chatPresentationInterfaceState.updatedPeer({ _ in self.mediaCollectionInterfaceState.peer.flatMap(RenderedPeer.init) }) + + if let selectionPanel = self.selectionPanel { + selectionPanel.selectedMessages = selectionState.selectedIds + let panelHeight = selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: 0.0, isSecondary: false, transition: transition, interfaceState: interfaceState, metrics: layout.metrics) + transition.updateFrame(node: selectionPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight))) + if let selectionPanelSeparatorNode = self.selectionPanelSeparatorNode { + transition.updateFrame(node: selectionPanelSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) + } + if let selectionPanelBackgroundNode = self.selectionPanelBackgroundNode { + transition.updateFrame(node: selectionPanelBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: insets.bottom + panelHeight))) + } + } else { + let selectionPanelBackgroundNode = ASDisplayNode() + selectionPanelBackgroundNode.isLayerBacked = true + selectionPanelBackgroundNode.backgroundColor = self.mediaCollectionInterfaceState.theme.chat.inputPanel.panelBackgroundColor + self.addSubnode(selectionPanelBackgroundNode) + self.selectionPanelBackgroundNode = selectionPanelBackgroundNode + + let selectionPanel = ChatMessageSelectionInputPanelNode(theme: self.chatPresentationInterfaceState.theme, strings: self.chatPresentationInterfaceState.strings, peerMedia: true) + selectionPanel.context = self.context + selectionPanel.backgroundColor = self.presentationData.theme.chat.inputPanel.panelBackgroundColor + selectionPanel.interfaceInteraction = self.interfaceInteraction + selectionPanel.selectedMessages = selectionState.selectedIds + let panelHeight = selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: 0.0, isSecondary: false, transition: .immediate, interfaceState: interfaceState, metrics: layout.metrics) + self.selectionPanel = selectionPanel + self.addSubnode(selectionPanel) + + let selectionPanelSeparatorNode = ASDisplayNode() + selectionPanelSeparatorNode.isLayerBacked = true + selectionPanelSeparatorNode.backgroundColor = self.mediaCollectionInterfaceState.theme.chat.inputPanel.panelSeparatorColor + self.addSubnode(selectionPanelSeparatorNode) + self.selectionPanelSeparatorNode = selectionPanelSeparatorNode + + selectionPanel.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: panelHeight)) + selectionPanelBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: 0.0)) + selectionPanelSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: UIScreenPixel)) + transition.updateFrame(node: selectionPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight))) + transition.updateFrame(node: selectionPanelBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: insets.bottom + panelHeight))) + transition.updateFrame(node: selectionPanelSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) + } + } else if let selectionPanel = self.selectionPanel { + self.selectionPanel = nil + transition.updateFrame(node: selectionPanel, frame: selectionPanel.frame.offsetBy(dx: 0.0, dy: selectionPanel.bounds.size.height + insets.bottom), completion: { [weak selectionPanel] _ in + selectionPanel?.removeFromSupernode() + }) + if let selectionPanelSeparatorNode = self.selectionPanelSeparatorNode { + transition.updateFrame(node: selectionPanelSeparatorNode, frame: selectionPanelSeparatorNode.frame.offsetBy(dx: 0.0, dy: selectionPanel.bounds.size.height + insets.bottom), completion: { [weak selectionPanelSeparatorNode] _ in + selectionPanelSeparatorNode?.removeFromSupernode() + }) + } + if let selectionPanelBackgroundNode = self.selectionPanelBackgroundNode { + transition.updateFrame(node: selectionPanelBackgroundNode, frame: selectionPanelBackgroundNode.frame.offsetBy(dx: 0.0, dy: selectionPanel.bounds.size.height + insets.bottom), completion: { [weak selectionPanelSeparatorNode] _ in + selectionPanelSeparatorNode?.removeFromSupernode() + }) + } + } + + let previousBounds = self.historyNode.bounds + self.historyNode.bounds = CGRect(x: previousBounds.origin.x, y: previousBounds.origin.y, width: layout.size.width, height: layout.size.height) + self.historyNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) + + self.historyNode.backgroundColor = self.mediaCollectionInterfaceState.theme.list.plainBackgroundColor + self.backgroundColor = self.mediaCollectionInterfaceState.theme.list.plainBackgroundColor + + self.historyEmptyNode.updateLayout(size: layout.size, insets: vanillaInsets, transition: transition, interfaceState: mediaCollectionInterfaceState) + transition.updateFrame(node: self.historyEmptyNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + + var additionalBottomInset: CGFloat = 0.0 + if let selectionPanel = self.selectionPanel { + additionalBottomInset = selectionPanel.bounds.size.height + } + + let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) + listViewTransaction(ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: insets.top, left: + insets.right + layout.safeInsets.right, bottom: insets.bottom + additionalBottomInset, right: insets.left + layout.safeInsets.right), duration: duration, curve: curve)) + + if let (candidateHistoryNode, _) = self.candidateHistoryNode { + let previousBounds = candidateHistoryNode.bounds + candidateHistoryNode.bounds = CGRect(x: previousBounds.origin.x, y: previousBounds.origin.y, width: layout.size.width, height: layout.size.height) + candidateHistoryNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) + + (candidateHistoryNode as! ChatHistoryNode).updateLayout(transition: transition, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: insets.top, left: + insets.right + layout.safeInsets.right, bottom: insets.bottom + additionalBottomInset, right: insets.left + layout.safeInsets.left), duration: duration, curve: curve)) + } + } + + func activateSearch() { + guard let (containerLayout, navigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar else { + return + } + + var maybePlaceholderNode: SearchBarPlaceholderNode? + if let listNode = historyNode as? ListView { + listNode.forEachItemNode { node in + if let node = node as? ChatListSearchItemNode { + maybePlaceholderNode = node.searchBarNode + } + } + } + + if let _ = self.searchDisplayController { + return + } + + if let placeholderNode = maybePlaceholderNode { + self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, contentNode: ChatHistorySearchContainerNode(context: self.context, peerId: self.peerId, tagMask: tagMaskForMode(self.mediaCollectionInterfaceState.mode), interfaceInteraction: self.controllerInteraction), cancel: { [weak self] in + self?.requestDeactivateSearch() + }) + + self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) + self.searchDisplayController?.activate(insertSubnode: { [weak self] subnode, isSearchBar in + if let strongSelf = self { + strongSelf.insertSubnode(subnode, belowSubnode: navigationBar) + } + }, placeholder: placeholderNode) + } + + } + + func deactivateSearch() { + if let searchDisplayController = self.searchDisplayController { + self.searchDisplayController = nil + var maybePlaceholderNode: SearchBarPlaceholderNode? + if let listNode = self.historyNode as? ListView { + listNode.forEachItemNode { node in + if let node = node as? ChatListSearchItemNode { + maybePlaceholderNode = node.searchBarNode + } + } + } + + searchDisplayController.deactivate(placeholder: maybePlaceholderNode) + } + } + + func updateMediaCollectionInterfaceState(_ mediaCollectionInterfaceState: PeerMediaCollectionInterfaceState, animated: Bool) { + if self.mediaCollectionInterfaceState != mediaCollectionInterfaceState { + if self.mediaCollectionInterfaceState.mode != mediaCollectionInterfaceState.mode { + let previousMode = self.mediaCollectionInterfaceState.mode + if let containerLayout = self.containerLayout, self.candidateHistoryNode == nil || self.candidateHistoryNode!.1 != mediaCollectionInterfaceState.mode { + let node = historyNodeImplForMode(mediaCollectionInterfaceState.mode, context: self.context, theme: self.presentationData.theme, peerId: self.peerId, messageId: nil, controllerInteraction: self.controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) + node.backgroundColor = mediaCollectionInterfaceState.theme.list.plainBackgroundColor + self.candidateHistoryNode = (node, mediaCollectionInterfaceState.mode) + + var vanillaInsets = containerLayout.0.insets(options: []) + vanillaInsets.top += containerLayout.1 + + if let searchDisplayController = self.searchDisplayController { + if !searchDisplayController.isDeactivating { + vanillaInsets.top += containerLayout.0.statusBarHeight ?? 0.0 + } + } + + var insets = vanillaInsets + + if !containerLayout.1.isZero { + insets.top += self.sectionsNode.bounds.size.height + } + + if let inputHeight = containerLayout.0.inputHeight { + insets.bottom += inputHeight + } + + let previousBounds = node.bounds + node.bounds = CGRect(x: previousBounds.origin.x, y: previousBounds.origin.y, width: containerLayout.0.size.width, height: containerLayout.0.size.height) + node.position = CGPoint(x: containerLayout.0.size.width / 2.0, y: containerLayout.0.size.height / 2.0) + + var additionalBottomInset: CGFloat = 0.0 + if let selectionPanel = self.selectionPanel { + additionalBottomInset = selectionPanel.bounds.size.height + } + + node.updateLayout(transition: .immediate, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: containerLayout.0.size, insets: UIEdgeInsets(top: insets.top, left: insets.right + containerLayout.0.safeInsets.right, bottom: insets.bottom + additionalBottomInset, right: insets.left + containerLayout.0.safeInsets.left), duration: 0.0, curve: .Default(duration: nil))) + + let historyEmptyNode = PeerMediaCollectionEmptyNode(mode: mediaCollectionInterfaceState.mode, theme: self.mediaCollectionInterfaceState.theme, strings: self.mediaCollectionInterfaceState.strings) + historyEmptyNode.isHidden = true + historyEmptyNode.updateLayout(size: containerLayout.0.size, insets: vanillaInsets, transition: .immediate, interfaceState: self.mediaCollectionInterfaceState) + historyEmptyNode.frame = CGRect(origin: CGPoint(), size: containerLayout.0.size) + + self.candidateHistoryNodeReadyDisposable.set((node.historyState.get() + |> deliverOnMainQueue).start(next: { [weak self, weak node] _ in + if let strongSelf = self, let strongNode = node, strongNode == strongSelf.candidateHistoryNode?.0 { + strongSelf.candidateHistoryNode = nil + strongSelf.insertSubnode(strongNode, belowSubnode: strongSelf.historyNode) + strongSelf.insertSubnode(historyEmptyNode, aboveSubnode: strongNode) + + let previousNode = strongSelf.historyNode + let previousEmptyNode = strongSelf.historyEmptyNode + strongSelf.historyNode = strongNode + strongSelf.historyEmptyNode = historyEmptyNode + updateLoadNodeState(strongSelf.historyEmptyNode, strongSelf.historyNode.loadState) + strongSelf.historyNode.setLoadStateUpdated { loadState, _ in + if let strongSelf = self { + updateLoadNodeState(strongSelf.historyEmptyNode, loadState) + } + } + + let directionMultiplier: CGFloat + if previousMode.rawValue < mediaCollectionInterfaceState.mode.rawValue { + directionMultiplier = 1.0 + } else { + directionMultiplier = -1.0 + } + + previousNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -directionMultiplier * strongSelf.bounds.width, y: 0.0), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak previousNode] _ in + previousNode?.removeFromSupernode() + }) + previousEmptyNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -directionMultiplier * strongSelf.bounds.width, y: 0.0), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak previousEmptyNode] _ in + previousEmptyNode?.removeFromSupernode() + }) + strongSelf.historyNode.layer.animatePosition(from: CGPoint(x: directionMultiplier * strongSelf.bounds.width, y: 0.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + strongSelf.historyEmptyNode.layer.animatePosition(from: CGPoint(x: directionMultiplier * strongSelf.bounds.width, y: 0.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + } + })) + } + } + + self.mediaCollectionInterfaceState = mediaCollectionInterfaceState + + self.requestLayout(animated ? .animated(duration: 0.4, curve: .spring) : .immediate) + } + } + + func updateHiddenMedia() { + self.historyNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? ChatMessageItemView { + itemNode.updateHiddenMedia() + } else if let itemNode = itemNode as? ListMessageNode { + itemNode.updateHiddenMedia() + } else if let itemNode = itemNode as? GridMessageItemNode { + itemNode.updateHiddenMedia() + } + } + + if let searchContentNode = self.searchDisplayController?.contentNode as? ChatHistorySearchContainerNode { + searchContentNode.updateHiddenMedia() + } + } + + func messageForGallery(_ id: MessageId) -> PeerMediaCollectionMessageForGallery? { + if let message = self.historyNode.messageInCurrentHistoryView(id) { + return PeerMediaCollectionMessageForGallery(message: message, fromSearchResults: false) + } + + if let searchContentNode = self.searchDisplayController?.contentNode as? ChatHistorySearchContainerNode { + if let message = searchContentNode.messageForGallery(id) { + return PeerMediaCollectionMessageForGallery(message: message, fromSearchResults: true) + } + } + + return nil + } + + func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + if let searchContentNode = self.searchDisplayController?.contentNode as? ChatHistorySearchContainerNode { + if let transitionNode = searchContentNode.transitionNodeForGallery(messageId: messageId, media: media) { + return transitionNode + } + } + + var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? + self.historyNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? ChatMessageItemView { + if let result = itemNode.transitionNode(id: messageId, media: media) { + transitionNode = result + } + } else if let itemNode = itemNode as? ListMessageNode { + if let result = itemNode.transitionNode(id: messageId, media: media) { + transitionNode = result + } + } else if let itemNode = itemNode as? GridMessageItemNode { + if let result = itemNode.transitionNode(id: messageId, media: media) { + transitionNode = result + } + } + } + if let transitionNode = transitionNode { + return transitionNode + } + + return nil + } + + func clearHighlightAnimated(_ animated: Bool) { + if let listView = self.historyNode as? ListView { + listView.clearHighlightAnimated(animated) + } + } +} diff --git a/submodules/TelegramUI/Sources/PeerMediaCollectionInterfaceState.swift b/submodules/TelegramUI/Sources/PeerMediaCollectionInterfaceState.swift index 5ef8ed2ef9..c0dea9dd1a 100644 --- a/submodules/TelegramUI/Sources/PeerMediaCollectionInterfaceState.swift +++ b/submodules/TelegramUI/Sources/PeerMediaCollectionInterfaceState.swift @@ -1,7 +1,6 @@ import Foundation import Postbox import TelegramPresentationData -import ChatInterfaceState enum PeerMediaCollectionMode: Int32 { case photoOrVideo diff --git a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift index 6d072820b2..b8be00a33d 100644 --- a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift +++ b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift @@ -140,26 +140,6 @@ private enum NavigatedMessageFromViewPosition { case exact } -private func aroundMessagesFromMessages(_ messages: [Message], centralIndex: MessageIndex) -> [Message] { - guard let index = messages.firstIndex(where: { $0.index.id == centralIndex.id }) else { - return [] - } - var result: [Message] = [] - if index != 0 { - for i in (0 ..< index).reversed() { - result.append(messages[i]) - break - } - } - if index != messages.count - 1 { - for i in index + 1 ..< messages.count { - result.append(messages[i]) - break - } - } - return result -} - private func aroundMessagesFromView(view: MessageHistoryView, centralIndex: MessageIndex) -> [Message] { guard let index = view.entries.firstIndex(where: { $0.index.id == centralIndex.id }) else { return [] @@ -180,45 +160,6 @@ private func aroundMessagesFromView(view: MessageHistoryView, centralIndex: Mess return result } -private func navigatedMessageFromMessages(_ messages: [Message], anchorIndex: MessageIndex, position: NavigatedMessageFromViewPosition) -> (message: Message, around: [Message], exact: Bool)? { - var index = 0 - for message in messages { - if message.index.id == anchorIndex.id { - switch position { - case .exact: - return (message, aroundMessagesFromMessages(messages, centralIndex: message.index), true) - case .later: - if index + 1 < messages.count { - let message = messages[index + 1] - return (message, aroundMessagesFromMessages(messages, centralIndex: messages[index + 1].index), true) - } else { - return nil - } - case .earlier: - if index != 0 { - let message = messages[index - 1] - return (message, aroundMessagesFromMessages(messages, centralIndex: messages[index - 1].index), true) - } else { - return nil - } - } - } - index += 1 - } - if !messages.isEmpty { - switch position { - case .later, .exact: - let message = messages[messages.count - 1] - return (message, aroundMessagesFromMessages(messages, centralIndex: messages[messages.count - 1].index), false) - case .earlier: - let message = messages[0] - return (message, aroundMessagesFromMessages(messages, centralIndex: messages[0].index), false) - } - } else { - return nil - } -} - private func navigatedMessageFromView(_ view: MessageHistoryView, anchorIndex: MessageIndex, position: NavigatedMessageFromViewPosition) -> (message: Message, around: [Message], exact: Bool)? { var index = 0 for entry in view.entries { @@ -258,6 +199,94 @@ private func navigatedMessageFromView(_ view: MessageHistoryView, anchorIndex: M } } +enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation { + case messages(peerId: PeerId, tagMask: MessageTags, at: MessageId) + case singleMessage(MessageId) + case recentActions(Message) + + var playlistId: PeerMessagesMediaPlaylistId { + switch self { + case let .messages(peerId, _, _): + return .peer(peerId) + case let .singleMessage(id): + return .peer(id.peerId) + case let .recentActions(message): + return .recentActions(message.id.peerId) + } + } + + var messageId: MessageId? { + switch self { + case let .messages(_, _, messageId), let .singleMessage(messageId): + return messageId + default: + return nil + } + } + + func isEqual(to: SharedMediaPlaylistLocation) -> Bool { + if let to = to as? PeerMessagesPlaylistLocation { + return self == to + } else { + return false + } + } + + static func ==(lhs: PeerMessagesPlaylistLocation, rhs: PeerMessagesPlaylistLocation) -> Bool { + switch lhs { + case let .messages(peerId, tagMask, at): + if case .messages(peerId, tagMask, at) = rhs { + return true + } else { + return false + } + case let .singleMessage(messageId): + if case .singleMessage(messageId) = rhs { + return true + } else { + return false + } + case let .recentActions(lhsMessage): + if case let .recentActions(rhsMessage) = rhs, lhsMessage.id == rhsMessage.id { + return true + } else { + return false + } + } + } +} + +enum PeerMessagesMediaPlaylistId: Equatable, SharedMediaPlaylistId { + case peer(PeerId) + case recentActions(PeerId) + + func isEqual(to: SharedMediaPlaylistId) -> Bool { + if let to = to as? PeerMessagesMediaPlaylistId { + return self == to + } + return false + } +} + +func peerMessageMediaPlayerType(_ message: Message) -> MediaManagerPlayerType? { + if let file = extractFileMedia(message) { + if file.isVoice || file.isInstantVideo { + return .voice + } else if file.isMusic { + return .music + } + } + return nil +} + +func peerMessagesMediaPlaylistAndItemId(_ message: Message, isRecentActions: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? { + if isRecentActions { + return (PeerMessagesMediaPlaylistId.recentActions(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id)) + } else { + return (PeerMessagesMediaPlaylistId.peer(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id)) + } +} + private struct PlaybackStack { var ids: [MessageId] = [] var set: Set = [] @@ -346,7 +375,9 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { self.messagesLocation = location switch self.messagesLocation { - case let .messages(_, _, messageId), let .singleMessage(messageId), let .custom(_, messageId, _): + case let .messages(_, _, messageId): + self.loadItem(anchor: .messageId(messageId), navigation: .later) + case let .singleMessage(messageId): self.loadItem(anchor: .messageId(messageId), navigation: .later) case let .recentActions(message): self.loadingItem = false @@ -399,7 +430,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { self.currentItem = nil self.updateState() } else { - self.loadItem(anchor: .index(currentItem.current.index), navigation: navigation) + self.loadItem(anchor: .index(currentItem.current.index), navigation: navigation) } } } @@ -475,78 +506,59 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { switch anchor { case let .messageId(messageId): - switch self.messagesLocation { - case let .messages(peerId, tagMask, _): - let historySignal = self.postbox.messageAtId(messageId) - |> take(1) - |> mapToSignal { message -> Signal<(Message, [Message])?, NoError> in - guard let message = message else { - return .single(nil) - } - - return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .index(message.index), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: []) - |> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in - if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: message.index, position: .exact) { - return .single((message, aroundMessages)) - } else { - return .single((message, [])) - } + if case let .messages(peerId, tagMask, _) = self.messagesLocation { + let historySignal = self.postbox.messageAtId(messageId) + |> take(1) + |> mapToSignal { message -> Signal<(Message, [Message])?, NoError> in + guard let message = message else { + return .single(nil) + } + + return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .index(message.index), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: []) + |> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in + if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: message.index, position: .exact) { + return .single((message, aroundMessages)) + } else { + return .single((message, [])) } } - |> take(1) - |> deliverOnMainQueue - self.navigationDisposable.set(historySignal.start(next: { [weak self] messageAndAroundMessages in - if let strongSelf = self { - assert(strongSelf.loadingItem) - - strongSelf.loadingItem = false - if let (message, aroundMessages) = messageAndAroundMessages { - strongSelf.playbackStack.clear() - strongSelf.playbackStack.push(message.id) - strongSelf.currentItem = (message, aroundMessages) - strongSelf.playedToEnd = false - } else { - strongSelf.playedToEnd = true - } - strongSelf.updateState() + } + |> take(1) + |> deliverOnMainQueue + self.navigationDisposable.set(historySignal.start(next: { [weak self] messageAndAroundMessages in + if let strongSelf = self { + assert(strongSelf.loadingItem) + + strongSelf.loadingItem = false + if let (message, aroundMessages) = messageAndAroundMessages { + strongSelf.playbackStack.clear() + strongSelf.playbackStack.push(message.id) + strongSelf.currentItem = (message, aroundMessages) + strongSelf.playedToEnd = false + } else { + strongSelf.playedToEnd = true } - })) - case let .custom(messages, at, _): - self.navigationDisposable.set((messages - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] messages in - if let strongSelf = self { - assert(strongSelf.loadingItem) - - strongSelf.loadingItem = false - if let message = messages.0.first(where: { $0.id == at }) { - strongSelf.playbackStack.clear() - strongSelf.playbackStack.push(message.id) - strongSelf.currentItem = (message, []) - } else { - strongSelf.currentItem = nil - } - strongSelf.updateState() + strongSelf.updateState() + } + })) + } else { + self.navigationDisposable.set((self.postbox.messageAtId(messageId) + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] message in + if let strongSelf = self { + assert(strongSelf.loadingItem) + + strongSelf.loadingItem = false + if let message = message { + strongSelf.playbackStack.clear() + strongSelf.playbackStack.push(message.id) + strongSelf.currentItem = (message, []) + } else { + strongSelf.currentItem = nil } - })) - default: - self.navigationDisposable.set((self.postbox.messageAtId(messageId) - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] message in - if let strongSelf = self { - assert(strongSelf.loadingItem) - - strongSelf.loadingItem = false - if let message = message { - strongSelf.playbackStack.clear() - strongSelf.playbackStack.push(message.id) - strongSelf.currentItem = (message, []) - } else { - strongSelf.currentItem = nil - } - strongSelf.updateState() - } - })) + strongSelf.updateState() + } + })) } case let .index(index): switch self.messagesLocation { @@ -667,107 +679,6 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { self.loadingItem = false self.currentItem = (message, []) self.updateState() - case let .custom(messages, _, loadMore): - let inputIndex: Signal - let looping = self.looping - switch self.order { - case .regular, .reversed: - inputIndex = .single(index) - case .random: - var playbackStack = self.playbackStack - inputIndex = messages - |> map { messages, _, _ -> MessageIndex in - if case let .random(previous) = navigation, previous { - let _ = playbackStack.pop() - while true { - if let id = playbackStack.pop() { - if let message = messages.first(where: { $0.id == id }) { - return message.index - } - } else { - break - } - } - } - return messages.randomElement()?.index ?? index - } - } - let historySignal = inputIndex - |> mapToSignal { inputIndex -> Signal<(Message, [Message])?, NoError> in - return messages - |> mapToSignal { messages, _, loadMore -> Signal<(Message, [Message])?, NoError> in - let position: NavigatedMessageFromViewPosition - switch navigation { - case .later: - position = .later - case .earlier: - position = .earlier - case .random: - position = .exact - } - - if let (message, aroundMessages, exact) = navigatedMessageFromMessages(messages, anchorIndex: inputIndex, position: position) { - switch navigation { - case .random: - return .single((message, [])) - default: - if exact { - return .single((message, aroundMessages)) - } - } - } - - if case .all = looping { - let viewIndex: HistoryViewInputAnchor - if case .earlier = navigation { - viewIndex = .upperBound - } else { - viewIndex = .lowerBound - } - return .single(nil) -// return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: viewIndex, count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: []) -// |> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in -// let position: NavigatedMessageFromViewPosition -// switch navigation { -// case .later, .random: -// position = .earlier -// case .earlier: -// position = .later -// } -// if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: MessageIndex.absoluteLowerBound(), position: position) { -// return .single((message, aroundMessages)) -// } else { -// return .single(nil) -// } -// } - } else { - return .single(nil) - } - - return .complete() - } - } - |> take(1) - |> deliverOnMainQueue - self.navigationDisposable.set(historySignal.start(next: { [weak self] messageAndAroundMessages in - if let strongSelf = self { - assert(strongSelf.loadingItem) - - strongSelf.loadingItem = false - if let (message, aroundMessages) = messageAndAroundMessages { - if case let .random(previous) = navigation, previous { - strongSelf.playbackStack.resetToId(message.id) - } else { - strongSelf.playbackStack.push(message.id) - } - strongSelf.currentItem = (message, aroundMessages) - strongSelf.playedToEnd = false - } else { - strongSelf.playedToEnd = true - } - strongSelf.updateState() - } - })) } } } diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index 553a51b2a3..b8cc8236fd 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -223,9 +223,8 @@ final class PeerSelectionControllerNode: ASDisplayNode { if let requestOpenMessageFromSearch = self?.requestOpenMessageFromSearch { requestOpenMessageFromSearch(peer, messageId) } - }, addContact: nil, peerContextAction: nil, present: { _, _ in - }, presentInGlobalOverlay: { _, _ in - }, navigationController: nil), cancel: { [weak self] in + }, addContact: nil, peerContextAction: nil, present: { _ in + }), cancel: { [weak self] in if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() } diff --git a/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift b/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift index 1efca4fc26..8d0a8134ae 100644 --- a/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift +++ b/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift @@ -5,7 +5,6 @@ import TelegramCore import SyncCore import Display import MergeLists -import AccountContext func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toView: ChatHistoryView, reason: ChatHistoryViewTransitionReason, reverse: Bool, chatLocation: ChatLocation, controllerInteraction: ChatControllerInteraction, scrollPosition: ChatHistoryViewScrollPosition?, initialData: InitialMessageHistoryData?, keyboardButtonsMessage: Message?, cachedData: CachedPeerData?, cachedDataMessages: [MessageId: Message]?, readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?, flashIndicators: Bool, updatedMessageSelection: Bool) -> ChatHistoryViewTransition { let mergeResult: (deleteIndices: [Int], indicesAndItems: [(Int, ChatHistoryEntry, Int?)], updateIndices: [(Int, ChatHistoryEntry, Int)]) diff --git a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift index 14d0be64c1..9fd3dc2846 100644 --- a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift @@ -256,7 +256,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { - self.interfaceInteraction?.navigateToMessage(self.messageId, false) + self.interfaceInteraction?.navigateToMessage(self.messageId) } } } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 682fe934b9..5e6fdc18cf 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -915,8 +915,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.navigateToChatImpl(accountId, peerId, messageId) } - public func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError> { - let historyView = preloadedChatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: tagMask, additionalData: []) + public func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError> { + let historyView = preloadedChatHistoryViewForLocation(location, account: account, chatLocation: chatLocation, fixedCombinedReadStates: nil, tagMask: tagMask, additionalData: []) return historyView |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in switch historyView { @@ -936,8 +936,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { }) } - public func makeOverlayAudioPlayerController(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, isGlobalSearch: Bool, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController { - return OverlayAudioPlayerControllerImpl(context: context, peerId: peerId, type: type, initialMessageId: initialMessageId, initialOrder: initialOrder, isGlobalSearch: isGlobalSearch, parentNavigationController: parentNavigationController) + public func makeOverlayAudioPlayerController(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController { + return OverlayAudioPlayerControllerImpl(context: context, peerId: peerId, type: type, initialMessageId: initialMessageId, initialOrder: initialOrder, parentNavigationController: parentNavigationController) } public func makeTempAccountContext(account: Account) -> AccountContext { @@ -1097,7 +1097,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { } public func makePeerSharedMediaController(context: AccountContext, peerId: PeerId) -> ViewController? { - return nil + return peerSharedMediaControllerImpl(context: context, peerId: peerId) } public func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?) -> ViewController { @@ -1194,7 +1194,6 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in - }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, diff --git a/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift b/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift index ecb2de7a00..9c3afbb6e3 100644 --- a/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift +++ b/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift @@ -8,7 +8,6 @@ import OpenInExternalAppUI import MusicAlbumArtResources import LocalMediaResources import LocationResources -import ChatInterfaceState public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerChatInputState: { interfaceState, inputState -> PeerChatInterfaceState? in if interfaceState == nil { diff --git a/submodules/TelegramUI/Sources/TextLinkHandling.swift b/submodules/TelegramUI/Sources/TextLinkHandling.swift index afab3cbc48..f25dd539ed 100644 --- a/submodules/TelegramUI/Sources/TextLinkHandling.swift +++ b/submodules/TelegramUI/Sources/TextLinkHandling.swift @@ -15,7 +15,6 @@ import HashtagSearchUI import StickerPackPreviewUI import JoinLinkPreviewUI import PresentationDataUtils -import UrlWhitelist func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) { let presentImpl: (ViewController, Any?) -> Void = { controllerToPresent, _ in @@ -59,10 +58,6 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate if let navigationController = controller.navigationController as? NavigationController { context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), subject: .message(messageId))) } - case let .replyThreadMessage(replyThreadMessageId, isChannelPost, maxReadMessageId, messageId): - if let navigationController = controller.navigationController as? NavigationController { - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .replyThread(threadMessageId: replyThreadMessageId, isChannelPost: isChannelPost, maxReadMessageId: maxReadMessageId), subject: .message(messageId))) - } case let .stickerPack(name): let packReference: StickerPackReference = .name(name) controller.present(StickerPackScreen(context: context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root)) diff --git a/submodules/TemporaryCachedPeerDataManager/Sources/ChannelMemberCategoryListContext.swift b/submodules/TemporaryCachedPeerDataManager/Sources/ChannelMemberCategoryListContext.swift index 21045ada0f..a21ed2c762 100644 --- a/submodules/TemporaryCachedPeerDataManager/Sources/ChannelMemberCategoryListContext.swift +++ b/submodules/TemporaryCachedPeerDataManager/Sources/ChannelMemberCategoryListContext.swift @@ -93,7 +93,7 @@ private func isParticipantMember(_ participant: ChannelParticipant, infoIsMember private extension CachedChannelAdminRank { init(participant: ChannelParticipant) { switch participant { - case let .creator(_, _, rank): + case let .creator(_, rank): if let rank = rank { self = .custom(rank) } else { diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 92d99b604a..743e955e10 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -13,13 +13,11 @@ import WalletUrl #endif private let baseTelegramMePaths = ["telegram.me", "t.me", "telegram.dog"] -private let baseTelegraPhPaths = ["telegra.ph/", "te.legra.ph/", "graph.org/", "t.me/iv?"] public enum ParsedInternalPeerUrlParameter { case botStart(String) case groupBotStart(String) case channelMessage(Int32) - case replyThread(Int32, Int32) } public enum ParsedInternalUrl { @@ -245,24 +243,7 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { return nil } } else if let value = Int(pathComponents[1]) { - var commentId: Int32? - if let queryItems = components.queryItems { - for queryItem in queryItems { - if let value = queryItem.value { - if queryItem.name == "comment" { - if let intValue = Int32(value) { - commentId = intValue - break - } - } - } - } - } - if let commentId = commentId { - return .peerName(peerName, .replyThread(Int32(value), commentId)) - } else { - return .peerName(peerName, .channelMessage(Int32(value))) - } + return .peerName(peerName, .channelMessage(Int32(value))) } else { return nil } @@ -288,35 +269,26 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig } } } - |> mapToSignal { peer -> Signal in + |> map { peer -> ResolvedUrl? in if let peer = peer { if let parameter = parameter { switch parameter { case let .botStart(payload): - return .single(.botStart(peerId: peer.id, payload: payload)) + return .botStart(peerId: peer.id, payload: payload) case let .groupBotStart(payload): - return .single(.groupBotStart(peerId: peer.id, payload: payload)) + return .groupBotStart(peerId: peer.id, payload: payload) case let .channelMessage(id): - return .single(.channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id))) - case let .replyThread(id, replyId): - let replyThreadMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id) - return fetchChannelReplyThreadMessage(account: account, messageId: replyThreadMessageId) - |> map { result -> ResolvedUrl? in - guard let result = result else { - return .channelMessage(peerId: peer.id, messageId: replyThreadMessageId) - } - return .replyThreadMessage(replyThreadMessageId: result.messageId, isChannelPost: true, maxReadMessageId: result.maxReadMessageId, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId)) - } + return .channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id)) } } else { if let peer = peer as? TelegramUser, peer.botInfo == nil { - return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil))) + return .peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil)) } else { - return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil))) + return .peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil)) } } } else { - return .single(.peer(nil, .info)) + return .peer(nil, .info) } } case let .peerId(peerId): @@ -476,6 +448,7 @@ public func resolveUrlImpl(account: Account, url: String) -> Signal Bool { } return false } - -public func parseUrl(url: String, wasConcealed: Bool) -> (string: String, concealed: Bool) { - var parsedUrlValue: URL? - if url.hasPrefix("tel:") { - return (url, false) - } else if let parsed = URL(string: url) { - parsedUrlValue = parsed - } else if let parsed = URL(string: "https://" + url) { - parsedUrlValue = parsed - } else if let encoded = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let parsed = URL(string: encoded) { - parsedUrlValue = parsed - } - let host = parsedUrlValue?.host ?? url - - let rawHost = (host as NSString).removingPercentEncoding ?? host - var latin = CharacterSet() - latin.insert(charactersIn: "A"..."Z") - latin.insert(charactersIn: "a"..."z") - latin.insert(charactersIn: "0"..."9") - var punctuation = CharacterSet() - punctuation.insert(charactersIn: ".-/+_") - var hasLatin = false - var hasNonLatin = false - for c in rawHost { - if c.unicodeScalars.allSatisfy(punctuation.contains) { - } else if c.unicodeScalars.allSatisfy(latin.contains) { - hasLatin = true - } else { - hasNonLatin = true - } - } - var concealed = wasConcealed - if hasLatin && hasNonLatin { - concealed = true - } - - var rawDisplayUrl: String - if hasNonLatin { - rawDisplayUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? url - } else { - rawDisplayUrl = url - } - - if let parsedUrlValue = parsedUrlValue, isConcealedUrlWhitelisted(parsedUrlValue) { - concealed = false - } - - let whitelistedSchemes: [String] = [ - "tel", - ] - if let parsedUrlValue = parsedUrlValue, let scheme = parsedUrlValue.scheme, whitelistedSchemes.contains(scheme) { - concealed = false - } - - return (rawDisplayUrl, concealed) -} diff --git a/submodules/WalletUI/Resources/WalletStrings.mapping b/submodules/WalletUI/Resources/WalletStrings.mapping index 0f39866777c67e74b19a01f779b9a0361509e1f3..1246758922535bca4d24a763b4150cc1ff0b4957 100644 GIT binary patch delta 2426 zcmZuydvKgp72lKO?(Vny?fd=u9@*?}o|~lD(h4=iVhaJ1V2k*GmNfZjs@vUmyFuh( zgEFXymEcF$%RA{nxi?wgJRhRZomO+G^ zNsPqQk?G3JtTkV&%r%W&Yz(qoKU1k#jq;V%n``CmR&|achhQ-<9ZLHpvaLR|@1TU8 zcsM+)xp);~X>DYWMtR5HM#YjYZk+LF)a&XMd7cCm9FG=+msj01AgK2os|FOd%~ zQLpf`C62qCF>Fq_MS!(}{4q|b9=w`yQxGd&UC959;+(S&uek#hhD$9_1pf$k6UQFE zlw!C#G%Vun-Os1PBEec3E3vHeBAKWpcDV;Bh4q1cO5;iDrVL8T0A=~cMap5!)lC{k zs3Z*A`#+rt-wx~61}hGeMaO}360gPm)Q!_AlX|c(-6INYX=ZA&Ha<63w+`BwDq@qz z)Jtp(v$Rv(sMpFnta>w>z4&K96MbxN-g2Y7Wj_a^;g7zM7+|dcGn>YuD@=p9J5Zt_ z{3$j>WlZQrF>G%<*A>Nn5(os`G=i~?9va1+T7lN#c7H!zgcV_k_3UZ1Xrg$pux45e z*nlB*P+ZK`-ge4|JYL#}8*(~r!h_U9Z(N$n(QNZufgW4I?_(q(ui&`0CTS{&yUpX8O}&3Lh+$K1lJC`&6h*VL+w`SLcaIszojVT!G6C?M2Pz*yWx+pv-`o$KV!uE3#id@17<6Rf0d%I^#+;wsik ztpbSC)wnH^6W1^nvk9jwE-}ek+|JDtnj)rH%Yt7Xxu{ChIOORQ*D{tzIhx0jPM3Hq zYX;7BMesKzDz>xM40)5h#SR>GYvMXyrLY)^2b!_5>UbMVFJC;dbT~=Z!`G3)PH&vv zj^|t^y#qP7M(@N~=SK0a3yyAV_6Nm{Yk^BbLE4FbC;Mp^{?%#HO_)#Q#0tZdGVX^n9@gk?0k+$U@zjXUV1kcDMRl8 zbtaZhsG`D#X0+?2&%UKI231?93cR8!RFkg0t=8`P!c5(M#{Ia@oudQGT1m*3q#^Cf z`Bs~SER__dL<0LG3N>(*D47e4t!YcL|FqlEEo{r-)l{ax-EU+c{Qoqn$}k4=&&3Pcsi$wk29`gRMiG2wogg=JML$Ru>4QNTV|@WmV9x?5=---y;_8p z;d5>#;FKuRComrz6c4an$AF@%t#0{A22yqv`Me5!3S+*MIyKvBSbZsq|foT9(oY3`wVe}Eoqx4H)cKbdAtzRXoZQFz?kNxhj6y5mmbDw z(iC5?J!Q||ASwC?7FAt*kyknncWBBg@xH`QH%gCUS6HVnW6o!audokymgcDRtv=LI zMu%Id2bV>n^i`bn8gvZj^&$Ei{*o%wV@t2-bez*$ps(XpS5|z3H;t~@z zQRVVlvjWdx0|)+dJfyCpr#YwrdWQQq)<$C3l| zYuphk(Q}AqO!^I~UH#&>>{nisTy>Rn&$FSKii_o0zr*9MtayP}S=lh!h4y4u7P z8=JaK(eOibI0ff6x4DhuruYe#vEy+dIyN_SdUm#RJZ$^R{=b9Ub8pkM`{T)ZpO5#w z@B4e6-}B_c;Dy1*RGg9A)PlR^B>E*uwAyU87>>pw{^iB-(efCpwlB+`gqwpBD2BqQ=zj9x5x+`4Ogb~d z&z>l64`W%IL^A$KT@=8!po-m+&$XjAR^|X(5Ce`3g>W*Z+ZPiX!Z@gPiwLh0cvw+g z8_T6HW}fqpP)x*G>%g{D7PD)`qoF^T(r1E62r5(Ezfm_K< zX8)9Ds=UPJ;R$MfUdwiX=9#bt@aXovC#)V|s zU;(?x1a>2Qv6R@vtDJ?#aEk<4ljsJ7<#cj0+hvxy(k_|xU1F2%Mx2X;usW&GP57)m zLpL)HhS+_+)G95 zapY+`mV`1i+N||Q+uT%Q#>AN7B0pQ>CVS^q)2i`WwNjYPu{UYZPV8}Y(-{6GG%;@NG276!+({}p5=uzTB<6`dGwv_hbjGUowq2HYp z-{x?$^i58-iw9UUnA;g)p8X)|?wokYI&h{LMd>^E)TxW_vaQ?dzQ+R!J&c`{rALqq z#%UjXYMLI!MQI^D#{DHtb)1d$(|+ue3}!WN1m8YO2XM^ZM~`Df>ZgOKQy)EnwKts7x+)W zpkJDaW#l;ES1j#ZjqMl`4myg5lmfkoDJ@IC#^2+8;y3J97!_r9KD@;6MKKhV#mnr< zS~NY-8l+>`n&_feury_eSJ@LsN^8d_NjJTQ&GD@EI@{)PVsoWhnkZaTnSt;PGi@`i z{mtg7!!1ENVcqm{^QLdHDCR(LZ?p!<6zg%kFGn#PmBdNblIBpH_hrE?*fe#dl9U|MzJ (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[0]!, self._r[0]!, [_0]) + public var Wallet_Updated_JustNow: String { return self._s[0]! } + public var Wallet_WordCheck_IncorrectText: String { return self._s[1]! } + public var Wallet_Month_ShortNovember: String { return self._s[2]! } + public var Wallet_Configuration_BlockchainIdPlaceholder: String { return self._s[3]! } + public var Wallet_Info_Send: String { return self._s[4]! } + public var Wallet_TransactionInfo_SendGrams: String { return self._s[5]! } + public func Wallet_Info_TransactionBlockchainFee(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[6]!, self._r[6]!, [_0]) } - public var Wallet_Receive_AddressHeader: String { return self._s[2]! } - public var Wallet_Navigation_Cancel: String { return self._s[3]! } - public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[4]! } - public var Wallet_Month_GenNovember: String { return self._s[5]! } - public var Wallet_Month_GenApril: String { return self._s[6]! } - public var Wallet_Weekday_Today: String { return self._s[7]! } - public var Wallet_Info_ReceiveGrams: String { return self._s[9]! } - public var Wallet_TransactionInfo_Title: String { return self._s[10]! } - public var Wallet_Receive_CommentHeader: String { return self._s[11]! } - public func Wallet_Sent_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[12]!, self._r[12]!, [_0]) - } - public var Wallet_Info_WalletCreated: String { return self._s[14]! } - public var Wallet_Month_GenJanuary: String { return self._s[15]! } - public var Wallet_Send_NetworkErrorTitle: String { return self._s[16]! } - public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[17]! } - public var Wallet_WordCheck_Continue: String { return self._s[18]! } - public func Wallet_Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[19]!, self._r[19]!, [_1, _2, _3]) - } - public var Wallet_Created_ExportErrorText: String { return self._s[20]! } - public var Wallet_Info_RefreshErrorText: String { return self._s[21]! } - public var Wallet_Month_GenSeptember: String { return self._s[22]! } - public var Wallet_Month_GenDecember: String { return self._s[23]! } - public var Wallet_Sent_Title: String { return self._s[24]! } - public func Wallet_SecureStorageChanged_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[25]!, self._r[25]!, [_0]) - } - public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[26]! } - public var Wallet_Receive_CopyAddress: String { return self._s[27]! } - public var Wallet_TransactionInfo_SenderHeader: String { return self._s[28]! } - public var Wallet_Words_NotDoneOk: String { return self._s[29]! } - public var Wallet_Receive_AmountHeader: String { return self._s[30]! } - public var Wallet_Configuration_SourceJSON: String { return self._s[31]! } - public var Wallet_Send_ErrorInvalidAddress: String { return self._s[32]! } - public var Wallet_Receive_ShareUrlInfo: String { return self._s[33]! } - public var Wallet_Words_Text: String { return self._s[34]! } - public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[35]! } - public var Wallet_Send_AddressInfo: String { return self._s[36]! } - public var Wallet_Month_ShortJuly: String { return self._s[37]! } - public var Wallet_AccessDenied_Settings: String { return self._s[38]! } - public var Wallet_WordImport_IncorrectTitle: String { return self._s[39]! } - public var Wallet_Completed_Text: String { return self._s[40]! } - public var Wallet_Info_TransactionFrom: String { return self._s[41]! } - public func Wallet_Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[42]!, self._r[42]!, [_1, _2, _3]) - } - public var Wallet_Configuration_ApplyErrorTextJSONInvalidData: String { return self._s[43]! } - public var Wallet_SecureStorageReset_PasscodeText: String { return self._s[44]! } - public var Wallet_Month_ShortNovember: String { return self._s[46]! } - public var Wallet_Configuration_BlockchainNameChangedText: String { return self._s[47]! } - public var Wallet_Configuration_ApplyErrorTextURLInvalid: String { return self._s[48]! } - public var Wallet_Send_UninitializedText: String { return self._s[49]! } - public var Wallet_WordImport_Title: String { return self._s[50]! } - public func Wallet_Info_TransactionDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[51]!, self._r[51]!, [_1, _2, _3]) - } - public var Wallet_Send_AddressHeader: String { return self._s[52]! } - public var Wallet_Send_Title: String { return self._s[53]! } - public var Wallet_Send_SendAnyway: String { return self._s[54]! } - public func Wallet_Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[55]!, self._r[55]!, [_1, _2, _3]) - } - public var Wallet_UnknownError: String { return self._s[56]! } - public var Wallet_Month_ShortApril: String { return self._s[57]! } - public var Wallet_Settings_ConfigurationInfo: String { return self._s[58]! } - public var Wallet_Qr_ScanCode: String { return self._s[59]! } - public var Wallet_Info_Address: String { return self._s[60]! } + public var Wallet_Sent_Title: String { return self._s[7]! } + public var Wallet_Receive_ShareUrlInfo: String { return self._s[8]! } + public var Wallet_RestoreFailed_Title: String { return self._s[9]! } + public var Wallet_TransactionInfo_CopyAddress: String { return self._s[11]! } + public var Wallet_Settings_BackupWallet: String { return self._s[12]! } + public var Wallet_Send_NetworkErrorTitle: String { return self._s[13]! } + public var Wallet_Month_ShortJune: String { return self._s[14]! } + public var Wallet_TransactionInfo_StorageFeeInfo: String { return self._s[15]! } + public var Wallet_Created_Title: String { return self._s[16]! } public func Wallet_Configuration_ApplyErrorTextURLUnreachable(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[61]!, self._r[61]!, [_0]) + return formatWithArgumentRanges(self._s[17]!, self._r[17]!, [_0]) } - public var Wallet_Month_ShortMay: String { return self._s[62]! } - public var Wallet_Send_OwnAddressAlertTitle: String { return self._s[63]! } - public var Wallet_TransactionInfo_OtherFeeInfoUrl: String { return self._s[64]! } - public var Wallet_WordCheck_IncorrectText: String { return self._s[65]! } - public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[66]! } - public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[67]! } - public var Wallet_TransactionInfo_StorageFeeInfoUrl: String { return self._s[68]! } - public var Wallet_Navigation_Back: String { return self._s[69]! } - public var Wallet_Send_Confirmation: String { return self._s[70]! } - public var Wallet_Configuration_SourceURL: String { return self._s[71]! } - public var Wallet_Intro_CreateErrorTitle: String { return self._s[72]! } - public var Wallet_Month_GenJuly: String { return self._s[73]! } - public var Wallet_Words_NotDoneTitle: String { return self._s[74]! } - public var Wallet_TransactionInfo_SendGrams: String { return self._s[75]! } - public func Wallet_SecureStorageReset_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[76]!, self._r[76]!, [_0]) - } - public var Wallet_Send_UninitializedTitle: String { return self._s[77]! } - public var Wallet_Created_Title: String { return self._s[78]! } - public var Wallet_Settings_Title: String { return self._s[79]! } - public var Wallet_Completed_ViewWallet: String { return self._s[80]! } - public var Wallet_Send_SyncInProgress: String { return self._s[81]! } - public var Wallet_Configuration_SourceHeader: String { return self._s[82]! } + public var Wallet_Send_SyncInProgress: String { return self._s[18]! } + public var Wallet_Info_YourBalance: String { return self._s[19]! } + public var Wallet_Configuration_ApplyErrorTextURLInvalidData: String { return self._s[20]! } + public var Wallet_TransactionInfo_CommentHeader: String { return self._s[21]! } + public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[22]! } public func Wallet_Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[23]!, self._r[23]!, [_1, _2, _3]) + } + public var Wallet_Settings_ConfigurationInfo: String { return self._s[24]! } + public var Wallet_WordImport_IncorrectText: String { return self._s[25]! } + public var Wallet_Month_GenJanuary: String { return self._s[26]! } + public var Wallet_Send_OwnAddressAlertTitle: String { return self._s[27]! } + public var Wallet_Receive_ShareAddress: String { return self._s[28]! } + public var Wallet_WordImport_Title: String { return self._s[29]! } + public var Wallet_TransactionInfo_Title: String { return self._s[30]! } + public var Wallet_Words_NotDoneText: String { return self._s[32]! } + public var Wallet_RestoreFailed_EnterWords: String { return self._s[33]! } + public var Wallet_WordImport_Text: String { return self._s[34]! } + public var Wallet_RestoreFailed_Text: String { return self._s[36]! } + public var Wallet_TransactionInfo_NoAddress: String { return self._s[37]! } + public var Wallet_Navigation_Back: String { return self._s[38]! } + public var Wallet_Intro_Terms: String { return self._s[39]! } + public func Wallet_Send_Balance(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[40]!, self._r[40]!, [_0]) + } + public func Wallet_Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[41]!, self._r[41]!, [_1, _2, _3]) + } + public var Wallet_TransactionInfo_AddressCopied: String { return self._s[42]! } + public func Wallet_Info_TransactionDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[43]!, self._r[43]!, [_1, _2, _3]) + } + public var Wallet_Send_NetworkErrorText: String { return self._s[44]! } + public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[45]! } + public var Wallet_Intro_ImportExisting: String { return self._s[46]! } + public var Wallet_Receive_CommentInfo: String { return self._s[47]! } + public var Wallet_WordCheck_Continue: String { return self._s[48]! } + public var Wallet_Send_EncryptComment: String { return self._s[49]! } + public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[50]! } + public var Wallet_Completed_Text: String { return self._s[51]! } + public var Wallet_WordCheck_IncorrectHeader: String { return self._s[53]! } + public var Wallet_Configuration_SourceHeader: String { return self._s[54]! } + public var Wallet_TransactionInfo_StorageFeeInfoUrl: String { return self._s[55]! } + public var Wallet_Receive_Title: String { return self._s[56]! } + public var Wallet_Info_WalletCreated: String { return self._s[57]! } + public var Wallet_Navigation_Cancel: String { return self._s[58]! } + public var Wallet_CreateInvoice_Title: String { return self._s[59]! } + public func Wallet_WordCheck_Text(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[60]!, self._r[60]!, [_1, _2, _3]) + } + public var Wallet_TransactionInfo_SenderHeader: String { return self._s[61]! } + public func Wallet_Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[62]!, self._r[62]!, [_1, _2, _3]) + } + public var Wallet_Month_GenAugust: String { return self._s[63]! } + public var Wallet_Info_UnknownTransaction: String { return self._s[64]! } + public var Wallet_Receive_CreateInvoice: String { return self._s[65]! } + public var Wallet_Month_GenSeptember: String { return self._s[66]! } + public var Wallet_Month_GenJuly: String { return self._s[67]! } + public var Wallet_Receive_AddressHeader: String { return self._s[68]! } + public var Wallet_Send_AmountText: String { return self._s[69]! } + public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[70]! } + public func Wallet_Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[71]!, self._r[71]!, [_1, _2, _3]) + } + public func Wallet_Updated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[72]!, self._r[72]!, [_0]) + } + public var Wallet_Configuration_Title: String { return self._s[74]! } + public var Wallet_Configuration_BlockchainIdHeader: String { return self._s[75]! } + public var Wallet_Words_Title: String { return self._s[76]! } + public var Wallet_Month_ShortMay: String { return self._s[77]! } + public var Wallet_WordCheck_Title: String { return self._s[78]! } + public var Wallet_Words_NotDoneResponse: String { return self._s[79]! } + public var Wallet_Configuration_SourceURL: String { return self._s[80]! } + public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[81]! } + public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[82]! } + public func Wallet_Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[83]!, self._r[83]!, [_1, _2, _3]) } - public var Wallet_Info_Updating: String { return self._s[84]! } - public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[85]! } - public var Wallet_Month_ShortMarch: String { return self._s[86]! } - public var Wallet_Send_Send: String { return self._s[87]! } - public var Wallet_Send_TransactionInProgress: String { return self._s[88]! } - public var Wallet_Month_ShortJanuary: String { return self._s[89]! } - public var Wallet_Navigation_Done: String { return self._s[90]! } - public var Wallet_Words_NotDoneText: String { return self._s[91]! } - public var Wallet_Month_GenMay: String { return self._s[92]! } - public var Wallet_TransactionInfo_AddressCopied: String { return self._s[93]! } - public var Wallet_Month_GenMarch: String { return self._s[94]! } - public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[95]! } - public var Wallet_RestoreFailed_CreateWallet: String { return self._s[96]! } - public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[97]! } - public var Wallet_Receive_AmountText: String { return self._s[98]! } - public var Wallet_Receive_ShareAddress: String { return self._s[99]! } - public var Wallet_Receive_CommentInfo: String { return self._s[100]! } - public var Wallet_Intro_Text: String { return self._s[101]! } - public var Wallet_WordImport_IncorrectText: String { return self._s[102]! } - public var Wallet_Month_GenFebruary: String { return self._s[104]! } - public var Wallet_Send_NetworkErrorText: String { return self._s[105]! } - public var Wallet_Created_Proceed: String { return self._s[106]! } - public var Wallet_Info_UnknownTransaction: String { return self._s[107]! } - public func Wallet_Send_Balance(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[108]!, self._r[108]!, [_0]) + public var Wallet_Info_Address: String { return self._s[84]! } + public var Wallet_Intro_CreateWallet: String { return self._s[85]! } + public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[86]! } + public func Wallet_SecureStorageReset_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[87]!, self._r[87]!, [_0]) } - public var Wallet_SecureStorageReset_Title: String { return self._s[110]! } - public var Wallet_Configuration_Title: String { return self._s[111]! } - public var Wallet_Send_ErrorDecryptionFailed: String { return self._s[112]! } - public var Wallet_CreateInvoice_Title: String { return self._s[113]! } - public var Wallet_Info_Receive: String { return self._s[114]! } - public var Wallet_Sending_Text: String { return self._s[115]! } - public var Wallet_Intro_NotNow: String { return self._s[116]! } - public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[117]! } - public var Wallet_TransactionInfo_CommentHeader: String { return self._s[118]! } - public var Wallet_Intro_CreateErrorText: String { return self._s[119]! } - public var Wallet_Weekday_Yesterday: String { return self._s[120]! } - public var Wallet_Configuration_ApplyErrorTitle: String { return self._s[121]! } - public var Wallet_Info_Send: String { return self._s[122]! } - public func Wallet_Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[123]!, self._r[123]!, [_1, _2, _3]) - } - public var Wallet_Intro_CreateWallet: String { return self._s[124]! } - public var Wallet_Sending_Title: String { return self._s[125]! } - public var Wallet_Updated_JustNow: String { return self._s[126]! } - public func Wallet_Info_TransactionBlockchainFee(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[127]!, self._r[127]!, [_0]) - } - public var Wallet_Month_ShortDecember: String { return self._s[128]! } - public var Wallet_Info_YourBalance: String { return self._s[129]! } - public var Wallet_Configuration_BlockchainNameChangedTitle: String { return self._s[130]! } - public var Wallet_AccessDenied_Title: String { return self._s[131]! } - public var Wallet_Words_Title: String { return self._s[132]! } - public var Wallet_Configuration_BlockchainNameChangedProceed: String { return self._s[133]! } + public var Wallet_Send_SendAnyway: String { return self._s[88]! } + public var Wallet_UnknownError: String { return self._s[89]! } + public var Wallet_Configuration_ApplyErrorTextURLInvalid: String { return self._s[90]! } + public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[91]! } + public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[93]! } + public var Wallet_Configuration_SourceInfo: String { return self._s[94]! } + public var Wallet_Words_NotDoneOk: String { return self._s[95]! } + public var Wallet_Intro_Title: String { return self._s[96]! } + public var Wallet_Info_Receive: String { return self._s[97]! } + public var Wallet_Completed_ViewWallet: String { return self._s[98]! } + public var Wallet_Month_ShortJuly: String { return self._s[99]! } + public var Wallet_Month_ShortApril: String { return self._s[100]! } public func Wallet_Info_TransactionDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[134]!, self._r[134]!, [_1, _2]) + return formatWithArgumentRanges(self._s[101]!, self._r[101]!, [_1, _2]) } - public var Wallet_Words_NotDoneResponse: String { return self._s[135]! } - public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[136]! } - public func Wallet_WordCheck_Text(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[102]! } + public func Wallet_Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[103]!, self._r[103]!, [_1, _2, _3]) + } + public var Wallet_Send_UninitializedText: String { return self._s[105]! } + public func Wallet_Sent_Text(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[106]!, self._r[106]!, [_0]) + } + public var Wallet_Month_GenNovember: String { return self._s[107]! } + public func Wallet_Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[108]!, self._r[108]!, [_1, _2, _3]) + } + public var Wallet_Month_GenApril: String { return self._s[109]! } + public var Wallet_Month_ShortMarch: String { return self._s[110]! } + public var Wallet_Month_GenFebruary: String { return self._s[111]! } + public var Wallet_Qr_ScanCode: String { return self._s[112]! } + public var Wallet_Receive_AddressCopied: String { return self._s[113]! } + public var Wallet_Send_UninitializedTitle: String { return self._s[114]! } + public var Wallet_AccessDenied_Title: String { return self._s[115]! } + public var Wallet_AccessDenied_Settings: String { return self._s[116]! } + public var Wallet_Send_Send: String { return self._s[117]! } + public var Wallet_Info_RefreshErrorTitle: String { return self._s[118]! } + public var Wallet_Month_GenJune: String { return self._s[119]! } + public var Wallet_Send_AddressHeader: String { return self._s[120]! } + public var Wallet_SecureStorageReset_BiometryTouchId: String { return self._s[121]! } + public var Wallet_Send_Confirmation: String { return self._s[122]! } + public var Wallet_Completed_Title: String { return self._s[123]! } + public var Wallet_Alert_OK: String { return self._s[124]! } + public var Wallet_Settings_DeleteWallet: String { return self._s[125]! } + public var Wallet_SecureStorageReset_PasscodeText: String { return self._s[126]! } + public var Wallet_Month_ShortSeptember: String { return self._s[127]! } + public var Wallet_Info_TransactionTo: String { return self._s[128]! } + public var Wallet_Send_ConfirmationConfirm: String { return self._s[129]! } + public var Wallet_TransactionInfo_OtherFeeInfo: String { return self._s[130]! } + public var Wallet_Receive_AmountText: String { return self._s[131]! } + public var Wallet_Receive_CopyAddress: String { return self._s[132]! } + public var Wallet_Intro_Text: String { return self._s[134]! } + public var Wallet_Configuration_Apply: String { return self._s[135]! } + public func Wallet_SecureStorageChanged_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[136]!, self._r[136]!, [_0]) + } + public func Wallet_Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[137]!, self._r[137]!, [_1, _2, _3]) } - public var Wallet_SecureStorageReset_BiometryTouchId: String { return self._s[138]! } - public var Wallet_RestoreFailed_Title: String { return self._s[140]! } - public var Wallet_Alert_OK: String { return self._s[141]! } - public var Wallet_Navigation_Close: String { return self._s[142]! } - public var Wallet_Configuration_BlockchainIdHeader: String { return self._s[143]! } - public var Wallet_Send_AddressText: String { return self._s[144]! } - public var Wallet_Configuration_BlockchainIdInfo: String { return self._s[145]! } - public func Wallet_Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[146]!, self._r[146]!, [_1, _2, _3]) + public var Wallet_RestoreFailed_CreateWallet: String { return self._s[138]! } + public var Wallet_Weekday_Yesterday: String { return self._s[139]! } + public var Wallet_Receive_AmountHeader: String { return self._s[140]! } + public var Wallet_TransactionInfo_OtherFeeInfoUrl: String { return self._s[141]! } + public var Wallet_Month_ShortFebruary: String { return self._s[142]! } + public var Wallet_Configuration_SourceJSON: String { return self._s[143]! } + public var Wallet_Alert_Cancel: String { return self._s[144]! } + public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[145]! } + public var Wallet_Configuration_ApplyErrorTextJSONInvalidData: String { return self._s[146]! } + public var Wallet_Info_TransactionFrom: String { return self._s[147]! } + public var Wallet_Send_ErrorDecryptionFailed: String { return self._s[148]! } + public var Wallet_Send_OwnAddressAlertText: String { return self._s[149]! } + public var Wallet_Words_NotDoneTitle: String { return self._s[150]! } + public var Wallet_Month_ShortOctober: String { return self._s[151]! } + public var Wallet_Month_GenMay: String { return self._s[152]! } + public var Wallet_Intro_CreateErrorTitle: String { return self._s[153]! } + public var Wallet_SecureStorageReset_BiometryFaceId: String { return self._s[154]! } + public var Wallet_Month_ShortJanuary: String { return self._s[155]! } + public var Wallet_Month_GenMarch: String { return self._s[156]! } + public var Wallet_AccessDenied_Camera: String { return self._s[157]! } + public var Wallet_Sending_Text: String { return self._s[158]! } + public var Wallet_Month_GenOctober: String { return self._s[159]! } + public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[160]! } + public var Wallet_ContextMenuCopy: String { return self._s[161]! } + public func Wallet_Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[162]!, self._r[162]!, [_1, _2, _3]) } - public func Wallet_Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[147]!, self._r[147]!, [_1, _2, _3]) + public var Wallet_Info_Updating: String { return self._s[164]! } + public var Wallet_Created_ExportErrorTitle: String { return self._s[165]! } + public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[166]! } + public var Wallet_Sending_Title: String { return self._s[167]! } + public var Wallet_Navigation_Done: String { return self._s[168]! } + public var Wallet_Configuration_BlockchainIdInfo: String { return self._s[169]! } + public var Wallet_Configuration_BlockchainNameChangedTitle: String { return self._s[170]! } + public var Wallet_Settings_Title: String { return self._s[171]! } + public func Wallet_Receive_ShareInvoiceUrlInfo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[172]!, self._r[172]!, [_0]) } - public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[148]! } - public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[149]! } - public var Wallet_TransactionInfo_CopyAddress: String { return self._s[150]! } - public var Wallet_Settings_BackupWallet: String { return self._s[151]! } - public var Wallet_Configuration_ApplyErrorTextURLInvalidData: String { return self._s[152]! } - public var Wallet_RestoreFailed_EnterWords: String { return self._s[153]! } - public var Wallet_Created_Text: String { return self._s[154]! } - public var Wallet_Month_ShortJune: String { return self._s[155]! } - public var Wallet_Send_AmountText: String { return self._s[156]! } - public var Wallet_Intro_Title: String { return self._s[157]! } - public var Wallet_Month_GenAugust: String { return self._s[158]! } - public var Wallet_Qr_Title: String { return self._s[159]! } - public var Wallet_Month_GenJune: String { return self._s[160]! } - public var Wallet_Configuration_Apply: String { return self._s[162]! } - public func Wallet_Send_ConfirmationText(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[163]!, self._r[163]!, [_1, _2, _3]) - } - public var Wallet_ContextMenuCopy: String { return self._s[164]! } - public var Wallet_SecureStorageReset_BiometryFaceId: String { return self._s[165]! } - public var Wallet_WordCheck_TryAgain: String { return self._s[166]! } - public var Wallet_Settings_DeleteWalletInfo: String { return self._s[167]! } - public var Wallet_Month_ShortOctober: String { return self._s[168]! } - public var Wallet_Configuration_SourceInfo: String { return self._s[169]! } - public var Wallet_TransactionInfo_NoAddress: String { return self._s[170]! } - public var Wallet_WordCheck_IncorrectHeader: String { return self._s[171]! } - public var Wallet_Completed_Title: String { return self._s[172]! } - public func Wallet_Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[173]!, self._r[173]!, [_1, _2, _3]) - } - public var Wallet_WordImport_Text: String { return self._s[174]! } - public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[175]! } - public func Wallet_Updated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[177]!, self._r[177]!, [_0]) - } - public var Wallet_Send_OwnAddressAlertProceed: String { return self._s[178]! } - public var Wallet_RestoreFailed_Text: String { return self._s[179]! } - public var Wallet_Settings_DeleteWallet: String { return self._s[180]! } - public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[181]! } - public var Wallet_Settings_Configuration: String { return self._s[182]! } - public var Wallet_Sent_ViewWallet: String { return self._s[183]! } - public var Wallet_WordImport_Continue: String { return self._s[184]! } - public var Wallet_WordCheck_ViewWords: String { return self._s[185]! } - public var Wallet_Words_Done: String { return self._s[186]! } - public func Wallet_Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[187]!, self._r[187]!, [_1, _2, _3]) - } - public var Wallet_TransactionInfo_StorageFeeInfo: String { return self._s[188]! } - public func Wallet_Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[190]!, self._r[190]!, [_1, _2, _3]) - } - public var Wallet_Info_TransactionTo: String { return self._s[191]! } - public var Wallet_AccessDenied_Camera: String { return self._s[192]! } - public var Wallet_Info_RefreshErrorTitle: String { return self._s[193]! } - public var Wallet_Month_ShortAugust: String { return self._s[194]! } - public var Wallet_TransactionInfo_OtherFeeInfo: String { return self._s[195]! } - public var Wallet_Receive_AmountInfo: String { return self._s[196]! } - public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[197]! } - public var Wallet_Receive_CreateInvoice: String { return self._s[198]! } - public var Wallet_Receive_Title: String { return self._s[200]! } - public var Wallet_Configuration_BlockchainIdPlaceholder: String { return self._s[201]! } - public var Wallet_Month_ShortFebruary: String { return self._s[202]! } - public func Wallet_Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[203]!, self._r[203]!, [_1, _2, _3]) - } - public var Wallet_Receive_AddressCopied: String { return self._s[204]! } - public var Wallet_Month_GenOctober: String { return self._s[205]! } - public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[206]! } - public var Wallet_Send_EncryptComment: String { return self._s[207]! } - public var Wallet_WordCheck_Title: String { return self._s[208]! } - public var Wallet_Alert_Cancel: String { return self._s[209]! } + public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[173]! } + public var Wallet_Weekday_Today: String { return self._s[175]! } + public var Wallet_Month_ShortDecember: String { return self._s[176]! } + public var Wallet_Words_Text: String { return self._s[177]! } + public var Wallet_Configuration_BlockchainNameChangedProceed: String { return self._s[178]! } + public var Wallet_WordCheck_ViewWords: String { return self._s[179]! } + public var Wallet_Send_AddressInfo: String { return self._s[180]! } public func Wallet_Updated_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[181]!, self._r[181]!, [_0]) + } + public var Wallet_Intro_NotNow: String { return self._s[182]! } + public var Wallet_Send_OwnAddressAlertProceed: String { return self._s[183]! } + public var Wallet_Navigation_Close: String { return self._s[184]! } + public var Wallet_Month_GenDecember: String { return self._s[186]! } + public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[187]! } + public var Wallet_WordImport_IncorrectTitle: String { return self._s[188]! } + public var Wallet_Send_AddressText: String { return self._s[189]! } + public var Wallet_Receive_AmountInfo: String { return self._s[190]! } + public func Wallet_Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[191]!, self._r[191]!, [_1, _2, _3]) + } + public var Wallet_Month_ShortAugust: String { return self._s[192]! } + public var Wallet_Qr_Title: String { return self._s[193]! } + public var Wallet_Settings_Configuration: String { return self._s[194]! } + public var Wallet_WordCheck_TryAgain: String { return self._s[195]! } + public var Wallet_Info_TransactionPendingHeader: String { return self._s[196]! } + public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[197]! } + public var Wallet_Configuration_ApplyErrorTitle: String { return self._s[198]! } + public var Wallet_Send_TransactionInProgress: String { return self._s[199]! } + public var Wallet_Created_Text: String { return self._s[200]! } + public var Wallet_Created_Proceed: String { return self._s[201]! } + public var Wallet_Words_Done: String { return self._s[202]! } + public var Wallet_WordImport_Continue: String { return self._s[203]! } + public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[204]! } + public var Wallet_WordImport_CanNotRemember: String { return self._s[205]! } + public func Wallet_Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[206]!, self._r[206]!, [_1, _2, _3]) + } + public func Wallet_Send_ConfirmationText(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[207]!, self._r[207]!, [_1, _2, _3]) + } + public var Wallet_Created_ExportErrorText: String { return self._s[209]! } + public func Wallet_Updated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[210]!, self._r[210]!, [_0]) } - public var Wallet_Intro_Terms: String { return self._s[211]! } - public func Wallet_Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[212]!, self._r[212]!, [_1, _2, _3]) + public var Wallet_Settings_DeleteWalletInfo: String { return self._s[211]! } + public var Wallet_Intro_CreateErrorText: String { return self._s[212]! } + public var Wallet_Sent_ViewWallet: String { return self._s[213]! } + public var Wallet_Send_ErrorInvalidAddress: String { return self._s[214]! } + public var Wallet_Configuration_BlockchainNameChangedText: String { return self._s[215]! } + public func Wallet_Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[216]!, self._r[216]!, [_1, _2, _3]) } - public var Wallet_Intro_ImportExisting: String { return self._s[213]! } - public var Wallet_WordImport_CanNotRemember: String { return self._s[214]! } - public var Wallet_Month_ShortSeptember: String { return self._s[215]! } - public var Wallet_Send_OwnAddressAlertText: String { return self._s[216]! } - public func Wallet_Receive_ShareInvoiceUrlInfo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[217]!, self._r[217]!, [_0]) - } - public var Wallet_Send_ConfirmationConfirm: String { return self._s[218]! } - public var Wallet_Created_ExportErrorTitle: String { return self._s[219]! } - public var Wallet_Info_TransactionPendingHeader: String { return self._s[220]! } - public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String { + public var Wallet_Send_Title: String { return self._s[217]! } + public var Wallet_Info_RefreshErrorText: String { return self._s[218]! } + public var Wallet_SecureStorageReset_Title: String { return self._s[219]! } + public var Wallet_Receive_CommentHeader: String { return self._s[220]! } + public var Wallet_Info_ReceiveGrams: String { return self._s[221]! } + public func Wallet_Updated_HoursAgo(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue) } - public func Wallet_Updated_HoursAgo(_ value: Int32) -> String { + public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue) diff --git a/submodules/WebsiteType/BUCK b/submodules/WebsiteType/BUCK index 7c8ecfbdd2..cc609e752b 100644 --- a/submodules/WebsiteType/BUCK +++ b/submodules/WebsiteType/BUCK @@ -5,9 +5,6 @@ static_library( srcs = glob([ "Sources/**/*.swift", ]), - deps = [ - "//submodules/SyncCore:SyncCore#shared", - ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", ], diff --git a/submodules/WebsiteType/BUILD b/submodules/WebsiteType/BUILD index dd3c2d8c69..ba2a658eb1 100644 --- a/submodules/WebsiteType/BUILD +++ b/submodules/WebsiteType/BUILD @@ -7,7 +7,6 @@ swift_library( "Sources/**/*.swift", ]), deps = [ - "//submodules/SyncCore:SyncCore", ], visibility = [ "//visibility:public", diff --git a/submodules/WebsiteType/Sources/WebsiteType.swift b/submodules/WebsiteType/Sources/WebsiteType.swift index 53b6950108..ce214994cb 100644 --- a/submodules/WebsiteType/Sources/WebsiteType.swift +++ b/submodules/WebsiteType/Sources/WebsiteType.swift @@ -1,5 +1,4 @@ import Foundation -import SyncCore public enum WebsiteType { case generic @@ -17,21 +16,3 @@ public func websiteType(of websiteName: String?) -> WebsiteType { } return .generic } - -public enum InstantPageType { - case generic - case album -} - -public func instantPageType(of webpage: TelegramMediaWebpageLoadedContent) -> InstantPageType { - if let type = webpage.type, type == "telegram_album" { - return .album - } - - switch websiteType(of: webpage.websiteName) { - case .instagram, .twitter: - return .album - default: - return .generic - } -} From e1c4ddc9aed227439a95a77b31489ed8f7adae0a Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 21 Sep 2020 17:02:26 +0300 Subject: [PATCH 2/6] Apply fixes --- build-system/bazel-rules/apple_support | 2 +- build-system/bazel-rules/rules_swift | 2 +- submodules/Camera/Sources/CameraOutput.swift | 8 +++++- .../ChatListFilterPresetController.swift | 2 +- .../ContextUI/Sources/ContextController.swift | 6 ++--- submodules/Display/Source/UIKitUtils.swift | 4 +-- .../Sources/InstantPageLayout.swift | 3 +++ .../TGMediaPickerGalleryVideoItemView.m | 2 +- .../Sources/TGPhotoTextEntityView.m | 26 +++++++++++++++++-- .../Sources/LocationActionListItem.swift | 8 +++--- .../Sources/LocationAnnotation.swift | 4 +-- .../Sources/LocationMapHeaderNode.swift | 8 +++--- .../LocationUI/Sources/LocationMapNode.swift | 4 +-- .../TabBarAccountSwitchControllerNode.swift | 6 +++-- .../Sources/TelegramIntents.swift | 4 +-- .../TelegramUI/Sources/ChatMessageItem.swift | 9 ++++++- ...SendMessageActionSheetControllerNode.swift | 2 +- .../Source/UIKitRuntimeUtils/UIKitUtils.h | 2 +- .../Source/UIKitRuntimeUtils/UIKitUtils.m | 10 +++++-- third-party/webrtc/webrtc-ios | 2 +- 20 files changed, 80 insertions(+), 34 deletions(-) diff --git a/build-system/bazel-rules/apple_support b/build-system/bazel-rules/apple_support index b8755bd288..2583fa0bfd 160000 --- a/build-system/bazel-rules/apple_support +++ b/build-system/bazel-rules/apple_support @@ -1 +1 @@ -Subproject commit b8755bd2884d6bf651827c30e00bd0ea318e41a2 +Subproject commit 2583fa0bfd6909e7936da5b30e3547ba13e198dc diff --git a/build-system/bazel-rules/rules_swift b/build-system/bazel-rules/rules_swift index 44e8c29afe..4f4b7f012e 160000 --- a/build-system/bazel-rules/rules_swift +++ b/build-system/bazel-rules/rules_swift @@ -1 +1 @@ -Subproject commit 44e8c29afe3baa7f5fc434f4a7e83f7ac786644e +Subproject commit 4f4b7f012e714eaecc7f16534690e1183aa1b9ca diff --git a/submodules/Camera/Sources/CameraOutput.swift b/submodules/Camera/Sources/CameraOutput.swift index d2c4203b6a..fca73424a2 100644 --- a/submodules/Camera/Sources/CameraOutput.swift +++ b/submodules/Camera/Sources/CameraOutput.swift @@ -106,7 +106,13 @@ extension CameraOutput: AVCaptureMetadataOutputObjectsDelegate { func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { let codes: [CameraCode] = metadataObjects.filter { $0.type == .qr }.compactMap { object in if let object = object as? AVMetadataMachineReadableCodeObject, let stringValue = object.stringValue, !stringValue.isEmpty { - return CameraCode(type: .qr, message: stringValue, corners: object.corners) + let corners: [CGPoint] + #if targetEnvironment(simulator) + corners = [] + #else + corners = object.corners + #endif + return CameraCode(type: .qr, message: stringValue, corners: corners) } else { return nil } diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index 604402f7b7..4a96bb80c8 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -989,7 +989,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat includePeers.setPeers(state.additionallyIncludePeers) var updatedFilter = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers)) if currentPreset == nil { - updatedFilter.id = max(2, filters.map({ $0.id + 1 }).max() ?? 2) + updatedFilter.id = generateNewChatListFilterId(filters: filters) } var filters = filters if let _ = currentPreset { diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 542ebd76ca..85cc527983 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -555,7 +555,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator propertyAnimator?.stopAnimation(true) } - self.effectView.effect = makeCustomZoomBlurEffect() + self.effectView.effect = makeCustomZoomBlurEffect(isLight: presentationData.theme.rootController.keyboardColor == .light) self.effectView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor) self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2 * animationDurationFactor * UIView.animationDurationFactor(), curve: .easeInOut, animations: { }) @@ -573,7 +573,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } } else { UIView.animate(withDuration: 0.2 * animationDurationFactor, animations: { - self.effectView.effect = makeCustomZoomBlurEffect() + self.effectView.effect = makeCustomZoomBlurEffect(isLight: self.presentationData.theme.rootController.keyboardColor == .light) }, completion: { [weak self] _ in self?.didCompleteAnimationIn = true self?.actionsContainerNode.animateIn() @@ -1082,7 +1082,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi propertyAnimator?.stopAnimation(true) } } - self.effectView.effect = makeCustomZoomBlurEffect() + self.effectView.effect = makeCustomZoomBlurEffect(isLight: presentationData.theme.rootController.keyboardColor == .light) self.dimNode.alpha = 1.0 } self.dimNode.isHidden = false diff --git a/submodules/Display/Source/UIKitUtils.swift b/submodules/Display/Source/UIKitUtils.swift index b4dc3d327d..123691766e 100644 --- a/submodules/Display/Source/UIKitUtils.swift +++ b/submodules/Display/Source/UIKitUtils.swift @@ -19,8 +19,8 @@ public func springAnimationValueAt(_ animation: CABasicAnimation, _ t: CGFloat) return springAnimationValueAtImpl(animation, t) } -public func makeCustomZoomBlurEffect() -> UIBlurEffect? { - return makeCustomZoomBlurEffectImpl() +public func makeCustomZoomBlurEffect(isLight: Bool) -> UIBlurEffect? { + return makeCustomZoomBlurEffectImpl(isLight) } public func applySmoothRoundedCorners(_ layer: CALayer) { diff --git a/submodules/InstantPageUI/Sources/InstantPageLayout.swift b/submodules/InstantPageUI/Sources/InstantPageLayout.swift index 65fe599451..4de40ca9b9 100644 --- a/submodules/InstantPageUI/Sources/InstantPageLayout.swift +++ b/submodules/InstantPageUI/Sources/InstantPageLayout.swift @@ -855,6 +855,9 @@ func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth: if let image = loadedContent.image, let id = image.id { media[id] = image } + if let video = loadedContent.file, let id = video.id { + media[id] = video + } var mediaIndexCounter: Int = 0 var embedIndexCounter: Int = 0 diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m index 208a6df4a3..022b8cc8e0 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m @@ -1120,7 +1120,7 @@ [_playerItemDisposable setDisposable:[[itemSignal deliverOn:[SQueue mainQueue]] startWithNext:^(AVPlayerItem *playerItem) { __strong TGMediaPickerGalleryVideoItemView *strongSelf = weakSelf; - if (strongSelf == nil) + if (strongSelf == nil || ![playerItem isKindOfClass:[AVPlayerItem class]]) return; strongSelf->_player = [AVPlayer playerWithPlayerItem:playerItem]; diff --git a/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m b/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m index c6f1895ed1..ad71a5b1fe 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m @@ -87,6 +87,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f; _textView.autocorrectionType = UITextAutocorrectionTypeNo; _textView.spellCheckingType = UITextSpellCheckingTypeNo; _textView.font = [UIFont boldSystemFontOfSize:_baseFontSize]; + _textView.typingAttributes = @{NSFontAttributeName: _textView.font}; // _textView.frameWidthInset = floor(_baseFontSize * 0.03); [self setSwatch:entity.swatch]; @@ -175,6 +176,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f; { _font = font; _textView.font = [UIFont boldSystemFontOfSize:_baseFontSize]; + _textView.typingAttributes = @{NSFontAttributeName: _textView.font}; // _textView.frameWidthInset = floor(_baseFontSize * 0.03); [self sizeToFit]; @@ -480,6 +482,9 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f; @implementation TGPhotoTextView +{ + UIFont *_font; +} - (instancetype)initWithFrame:(CGRect)frame { @@ -554,10 +559,29 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f; - (void)setFont:(UIFont *)font { [super setFont:font]; + _font = font; self.layoutManager.textContainers.firstObject.lineFragmentPadding = floor(font.pointSize * 0.3); } +- (void)insertText:(NSString *)text { + [self fixTypingAttributes]; + [super insertText:text]; + [self fixTypingAttributes]; +} + +- (void)paste:(id)sender { + [self fixTypingAttributes]; + [super paste:sender]; + [self fixTypingAttributes]; +} + +- (void)fixTypingAttributes { + if (_font != nil) { + self.typingAttributes = @{NSFontAttributeName: _font}; + } +} + @end @@ -802,8 +826,6 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f; return [_impl attributesAtIndex:location effectiveRange:range]; } - - - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str { [self beginEditing]; [_impl replaceCharactersInRange:range withString:str]; diff --git a/submodules/LocationUI/Sources/LocationActionListItem.swift b/submodules/LocationUI/Sources/LocationActionListItem.swift index 1e7d8556e7..c90736413a 100644 --- a/submodules/LocationUI/Sources/LocationActionListItem.swift +++ b/submodules/LocationUI/Sources/LocationActionListItem.swift @@ -48,7 +48,7 @@ public enum LocationActionListItemIcon: Equatable { } private func generateLocationIcon(theme: PresentationTheme) -> UIImage { - return generateImage(CGSize(width: 40.0, height: 40.0)) { size, context in + return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(theme.chat.inputPanel.actionControlFillColor.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) @@ -60,11 +60,11 @@ private func generateLocationIcon(theme: PresentationTheme) -> UIImage { if let image = generateTintedImage(image: UIImage(bundleImageName: "Location/SendLocationIcon"), color: theme.chat.inputPanel.actionControlForegroundColor) { context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)) } - }! + })! } private func generateLiveLocationIcon(theme: PresentationTheme) -> UIImage { - return generateImage(CGSize(width: 40.0, height: 40.0)) { size, context in + return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(UIColor(rgb: 0x6cc139).cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) @@ -76,7 +76,7 @@ private func generateLiveLocationIcon(theme: PresentationTheme) -> UIImage { if let image = generateTintedImage(image: UIImage(bundleImageName: "Location/SendLiveLocationIcon"), color: .white) { context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)) } - }! + })! } final class LocationActionListItem: ListViewItem { diff --git a/submodules/LocationUI/Sources/LocationAnnotation.swift b/submodules/LocationUI/Sources/LocationAnnotation.swift index 4dd1184bac..07e9ec9343 100644 --- a/submodules/LocationUI/Sources/LocationAnnotation.swift +++ b/submodules/LocationUI/Sources/LocationAnnotation.swift @@ -15,7 +15,7 @@ import AccountContext let locationPinReuseIdentifier = "locationPin" private func generateSmallBackgroundImage(color: UIColor) -> UIImage? { - return generateImage(CGSize(width: 56.0, height: 56.0)) { size, context in + return generateImage(CGSize(width: 56.0, height: 56.0), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setShadow(offset: CGSize(), blur: 4.0, color: UIColor(rgb: 0x000000, alpha: 0.5).cgColor) @@ -25,7 +25,7 @@ private func generateSmallBackgroundImage(color: UIColor) -> UIImage? { context.setShadow(offset: CGSize(), blur: 0.0, color: nil) context.setFillColor(color.cgColor) context.fillEllipse(in: CGRect(x: 17.0 + UIScreenPixel, y: 17.0 + UIScreenPixel, width: 22.0 - 2.0 * UIScreenPixel, height: 22.0 - 2.0 * UIScreenPixel)) - } + }) } class LocationPinAnnotation: NSObject, MKAnnotation { diff --git a/submodules/LocationUI/Sources/LocationMapHeaderNode.swift b/submodules/LocationUI/Sources/LocationMapHeaderNode.swift index d2eaff4440..dd20679a71 100644 --- a/submodules/LocationUI/Sources/LocationMapHeaderNode.swift +++ b/submodules/LocationUI/Sources/LocationMapHeaderNode.swift @@ -10,7 +10,7 @@ private let panelSize = CGSize(width: 46.0, height: 90.0) private func generateBackgroundImage(theme: PresentationTheme) -> UIImage? { let cornerRadius: CGFloat = 9.0 - return generateImage(CGSize(width: (cornerRadius + panelInset) * 2.0, height: (cornerRadius + panelInset) * 2.0)) { size, context in + return generateImage(CGSize(width: (cornerRadius + panelInset) * 2.0, height: (cornerRadius + panelInset) * 2.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setShadow(offset: CGSize(), blur: 10.0, color: UIColor(rgb: 0x000000, alpha: 0.2).cgColor) @@ -18,11 +18,11 @@ private func generateBackgroundImage(theme: PresentationTheme) -> UIImage? { let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: panelInset, y: panelInset), size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0)), cornerRadius: cornerRadius) context.addPath(path.cgPath) context.fillPath() - }?.stretchableImage(withLeftCapWidth: Int(cornerRadius + panelInset), topCapHeight: Int(cornerRadius + panelInset)) + })?.stretchableImage(withLeftCapWidth: Int(cornerRadius + panelInset), topCapHeight: Int(cornerRadius + panelInset)) } private func generateShadowImage(theme: PresentationTheme, highlighted: Bool) -> UIImage? { - return generateImage(CGSize(width: 26.0, height: 14.0)) { size, context in + return generateImage(CGSize(width: 26.0, height: 14.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setShadow(offset: CGSize(), blur: 10.0, color: UIColor(rgb: 0x000000, alpha: 0.2).cgColor) @@ -30,7 +30,7 @@ private func generateShadowImage(theme: PresentationTheme, highlighted: Bool) -> let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: 0.0, y: 4.0), size: CGSize(width: 26.0, height: 20.0)), cornerRadius: 9.0) context.addPath(path.cgPath) context.fillPath() - }?.stretchableImage(withLeftCapWidth: 13, topCapHeight: 0) + })?.stretchableImage(withLeftCapWidth: 13, topCapHeight: 0) } final class LocationMapHeaderNode: ASDisplayNode { diff --git a/submodules/LocationUI/Sources/LocationMapNode.swift b/submodules/LocationUI/Sources/LocationMapNode.swift index c330c2017e..4d89d38ad9 100644 --- a/submodules/LocationUI/Sources/LocationMapNode.swift +++ b/submodules/LocationUI/Sources/LocationMapNode.swift @@ -69,7 +69,7 @@ private class LocationMapView: MKMapView, UIGestureRecognizerDelegate { } private func generateHeadingArrowImage() -> UIImage? { - return generateImage(CGSize(width: 28.0, height: 28.0)) { size, context in + return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) @@ -83,7 +83,7 @@ private func generateHeadingArrowImage() -> UIImage? { context.setBlendMode(.clear) context.fillEllipse(in: bounds.insetBy(dx: 5.0, dy: 5.0)) - } + }) } final class LocationMapNode: ASDisplayNode, MKMapViewDelegate { diff --git a/submodules/SettingsUI/Sources/TabBarAccountSwitchControllerNode.swift b/submodules/SettingsUI/Sources/TabBarAccountSwitchControllerNode.swift index ca1c14f2dd..5898b6c559 100644 --- a/submodules/SettingsUI/Sources/TabBarAccountSwitchControllerNode.swift +++ b/submodules/SettingsUI/Sources/TabBarAccountSwitchControllerNode.swift @@ -306,7 +306,9 @@ final class TabBarAccountSwitchControllerNode: ViewControllerTracingNode { propertyAnimator?.stopAnimation(true) } self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2 * animationDurationFactor, curve: .easeInOut, animations: { [weak self] in - self?.effectView.effect = makeCustomZoomBlurEffect() + if let strongSelf = self { + strongSelf.effectView.effect = makeCustomZoomBlurEffect(isLight: strongSelf.presentationData.theme.rootController.keyboardColor == .light) + } }) } @@ -319,7 +321,7 @@ final class TabBarAccountSwitchControllerNode: ViewControllerTracingNode { } } else { UIView.animate(withDuration: 0.2 * animationDurationFactor, animations: { - self.effectView.effect = makeCustomZoomBlurEffect() + self.effectView.effect = makeCustomZoomBlurEffect(isLight: self.presentationData.theme.rootController.keyboardColor == .light) }, completion: { _ in }) } diff --git a/submodules/TelegramIntents/Sources/TelegramIntents.swift b/submodules/TelegramIntents/Sources/TelegramIntents.swift index a06109c8d2..d854d12d3d 100644 --- a/submodules/TelegramIntents/Sources/TelegramIntents.swift +++ b/submodules/TelegramIntents/Sources/TelegramIntents.swift @@ -12,7 +12,7 @@ import AvatarNode import AccountContext private let savedMessagesAvatar: UIImage = { - return generateImage(CGSize(width: 60.0, height: 60.0)) { size, context in + return generateImage(CGSize(width: 60.0, height: 60.0), contextGenerator: { size, context in var locations: [CGFloat] = [1.0, 0.0] let colorSpace = CGColorSpaceCreateDeviceRGB() @@ -28,7 +28,7 @@ private let savedMessagesAvatar: UIImage = { if let savedMessagesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/SavedMessagesIcon"), color: .white) { context.draw(savedMessagesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - savedMessagesIcon.size.width) / 2.0), y: floor((size.height - savedMessagesIcon.size.height) / 2.0)), size: savedMessagesIcon.size)) } - }! + })! }() public enum SendMessageIntentContext { diff --git a/submodules/TelegramUI/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Sources/ChatMessageItem.swift index ad139684c6..3d0eedc41b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItem.swift @@ -383,7 +383,14 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { if telegramFile.isAnimatedSticker, (self.message.id.peerId.namespace == Namespaces.Peer.SecretChat || !telegramFile.previewRepresentations.isEmpty), let size = telegramFile.size, size > 0 && size <= 128 * 1024 { if self.message.id.peerId.namespace == Namespaces.Peer.SecretChat { if telegramFile.fileId.namespace == Namespaces.Media.CloudFile { - viewClassName = ChatMessageAnimatedStickerItemNode.self + inner: for attribute in telegramFile.attributes { + if case let .Sticker(_, packReference, _) = attribute { + if case .name = packReference { + viewClassName = ChatMessageAnimatedStickerItemNode.self + } + break inner + } + } } } else { viewClassName = ChatMessageAnimatedStickerItemNode.self diff --git a/submodules/TelegramUI/Sources/ChatSendMessageActionSheetControllerNode.swift b/submodules/TelegramUI/Sources/ChatSendMessageActionSheetControllerNode.swift index 29415a9bb9..3e5c2c3d19 100644 --- a/submodules/TelegramUI/Sources/ChatSendMessageActionSheetControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSendMessageActionSheetControllerNode.swift @@ -395,7 +395,7 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode, UIView.animate(withDuration: 0.2, animations: { if #available(iOS 9.0, *) { - self.effectView.effect = makeCustomZoomBlurEffect() + self.effectView.effect = makeCustomZoomBlurEffect(isLight: self.presentationData.theme.rootController.keyboardColor == .light) } else { self.effectView.alpha = 1.0 } diff --git a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h index 746f56d6c7..96e40fc67d 100644 --- a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h +++ b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h @@ -7,5 +7,5 @@ CABasicAnimation * _Nonnull makeSpringAnimationImpl(NSString * _Nonnull keyPath) CABasicAnimation * _Nonnull makeSpringBounceAnimationImpl(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping); CGFloat springAnimationValueAtImpl(CABasicAnimation * _Nonnull animation, CGFloat t); -UIBlurEffect *makeCustomZoomBlurEffectImpl(); +UIBlurEffect *makeCustomZoomBlurEffectImpl(bool isLight); void applySmoothRoundedCornersImpl(CALayer * _Nonnull layer); diff --git a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m index 267fd0d26c..76eac05f37 100644 --- a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m +++ b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m @@ -159,8 +159,14 @@ static void setBoolField(CustomBlurEffect *object, NSString *name, BOOL value) { [inv invoke]; } -UIBlurEffect *makeCustomZoomBlurEffectImpl() { - if (@available(iOS 11.0, *)) { +UIBlurEffect *makeCustomZoomBlurEffectImpl(bool isLight) { + if (@available(iOS 13.0, *)) { + if (isLight) { + return [UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemUltraThinMaterialLight]; + } else { + return [UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemUltraThinMaterialDark]; + } + } else if (@available(iOS 11.0, *)) { NSString *string = [@[@"_", @"UI", @"Custom", @"BlurEffect"] componentsJoinedByString:@""]; CustomBlurEffect *result = (CustomBlurEffect *)[NSClassFromString(string) effectWithStyle:0]; diff --git a/third-party/webrtc/webrtc-ios b/third-party/webrtc/webrtc-ios index e4d49e73cd..11255bcfff 160000 --- a/third-party/webrtc/webrtc-ios +++ b/third-party/webrtc/webrtc-ios @@ -1 +1 @@ -Subproject commit e4d49e73cd8206518e7b3dd75d54af0f0ef5b810 +Subproject commit 11255bcfff3180210a012f368e2d2bcd169b6877 From 6392536e8e18cc9a7993ff0f7c002e1643c77f9d Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 21 Sep 2020 17:38:23 +0300 Subject: [PATCH 3/6] Bump version [nocache] --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c781f082f2..431a14c055 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ include Utils.makefile -APP_VERSION="7.0.1" +APP_VERSION="7.0.2" CORE_COUNT=$(shell sysctl -n hw.logicalcpu) CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1) From 7f77b9946e22b5f782f07152c94f8e54fc6c865c Mon Sep 17 00:00:00 2001 From: Ali <> Date: Sat, 26 Sep 2020 12:34:31 +0400 Subject: [PATCH 4/6] Revert "Bump version [nocache]" This reverts commit 6392536e8e18cc9a7993ff0f7c002e1643c77f9d. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 431a14c055..c781f082f2 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ include Utils.makefile -APP_VERSION="7.0.2" +APP_VERSION="7.0.1" CORE_COUNT=$(shell sysctl -n hw.logicalcpu) CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1) From 9e892ca79a595148c84d98e3e2a72dd2b545e68a Mon Sep 17 00:00:00 2001 From: Ali <> Date: Sat, 26 Sep 2020 12:34:40 +0400 Subject: [PATCH 5/6] Revert "Apply fixes" This reverts commit e1c4ddc9aed227439a95a77b31489ed8f7adae0a. --- build-system/bazel-rules/apple_support | 2 +- build-system/bazel-rules/rules_swift | 2 +- submodules/Camera/Sources/CameraOutput.swift | 8 +----- .../ChatListFilterPresetController.swift | 2 +- .../ContextUI/Sources/ContextController.swift | 6 ++--- submodules/Display/Source/UIKitUtils.swift | 4 +-- .../Sources/InstantPageLayout.swift | 3 --- .../TGMediaPickerGalleryVideoItemView.m | 2 +- .../Sources/TGPhotoTextEntityView.m | 26 ++----------------- .../Sources/LocationActionListItem.swift | 8 +++--- .../Sources/LocationAnnotation.swift | 4 +-- .../Sources/LocationMapHeaderNode.swift | 8 +++--- .../LocationUI/Sources/LocationMapNode.swift | 4 +-- .../TabBarAccountSwitchControllerNode.swift | 6 ++--- .../Sources/TelegramIntents.swift | 4 +-- .../TelegramUI/Sources/ChatMessageItem.swift | 9 +------ ...SendMessageActionSheetControllerNode.swift | 2 +- .../Source/UIKitRuntimeUtils/UIKitUtils.h | 2 +- .../Source/UIKitRuntimeUtils/UIKitUtils.m | 10 ++----- third-party/webrtc/webrtc-ios | 2 +- 20 files changed, 34 insertions(+), 80 deletions(-) diff --git a/build-system/bazel-rules/apple_support b/build-system/bazel-rules/apple_support index 2583fa0bfd..b8755bd288 160000 --- a/build-system/bazel-rules/apple_support +++ b/build-system/bazel-rules/apple_support @@ -1 +1 @@ -Subproject commit 2583fa0bfd6909e7936da5b30e3547ba13e198dc +Subproject commit b8755bd2884d6bf651827c30e00bd0ea318e41a2 diff --git a/build-system/bazel-rules/rules_swift b/build-system/bazel-rules/rules_swift index 4f4b7f012e..44e8c29afe 160000 --- a/build-system/bazel-rules/rules_swift +++ b/build-system/bazel-rules/rules_swift @@ -1 +1 @@ -Subproject commit 4f4b7f012e714eaecc7f16534690e1183aa1b9ca +Subproject commit 44e8c29afe3baa7f5fc434f4a7e83f7ac786644e diff --git a/submodules/Camera/Sources/CameraOutput.swift b/submodules/Camera/Sources/CameraOutput.swift index fca73424a2..d2c4203b6a 100644 --- a/submodules/Camera/Sources/CameraOutput.swift +++ b/submodules/Camera/Sources/CameraOutput.swift @@ -106,13 +106,7 @@ extension CameraOutput: AVCaptureMetadataOutputObjectsDelegate { func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { let codes: [CameraCode] = metadataObjects.filter { $0.type == .qr }.compactMap { object in if let object = object as? AVMetadataMachineReadableCodeObject, let stringValue = object.stringValue, !stringValue.isEmpty { - let corners: [CGPoint] - #if targetEnvironment(simulator) - corners = [] - #else - corners = object.corners - #endif - return CameraCode(type: .qr, message: stringValue, corners: corners) + return CameraCode(type: .qr, message: stringValue, corners: object.corners) } else { return nil } diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index 4a96bb80c8..604402f7b7 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -989,7 +989,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat includePeers.setPeers(state.additionallyIncludePeers) var updatedFilter = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers)) if currentPreset == nil { - updatedFilter.id = generateNewChatListFilterId(filters: filters) + updatedFilter.id = max(2, filters.map({ $0.id + 1 }).max() ?? 2) } var filters = filters if let _ = currentPreset { diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 85cc527983..542ebd76ca 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -555,7 +555,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator propertyAnimator?.stopAnimation(true) } - self.effectView.effect = makeCustomZoomBlurEffect(isLight: presentationData.theme.rootController.keyboardColor == .light) + self.effectView.effect = makeCustomZoomBlurEffect() self.effectView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor) self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2 * animationDurationFactor * UIView.animationDurationFactor(), curve: .easeInOut, animations: { }) @@ -573,7 +573,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } } else { UIView.animate(withDuration: 0.2 * animationDurationFactor, animations: { - self.effectView.effect = makeCustomZoomBlurEffect(isLight: self.presentationData.theme.rootController.keyboardColor == .light) + self.effectView.effect = makeCustomZoomBlurEffect() }, completion: { [weak self] _ in self?.didCompleteAnimationIn = true self?.actionsContainerNode.animateIn() @@ -1082,7 +1082,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi propertyAnimator?.stopAnimation(true) } } - self.effectView.effect = makeCustomZoomBlurEffect(isLight: presentationData.theme.rootController.keyboardColor == .light) + self.effectView.effect = makeCustomZoomBlurEffect() self.dimNode.alpha = 1.0 } self.dimNode.isHidden = false diff --git a/submodules/Display/Source/UIKitUtils.swift b/submodules/Display/Source/UIKitUtils.swift index 123691766e..b4dc3d327d 100644 --- a/submodules/Display/Source/UIKitUtils.swift +++ b/submodules/Display/Source/UIKitUtils.swift @@ -19,8 +19,8 @@ public func springAnimationValueAt(_ animation: CABasicAnimation, _ t: CGFloat) return springAnimationValueAtImpl(animation, t) } -public func makeCustomZoomBlurEffect(isLight: Bool) -> UIBlurEffect? { - return makeCustomZoomBlurEffectImpl(isLight) +public func makeCustomZoomBlurEffect() -> UIBlurEffect? { + return makeCustomZoomBlurEffectImpl() } public func applySmoothRoundedCorners(_ layer: CALayer) { diff --git a/submodules/InstantPageUI/Sources/InstantPageLayout.swift b/submodules/InstantPageUI/Sources/InstantPageLayout.swift index 4de40ca9b9..65fe599451 100644 --- a/submodules/InstantPageUI/Sources/InstantPageLayout.swift +++ b/submodules/InstantPageUI/Sources/InstantPageLayout.swift @@ -855,9 +855,6 @@ func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth: if let image = loadedContent.image, let id = image.id { media[id] = image } - if let video = loadedContent.file, let id = video.id { - media[id] = video - } var mediaIndexCounter: Int = 0 var embedIndexCounter: Int = 0 diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m index 022b8cc8e0..208a6df4a3 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m @@ -1120,7 +1120,7 @@ [_playerItemDisposable setDisposable:[[itemSignal deliverOn:[SQueue mainQueue]] startWithNext:^(AVPlayerItem *playerItem) { __strong TGMediaPickerGalleryVideoItemView *strongSelf = weakSelf; - if (strongSelf == nil || ![playerItem isKindOfClass:[AVPlayerItem class]]) + if (strongSelf == nil) return; strongSelf->_player = [AVPlayer playerWithPlayerItem:playerItem]; diff --git a/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m b/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m index ad71a5b1fe..c6f1895ed1 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m @@ -87,7 +87,6 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f; _textView.autocorrectionType = UITextAutocorrectionTypeNo; _textView.spellCheckingType = UITextSpellCheckingTypeNo; _textView.font = [UIFont boldSystemFontOfSize:_baseFontSize]; - _textView.typingAttributes = @{NSFontAttributeName: _textView.font}; // _textView.frameWidthInset = floor(_baseFontSize * 0.03); [self setSwatch:entity.swatch]; @@ -176,7 +175,6 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f; { _font = font; _textView.font = [UIFont boldSystemFontOfSize:_baseFontSize]; - _textView.typingAttributes = @{NSFontAttributeName: _textView.font}; // _textView.frameWidthInset = floor(_baseFontSize * 0.03); [self sizeToFit]; @@ -482,9 +480,6 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f; @implementation TGPhotoTextView -{ - UIFont *_font; -} - (instancetype)initWithFrame:(CGRect)frame { @@ -559,29 +554,10 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f; - (void)setFont:(UIFont *)font { [super setFont:font]; - _font = font; self.layoutManager.textContainers.firstObject.lineFragmentPadding = floor(font.pointSize * 0.3); } -- (void)insertText:(NSString *)text { - [self fixTypingAttributes]; - [super insertText:text]; - [self fixTypingAttributes]; -} - -- (void)paste:(id)sender { - [self fixTypingAttributes]; - [super paste:sender]; - [self fixTypingAttributes]; -} - -- (void)fixTypingAttributes { - if (_font != nil) { - self.typingAttributes = @{NSFontAttributeName: _font}; - } -} - @end @@ -826,6 +802,8 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f; return [_impl attributesAtIndex:location effectiveRange:range]; } + + - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str { [self beginEditing]; [_impl replaceCharactersInRange:range withString:str]; diff --git a/submodules/LocationUI/Sources/LocationActionListItem.swift b/submodules/LocationUI/Sources/LocationActionListItem.swift index c90736413a..1e7d8556e7 100644 --- a/submodules/LocationUI/Sources/LocationActionListItem.swift +++ b/submodules/LocationUI/Sources/LocationActionListItem.swift @@ -48,7 +48,7 @@ public enum LocationActionListItemIcon: Equatable { } private func generateLocationIcon(theme: PresentationTheme) -> UIImage { - return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in + return generateImage(CGSize(width: 40.0, height: 40.0)) { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(theme.chat.inputPanel.actionControlFillColor.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) @@ -60,11 +60,11 @@ private func generateLocationIcon(theme: PresentationTheme) -> UIImage { if let image = generateTintedImage(image: UIImage(bundleImageName: "Location/SendLocationIcon"), color: theme.chat.inputPanel.actionControlForegroundColor) { context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)) } - })! + }! } private func generateLiveLocationIcon(theme: PresentationTheme) -> UIImage { - return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in + return generateImage(CGSize(width: 40.0, height: 40.0)) { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(UIColor(rgb: 0x6cc139).cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) @@ -76,7 +76,7 @@ private func generateLiveLocationIcon(theme: PresentationTheme) -> UIImage { if let image = generateTintedImage(image: UIImage(bundleImageName: "Location/SendLiveLocationIcon"), color: .white) { context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)) } - })! + }! } final class LocationActionListItem: ListViewItem { diff --git a/submodules/LocationUI/Sources/LocationAnnotation.swift b/submodules/LocationUI/Sources/LocationAnnotation.swift index 07e9ec9343..4dd1184bac 100644 --- a/submodules/LocationUI/Sources/LocationAnnotation.swift +++ b/submodules/LocationUI/Sources/LocationAnnotation.swift @@ -15,7 +15,7 @@ import AccountContext let locationPinReuseIdentifier = "locationPin" private func generateSmallBackgroundImage(color: UIColor) -> UIImage? { - return generateImage(CGSize(width: 56.0, height: 56.0), contextGenerator: { size, context in + return generateImage(CGSize(width: 56.0, height: 56.0)) { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setShadow(offset: CGSize(), blur: 4.0, color: UIColor(rgb: 0x000000, alpha: 0.5).cgColor) @@ -25,7 +25,7 @@ private func generateSmallBackgroundImage(color: UIColor) -> UIImage? { context.setShadow(offset: CGSize(), blur: 0.0, color: nil) context.setFillColor(color.cgColor) context.fillEllipse(in: CGRect(x: 17.0 + UIScreenPixel, y: 17.0 + UIScreenPixel, width: 22.0 - 2.0 * UIScreenPixel, height: 22.0 - 2.0 * UIScreenPixel)) - }) + } } class LocationPinAnnotation: NSObject, MKAnnotation { diff --git a/submodules/LocationUI/Sources/LocationMapHeaderNode.swift b/submodules/LocationUI/Sources/LocationMapHeaderNode.swift index dd20679a71..d2eaff4440 100644 --- a/submodules/LocationUI/Sources/LocationMapHeaderNode.swift +++ b/submodules/LocationUI/Sources/LocationMapHeaderNode.swift @@ -10,7 +10,7 @@ private let panelSize = CGSize(width: 46.0, height: 90.0) private func generateBackgroundImage(theme: PresentationTheme) -> UIImage? { let cornerRadius: CGFloat = 9.0 - return generateImage(CGSize(width: (cornerRadius + panelInset) * 2.0, height: (cornerRadius + panelInset) * 2.0), rotatedContext: { size, context in + return generateImage(CGSize(width: (cornerRadius + panelInset) * 2.0, height: (cornerRadius + panelInset) * 2.0)) { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setShadow(offset: CGSize(), blur: 10.0, color: UIColor(rgb: 0x000000, alpha: 0.2).cgColor) @@ -18,11 +18,11 @@ private func generateBackgroundImage(theme: PresentationTheme) -> UIImage? { let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: panelInset, y: panelInset), size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0)), cornerRadius: cornerRadius) context.addPath(path.cgPath) context.fillPath() - })?.stretchableImage(withLeftCapWidth: Int(cornerRadius + panelInset), topCapHeight: Int(cornerRadius + panelInset)) + }?.stretchableImage(withLeftCapWidth: Int(cornerRadius + panelInset), topCapHeight: Int(cornerRadius + panelInset)) } private func generateShadowImage(theme: PresentationTheme, highlighted: Bool) -> UIImage? { - return generateImage(CGSize(width: 26.0, height: 14.0), rotatedContext: { size, context in + return generateImage(CGSize(width: 26.0, height: 14.0)) { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setShadow(offset: CGSize(), blur: 10.0, color: UIColor(rgb: 0x000000, alpha: 0.2).cgColor) @@ -30,7 +30,7 @@ private func generateShadowImage(theme: PresentationTheme, highlighted: Bool) -> let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: 0.0, y: 4.0), size: CGSize(width: 26.0, height: 20.0)), cornerRadius: 9.0) context.addPath(path.cgPath) context.fillPath() - })?.stretchableImage(withLeftCapWidth: 13, topCapHeight: 0) + }?.stretchableImage(withLeftCapWidth: 13, topCapHeight: 0) } final class LocationMapHeaderNode: ASDisplayNode { diff --git a/submodules/LocationUI/Sources/LocationMapNode.swift b/submodules/LocationUI/Sources/LocationMapNode.swift index 4d89d38ad9..c330c2017e 100644 --- a/submodules/LocationUI/Sources/LocationMapNode.swift +++ b/submodules/LocationUI/Sources/LocationMapNode.swift @@ -69,7 +69,7 @@ private class LocationMapView: MKMapView, UIGestureRecognizerDelegate { } private func generateHeadingArrowImage() -> UIImage? { - return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in + return generateImage(CGSize(width: 28.0, height: 28.0)) { size, context in let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) @@ -83,7 +83,7 @@ private func generateHeadingArrowImage() -> UIImage? { context.setBlendMode(.clear) context.fillEllipse(in: bounds.insetBy(dx: 5.0, dy: 5.0)) - }) + } } final class LocationMapNode: ASDisplayNode, MKMapViewDelegate { diff --git a/submodules/SettingsUI/Sources/TabBarAccountSwitchControllerNode.swift b/submodules/SettingsUI/Sources/TabBarAccountSwitchControllerNode.swift index 5898b6c559..ca1c14f2dd 100644 --- a/submodules/SettingsUI/Sources/TabBarAccountSwitchControllerNode.swift +++ b/submodules/SettingsUI/Sources/TabBarAccountSwitchControllerNode.swift @@ -306,9 +306,7 @@ final class TabBarAccountSwitchControllerNode: ViewControllerTracingNode { propertyAnimator?.stopAnimation(true) } self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2 * animationDurationFactor, curve: .easeInOut, animations: { [weak self] in - if let strongSelf = self { - strongSelf.effectView.effect = makeCustomZoomBlurEffect(isLight: strongSelf.presentationData.theme.rootController.keyboardColor == .light) - } + self?.effectView.effect = makeCustomZoomBlurEffect() }) } @@ -321,7 +319,7 @@ final class TabBarAccountSwitchControllerNode: ViewControllerTracingNode { } } else { UIView.animate(withDuration: 0.2 * animationDurationFactor, animations: { - self.effectView.effect = makeCustomZoomBlurEffect(isLight: self.presentationData.theme.rootController.keyboardColor == .light) + self.effectView.effect = makeCustomZoomBlurEffect() }, completion: { _ in }) } diff --git a/submodules/TelegramIntents/Sources/TelegramIntents.swift b/submodules/TelegramIntents/Sources/TelegramIntents.swift index d854d12d3d..a06109c8d2 100644 --- a/submodules/TelegramIntents/Sources/TelegramIntents.swift +++ b/submodules/TelegramIntents/Sources/TelegramIntents.swift @@ -12,7 +12,7 @@ import AvatarNode import AccountContext private let savedMessagesAvatar: UIImage = { - return generateImage(CGSize(width: 60.0, height: 60.0), contextGenerator: { size, context in + return generateImage(CGSize(width: 60.0, height: 60.0)) { size, context in var locations: [CGFloat] = [1.0, 0.0] let colorSpace = CGColorSpaceCreateDeviceRGB() @@ -28,7 +28,7 @@ private let savedMessagesAvatar: UIImage = { if let savedMessagesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/SavedMessagesIcon"), color: .white) { context.draw(savedMessagesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - savedMessagesIcon.size.width) / 2.0), y: floor((size.height - savedMessagesIcon.size.height) / 2.0)), size: savedMessagesIcon.size)) } - })! + }! }() public enum SendMessageIntentContext { diff --git a/submodules/TelegramUI/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Sources/ChatMessageItem.swift index 3d0eedc41b..ad139684c6 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItem.swift @@ -383,14 +383,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { if telegramFile.isAnimatedSticker, (self.message.id.peerId.namespace == Namespaces.Peer.SecretChat || !telegramFile.previewRepresentations.isEmpty), let size = telegramFile.size, size > 0 && size <= 128 * 1024 { if self.message.id.peerId.namespace == Namespaces.Peer.SecretChat { if telegramFile.fileId.namespace == Namespaces.Media.CloudFile { - inner: for attribute in telegramFile.attributes { - if case let .Sticker(_, packReference, _) = attribute { - if case .name = packReference { - viewClassName = ChatMessageAnimatedStickerItemNode.self - } - break inner - } - } + viewClassName = ChatMessageAnimatedStickerItemNode.self } } else { viewClassName = ChatMessageAnimatedStickerItemNode.self diff --git a/submodules/TelegramUI/Sources/ChatSendMessageActionSheetControllerNode.swift b/submodules/TelegramUI/Sources/ChatSendMessageActionSheetControllerNode.swift index 3e5c2c3d19..29415a9bb9 100644 --- a/submodules/TelegramUI/Sources/ChatSendMessageActionSheetControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSendMessageActionSheetControllerNode.swift @@ -395,7 +395,7 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode, UIView.animate(withDuration: 0.2, animations: { if #available(iOS 9.0, *) { - self.effectView.effect = makeCustomZoomBlurEffect(isLight: self.presentationData.theme.rootController.keyboardColor == .light) + self.effectView.effect = makeCustomZoomBlurEffect() } else { self.effectView.alpha = 1.0 } diff --git a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h index 96e40fc67d..746f56d6c7 100644 --- a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h +++ b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h @@ -7,5 +7,5 @@ CABasicAnimation * _Nonnull makeSpringAnimationImpl(NSString * _Nonnull keyPath) CABasicAnimation * _Nonnull makeSpringBounceAnimationImpl(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping); CGFloat springAnimationValueAtImpl(CABasicAnimation * _Nonnull animation, CGFloat t); -UIBlurEffect *makeCustomZoomBlurEffectImpl(bool isLight); +UIBlurEffect *makeCustomZoomBlurEffectImpl(); void applySmoothRoundedCornersImpl(CALayer * _Nonnull layer); diff --git a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m index 76eac05f37..267fd0d26c 100644 --- a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m +++ b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m @@ -159,14 +159,8 @@ static void setBoolField(CustomBlurEffect *object, NSString *name, BOOL value) { [inv invoke]; } -UIBlurEffect *makeCustomZoomBlurEffectImpl(bool isLight) { - if (@available(iOS 13.0, *)) { - if (isLight) { - return [UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemUltraThinMaterialLight]; - } else { - return [UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemUltraThinMaterialDark]; - } - } else if (@available(iOS 11.0, *)) { +UIBlurEffect *makeCustomZoomBlurEffectImpl() { + if (@available(iOS 11.0, *)) { NSString *string = [@[@"_", @"UI", @"Custom", @"BlurEffect"] componentsJoinedByString:@""]; CustomBlurEffect *result = (CustomBlurEffect *)[NSClassFromString(string) effectWithStyle:0]; diff --git a/third-party/webrtc/webrtc-ios b/third-party/webrtc/webrtc-ios index 11255bcfff..e4d49e73cd 160000 --- a/third-party/webrtc/webrtc-ios +++ b/third-party/webrtc/webrtc-ios @@ -1 +1 @@ -Subproject commit 11255bcfff3180210a012f368e2d2bcd169b6877 +Subproject commit e4d49e73cd8206518e7b3dd75d54af0f0ef5b810 From a9c67e4210c547c734b78d6e2744b2916fe88e00 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Sat, 26 Sep 2020 12:34:47 +0400 Subject: [PATCH 6/6] Revert "Revert to release-7.0.1" This reverts commit 5a32d25fc621639ac3950274b912b361d68e3908. --- Makefile | 2 +- Telegram/NotificationService/FetchImage.m | 5 +- Telegram/NotificationService/Serialization.m | 2 +- .../Telegram-iOS/en.lproj/Localizable.strings | 29 + .../en.lproj/Localizable.strings.orig | 5793 +++++++++ .../Sources/AccountContext.swift | 17 +- .../Sources/ChatController.swift | 87 + .../Sources/ChatHistoryLocation.swift | 4 +- .../Sources/GalleryController.swift | 8 + .../AccountContext/Sources/MediaManager.swift | 114 + .../Sources/OpenChatMessage.swift | 15 +- .../Sources/StoredMessageFromSearchPeer.swift | 4 +- .../AvatarNode/Sources/AvatarNode.swift | 17 + submodules/ChatInterfaceState/BUCK | 22 + submodules/ChatInterfaceState/BUILD | 22 + .../Sources/ChatInterfaceState.swift | 129 +- .../Sources/ChatListSearchItemHeader.swift | 184 +- submodules/ChatListUI/BUCK | 9 + submodules/ChatListUI/BUILD | 9 + .../Sources/ChatListController.swift | 67 +- .../Sources/ChatListControllerNode.swift | 45 +- .../ChatListFilterTabContainerNode.swift | 2 +- .../Sources/ChatListSearchContainerNode.swift | 1562 ++- .../ChatListSearchFiltersContainerNode.swift | 306 + .../Sources/ChatListSearchMediaNode.swift | 1135 ++ ...tListSearchMessageSelectionPanelNode.swift | 182 + .../ChatListUI/Sources/DateSuggestion.swift | 144 + .../Sources/Node/ChatListItem.swift | 8 +- .../ChatMessageInteractiveMediaBadge/BUCK | 20 + .../ChatMessageInteractiveMediaBadge/BUILD | 20 + .../ChatMessageInteractiveMediaBadge.swift | 24 +- .../Sources/ContactsPeerItem.swift | 48 +- .../DeleteChatPeerActionSheetItem.swift | 2 + submodules/Display/Source/NavigationBar.swift | 96 +- .../Display/Source/TabBarController.swift | 4 +- .../Display/Source/ViewController.swift | 3 + submodules/FileMediaResourceStatus/BUCK | 23 + submodules/FileMediaResourceStatus/BUILD | 22 + .../Sources/FileMediaResourceStatus.swift | 16 +- submodules/GalleryData/BUCK | 31 + submodules/GalleryData/BUILD | 29 + .../GalleryData/Sources/GalleryData.swift | 296 + .../GalleryUI/Sources/GalleryController.swift | 232 +- .../GalleryUI/Sources/GalleryItemNode.swift | 3 + .../Items/UniversalVideoGalleryItem.swift | 10 +- submodules/HashtagSearchUI/BUCK | 1 + submodules/HashtagSearchUI/BUILD | 1 + .../Sources/HashtagSearchController.swift | 28 +- .../Sources/HashtagSearchControllerNode.swift | 2 + .../Sources/InstantPageController.swift | 50 + .../Sources/InstantPageLayout.swift | 3 + .../Sources/ItemListPeerItem.swift | 4 + .../Sources/LegacyChatImport.swift | 2 +- submodules/ListMessageItem/BUCK | 36 + submodules/ListMessageItem/BUILD | 38 + .../Sources/ListMessageDateHeader.swift | 10 +- .../Sources/ListMessageFileItemNode.swift | 218 +- .../Sources/ListMessageHoleItem.swift | 0 .../Sources/ListMessageItem.swift | 68 +- .../Sources/ListMessageNode.swift | 13 +- .../Sources/ListMessageSnippetItemNode.swift | 199 +- .../Sources/LiveLocationManager.swift | 2 +- submodules/MediaResources/BUCK | 1 + submodules/MediaResources/BUILD | 1 + .../Sources/MediaPlaybackStoredState.swift | 0 submodules/MtProtoKit/BUCK | 4 +- submodules/MtProtoKit/BUILD | 4 +- .../MtProtoKit/MTBindKeyMessageService.h | 9 + .../PublicHeaders/MtProtoKit/MTContext.h | 10 +- .../MtProtoKit/MTDatacenterAuthAction.h | 15 +- .../MtProtoKit/MTDatacenterAuthInfo.h | 19 +- .../MtProtoKit/MTMessageService.h | 6 +- .../PublicHeaders/MtProtoKit/MTProto.h | 2 + .../PublicHeaders/MtProtoKit/MTProtoEngine.h | 13 + .../MtProtoKit/MTProtoInstance.h | 12 + .../MtProtoKit/MTProtoPersistenceInterface.h | 14 + .../Sources/MTBindKeyMessageService.m | 156 + submodules/MtProtoKit/Sources/MTContext.m | 181 +- .../Sources/MTDatacenterAuthAction.m | 184 +- .../MtProtoKit/Sources/MTDatacenterAuthInfo.m | 36 +- .../Sources/MTDatacenterAuthMessageService.m | 6 +- .../Sources/MTDiscoverConnectionSignals.m | 7 +- .../MTDiscoverDatacenterAddressAction.m | 6 +- submodules/MtProtoKit/Sources/MTProto.m | 699 +- submodules/MtProtoKit/Sources/MTProtoEngine.m | 52 + .../MtProtoKit/Sources/MTProtoInstance.m | 48 + .../Sources/MTRequestMessageService.m | 18 +- .../Sources/MTResendMessageService.m | 4 +- .../MtProtoKit/Sources/MTTcpTransport.m | 2 +- .../Sources/MTTimeSyncMessageService.m | 4 +- .../Sources/Utils/MTQueueLocalObject.h | 14 + .../Sources/Utils/MTQueueLocalObject.m | 56 + .../Sources/ChannelAdminController.swift | 39 +- .../Sources/ChannelAdminsController.swift | 2 +- ...hannelDiscussionGroupSetupController.swift | 6 +- .../Sources/ChannelInfoController.swift | 6 +- .../ChannelMembersSearchContainerNode.swift | 2 +- .../ChannelMembersSearchControllerNode.swift | 2 +- .../Sources/GroupInfoController.swift | 6 +- .../AdditionalMessageHistoryViewData.swift | 2 + .../Postbox/Sources/ChatListViewState.swift | 2 +- submodules/Postbox/Sources/ChatLocation.swift | 10 +- .../Sources/GlobalMessageTagsView.swift | 4 +- .../Postbox/Sources/IntermediateMessage.swift | 12 +- submodules/Postbox/Sources/Message.swift | 48 +- .../Sources/MessageHistoryHolesView.swift | 37 + .../Postbox/Sources/MessageHistoryTable.swift | 112 +- .../Sources/MessageHistoryTagsTable.swift | 4 + .../Sources/MessageHistoryThreadsTable.swift | 98 + .../Postbox/Sources/MessageHistoryView.swift | 348 +- .../Sources/MessageHistoryViewState.swift | 202 +- .../Sources/MessageOfInterestHolesView.swift | 17 +- submodules/Postbox/Sources/Postbox.swift | 220 +- submodules/Postbox/Sources/ViewTracker.swift | 7 +- submodules/PresentationDataUtils/BUCK | 1 + submodules/PresentationDataUtils/BUILD | 1 + .../Sources/OpenUrl.swift | 62 + .../SearchBarNode/Sources/SearchBarNode.swift | 398 +- .../Sources/SearchBarPlaceholderNode.swift | 4 +- .../Sources/SearchDisplayController.swift | 38 +- .../SearchDisplayControllerContentNode.swift | 9 +- .../Sources/SelectablePeerNode.swift | 3 + .../BubbleSettingsController.swift | 10 +- .../Sources/CachedFaqInstantPage.swift | 6 +- .../SettingsUI/Sources/DebugController.swift | 1 + .../ForwardPrivacyChatPreviewItem.swift | 2 +- .../Sources/Search/SettingsSearchItem.swift | 18 +- .../Sources/SettingsController.swift | 2 +- .../TextSizeSelectionController.swift | 22 +- .../ThemeAccentColorControllerNode.swift | 24 +- .../Themes/ThemeGridSearchColorsItem.swift | 31 +- .../Themes/ThemeGridSearchContentNode.swift | 55 +- .../Themes/ThemePreviewControllerNode.swift | 30 +- .../Themes/ThemeSettingsChatPreviewItem.swift | 4 +- .../Sources/Themes/WallpaperGalleryItem.swift | 4 +- .../SyncCore/Sources/CachedChannelData.swift | 44 +- .../Sources/ReplyMessageAttribute.swift | 24 +- .../Sources/ReplyThreadMessageAttribute.swift | 34 + .../Sources/TelegramChatAdminRights.swift | 2 + submodules/TelegramApi/Sources/Api0.swift | 32 +- submodules/TelegramApi/Sources/Api1.swift | 854 +- submodules/TelegramApi/Sources/Api2.swift | 36 + submodules/TelegramApi/Sources/Api3.swift | 223 +- .../Sources/TelegramBaseController.swift | 6 +- submodules/TelegramCore/Sources/Account.swift | 14 +- .../TelegramCore/Sources/AccountManager.swift | 1 + .../Sources/AccountStateManagementUtils.swift | 106 +- .../Sources/AccountViewTracker.swift | 94 +- .../ApplyMaxReadIndexInteractively.swift | 4 +- .../Sources/ApplyUpdateMessage.swift | 34 +- .../Sources/CachedChannelParticipants.swift | 23 +- .../TelegramCore/Sources/ChannelAdmins.swift | 2 +- .../Sources/ChannelOwnershipTransfer.swift | 2 +- .../Sources/ChatHistoryPreloadManager.swift | 3 +- .../TelegramCore/Sources/DeleteMessages.swift | 18 +- .../Sources/DeleteMessagesInteractively.swift | 2 +- .../TelegramCore/Sources/EnqueueMessage.swift | 49 +- .../Sources/ExportMessageLink.swift | 33 +- .../Sources/HistoryViewStateValidation.swift | 12 +- submodules/TelegramCore/Sources/Holes.swift | 225 +- ...InstallInteractiveReadMessagesAction.swift | 2 +- .../ManageChannelDiscussionGroup.swift | 20 +- .../ManagedAutoremoveMessageOperations.swift | 2 +- ...anagedConsumePersonalMessagesActions.swift | 4 +- .../Sources/ManagedMessageHistoryHoles.swift | 15 +- .../ManagedSecretChatOutgoingOperations.swift | 4 +- ...kAllUnseenPersonalMessagesOperations.swift | 2 +- ...essageContentAsConsumedInteractively.swift | 4 +- .../Sources/MessageReactions.swift | 4 +- .../TelegramCore/Sources/MessageUtils.swift | 6 +- submodules/TelegramCore/Sources/Network.swift | 29 +- .../TelegramCore/Sources/PeerAdmins.swift | 8 +- .../TelegramCore/Sources/PeerUtils.swift | 21 + .../Sources/PendingMessageManager.swift | 20 +- .../PendingMessageUploadedContent.swift | 4 +- ...ecretChatIncomingDecryptedOperations.swift | 30 +- .../Sources/ReplyThreadHistory.swift | 236 + .../Sources/RequestEditMessage.swift | 2 +- .../Sources/RequestUserPhotos.swift | 4 +- .../TelegramCore/Sources/SearchMessages.swift | 33 +- .../TelegramCore/Sources/Serialization.swift | 2 +- .../TelegramCore/Sources/SplitTest.swift | 2 +- .../Sources/StoreMessage_Telegram.swift | 242 +- .../SynchronizeAppLogEventsOperation.swift | 2 +- .../Sources/SynchronizePeerReadState.swift | 36 +- .../Sources/TelegramChannel.swift | 13 + .../UnauthorizedAccountStateManager.swift | 2 +- .../Sources/UpdateCachedPeerData.swift | 2 +- .../Sources/UpdateMessageMedia.swift | 63 +- .../Sources/UpdateMessageService.swift | 23 +- .../Sources/UpdatesApiUtils.swift | 29 +- .../TelegramCore/Sources/WebpagePreview.swift | 1 - .../Sources/ChatPresentationData.swift | 71 + .../Sources/DefaultDayPresentationTheme.swift | 8 +- .../Sources/PresentationStrings.swift | 10347 ++++++++-------- .../PresentationThemeEssentialGraphics.swift | 18 +- .../Resources/PresentationResourceKey.swift | 7 + .../Resources/PresentationResourcesChat.swift | 37 + submodules/TelegramUI/BUCK | 5 + submodules/TelegramUI/BUILD | 5 + .../Contents.json | 12 + .../repliesavatar.pdf} | Bin 4177 -> 4083 bytes .../Search/Arrow.imageset/Contents.json | 12 + .../Search/Arrow.imageset/ic_addresult.pdf | Bin 0 -> 4138 bytes .../Search/Calendar.imageset/Contents.json | 12 + .../ic_search_calendar (2).pdf | Bin 0 -> 4358 bytes .../Chat List/Search/Contents.json | 9 + .../Search/Files.imageset/Contents.json | 12 + .../Search/Files.imageset/ic_search_docs.pdf | Bin 0 -> 4216 bytes .../Search/Links.imageset/Contents.json | 12 + .../Search/Links.imageset/ic_search_links.pdf | Bin 0 -> 4572 bytes .../Search/M_Files.imageset/Contents.json | 12 + .../Search/M_Files.imageset/Files.png | Bin 0 -> 18218 bytes .../Search/M_Links.imageset/Contents.json | 12 + .../Search/M_Links.imageset/Links.png | Bin 0 -> 17164 bytes .../Search/M_Music.imageset/Contents.json | 12 + .../Search/M_Music.imageset/Music.png | Bin 0 -> 23163 bytes .../Search/Media.imageset/Contents.json | 12 + .../Search/Media.imageset/ic_search_media.pdf | Bin 0 -> 4387 bytes .../Search/Music.imageset/Contents.json | 12 + .../Search/Music.imageset/ic_search_music.pdf | Bin 0 -> 4057 bytes .../Search/User.imageset/Contents.json | 12 + .../Search/User.imageset/ic_search_user.pdf | Bin 0 -> 4285 bytes .../Search/Voice.imageset/Contents.json | 12 + .../Search/Voice.imageset/ic_search_voice.pdf | Bin 0 -> 4187 bytes .../Replies.imageset/Contents.json | 12 + .../Replies.imageset/ic_viewreplies.pdf | Bin 0 -> 4676 bytes .../BubbleComments.imageset/Contents.json | 12 + .../ic_leaveacomment (1).pdf | Bin 0 -> 4385 bytes .../BubbleReplies.imageset/Contents.json | 12 + .../BubbleReplies.imageset/Ic_viewinchat.pdf | Bin 0 -> 4626 bytes .../FreeRepliesIcon.imageset/Contents.json | 12 + .../replies_sticker.pdf | Bin 0 -> 3999 bytes .../Message/ReplyCount.imageset/Contents.json | 12 + .../Message/ReplyCount.imageset/replies.pdf | Bin 0 -> 4117 bytes .../Search Bar/Clear.imageset/Contents.json | 10 +- .../Clear.imageset/ic_search_clear.pdf | Bin 0 -> 4127 bytes .../Search Bar/Loupe.imageset/Contents.json | 20 +- .../Loupe.imageset/IconSearch@2x.png | Bin 964 -> 0 bytes .../Loupe.imageset/IconSearch@3x.png | Bin 1535 -> 0 bytes .../Loupe.imageset/ic_search_search.pdf | Bin 0 -> 4046 bytes .../Contents.json | 12 + .../ic_search_color.pdf | Bin 0 -> 4114 bytes .../Animations/ChatListNoResults.tgs | Bin 0 -> 8590 bytes .../Resources/PresentationStrings.mapping | Bin 154970 -> 155792 bytes .../TelegramUI/Sources/AccountContext.swift | 39 + .../Sources/AudioWaveformNode.swift | 3 - .../ChatChannelSubscriberInputPanelNode.swift | 18 +- .../TelegramUI/Sources/ChatController.swift | 1751 +-- .../Sources/ChatControllerInteraction.swift | 87 +- .../Sources/ChatControllerNode.swift | 51 +- .../TelegramUI/Sources/ChatEmptyNode.swift | 16 +- .../Sources/ChatHistoryEntriesForView.swift | 53 + .../TelegramUI/Sources/ChatHistoryEntry.swift | 38 +- .../Sources/ChatHistoryGridNode.swift | 513 - .../Sources/ChatHistoryListNode.swift | 139 +- .../ChatHistorySearchContainerNode.swift | 5 +- .../Sources/ChatHistoryViewForLocation.swift | 50 +- submodules/TelegramUI/Sources/ChatInfo.swift | 3 - .../Sources/ChatInfoTitlePanelNode.swift | 2 + .../Sources/ChatInterfaceInputContexts.swift | 1 + .../Sources/ChatInterfaceInputNodes.swift | 2 +- .../ChatInterfaceStateContextMenus.swift | 217 +- .../ChatInterfaceStateInputPanels.swift | 31 +- .../ChatInterfaceStateNavigationButtons.swift | 48 + .../Sources/ChatMediaInputNode.swift | 7 +- .../ChatMessageAnimatedStickerItemNode.swift | 176 +- .../ChatMessageAttachedContentNode.swift | 12 +- .../ChatMessageBubbleContentNode.swift | 8 +- .../Sources/ChatMessageBubbleItemNode.swift | 280 +- .../ChatMessageCommentFooterContentNode.swift | 294 + .../ChatMessageContactBubbleContentNode.swift | 9 +- .../ChatMessageDateAndStatusNode.swift | 122 +- .../ChatMessageFileBubbleContentNode.swift | 2 +- .../ChatMessageInstantVideoItemNode.swift | 86 +- .../ChatMessageInteractiveFileNode.swift | 14 +- ...atMessageInteractiveInstantVideoNode.swift | 10 +- .../ChatMessageInteractiveMediaNode.swift | 1 + .../TelegramUI/Sources/ChatMessageItem.swift | 106 +- .../Sources/ChatMessageItemView.swift | 1 + .../ChatMessageMapBubbleContentNode.swift | 11 +- .../ChatMessageMediaBubbleContentNode.swift | 9 +- .../ChatMessagePollBubbleContentNode.swift | 51 +- ...atMessageRestrictedBubbleContentNode.swift | 9 +- .../Sources/ChatMessageStickerItemNode.swift | 85 +- .../ChatMessageTextBubbleContentNode.swift | 41 +- .../ChatMessageWebpageBubbleContentNode.swift | 78 +- .../ChatPanelInterfaceInteraction.swift | 7 +- .../ChatPinnedMessageTitlePanelNode.swift | 36 +- .../Sources/ChatPresentationData.swift | 65 - .../ChatPresentationInterfaceState.swift | 1 + .../Sources/ChatRecentActionsController.swift | 4 +- .../ChatRecentActionsControllerNode.swift | 7 +- .../ChatRecentActionsHistoryTransition.swift | 62 +- .../Sources/ChatReplyCountItem.swift | 176 + .../ChatScheduleTimeControllerNode.swift | 1 - .../Sources/ChatSearchInputPanelNode.swift | 4 + .../ChatSearchNavigationContentNode.swift | 21 +- ...ChatSendMessageActionSheetController.swift | 4 +- .../ChatTextInputMediaRecordingButton.swift | 6 +- .../Sources/ChatTextInputPanelNode.swift | 9 + .../TelegramUI/Sources/ChatTitleView.swift | 37 +- .../ContactMultiselectionController.swift | 2 + .../Sources/DeclareEncodables.swift | 2 + .../Sources/DrawingStickersScreen.swift | 1 + .../Sources/EditAccessoryPanelNode.swift | 2 +- .../TelegramUI/Sources/GridMessageItem.swift | 1 + .../Sources/ManageSharedAccountInfo.swift | 2 +- .../TelegramUI/Sources/MediaManager.swift | 1 + .../Sources/NavigateToChatController.swift | 4 + .../TelegramUI/Sources/OpenChatMessage.swift | 283 +- .../TelegramUI/Sources/OpenResolvedUrl.swift | 5 + .../OverlayAudioPlayerController.swift | 6 +- ...=> OverlayAudioPlayerControllerNode.swift} | 14 +- .../PeerInfo/Panes/PeerInfoListPaneNode.swift | 8 +- .../Panes/PeerInfoVisualMediaPaneNode.swift | 2 + .../Sources/PeerInfo/PeerInfoData.swift | 4 +- .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 6 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 16 +- .../PeerMediaCollectionController.swift | 971 -- .../PeerMediaCollectionControllerNode.swift | 546 - .../PeerMediaCollectionInterfaceState.swift | 1 + .../Sources/PeerMessagesMediaPlaylist.swift | 369 +- .../Sources/PeerSelectionControllerNode.swift | 5 +- .../PreparedChatHistoryViewTransition.swift | 1 + .../Sources/ReplyAccessoryPanelNode.swift | 2 +- .../Sources/SharedAccountContext.swift | 11 +- .../TelegramAccountAuxiliaryMethods.swift | 1 + .../TelegramUI/Sources/TextLinkHandling.swift | 5 + .../ChannelMemberCategoryListContext.swift | 2 +- .../UrlHandling/Sources/UrlHandling.swift | 45 +- .../UrlWhitelist/Sources/UrlWhitelist.swift | 56 + .../WalletUI/Resources/WalletStrings.mapping | Bin 8422 -> 8422 bytes .../WalletUI/Sources/WalletStrings.swift | 492 +- submodules/WebsiteType/BUCK | 3 + submodules/WebsiteType/BUILD | 1 + .../WebsiteType/Sources/WebsiteType.swift | 19 + 337 files changed, 24280 insertions(+), 11996 deletions(-) create mode 100644 Telegram/Telegram-iOS/en.lproj/Localizable.strings.orig create mode 100644 submodules/ChatInterfaceState/BUCK create mode 100644 submodules/ChatInterfaceState/BUILD rename submodules/{TelegramUI => ChatInterfaceState}/Sources/ChatInterfaceState.swift (84%) create mode 100644 submodules/ChatListUI/Sources/ChatListSearchFiltersContainerNode.swift create mode 100644 submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift create mode 100644 submodules/ChatListUI/Sources/ChatListSearchMessageSelectionPanelNode.swift create mode 100644 submodules/ChatListUI/Sources/DateSuggestion.swift create mode 100644 submodules/ChatMessageInteractiveMediaBadge/BUCK create mode 100644 submodules/ChatMessageInteractiveMediaBadge/BUILD rename submodules/{TelegramUI => ChatMessageInteractiveMediaBadge}/Sources/ChatMessageInteractiveMediaBadge.swift (95%) create mode 100644 submodules/FileMediaResourceStatus/BUCK create mode 100644 submodules/FileMediaResourceStatus/BUILD rename submodules/{TelegramUI => FileMediaResourceStatus}/Sources/FileMediaResourceStatus.swift (80%) create mode 100644 submodules/GalleryData/BUCK create mode 100644 submodules/GalleryData/BUILD create mode 100644 submodules/GalleryData/Sources/GalleryData.swift create mode 100644 submodules/ListMessageItem/BUCK create mode 100644 submodules/ListMessageItem/BUILD rename submodules/{TelegramUI => ListMessageItem}/Sources/ListMessageDateHeader.swift (89%) rename submodules/{TelegramUI => ListMessageItem}/Sources/ListMessageFileItemNode.swift (81%) rename submodules/{TelegramUI => ListMessageItem}/Sources/ListMessageHoleItem.swift (100%) rename submodules/{TelegramUI => ListMessageItem}/Sources/ListMessageItem.swift (61%) rename submodules/{TelegramUI => ListMessageItem}/Sources/ListMessageNode.swift (56%) rename submodules/{TelegramUI => ListMessageItem}/Sources/ListMessageSnippetItemNode.swift (73%) rename submodules/{TelegramUI => MediaResources}/Sources/MediaPlaybackStoredState.swift (100%) create mode 100644 submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTBindKeyMessageService.h create mode 100644 submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoEngine.h create mode 100644 submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoInstance.h create mode 100644 submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoPersistenceInterface.h create mode 100644 submodules/MtProtoKit/Sources/MTBindKeyMessageService.m create mode 100644 submodules/MtProtoKit/Sources/MTProtoEngine.m create mode 100644 submodules/MtProtoKit/Sources/MTProtoInstance.m create mode 100644 submodules/MtProtoKit/Sources/Utils/MTQueueLocalObject.h create mode 100644 submodules/MtProtoKit/Sources/Utils/MTQueueLocalObject.m create mode 100644 submodules/Postbox/Sources/MessageHistoryThreadsTable.swift create mode 100644 submodules/PresentationDataUtils/Sources/OpenUrl.swift create mode 100644 submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift create mode 100644 submodules/TelegramCore/Sources/ReplyThreadHistory.swift create mode 100644 submodules/TelegramPresentationData/Sources/ChatPresentationData.swift create mode 100644 submodules/TelegramUI/Images.xcassets/Avatar/RepliesMessagesIcon.imageset/Contents.json rename submodules/TelegramUI/Images.xcassets/{Components/Search Bar/Clear.imageset/Clear.pdf => Avatar/RepliesMessagesIcon.imageset/repliesavatar.pdf} (74%) create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Arrow.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Arrow.imageset/ic_addresult.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Calendar.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Calendar.imageset/ic_search_calendar (2).pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Files.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Files.imageset/ic_search_docs.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Links.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Links.imageset/ic_search_links.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Files.png create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Links.png create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Music.png create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Media.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Media.imageset/ic_search_media.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Music.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Music.imageset/ic_search_music.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/User.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/User.imageset/ic_search_user.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Voice.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat List/Search/Voice.imageset/ic_search_voice.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/ic_viewreplies.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/BubbleComments.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/BubbleComments.imageset/ic_leaveacomment (1).pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/BubbleReplies.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/BubbleReplies.imageset/Ic_viewinchat.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/FreeRepliesIcon.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/FreeRepliesIcon.imageset/replies_sticker.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/replies.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Components/Search Bar/Clear.imageset/ic_search_clear.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Components/Search Bar/Loupe.imageset/IconSearch@2x.png delete mode 100644 submodules/TelegramUI/Images.xcassets/Components/Search Bar/Loupe.imageset/IconSearch@3x.png create mode 100644 submodules/TelegramUI/Images.xcassets/Components/Search Bar/Loupe.imageset/ic_search_search.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/WallpaperSearchColorIcon.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Settings/WallpaperSearchColorIcon.imageset/ic_search_color.pdf create mode 100644 submodules/TelegramUI/Resources/Animations/ChatListNoResults.tgs delete mode 100644 submodules/TelegramUI/Sources/ChatHistoryGridNode.swift create mode 100644 submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift delete mode 100644 submodules/TelegramUI/Sources/ChatPresentationData.swift create mode 100644 submodules/TelegramUI/Sources/ChatReplyCountItem.swift rename submodules/TelegramUI/Sources/{OverlayPlayerControllerNode.swift => OverlayAudioPlayerControllerNode.swift} (94%) delete mode 100644 submodules/TelegramUI/Sources/PeerMediaCollectionController.swift delete mode 100644 submodules/TelegramUI/Sources/PeerMediaCollectionControllerNode.swift diff --git a/Makefile b/Makefile index c781f082f2..625c56dc0f 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ include Utils.makefile -APP_VERSION="7.0.1" +APP_VERSION="7.1" CORE_COUNT=$(shell sysctl -n hw.logicalcpu) CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1) diff --git a/Telegram/NotificationService/FetchImage.m b/Telegram/NotificationService/FetchImage.m index 68d971fc1e..6da9bb2e90 100644 --- a/Telegram/NotificationService/FetchImage.m +++ b/Telegram/NotificationService/FetchImage.m @@ -108,7 +108,8 @@ dispatch_block_t fetchImage(BuildConfig *buildConfig, AccountProxyConnection * _ apiEnvironment = [apiEnvironment withUpdatedSocksProxySettings:[[MTSocksProxySettings alloc] initWithIp:proxyConnection.host port:(uint16_t)proxyConnection.port username:proxyConnection.username password:proxyConnection.password secret:proxyConnection.secret]]; } - MTContext *context = [[MTContext alloc] initWithSerialization:serialization encryptionProvider:[[EmptyEncryptionProvider alloc] init] apiEnvironment:apiEnvironment isTestingEnvironment:account.isTestingEnvironment useTempAuthKeys:false]; + MTContext *context = [[MTContext alloc] initWithSerialization:serialization encryptionProvider:[[EmptyEncryptionProvider alloc] init] apiEnvironment:apiEnvironment isTestingEnvironment:account.isTestingEnvironment useTempAuthKeys:true]; + context.tempKeyExpiration = 10 * 60 * 60; NSDictionary *seedAddressList = @{}; @@ -153,7 +154,7 @@ dispatch_block_t fetchImage(BuildConfig *buildConfig, AccountProxyConnection * _ for (NSNumber *datacenterId in account.datacenters) { AccountDatacenterInfo *info = account.datacenters[datacenterId]; - [context updateAuthInfoForDatacenterWithId:[datacenterId intValue] authInfo:[[MTDatacenterAuthInfo alloc] initWithAuthKey:info.masterKey.data authKeyId:info.masterKey.keyId saltSet:@[] authKeyAttributes:@{} mainTempAuthKey:nil mediaTempAuthKey:nil]]; + [context updateAuthInfoForDatacenterWithId:[datacenterId intValue] authInfo:[[MTDatacenterAuthInfo alloc] initWithAuthKey:info.masterKey.data authKeyId:info.masterKey.keyId saltSet:@[] authKeyAttributes:@{}] selector:MTDatacenterAuthInfoSelectorPersistent]; } MTProto *mtProto = [[MTProto alloc] initWithContext:context datacenterId:datacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0]; diff --git a/Telegram/NotificationService/Serialization.m b/Telegram/NotificationService/Serialization.m index ddfa890a93..f137bbe5df 100644 --- a/Telegram/NotificationService/Serialization.m +++ b/Telegram/NotificationService/Serialization.m @@ -3,7 +3,7 @@ @implementation Serialization - (NSUInteger)currentLayer { - return 118; + return 119; } - (id _Nullable)parseMessage:(NSData * _Nullable)data { diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 2f1cba8cc0..74585672ff 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -2607,6 +2607,7 @@ Unused sets are archived when you add more."; "Channel.AdminLog.CanInviteUsers" = "Add Users"; "Channel.AdminLog.CanPinMessages" = "Pin Messages"; "Channel.AdminLog.CanAddAdmins" = "Add New Admins"; +"Channel.AdminLog.CanBeAnonymous" = "Remain Anonymous"; "Channel.AdminLog.CanEditMessages" = "Edit Messages"; "Channel.AdminLog.MessageToggleInvitesOn" = "%@ enabled group invites"; @@ -5741,3 +5742,31 @@ Any member of this group will be able to see messages in the channel."; "AccessDenied.VideoCallCamera" = "Telegram needs access to your camera to make video calls.\n\nPlease go to Settings > Privacy > Camera and set Telegram to ON."; "Call.AccountIsLoggedOnCurrentDevice" = "Sorry, you can't call %@ because that account is logged in to Telegram on the device you're using for the call."; + +"ChatList.Search.FilterMedia" = "Media"; +"ChatList.Search.FilterLinks" = "Links"; +"ChatList.Search.FilterFiles" = "Files"; +"ChatList.Search.FilterMusic" = "Music"; +"ChatList.Search.FilterVoice" = "Voice"; + +"ChatList.Search.NoResults" = "No Results"; +"ChatList.Search.NoResultsQueryDescription" = "There were no results for \"%@\".\nTry a new search."; +"ChatList.Search.NoResultsDescription" = "There were no results.\nTry a new search."; + +"ChatList.Search.NoResultsFilter" = "Nothing Yet"; +"ChatList.Search.NoResultsFitlerMedia" = "Photos and videos from all your chats will be shown here."; +"ChatList.Search.NoResultsFitlerLinks" = "Links from all your chats will be shown here."; +"ChatList.Search.NoResultsFitlerFiles" = "Files from all your chats will be shown here."; +"ChatList.Search.NoResultsFitlerMusic" = "Music from all your chats will be shown here."; +"ChatList.Search.NoResultsFitlerVoice" = "Voice and video messages from all your chats will be shown here."; + +"ChatList.Search.Messages_0" = "%@ messages"; +"ChatList.Search.Messages_1" = "%@ message"; +"ChatList.Search.Messages_2" = "%@ messages"; +"ChatList.Search.Messages_3_10" = "%@ messages"; +"ChatList.Search.Messages_many" = "%@ messages"; +"ChatList.Search.Messages_any" = "%@ messages"; + +"Conversation.InputTextAnonymousPlaceholder" = "Send anonymously"; + +"DialogList.Replies" = "Replies"; diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings.orig b/Telegram/Telegram-iOS/en.lproj/Localizable.strings.orig new file mode 100644 index 0000000000..9d490177c0 --- /dev/null +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings.orig @@ -0,0 +1,5793 @@ +// Notifications +"PUSH_MESSAGE_TEXT" = "%1$@|%2$@"; +"PUSH_MESSAGE_NOTEXT" = "%1$@|sent you a message"; +"PUSH_MESSAGE_PHOTO" = "%1$@|sent you a photo"; +"PUSH_MESSAGE_PHOTO_SECRET" = "%1$@|sent you a self-destructing photo"; +"PUSH_MESSAGE_VIDEO" = "%1$@|sent you a video"; +"PUSH_MESSAGE_VIDEO_SECRET" = "%1$@|sent you a self-destructing video"; +"PUSH_MESSAGE_ROUND" = "%1$@|sent you a video message"; +"PUSH_MESSAGE_CONTACT" = "%1$@|shared a contact %2$@ with you"; +"PUSH_MESSAGE_GEO" = "%1$@|sent you a map"; +"PUSH_MESSAGE_GEOLIVE" = "%1$@|started sharing their live location"; +"PUSH_MESSAGE_DOC" = "%1$@|sent you a file"; +"PUSH_MESSAGE_AUDIO" = "%1$@|sent you a voice message"; +"PUSH_MESSAGE_GIF" = "%1$@|sent you a GIF"; +"PUSH_ENCRYPTED_MESSAGE" = "You have a new message%1$@"; +"PUSH_LOCKED_MESSAGE" = "You have a new message%1$@"; +"PUSH_MESSAGE_SCREENSHOT" = "%1$@|took a screenshot!"; +"PUSH_ENCRYPTION_REQUEST" = "New encryption request%1$@"; +"PUSH_ENCRYPTION_ACCEPT" = "Your encryption request was accepted%1$@"; + +"PUSH_MESSAGE_POLL" = "%1$@|sent you a poll %2$@"; +"PUSH_CHANNEL_MESSAGE_POLL" = "%1$@|posted a poll %2$@"; +"PUSH_PINNED_POLL" = "%1$@|pinned a poll"; + +"PUSH_MESSAGE_QUIZ" = "%1$@|sent you a quiz %2$@"; +"PUSH_CHANNEL_MESSAGE_QUIZ" = "%1$@|posted a quiz %2$@"; +"PUSH_PINNED_QUIZ" = "%1$@|pinned a quiz"; + +"PUSH_CHAT_MESSAGE_TEXT" = "%2$@|%1$@: %3$@"; +"PUSH_CHAT_MESSAGE_NOTEXT" = "%2$@|%1$@ sent a message"; +"PUSH_CHAT_MESSAGE_PHOTO" = "%2$@|%1$@ sent a photo"; +"PUSH_CHAT_MESSAGE_VIDEO" = "%2$@|%1$@ sent a video"; +"PUSH_CHAT_MESSAGE_ROUND" = "%2$@|%1$@ sent a video message"; +"PUSH_CHAT_MESSAGE_CONTACT" = "%2$@|%1$@ shared a contact %3$@"; +"PUSH_CHAT_MESSAGE_GEO" = "%2$@|%1$@ sent a map"; +"PUSH_CHAT_MESSAGE_GEOLIVE" = "%2$@|%1$@ started sharing their live location"; +"PUSH_CHAT_MESSAGE_DOC" = "%2$@|%1$@ sent a file"; +"PUSH_CHAT_MESSAGE_AUDIO" = "%2$@|%1$@ sent a voice message"; +"PUSH_CHAT_MESSAGE_GIF" = "%2$@|%1$@ sent a GIF"; +"PUSH_CHAT_CREATED" = "%2$@|%1$@ invited you to the group"; +"PUSH_CHAT_TITLE_EDITED" = "%2$@|%1$@ edited the group's name"; +"PUSH_CHAT_PHOTO_EDITED" = "%2$@|%1$@ edited the group's photo"; +"PUSH_CHAT_ADD_MEMBER" = "%2$@|%1$@ invited %3$@ to the group"; +"PUSH_CHAT_ADD_YOU" = "%2$@|%1$@ invited you to the group"; +"PUSH_CHAT_DELETE_YOU" = "%2$@|%1$@ removed you from the group"; +"PUSH_CHAT_DELETE_MEMBER" = "%2$@|%1$@ removed %3$@ from the group"; +"PUSH_CHAT_LEFT" = "%2$@|%1$@ left the group"; +"PUSH_CHAT_RETURNED" = "%2$@|%1$@ returned to the group"; + +"PUSH_MESSAGE_STICKER" = "%1$@|sent you a %2$@sticker"; +"PUSH_CHAT_MESSAGE_STICKER" = "%2$@|%1$@ sent a %3$@sticker"; + +"PUSH_CONTACT_JOINED" = "%1$@|joined Telegram!"; + +"PUSH_CHANNEL_MESSAGE_TEXT" = "%1$@|%2$@"; +"PUSH_CHANNEL_MESSAGE_NOTEXT" = "%1$@|posted a message"; +"PUSH_CHANNEL_MESSAGE_PHOTO" = "%1$@|posted a photo"; +"PUSH_CHANNEL_MESSAGE_VIDEO" = "%1$@|posted a video"; +"PUSH_CHANNEL_MESSAGE_ROUND" = "%1$@|posted a video message"; +"PUSH_CHANNEL_MESSAGE_DOC" = "%1$@|posted a document"; +"PUSH_CHANNEL_MESSAGE_STICKER" = "%1$@|posted a %2$@sticker"; +"PUSH_CHANNEL_MESSAGE_AUDIO" = "%1$@|posted a voice message"; +"PUSH_CHANNEL_MESSAGE_CONTACT" = "%1$@|posted a %2$@ contact"; +"PUSH_CHANNEL_MESSAGE_GEO" = "%1$@|posted a map"; +"PUSH_CHANNEL_MESSAGE_GEOLIVE" = "%1$@|posted a live location"; +"PUSH_CHANNEL_MESSAGE_GIF" = "%1$@|posted a GIF"; + +"PUSH_PINNED_TEXT" = "%1$@ pinned \"%2$@\" "; +"PUSH_PINNED_NOTEXT" = "%1$@ pinned a message"; +"PUSH_PINNED_PHOTO" = "%1$@ pinned a photo"; +"PUSH_PINNED_VIDEO" = "%1$@ pinned a video"; +"PUSH_PINNED_ROUND" = "%1$@ pinned a video message"; +"PUSH_PINNED_DOC" = "%1$@ pinned a file"; +"PUSH_PINNED_STICKER" = "%1$@ pinned a %2$@sticker"; +"PUSH_PINNED_AUDIO" = "%1$@ pinned a voice message"; +"PUSH_PINNED_GEO" = "%1$@ pinned a map"; +"PUSH_PINNED_GEOLIVE" = "%1$@ pinned a live location"; +"PUSH_PINNED_GIF" = "%1$@ pinned a GIF"; + +"PUSH_MESSAGE_GAME" = "%1$@|invited you to play %2$@"; +"PUSH_CHANNEL_MESSAGE_GAME" = "%1$@|invited you to play %2$@"; +"PUSH_CHAT_MESSAGE_GAME" = "%2$@|%1$@ invited the group to play %3$@"; +"PUSH_PINNED_GAME" = "%1$@ pinned a game"; + +"PUSH_MESSAGE_TEXT" = "%1$@|%2$@"; +"PUSH_MESSAGE_NOTEXT" = "%1$@|sent you a message"; +"PUSH_MESSAGE_PHOTO" = "%1$@|sent you a photo"; +"PUSH_MESSAGE_PHOTO_SECRET" = "%1$@|sent you a self-destructing photo"; +"PUSH_MESSAGE_VIDEO" = "%1$@|sent you a video"; +"PUSH_MESSAGE_VIDEO_SECRET" = "%1$@|sent you a self-destructing video"; +"PUSH_MESSAGE_SCREENSHOT" = "%1$@|took a screenshot"; +"PUSH_MESSAGE_ROUND" = "%1$@|sent you a video message"; +"PUSH_MESSAGE_DOC" = "%1$@|sent you a file"; +"PUSH_MESSAGE_STICKER" = "%1$@|sent you a %2$@sticker"; +"PUSH_MESSAGE_AUDIO" = "%1$@|sent you a voice message"; +"PUSH_MESSAGE_CONTACT" = "%1$@|shared a contact with you"; +"PUSH_MESSAGE_GEO" = "%1$@|sent you a map"; +"PUSH_MESSAGE_GEOLIVE" = "%1$@|started sharing their live location"; +"PUSH_MESSAGE_POLL" = "%1$@|sent you a poll"; +"PUSH_MESSAGE_QUIZ" = "%1$@|sent you a quiz"; +"PUSH_MESSAGE_GIF" = "%1$@|sent you a GIF"; +"PUSH_MESSAGE_GAME" = "%1$@|invited you to play %2$@"; +"PUSH_MESSAGE_INVOICE" = "%1$@|sent you an invoice for %2$@"; +"PUSH_MESSAGE_FWD" = "%1$@|forwarded you a message"; +"PUSH_MESSAGE_FWDS_1" = "%1$@|forwarded you a message"; +"PUSH_MESSAGE_FWDS_any" = "%1$@|forwarded you %2$d messages"; +"PUSH_MESSAGE_PHOTO" = "%1$@|sent you a photo"; +"PUSH_MESSAGE_PHOTOS_1" = "%1$@|sent you a photo"; +"PUSH_MESSAGE_PHOTOS_any" = "%1$@|sent you %2$d photos"; +"PUSH_MESSAGE_VIDEO" = "%1$@|sent you a video"; +"PUSH_MESSAGE_VIDEOS_1" = "%1$@|sent you a video"; +"PUSH_MESSAGE_VIDEOS_any" = "%1$@|sent you %2$d videos"; +"PUSH_MESSAGE_ROUND" = "%1$@|sent you a video message"; +"PUSH_MESSAGE_ROUNDS_1" = "%1$@|sent you a video message"; +"PUSH_MESSAGE_ROUNDS_any" = "%1$@|sent you %2$d video messages"; +"PUSH_MESSAGE" = "%1$@|sent you a message"; +"PUSH_MESSAGES_1" = "%1$@|sent you a message"; +"PUSH_MESSAGES_any" = "%1$@|sent you %2$d messages"; +"PUSH_ALBUM" = "%1$@|sent you an album"; + +"PUSH_CHANNEL_MESSAGE_TEXT" = "%1$@|%2$@"; +"PUSH_CHANNEL_MESSAGE_NOTEXT" = "%1$@|posted a message"; +"PUSH_CHANNEL_MESSAGE_PHOTO" = "%1$@|posted a photo"; +"PUSH_CHANNEL_MESSAGE_VIDEO" = "%1$@|posted a video"; +"PUSH_CHANNEL_MESSAGE_ROUND" = "%1$@|posted a video message"; +"PUSH_CHANNEL_MESSAGE_DOC" = "%1$@|posted a file"; +"PUSH_CHANNEL_MESSAGE_STICKER" = "%1$@|posted a %2$@sticker"; +"PUSH_CHANNEL_MESSAGE_AUDIO" = "%1$@|posted a voice message"; +"PUSH_CHANNEL_MESSAGE_CONTACT" = "%1$@|posted a contact"; +"PUSH_CHANNEL_MESSAGE_GEO" = "%1$@|posted a map"; +"PUSH_CHANNEL_MESSAGE_GEOLIVE" = "%1$@|posted a live location"; +"PUSH_CHANNEL_MESSAGE_POLL" = "%1$@|posted a poll"; +"PUSH_CHANNEL_MESSAGE_QUIZ" = "%1$@|posted a quiz"; +"PUSH_CHANNEL_MESSAGE_GIF" = "%1$@|posted a GIF"; +"PUSH_CHANNEL_MESSAGE_GAME" = "%1$@|invited you to play %2$@"; +"PUSH_CHANNEL_MESSAGE_FWD" = "%1$@|posted a forwarded message"; +"PUSH_CHANNEL_MESSAGE_FWDS_1" = "%1$@|posted a forwarded message"; +"PUSH_CHANNEL_MESSAGE_FWDS_any" = "%1$@|posted %2$d forwarded messages"; +"PUSH_CHANNEL_MESSAGE_PHOTO" = "%1$@|posted a photo"; +"PUSH_CHANNEL_MESSAGE_PHOTOS_1" = "%1$@|posted a photo"; +"PUSH_CHANNEL_MESSAGE_PHOTOS_any" = "%1$@|posted %2$d photos"; +"PUSH_CHANNEL_MESSAGE_VIDEO" = "%1$@|posted a video"; +"PUSH_CHANNEL_MESSAGE_VIDEOS_1" = "%1$@|posted a video"; +"PUSH_CHANNEL_MESSAGE_VIDEOS_any" = "%1$@|posted %2$d videos"; +"PUSH_CHANNEL_MESSAGE_ROUND" = "%1$@|posted a video message"; +"PUSH_CHANNEL_MESSAGE_ROUNDS_1" = "%1$@|posted a video message"; +"PUSH_CHANNEL_MESSAGE_ROUNDS_any" = "%1$@|posted %2$d video messages"; +"PUSH_CHANNEL_MESSAGE" = "%1$@|posted a message"; +"PUSH_CHANNEL_MESSAGES_1" = "%1$@|posted a message"; +"PUSH_CHANNEL_MESSAGES_any" = "%1$@|posted %2$d messages"; +"PUSH_CHANNEL_ALBUM" = "%1$@|posted an album"; + +"PUSH_CHAT_MESSAGE_TEXT" = "%2$@|%1$@:%3$@"; +"PUSH_CHAT_MESSAGE_NOTEXT" = "%2$@|%1$@ sent a message to the group"; +"PUSH_CHAT_MESSAGE_VIDEO" = "%2$@|%1$@ sent a video "; +"PUSH_CHAT_MESSAGE_ROUND" = "%2$@|%1$@ sent a video message"; +"PUSH_CHAT_MESSAGE_DOC" = "%2$@|%1$@ sent a file"; +"PUSH_CHAT_MESSAGE_STICKER" = "%2$@|%1$@ sent a %3$@sticker"; +"PUSH_CHAT_MESSAGE_AUDIO" = "%2$@|%1$@ sent a voice message"; +"PUSH_CHAT_MESSAGE_CONTACT" = "%2$@|%1$@ shared a contact"; +"PUSH_CHAT_MESSAGE_GEO" = "%2$@|%1$@ sent a map"; +"PUSH_CHAT_MESSAGE_GEOLIVE" = "%2$@|%1$@ started sharing their live location"; +"PUSH_CHAT_MESSAGE_POLL" = "%2$@|%1$@ sent a poll %3$@ to the group"; +"PUSH_CHAT_MESSAGE_QUIZ" = "%2$@|%1$@ sent a quiz %3$@ to the group"; +"PUSH_CHAT_MESSAGE_GIF" = "%2$@|%1$@ sent a GIF"; +"PUSH_CHAT_MESSAGE_GAME" = "%2$@|%1$@ invited the group to play %3$@"; +"PUSH_CHAT_MESSAGE_INVOICE" = "%2$@|%1$@ sent an invoice for %3$@"; +"PUSH_CHAT_CREATED" = "%2$@|%1$@ invited you to the group"; +"PUSH_CHAT_TITLE_EDITED" = "%2$@|%1$@ edited the group\'s name"; +"PUSH_CHAT_PHOTO_EDITED" = "%2$@|%1$@ edited the group\'s photo"; +"PUSH_CHAT_ADD_MEMBER" = "%2$@|%1$@ invited %3$@ to the group"; +"PUSH_CHAT_ADD_YOU" = "%2$@|%1$@ invited you to the group"; +"PUSH_CHAT_DELETE_MEMBER" = "%2$@|%1$@ kicked %3$@ from the group"; +"PUSH_CHAT_DELETE_YOU" = "%2$@|%1$@ kicked you from the group "; +"PUSH_CHAT_LEFT" = "%2$@|%1$@ has left the group"; +"PUSH_CHAT_RETURNED" = "%2$@|%1$@ has returned to the group"; +"PUSH_CHAT_JOINED" = "%2$@|%1$@ has joined the group"; +"PUSH_CHAT_MESSAGE_FWD" = "%2$@|%1$@ forwarded a message"; +"PUSH_CHAT_MESSAGE_FWDS_1" = "%2$@|%1$@ forwarded a message"; +"PUSH_CHAT_MESSAGE_FWDS_any" = "%2$@|%1$@ forwarded %3$d messages"; +"PUSH_CHAT_MESSAGE_PHOTO" = "%2$@|%1$@ sent a photo"; +"PUSH_CHAT_MESSAGE_PHOTOS_1" = "%2$@|%1$@ sent a photo"; +"PUSH_CHAT_MESSAGE_PHOTOS_any" = "%2$@|%1$@ sent %3$d photos"; +"PUSH_CHAT_MESSAGE_VIDEO" = "%2$@|%1$@ sent a video"; +"PUSH_CHAT_MESSAGE_VIDEOS_1" = "%2$@|%1$@ sent a video"; +"PUSH_CHAT_MESSAGE_VIDEOS_any" = "%2$@|%1$@ sent %3$d videos"; +"PUSH_CHAT_MESSAGE_ROUND" = "%2$@|%1$@ sent a video message"; +"PUSH_CHAT_MESSAGE_ROUNDS_1" = "%2$@|%1$@ sent a video message"; +"PUSH_CHAT_MESSAGE_ROUNDS_any" = "%2$@|%1$@ sent %3$d video messages"; +"PUSH_CHAT_MESSAGE" = "%2$@|%1$@ sent a message"; +"PUSH_CHAT_MESSAGES_1" = "%2$@|%1$@ sent a message"; +"PUSH_CHAT_MESSAGES_any" = "%2$@|%1$@ sent %3$d messages"; +"PUSH_CHAT_ALBUM" = "%2$@|%1$@ sent an album"; + +"PUSH_PINNED_TEXT" = "%1$@|pinned \"%2$@\" "; +"PUSH_PINNED_NOTEXT" = "%1$@|pinned a message"; +"PUSH_PINNED_PHOTO" = "%1$@|pinned a photo"; +"PUSH_PINNED_VIDEO" = "%1$@|pinned a video"; +"PUSH_PINNED_ROUND" = "%1$@|pinned a video message"; +"PUSH_PINNED_DOC" = "%1$@|pinned a file"; +"PUSH_PINNED_STICKER" = "%1$@|pinned a %2$@sticker"; +"PUSH_PINNED_AUDIO" = "%1$@|pinned a voice message"; +"PUSH_PINNED_CONTACT" = "%1$@|pinned a %2$@ contact"; +"PUSH_PINNED_GEO" = "%1$@|pinned a map"; +"PUSH_PINNED_GEOLIVE" = "%1$@|pinned a live location"; +"PUSH_PINNED_POLL" = "|%1$@|pinned a poll %2$@"; +"PUSH_PINNED_QUIZ" = "|%1$@|pinned a quiz %2$@"; +"PUSH_PINNED_GAME" = "%1$@|pinned a game"; +"PUSH_PINNED_INVOICE" = "%1$@|pinned an invoice"; +"PUSH_PINNED_GIF" = "%1$@|pinned a GIF"; + +"PUSH_CONTACT_JOINED" = "%1$@|joined Telegram!"; + +"PUSH_AUTH_UNKNOWN" = "New login|from unrecognized device %1$@"; +"PUSH_AUTH_REGION" = "New login|from unrecognized device %1$@, location: %2$@"; + +"PUSH_PHONE_CALL_REQUEST" = "%1$@|is calling you!"; +"PUSH_VIDEO_CALL_REQUEST" = "%1$@|is calling you!"; +"PUSH_PHONE_CALL_MISSED" = "%1$@|You missed a call"; +"PUSH_VIDEO_CALL_MISSED" = "%1$@|You missed a video call"; + +"PUSH_MESSAGE_GAME_SCORE" = "%1$@ scored %3$@ in game %2$@"; +"PUSH_MESSAGE_VIDEOS" = "%1$@ sent you %2$@ videos"; +"PUSH_MESSAGE_CHANNEL_MESSAGE_GAME_SCORE" = "%1$@ scored %3$@ in game %2$@"; +"PUSH_CHANNEL_MESSAGE_VIDEOS" = "%1$@ posted %2$@ videos"; +"PUSH_PINNED_GAME_SCORE" = "%1$@ pinned a game score"; +"PUSH_CHAT_MESSAGE_GAME_SCORE" = "%1$@ scored %4$@ in game %3$@ in the group %2$@"; +"PUSH_CHAT_MESSAGE_VIDEOS" = "%1$@ sent %3$@ videos to the group %2$@"; + +"PUSH_REMINDER_TITLE" = "🗓 Reminder"; +"PUSH_SENDER_YOU" = "📅 You"; + +"LOCAL_MESSAGE_FWDS" = "%1$@ forwarded you %2$d messages"; +"LOCAL_CHANNEL_MESSAGE_FWDS" = "%1$@ posted %2$d forwarded messages"; +"LOCAL_CHAT_MESSAGE_FWDS" = "%1$@ forwarded %2$d messages"; + +// Common +"Common.OK" = "OK"; +"Common.Cancel" = "Cancel"; +"Common.Edit" = "Edit"; +"Common.edit" = "edit"; +"Common.Done" = "Done"; +"Common.Next" = "Next"; +"Common.Delete" = "Delete"; +"Common.Create" = "Create"; +"Common.Back" = "Back"; +"Common.Close" = "Close"; +"Common.Yes" = "Yes"; +"Common.No" = "No"; +"Common.TakePhotoOrVideo" = "Take Photo or Video"; +"Common.TakePhoto" = "Take Photo"; +"Common.ChoosePhoto" = "Choose Photo"; +"Common.of" = "of"; +"Common.Search" = "Search"; +"Common.More" = "More"; +"Common.Select" = "Select"; +"Items.NOfM" = "%1$@ of %2$@"; + +// State +"State.Connecting" = "Connecting..."; +"State.connecting" = "connecting..."; +"State.ConnectingToProxy" = "Connecting to Proxy..."; +"State.ConnectingToProxyInfo" = "tap here for settings"; +"State.Updating" = "Updating..."; +"State.WaitingForNetwork" = "Waiting for network"; + +"ChatState.Connecting" = "connecting..."; +"ChatState.ConnectingToProxy" = "connecting to proxy..."; +"ChatState.Updating" = "updating..."; +"ChatState.WaitingForNetwork" = "waiting for network..."; + +// Presence +"Presence.online" = "online"; + +// Date +"Month.GenJanuary" = "January"; +"Month.GenFebruary" = "February"; +"Month.GenMarch" = "March"; +"Month.GenApril" = "April"; +"Month.GenMay" = "May"; +"Month.GenJune" = "June"; +"Month.GenJuly" = "July"; +"Month.GenAugust" = "August"; +"Month.GenSeptember" = "September"; +"Month.GenOctober" = "October"; +"Month.GenNovember" = "November"; +"Month.GenDecember" = "December"; +"Month.ShortJanuary" = "Jan"; +"Month.ShortFebruary" = "Feb"; +"Month.ShortMarch" = "Mar"; +"Month.ShortApril" = "Apr"; +"Month.ShortMay" = "May"; +"Month.ShortJune" = "Jun"; +"Month.ShortJuly" = "Jul"; +"Month.ShortAugust" = "Aug"; +"Month.ShortSeptember" = "Sep"; +"Month.ShortOctober" = "Oct"; +"Month.ShortNovember" = "Nov"; +"Month.ShortDecember" = "Dec"; +"Weekday.ShortMonday" = "Mon"; +"Weekday.ShortTuesday" = "Tue"; +"Weekday.ShortWednesday" = "Wed"; +"Weekday.ShortThursday" = "Thu"; +"Weekday.ShortFriday" = "Fri"; +"Weekday.ShortSaturday" = "Sat"; +"Weekday.ShortSunday" = "Sun"; +"Weekday.Today" = "Today"; +"Weekday.Yesterday" = "Yesterday"; + +"Time.TodayAt" = "today at %@"; +"Time.YesterdayAt" = "yesterday at %@"; + +"LastSeen.JustNow" = "last seen just now"; +"LastSeen.MinutesAgo_0" = "last seen %@ minutes ago"; //three to ten +"LastSeen.MinutesAgo_1" = "last seen 1 minute ago"; //one +"LastSeen.MinutesAgo_2" = "last seen 2 minutes ago"; //two +"LastSeen.MinutesAgo_3_10" = "last seen %@ minutes ago"; //three to ten +"LastSeen.MinutesAgo_many" = "last seen %@ minutes ago"; // more than ten +"LastSeen.MinutesAgo_any" = "last seen %@ minutes ago"; // more than ten +"LastSeen.HoursAgo_0" = "last seen %@ hours ago"; +"LastSeen.HoursAgo_1" = "last seen 1 hour ago"; +"LastSeen.HoursAgo_2" = "last seen 2 hours ago"; +"LastSeen.HoursAgo_3_10" = "last seen %@ hours ago"; +"LastSeen.HoursAgo_any" = "last seen %@ hours ago"; +"LastSeen.HoursAgo_many" = "last seen %@ hours ago"; +"LastSeen.HoursAgo_0" = "last seen %@ hours ago"; +"LastSeen.YesterdayAt" = "last seen yesterday at %@"; +"LastSeen.AtDate" = "last seen %@"; +"LastSeen.TodayAt" = "last seen today at %@"; +"LastSeen.Lately" = "last seen recently"; +"LastSeen.WithinAWeek" = "last seen within a week"; +"LastSeen.WithinAMonth" = "last seen within a month"; +"LastSeen.ALongTimeAgo" = "last seen a long time ago"; +"LastSeen.Offline" = "offline"; + +"Date.DialogDateFormat" = "{month} {day}"; +"Date.ChatDateHeader" = "%1$@ %2$@"; +"Date.ChatDateHeaderYear" = "%1$@ %2$@, %3$@"; + +// Tour +"Tour.Title1" = "Telegram"; +"Tour.Text1" = "The world's **fastest** messaging app.\nIt is **free** and **secure**."; + +"Tour.Title2" = "Fast"; +"Tour.Text2" = "**Telegram** delivers messages\nfaster than any other application."; + +"Tour.Title3" = "Powerful"; +"Tour.Text3" = "**Telegram** has no limits on\nthe size of your chats and media."; + +"Tour.Title4" = "Secure"; +"Tour.Text4" = "**Telegram** keeps your messages\nsafe from hacker attacks."; + +"Tour.Title5" = "Cloud-Based"; +"Tour.Text5" = "**Telegram** lets you access your\nmessages from multiple devices."; + +"Tour.Title6" = "Free"; +"Tour.Text6" = "**Telegram** is free forever. No ads.\nNo subscription fees."; + +"Tour.StartButton" = "Start Messaging"; + +// Login +"Login.PhoneAndCountryHelp" = "Please confirm your country code and enter your phone number."; +"Login.CodeSentInternal" = "We've sent the code to the **Telegram** app on your other device"; +"Login.HaveNotReceivedCodeInternal" = "Haven't received the code?"; +"Login.CodeSentSms" = "We have sent you an SMS with the code"; +"Login.Code" = "Code"; +"Login.WillCallYou" = "Telegram will call you in %@"; +"Login.CallRequestState2" = "Requesting a call from Telegram..."; +"Login.CallRequestState3" = "Telegram dialed your number\n[Didn't get the code?]"; +"Login.EmailNotConfiguredError" = "Please set up an email account."; +"Login.EmailCodeSubject" = "%@, no code"; +"Login.EmailCodeBody" = "My phone number is:\n%@\nI can't get an activation code for Telegram."; +"Login.UnknownError" = "An error occurred. Please try again later"; +"Login.InvalidCodeError" = "You have entered an invalid code. Please try again."; +"Login.NetworkError" = "Please check your internet connection and try again."; +"Login.CodeExpiredError" = "Code expired. Please try again."; +"Login.CodeFloodError" = "Limit exceeded. Please try again later."; +"Login.InvalidPhoneError" = "Invalid phone number. Please try again."; +"Login.InvalidFirstNameError" = "Invalid first name. Please try again."; +"Login.InvalidLastNameError" = "Invalid last name. Please try again."; + +"Login.InvalidPhoneEmailSubject" = "Invalid phone number: %@"; +"Login.InvalidPhoneEmailBody" = "I'm trying to use my mobile phone number: %1$@\nBut Telegram says it's invalid. Please help.\n\nApp version: %2$@\nOS version: %3$@\nLocale: %4$@\nMNC: %5$@"; + +"Login.PhoneBannedEmailSubject" = "Banned phone number: %@"; +"Login.PhoneBannedEmailBody" = "I'm trying to use my mobile phone number: %1$@\nBut Telegram says it's banned. Please help.\n\nApp version: %2$@\nOS version: %3$@\nLocale: %4$@\nMNC: %5$@"; + +"Login.PhoneGenericEmailSubject" = "Telegram iOS error: %@"; +"Login.PhoneGenericEmailBody" = "I'm trying to use my mobile phone number: %1$@\nBut Telegram shows an error. Please help.\n\nError: %2$@\nApp version: %3$@\nOS version: %4$@\nLocale: %5$@\nMNC: %6$@"; + + +"Login.PhoneTitle" = "Your Phone"; +"Login.PhonePlaceholder" = "Your phone number"; +"Login.CountryCode" = "Country Code"; +"Login.InvalidCountryCode" = "Invalid Country Code"; + +"Login.InfoTitle" = "Your Info"; +"Login.InfoAvatarAdd" = "add"; +"Login.InfoAvatarPhoto" = "photo"; +"Login.InfoFirstNamePlaceholder" = "First Name"; +"Login.InfoLastNamePlaceholder" = "Last Name"; +"Login.InfoDeletePhoto" = "Delete Photo"; +"Login.InfoHelp" = "Enter your name and add a profile picture."; + +// Login.SelectCountry +"Login.SelectCountry.Title" = "Country"; + +// Dialog List +"DialogList.TabTitle" = "Chats"; +"DialogList.Title" = "Chats"; +"DialogList.SearchLabel" = "Search for messages or users"; +"DialogList.NoMessagesTitle" = "You have no conversations yet"; +"DialogList.NoMessagesText" = "Start messaging by pressing the pencil button in the top right corner or go to the Contacts section."; +"DialogList.SingleTypingSuffix" = "%@ is typing"; +"DialogList.SingleRecordingAudioSuffix" = "%@ is recording audio"; +"DialogList.SingleUploadingPhotoSuffix" = "%@ is sending photo"; +"DialogList.SingleUploadingVideoSuffix" = "%@ is sending video"; +"DialogList.SingleRecordingVideoMessageSuffix" = "%@ is recording video"; +"DialogList.SingleUploadingFileSuffix" = "%@ is sending file"; +"DialogList.MultipleTypingSuffix" = "%d are typing"; +"DialogList.Typing" = "typing"; +"DialogList.ClearHistoryConfirmation" = "Clear History"; +"DialogList.DeleteConversationConfirmation" = "Delete and Exit"; +"DialogList.AwaitingEncryption" = "Waiting for %@ to get online..."; +"DialogList.EncryptionRejected" = "Secret chat cancelled"; +"DialogList.EncryptionProcessing" = "Exchanging encryption keys..."; +"DialogList.EncryptedChatStartedOutgoing" = "%@ joined your secret chat."; +"DialogList.EncryptedChatStartedIncoming" = "%@ created a secret chat."; + +// Compose +"Compose.TokenListPlaceholder" = "Whom would you like to message?"; +"Compose.NewMessage" = "New Message"; +"Compose.NewGroup" = "New Group"; +"Compose.NewGroupTitle" = "New Group"; +"Compose.NewEncryptedChat" = "New Secret Chat"; +"Compose.NewEncryptedChatTitle" = "New Secret Chat"; +"Compose.Create" = "Create"; + +// Contacts +"Contacts.TabTitle" = "Contacts"; +"Contacts.Title" = "Contacts"; +"Contacts.FailedToSendInvitesMessage" = "An error occurred."; +"Contacts.AccessDeniedError" = "Telegram does not have access to your contacts"; +"Contacts.AccessDeniedHelpLandscape" = "Please go to your %@ Settings — Privacy — Contacts.\nThen select ON for Telegram."; +"Contacts.AccessDeniedHelpPortrait" = "Please go to your %@ Settings — Privacy — Contacts. Then select ON for Telegram."; +"Contacts.AccessDeniedHelpON" = "ON"; +"Contacts.InviteToTelegram" = "Invite to Telegram"; +"Contacts.InviteFriends" = "Invite Friends"; +"Contacts.SelectAll" = "Select All"; + +// Conversation +"Conversation.InputTextPlaceholder" = "Message"; +"Conversation.typing" = "typing"; +"Conversation.MessageDeliveryFailed" = "Your message was not sent. Tap \"Resend\" to send this message."; +"Conversation.MessageDialogEdit" = "Edit"; +"Conversation.MessageDialogRetry" = "Resend"; +"Conversation.MessageDialogRetryAll" = "Resend %1$d Messages"; +"Conversation.MessageDialogDelete" = "Delete"; +"Conversation.LinkDialogOpen" = "Open"; +"Conversation.LinkDialogCopy" = "Copy"; +"Conversation.ForwardTitle" = "Forward"; +"Conversation.ForwardChats" = "Chats"; +"Conversation.ForwardContacts" = "Contacts"; +"Conversation.StatusKickedFromGroup" = "you were removed from the group"; +"Conversation.StatusLeftGroup" = "you have left the group"; +"Conversation.StatusTyping" = "typing"; +"Conversation.Call" = "Call"; +"Conversation.Mute" = "Mute"; +"ChatList.Mute" = "Mute"; +"Conversation.TitleMute" = "Mute"; +"Conversation.Unmute" = "Unmute"; +"ChatList.Unmute" = "Unmute"; +"Conversation.TitleUnmute" = "Unmute"; +"Conversation.Edit" = "Edit"; +"Conversation.Info" = "Info"; +"Conversation.Search" = "Search"; +"Conversation.Unblock" = "Unblock"; +"Conversation.ClearAll" = "Delete All"; +"Conversation.Location" = "Location"; +"Conversation.Contact" = "Contact"; +"Conversation.BlockUser" = "Block User"; +"Conversation.UnblockUser" = "Unblock User"; +"Conversation.UnsupportedMedia" = "This message is not supported on your version of Telegram. Update the app to view:\nhttps://telegram.org/update"; +"Conversation.EncryptionWaiting" = "Waiting for %@ to get online..."; +"Conversation.EncryptionProcessing" = "Exchanging encryption keys..."; +"Conversation.EmptyPlaceholder" = "No messages here yet..."; +"Conversation.EncryptedPlaceholderTitleIncoming" = "%@ invited you to join a secret chat."; +"Conversation.EncryptedPlaceholderTitleOutgoing" = "You have invited %@ to join a secret chat."; +"Conversation.EncryptedDescriptionTitle" = "Secret chats:"; +"Conversation.EncryptedDescription1" = "Use end-to-end encryption"; +"Conversation.EncryptedDescription2" = "Leave no trace on our servers"; +"Conversation.EncryptedDescription3" = "Have a self-destruct timer"; +"Conversation.EncryptedDescription4" = "Do not allow forwarding"; +"Conversation.ContextMenuCopy" = "Copy"; +"Conversation.ContextMenuDelete" = "Delete"; +"Conversation.ContextMenuForward" = "Forward"; +"Conversation.ContextMenuMore" = "More..."; + +"Conversation.StatusMembers_0" = "%@ members"; +"Conversation.StatusMembers_1" = "1 member"; +"Conversation.StatusMembers_2" = "2 members"; +"Conversation.StatusMembers_3_10" = "%@ members"; +"Conversation.StatusMembers_many" = "%@ members"; +"Conversation.StatusMembers_any" = "%@ members"; + +"Conversation.StatusOnline_1" = "1 online"; +"Conversation.StatusOnline_2" = "2 online"; +"Conversation.StatusOnline_3_10" = "%@ online"; +"Conversation.StatusOnline_any" = "%@ online"; +"Conversation.StatusOnline_many" = "%@ online"; +"Conversation.StatusOnline_0" = "%@ online"; + +"Conversation.UnreadMessages" = "Unread Messages"; + +// Notification +"Notification.RenamedChat" = "%@ renamed group"; +"Notification.RenamedChannel" = "Channel renamed"; +"Notification.ChangedGroupPhoto" = "%@ changed group photo"; +"Notification.RemovedGroupPhoto" = "%@ removed group photo"; +"Notification.JoinedChat" = "%@ joined the group"; +"Notification.JoinedChannel" = "%@ joined the channel"; +"Notification.Invited" = "%@ invited %@"; +"Notification.InvitedMultiple" = "%@ invited %@"; +"Notification.LeftChat" = "%@ left the group"; +"Notification.LeftChannel" = "%@ left the channel"; +"Notification.Kicked" = "%@ removed %@"; +"Notification.CreatedChat" = "%@ created a group"; +"Notification.CreatedChannel" = "Channel created"; +"Notification.CreatedChatWithTitle" = "%@ created the group \"%@\" "; +"Notification.Joined" = "%@ joined Telegram"; +"Notification.ChangedGroupName" = "%@ changed group name to \"%@\" "; +"Notification.NewAuthDetected" = "%1$@,\nWe detected a login into your account from a new device on %2$@, %3$@ at %4$@\n\nDevice: %5$@\nLocation: %6$@\n\nIf this wasn't you, you can go to Settings — Privacy and Security — Sessions and terminate that session.\n\nIf you think that somebody logged in to your account against your will, you can enable two-step verification in Privacy and Security settings.\n\nSincerely,\nThe Telegram Team"; +"Notification.MessageLifetimeChanged" = "%1$@ set the self-destruct timer to %2$@"; +"Notification.MessageLifetimeChangedOutgoing" = "You set the self-destruct timer to %1$@"; +"Notification.MessageLifetimeRemoved" = "%1$@ disabled the self-destruct timer"; +"Notification.MessageLifetimeRemovedOutgoing" = "You disabled the self-destruct timer"; +"Notification.MessageLifetime2s" = "2 seconds"; +"Notification.MessageLifetime5s" = "5 seconds"; +"Notification.MessageLifetime1m" = "1 minute"; +"Notification.MessageLifetime1h" = "1 hour"; +"Notification.MessageLifetime1d" = "1 day"; +"Notification.MessageLifetime1w" = "1 week"; + +"Notification.Exceptions.AlwaysOn" = "Always On"; +"Notification.Exceptions.AlwaysOff" = "Always Off"; +"Notification.Exceptions.MutedUntil" = "Muted until %@"; + +"Notification.Exceptions.AddException" = "Add an Exception"; +"Notification.Exceptions.NewException" = "New Exception"; +"Notification.Exceptions.NewException.NotificationHeader" = "NOTIFICATIONS"; +"Notification.Exceptions.Sound" = "Sound: %@"; + + + +// Message +"Message.Photo" = "Photo"; +"Message.Video" = "Video"; +"Message.Location" = "Location"; +"Message.Contact" = "Contact"; +"Message.File" = "File"; +"Message.Sticker" = "Sticker"; +"Message.StickerText" = "Sticker %@"; +"Message.Audio" = "Voice Message"; +"Message.ForwardedMessage" = "Forwarded Message\nFrom: %@"; +"Message.Animation" = "GIF"; +"Message.Game" = "Game"; + +// Conversation Profile +"ConversationProfile.ErrorCreatingConversation" = "An error occurred"; +"ConversationProfile.UnknownAddMemberError" = "An unexpected error has occurred. Our wizards have been notified and will fix the problem soon. Sorry."; +"ConversationProfile.UsersTooMuchError" = "Sorry, this group is full. You cannot add any more members here."; + +"ConversationProfile.LeaveDeleteAndExit" = "Delete and Exit"; +"Group.LeaveGroup" = "Leave Group"; + +"Conversation.Megabytes" = "%.1f MB"; +"Conversation.Kilobytes" = "%d KB"; +"Conversation.Bytes" = "%d B"; +"Conversation.ShareMyContactInfo" = "Share My Contact Info"; +"Conversation.AddContact" = "Add Contact"; +"Conversation.SendMessage" = "Send Message"; +"Conversation.EncryptionCanceled" = "Secret chat cancelled"; +"Conversation.DeleteManyMessages" = "Delete Messages"; +"Conversation.SlideToCancel" = "Slide to cancel"; +"Conversation.ApplyLocalization" = "Apply Localization"; +"Conversation.OpenFile" = "Open File"; + +// Media Picker +"MediaPicker.Send" = "Send"; +"SearchImages.Title" = "Albums"; +"MediaPicker.CameraRoll" = "Camera Roll"; +"SearchImages.NoImagesFound" = "No images found"; + +// User Profile +"Profile.CreateEncryptedChatError" = "An error occurred."; +"Profile.CreateEncryptedChatOutdatedError" = "Cannot create a secret chat with %@.\n%@ is using an older version of Telegram and needs to update first."; +"Profile.CreateNewContact" = "Create New Contact"; +"Profile.AddToExisting" = "Add to Existing Contact"; +"Profile.EncryptionKey" = "Encryption Key"; +"Profile.MessageLifetimeForever" = "Off"; +"Profile.MessageLifetime2s" = "2s"; +"Profile.MessageLifetime5s" = "5s"; +"Profile.MessageLifetime1m" = "1m"; +"Profile.MessageLifetime1h" = "1h"; +"Profile.MessageLifetime1d" = "1d"; +"Profile.MessageLifetime1w" = "1w"; +"Profile.ShareContactButton" = "Share Contact"; + +// User Info +"UserInfo.Title" = "Info"; +"UserInfo.FirstNamePlaceholder" = "First Name"; +"UserInfo.LastNamePlaceholder" = "Last Name"; +"UserInfo.GenericPhoneLabel" = "mobile"; +"UserInfo.SendMessage" = "Send Message"; +"UserInfo.AddContact" = "Add Contact"; +"UserInfo.ShareContact" = "Share Contact"; +"UserInfo.StartSecretChat" = "Start Secret Chat"; +"UserInfo.StartSecretChatConfirmation" = "Are you sure you want to start a secret chat with %@?"; +"UserInfo.StartSecretChatStart" = "Start"; +"UserInfo.DeleteContact" = "Delete Contact"; +"UserInfo.CreateNewContact" = "Create New Contact"; +"UserInfo.AddToExisting" = "Add to Existing"; +"UserInfo.AddPhone" = "add phone"; +"UserInfo.NotificationsEnabled" = "Enabled"; +"UserInfo.NotificationsDisabled" = "Disabled"; +"UserInfo.NotificationsEnable" = "Enable"; +"UserInfo.NotificationsDisable" = "Disable"; +"UserInfo.Invite" = "Invite to Telegram"; + +// New Contact +"NewContact.Title" = "New Contact"; + +// Phone Label +"PhoneLabel.Title" = "Label"; + +// Secret Chat +"SecretChat.Title" = "Secret Chat"; + +// Group Info +"GroupInfo.Title" = "Group Info"; +"GroupInfo.GroupNamePlaceholder" = "Group Name"; +"GroupInfo.BroadcastListNamePlaceholder" = "List Name"; +"GroupInfo.SetGroupPhoto" = "Set Group Photo"; +"GroupInfo.SetGroupPhotoStop" = "Stop"; +"GroupInfo.SetGroupPhotoDelete" = "Delete Photo"; +"GroupInfo.Notifications" = "Notifications"; +"GroupInfo.Sound" = "Sound"; +"GroupInfo.SetSound" = "Set Sound"; +"GroupInfo.SharedMedia" = "Shared Media"; +"GroupInfo.SharedMediaNone" = "None"; +"GroupInfo.DeleteAndExit" = "Delete and Exit"; +"GroupInfo.DeleteAndExitConfirmation" = "You will not be able to join this group again."; +"GroupInfo.ParticipantCount_1" = "1 MEMBER"; +"GroupInfo.ParticipantCount_2" = "2 MEMBERS"; +"GroupInfo.ParticipantCount_3_10" = "%@ MEMBERS"; +"GroupInfo.ParticipantCount_any" = "%@ MEMBERS"; +"GroupInfo.ParticipantCount_many" = "%@ MEMBERS"; +"GroupInfo.ParticipantCount_0" = "%@ MEMBERS"; +"GroupInfo.AddParticipant" = "Add Member"; +"GroupInfo.AddParticipantTitle" = "Contacts"; +"GroupInfo.AddParticipantConfirmation" = "Add %@ to the group?"; +"GroupInfo.LeftStatus" = "You have left the group"; + +// Encryption Key +"EncryptionKey.Title" = "Encryption Key"; +"EncryptionKey.Description" = "This image and text were derived from the encryption key for this secret chat with %1$@.\n\n If they look the same on %2$@'s device, end-to-end encryption is guaranteed.\n\nLearn more at telegram.org"; + +// Conversation media +"ConversationMedia.Title" = "Media"; + +// Preview +"Preview.DeletePhoto" = "Delete Photo"; +"Preview.SaveToCameraRoll" = "Save to Camera Roll"; + +// Map +"Map.ChooseLocationTitle" = "Location"; +"Map.Map" = "Map"; +"Map.Satellite" = "Satellite"; +"Map.Hybrid" = "Hybrid"; +"Map.GetDirections" = "Get Directions"; +"Map.OpenInGoogleMaps" = "Open in Google Maps"; + +// Web +"Web.Error" = "Couldn't load page"; +"Web.OpenExternal" = "Open in Safari"; + +// Document +"Document.TargetConfirmationFormat" = "Send file ({size}) to {target}?"; + +// Dialog List +"DialogList.You" = "You"; + +// Settings +"Settings.SetProfilePhoto" = "Set Profile Photo"; +"Settings.Logout" = "Log Out"; +"Settings.Title" = "Settings"; +"Settings.NotificationsAndSounds" = "Notifications and Sounds"; +"Settings.ChatSettings" = "Data and Storage"; +"Settings.BlockedUsers" = "Blocked Users"; +"Settings.ChatBackground" = "Chat Background"; +"Settings.Support" = "Ask a Question"; +"Settings.FAQ" = "Telegram FAQ"; +"Settings.FAQ_URL" = "https://telegram.org/faq#general"; +"Settings.FAQ_Intro" = "Please note that Telegram Support is done by volunteers. We try to respond as quickly as possible, but it may take a while.\n\nPlease take a look at the Telegram FAQ: it has important troubleshooting tips and answers to most questions."; +"Settings.FAQ_Button" = "FAQ"; +"Settings.SaveIncomingPhotos" = "Save Incoming Photos"; + +// Notifications and Sounds +"Notifications.Title" = "Notifications"; +"Notifications.MessageNotifications" = "MESSAGE NOTIFICATIONS"; +"Notifications.MessageNotificationsAlert" = "Alert"; +"Notifications.MessageNotificationsPreview" = "Message Preview"; +"Notifications.MessageNotificationsSound" = "Sound"; +"Notifications.MessageNotificationsHelp" = "You can set custom notifications for specific users on their Info page."; +"Notifications.MessageNotificationsExceptionsHelp" = "Set custom notifications for specific users."; + + + +"Notifications.GroupNotifications" = "GROUP NOTIFICATIONS"; +"Notifications.GroupNotificationsAlert" = "Alert"; +"Notifications.GroupNotificationsPreview" = "Message Preview"; +"Notifications.GroupNotificationsSound" = "Sound"; +"Notifications.GroupNotificationsHelp" = "You can set custom notifications for specific groups on the Group Info page."; +"Notifications.GroupNotificationsExceptionsHelp" = "Set custom notifications for specific groups."; + +"Notifications.ChannelNotifications" = "CHANNEL NOTIFICATIONS"; +"Notifications.ChannelNotificationsAlert" = "Alert"; +"Notifications.ChannelNotificationsPreview" = "Message Preview"; +"Notifications.ChannelNotificationsSound" = "Sound"; +"Notifications.ChannelNotificationsHelp" = "You can set custom notifications for specific channels on the Channel Info page."; +"Notifications.ChannelNotificationsExceptionsHelp" = "Set custom notifications for specific channels."; + +"Notifications.TextTone" = "Text Tone"; +"Notifications.AlertTones" = "ALERT TONES"; +"Notifications.ClassicTones" = "CLASSIC"; + +"Notifications.InAppNotifications" = "IN-APP NOTIFICATIONS"; +"Notifications.InAppNotificationsSounds" = "In-App Sounds"; +"Notifications.InAppNotificationsVibrate" = "In-App Vibrate"; +"Notifications.InAppNotificationsPreview" = "In-App Preview"; + +"Notifications.Reset" = "Reset"; +"Notifications.ResetAllNotifications" = "Reset All Notifications"; +"Notifications.ResetAllNotificationsHelp" = "Undo all custom notification settings for all your contacts and groups."; + +// Chat Settings +"ChatSettings.Title" = "Data and Storage"; +"ChatSettings.Appearance" = "APPEARANCE"; +"ChatSettings.TextSize" = "Text Size"; +"ChatSettings.TextSizeUnits" = "pt"; +"ChatSettings.AutomaticPhotoDownload" = "AUTOMATIC PHOTO DOWNLOAD"; +"ChatSettings.AutomaticAudioDownload" = "AUTOMATIC AUDIO DOWNLOAD"; +"ChatSettings.PrivateChats" = "Private Chats"; +"ChatSettings.Groups" = "Groups"; +"ChatSettings.Cache" = "Storage Usage"; + +// Usage +"Cache.Title" = "Storage Usage"; +"Cache.ClearCache" = "Clear Cache"; +"Cache.KeepMedia" = "Keep Media"; +"Cache.Help" = "Photos, videos and other files from cloud chats that you have **not accessed** during this period will be removed from this device to save disk space.\n\nAll media will stay in the Telegram cloud and can be re-downloaded if you need it again."; + +// Blocked Users +"BlockedUsers.Title" = "Blocked"; +"BlockedUsers.SelectUserTitle" = "Block User"; +"BlockedUsers.BlockUser" = "Block User..."; +"BlockedUsers.BlockTitle" = "Block"; +"BlockedUsers.LeavePrefix" = "Leave "; +"BlockedUsers.Info" = "Blocked users can't send you messages or add you to groups. They will not see your profile pictures, online and last seen status."; +"BlockedUsers.AddNew" = "Add New..."; +"BlockedUsers.Unblock" = "Unblock"; + +// Wallpaper +"Wallpaper.Title" = "Chat Background"; +"Wallpaper.PhotoLibrary" = "Photo Library"; +"Wallpaper.Set" = "Set"; +"Wallpaper.Wallpaper" = "Wallpaper"; + +"Notification.SecretChatMessageScreenshot" = "%@ took a screenshot!"; +"Notification.SecretChatScreenshot" = "Screenshot taken!"; + +"BroadcastListInfo.AddRecipient" = "Add Recipient"; + +"Settings.LogoutConfirmationTitle" = "Log out?"; +"Settings.LogoutConfirmationText" = "\nNote that you can seamlessly use Telegram on all your devices at once.\n\nRemember, logging out kills all your Secret Chats."; + +"Login.PadPhoneHelp" = "\nYou can use your main mobile number to log in to Telegram on all devices.\nDon't use your iPad's SIM number here — we'll need to send you an SMS.\n\nIs this number correct?\n{number}"; +"Login.PadPhoneHelpTitle" = "Your Number"; + +"MessageTimer.Custom" = "Custom"; + +"MessageTimer.Forever" = "Forever"; + +"MessageTimer.Seconds_1" = "%@ second"; +"MessageTimer.Seconds_2" = "%@ seconds"; +"MessageTimer.Seconds_3_10" = "%@ seconds"; +"MessageTimer.Seconds_any" = "%@ seconds"; +"MessageTimer.Seconds_many" = "%@ seconds"; +"MessageTimer.Seconds_0" = "%@ seconds"; +"MessageTimer.Minutes_1" = "%@ minute"; +"MessageTimer.Minutes_2" = "%@ minutes"; +"MessageTimer.Minutes_3_10" = "%@ minutes"; +"MessageTimer.Minutes_any" = "%@ minutes"; +"MessageTimer.Minutes_many" = "%@ minutes"; +"MessageTimer.Minutes_0" = "%@ minutes"; +"MessageTimer.Hours_1" = "%@ hour"; +"MessageTimer.Hours_2" = "%@ hours"; +"MessageTimer.Hours_3_10" = "%@ hours"; +"MessageTimer.Hours_any" = "%@ hours"; +"MessageTimer.Hours_many" = "%@ hours"; +"MessageTimer.Hours_0" = "%@ hours"; +"MessageTimer.Days_1" = "%@ day"; +"MessageTimer.Days_2" = "%@ days"; +"MessageTimer.Days_3_10" = "%@ days"; +"MessageTimer.Days_any" = "%@ days"; +"MessageTimer.Days_many" = "%@ days"; +"MessageTimer.Days_0" = "%@ days"; +"MessageTimer.Weeks_1" = "%@ week"; +"MessageTimer.Weeks_2" = "%@ weeks"; +"MessageTimer.Weeks_3_10" = "%@ weeks"; +"MessageTimer.Weeks_any" = "%@ weeks"; +"MessageTimer.Weeks_many" = "%@ weeks"; +"MessageTimer.Weeks_0" = "%@ weeks"; +"MessageTimer.Months_1" = "%@ month"; +"MessageTimer.Months_2" = "%@ months"; +"MessageTimer.Months_3_10" = "%@ months"; +"MessageTimer.Months_any" = "%@ months"; +"MessageTimer.Months_many" = "%@ months"; +"MessageTimer.Months_0" = "%@ months"; +"MessageTimer.Years_1" = "%@ year"; +"MessageTimer.Years_2" = "%@ years"; +"MessageTimer.Years_3_10" = "%@ years"; +"MessageTimer.Years_any" = "%@ years"; +"MessageTimer.Months_many" = "%@ years"; + +"MessageTimer.ShortSeconds_1" = "%@s"; +"MessageTimer.ShortSeconds_2" = "%@s"; +"MessageTimer.ShortSeconds_3_10" = "%@s"; +"MessageTimer.ShortSeconds_any" = "%@s"; +"MessageTimer.ShortSeconds_many" = "%@s"; +"MessageTimer.ShortSeconds_0" = "%@s"; +"MessageTimer.ShortMinutes_1" = "%@m"; +"MessageTimer.ShortMinutes_2" = "%@m"; +"MessageTimer.ShortMinutes_3_10" = "%@m"; +"MessageTimer.ShortMinutes_any" = "%@m"; +"MessageTimer.ShortMinutes_many" = "%@m"; +"MessageTimer.ShortMinutes_0" = "%@m"; +"MessageTimer.ShortHours_1" = "%@h"; +"MessageTimer.ShortHours_2" = "%@h"; +"MessageTimer.ShortHours_3_10" = "%@h"; +"MessageTimer.ShortHours_any" = "%@h"; +"MessageTimer.ShortHours_many" = "%@h"; +"MessageTimer.ShortHours_0" = "%@h"; +"MessageTimer.ShortDays_1" = "%@d"; +"MessageTimer.ShortDays_2" = "%@d"; +"MessageTimer.ShortDays_3_10" = "%@d"; +"MessageTimer.ShortDays_any" = "%@d"; +"MessageTimer.ShortDays_many" = "%@d"; +"MessageTimer.ShortDays_0" = "%@d"; +"MessageTimer.ShortWeeks_1" = "%@w"; +"MessageTimer.ShortWeeks_2" = "%@w"; +"MessageTimer.ShortWeeks_3_10" = "%@w"; +"MessageTimer.ShortWeeks_any" = "%@w"; +"MessageTimer.ShortWeeks_many" = "%@w"; +"MessageTimer.ShortWeeks_0" = "%@w"; + +"Activity.UploadingPhoto" = "sending photo"; +"Activity.UploadingVideo" = "sending video"; +"Activity.UploadingDocument" = "sending file"; +"Activity.RecordingAudio" = "recording audio"; +"Activity.RecordingVideoMessage" = "recording video"; + +"Compatibility.SecretMediaVersionTooLow" = "%@ is using an older version of Telegram, so secret photos will be shown in compatibility mode.\n\nOnce %@ updates Telegram, photos with timers for 1 minute or less will start working in 'Tap and hold to view' mode, and you will be notified whenever the other party takes a screenshot."; + +"Contacts.GlobalSearch" = "Global Search"; +"Profile.Username" = "username"; +"Settings.Username" = "Username"; +"Settings.UsernameEmpty" = "Add"; + +"Username.Title" = "Username"; +"Username.Placeholder" = "Your Username"; +"Username.Help" = "You can choose a username on **Telegram**. If you do, other people will be able to find you by this username and contact you without knowing your phone number.\n\nYou can use **a-z**, **0-9** and underscores. Minimum length is **5** characters."; +"Username.InvalidTooShort" = "A username must have at least 5 characters."; +"Username.InvalidStartsWithNumber" = "Sorry, a username can't start with a number."; +"Username.InvalidCharacters" = "Sorry, this username is invalid."; +"Username.InvalidTaken" = "Sorry, this username is already taken."; + +"Username.CheckingUsername" = "Checking username..."; +"Username.UsernameIsAvailable" = "%@ is available."; + +"WebSearch.Images" = "Images"; +"WebSearch.GIFs" = "GIFs"; +"WebSearch.RecentSectionTitle" = "Recent"; +"WebSearch.RecentSectionClear" = "Clear"; + +"Settings.PrivacySettings" = "Privacy and Security"; + +"UserCount_1" = "1 user"; +"UserCount_2" = "2 users"; +"UserCount_3_10" = "%@ users"; +"UserCount_any" = "%@ users"; +"UserCount_many" = "%@ users"; +"UserCount_0" = "%@ users"; + +"PrivacySettings.Title" = "Privacy and Security"; + +"PrivacySettings.PrivacyTitle" = "PRIVACY"; +"PrivacySettings.LastSeen" = "Last Seen"; +"PrivacySettings.LastSeenTitle" = "Last Seen"; +"PrivacySettings.LastSeenEverybody" = "Everybody"; +"PrivacySettings.LastSeenContacts" = "My Contacts"; +"PrivacySettings.LastSeenNobody" = "Nobody"; + +"PrivacySettings.LastSeenEverybodyMinus" = "Everybody (-%@)"; +"PrivacySettings.LastSeenContactsPlus" = "My Contacts (+%@)"; +"PrivacySettings.LastSeenContactsMinus" = "My Contacts (-%@)"; +"PrivacySettings.LastSeenContactsMinusPlus" = "My Contacts (-%@, +%@)"; +"PrivacySettings.LastSeenNobodyPlus" = "Nobody (+%@)"; + +"PrivacySettings.SecurityTitle" = "SECURITY"; + +"PrivacySettings.DeleteAccountTitle" = "DELETE MY ACCOUNT"; +"PrivacySettings.DeleteAccountIfAwayFor" = "If Away For"; +"PrivacySettings.DeleteAccountHelp" = "If you do not log in at least once within this period, your account will be deleted along with all groups, messages and contacts."; + +"PrivacyLastSeenSettings.Title" = "Last Seen"; +"PrivacyLastSeenSettings.CustomHelp" = "Important: you won't be able to see Last Seen times for people with whom you don't share your Last Seen time. Approximate last seen will be shown instead (recently, within a week, within a month)."; +"PrivacyLastSeenSettings.AlwaysShareWith" = "Always Share With"; +"PrivacyLastSeenSettings.NeverShareWith" = "Never Share With"; +"PrivacyLastSeenSettings.CustomShareSettingsHelp" = "These settings will override the values above."; + +"PrivacyLastSeenSettings.CustomShareSettings.Delete" = "Delete"; +"PrivacyLastSeenSettings.AlwaysShareWith.Title" = "Always Share"; +"PrivacyLastSeenSettings.AlwaysShareWith.Placeholder" = "Always share with users..."; +"PrivacyLastSeenSettings.NeverShareWith.Title" = "Never Share"; +"PrivacyLastSeenSettings.NeverShareWith.Placeholder" = "Never share with users..."; +"PrivacyLastSeenSettings.EmpryUsersPlaceholder" = "Add Users"; +"PrivacyLastSeenSettings.AddUsers_1" = "Add 1 user to this list?"; +"PrivacyLastSeenSettings.AddUsers_2" = "Add 2 users to this list?"; +"PrivacyLastSeenSettings.AddUsers_3_10" = "Add %@ users to this list?"; +"PrivacyLastSeenSettings.AddUsers_any" = "Add %@ users to this list?"; +"PrivacyLastSeenSettings.AddUsers_many" = "Add %@ users to this list?"; +"PrivacyLastSeenSettings.AddUsers_0" = "Add %@ users to this list?"; + +// Photo Editor +"PhotoEditor.DiscardChanges" = "Discard Changes"; + +"PhotoEditor.Original" = "Original"; + +"PhotoEditor.CropReset" = "RESET"; +"PhotoEditor.CropAuto" = "AUTO"; +"PhotoEditor.CropAspectRatioOriginal" = "Original"; +"PhotoEditor.CropAspectRatioSquare" = "Square"; + +"PhotoEditor.EnhanceTool" = "Enhance"; +"PhotoEditor.ExposureTool" = "Brightness"; +"PhotoEditor.ContrastTool" = "Contrast"; +"PhotoEditor.WarmthTool" = "Warmth"; +"PhotoEditor.SaturationTool" = "Saturation"; +"PhotoEditor.HighlightsTool" = "Highlights"; +"PhotoEditor.ShadowsTool" = "Shadows"; +"PhotoEditor.VignetteTool" = "Vignette"; +"PhotoEditor.GrainTool" = "Grain"; +"PhotoEditor.SharpenTool" = "Sharpen"; + +"PhotoEditor.BlurToolOff" = "Off"; +"PhotoEditor.BlurToolRadial" = "Radial"; +"PhotoEditor.BlurToolLinear" = "Linear"; + +"PhotoEditor.Set" = "Set"; +"PhotoEditor.Skip" = "Skip"; + +// Camera +"Camera.PhotoMode" = "PHOTO"; +"Camera.VideoMode" = "VIDEO"; +"Camera.SquareMode" = "SQUARE"; +"Camera.FlashOff" = "Off"; +"Camera.FlashOn" = "On"; +"Camera.FlashAuto" = "Auto"; +"Camera.Retake" = "Retake"; + +"Settings.PhoneNumber" = "Change Number"; + +"PhoneNumberHelp.Help" = "You can change your Telegram number here. Your account and all your cloud data — messages, media, contacts, etc. will be moved to the new number.\n\n**Important:** all your Telegram contacts will get your **new number** added to their address book, provided they had your old number and you haven't blocked them in Telegram."; +"PhoneNumberHelp.Alert" = "All your Telegram contacts will get your new number added to their address book, provided they had your old number and you haven't blocked them in Telegram."; +"PhoneNumberHelp.ChangeNumber" = "Change Number"; + +"ChangePhoneNumberNumber.Title" = "Change Number"; +"ChangePhoneNumberNumber.NewNumber" = "NEW NUMBER"; +"ChangePhoneNumberNumber.Help" = "We will send an SMS with a confirmation code to your new number."; +"ChangePhoneNumberNumber.NumberPlaceholder" = "Enter your new number"; + +"ChangePhoneNumberCode.Code" = "YOUR CODE"; +"ChangePhoneNumberCode.CodePlaceholder" = "Code"; +"ChangePhoneNumberCode.Help" = "We have sent you an SMS with the code"; +"ChangePhoneNumberCode.CallTimer" = "Telegram will call you in %@"; +"ChangePhoneNumberCode.RequestingACall" = "Requesting a call from Telegram..."; +"ChangePhoneNumberCode.Called" = "Telegram dialed your number"; + +"LoginPassword.Title" = "Your Password"; +"LoginPassword.PasswordPlaceholder" = "Password"; +"LoginPassword.InvalidPasswordError" = "Invalid password. Please try again."; +"LoginPassword.FloodError" = "Limit exceeded. Please try again later."; +"LoginPassword.ForgotPassword" = "Forgot password?"; +"LoginPassword.PasswordHelp" = "Two-Step verification enabled. Your account is protected with an additional password."; +"LoginPassword.ResetAccount" = "Reset Account"; + +"QuickSend.Photos_1" = "Send 1 Photo"; +"QuickSend.Photos_2" = "Send 2 Photos"; +"QuickSend.Photos_3_10" = "Send %@ Photos"; +"QuickSend.Photos_any" = "Send %@ Photos"; +"QuickSend.Photos_many" = "Send %@ Photos"; +"QuickSend.Photos_0" = "Send %@ Photos"; + +"Share.Title" = "Share"; +"Forward.ConfirmMultipleFiles_1" = "Send 1 file to {target}?"; +"Forward.ConfirmMultipleFiles_2" = "Send 2 files to {target}?"; +"Forward.ConfirmMultipleFiles_3_10" = "Send %@ files to {target}?"; +"Forward.ConfirmMultipleFiles_any" = "Send %@ files to {target}?"; +"Forward.ConfirmMultipleFiles_many" = "Send %@ files to {target}?"; +"Forward.ConfirmMultipleFiles_0" = "Send %@ files to {target}?"; + +"Notification.Reply" = "Reply"; +"Notification.Mute1h" = "Mute for 1 hour"; +"Notification.Mute1hMin" = "Mute for 1h"; +"Conversation.ContextMenuShare" = "Share"; +"Conversation.ContextMenuLookUp" = "Look Up"; + +"SharedMedia.TitleAll" = "Shared Media"; + +"SharedMedia.Photo_1" = "1 photo"; +"SharedMedia.Photo_2" = "2 photos"; +"SharedMedia.Photo_3_10" = "%@ photos"; +"SharedMedia.Photo_any" = "%@ photos"; +"SharedMedia.Photo_many" = "%@ photos"; +"SharedMedia.Photo_0" = "%@ photos"; + +"SharedMedia.Video_1" = "1 video"; +"SharedMedia.Video_2" = "2 videos"; +"SharedMedia.Video_3_10" = "%@ videos"; +"SharedMedia.Video_any" = "%@ videos"; +"SharedMedia.Video_many" = "%@ videos"; +"SharedMedia.Video_0" = "%@ videos"; + +"SharedMedia.File_1" = "1 file"; +"SharedMedia.File_2" = "2 files"; +"SharedMedia.File_3_10" = "%@ files"; +"SharedMedia.File_any" = "%@ files"; +"SharedMedia.File_many" = "%@ files"; +"SharedMedia.File_0" = "%@ files"; + +"SharedMedia.Generic_1" = "1 media file"; +"SharedMedia.Generic_2" = "2 media files"; +"SharedMedia.Generic_3_10" = "%@ media files"; +"SharedMedia.Generic_any" = "%@ media files"; +"SharedMedia.Generic_many" = "%@ media files"; +"SharedMedia.Generic_0" = "%@ media files"; + +"FileSize.B" = "%@ B"; +"FileSize.KB" = "%@ KB"; +"FileSize.MB" = "%@ MB"; +"FileSize.GB" = "%@ GB"; + +"DownloadingStatus" = "Downloading %@ of %@"; + +"Time.MonthOfYear_m1" = "January %@"; +"Time.MonthOfYear_m2" = "February %@"; +"Time.MonthOfYear_m3" = "March %@"; +"Time.MonthOfYear_m4" = "April %@"; +"Time.MonthOfYear_m5" = "May %@"; +"Time.MonthOfYear_m6" = "June %@"; +"Time.MonthOfYear_m7" = "July %@"; +"Time.MonthOfYear_m8" = "August %@"; +"Time.MonthOfYear_m9" = "September %@"; +"Time.MonthOfYear_m10" = "October %@"; +"Time.MonthOfYear_m11" = "November %@"; +"Time.MonthOfYear_m12" = "December %@"; + +"Time.PreciseDate_m1" = "Jan %1$@, %2$@ at %3$@"; +"Time.PreciseDate_m2" = "Feb %1$@, %2$@ at %3$@"; +"Time.PreciseDate_m3" = "Mar %1$@, %2$@ at %3$@"; +"Time.PreciseDate_m4" = "Apr %1$@, %2$@ at %3$@"; +"Time.PreciseDate_m5" = "May %1$@, %2$@ at %3$@"; +"Time.PreciseDate_m6" = "Jun %1$@, %2$@ at %3$@"; +"Time.PreciseDate_m7" = "Jul %1$@, %2$@ at %3$@"; +"Time.PreciseDate_m8" = "Aug %1$@, %2$@ at %3$@"; +"Time.PreciseDate_m9" = "Sep %1$@, %2$@ at %3$@"; +"Time.PreciseDate_m10" = "Oct %1$@, %2$@ at %3$@"; +"Time.PreciseDate_m11" = "Nov %1$@, %2$@ at %3$@"; +"Time.PreciseDate_m12" = "Dec %1$@, %2$@ at %3$@"; + +"MuteFor.Hours_1" = "Mute for 1 hour"; +"MuteFor.Hours_2" = "Mute for 2 hours"; +"MuteFor.Hours_3_10" = "Mute for %@ hours"; +"MuteFor.Hours_any" = "Mute for %@ hours"; +"MuteFor.Hours_many" = "Mute for %@ hours"; +"MuteFor.Hours_0" = "Mute for %@ hours"; + +"MuteFor.Days_1" = "Mute for 1 day"; +"MuteFor.Days_2" = "Mute for 2 days"; +"MuteFor.Days_3_10" = "Mute for %@ days"; +"MuteFor.Days_any" = "Mute for %@ days"; +"MuteFor.Days_many" = "Mute for %@ days"; +"MuteFor.Days_0" = "Mute for %@ days"; + +"MuteExpires.Minutes_1" = "in 1 minute"; +"MuteExpires.Minutes_2" = "in 2 minutes"; +"MuteExpires.Minutes_3_10" = "in %@ minutes"; +"MuteExpires.Minutes_any" = "in %@ minutes"; +"MuteExpires.Minutes_many" = "in %@ minutes"; +"MuteExpires.Minutes_0" = "in %@ minutes"; + +"MuteExpires.Hours_1" = "in 1 hour"; +"MuteExpires.Hours_2" = "in 2 hours"; +"MuteExpires.Hours_3_10" = "in %@ hours"; +"MuteExpires.Hours_any" = "in %@ hours"; +"MuteExpires.Hours_many" = "in %@ hours"; +"MuteExpires.Hours_0" = "in %@ hours"; + +"MuteExpires.Days_1" = "in 1 day"; +"MuteExpires.Days_2" = "in 2 days"; +"MuteExpires.Days_3_10" = "in %@ days"; +"MuteExpires.Days_any" = "in %@ days"; +"MuteExpires.Days_many" = "in %@ days"; +"MuteExpires.Days_0" = "in %@ days"; + +"SharedMedia.EmptyTitle" = "No media files yet"; +"SharedMedia.EmptyText" = "Share photos and videos in this chat\n — or this paperclip stays unhappy."; +"SharedMedia.EmptyFilesText" = "You can send and receive\nfiles of any type up to 1.5 GB each\nand access them anywhere."; + +"ShareFileTip.Title" = "Sharing Files"; +"ShareFileTip.Text" = "You can share **uncompressed** media files from your Camera Roll here.\n\nTo share files of any other type, open them on your %@ (e.g. in your browser), tap **Open in...** or the action button and choose Telegram."; +"ShareFileTip.CloseTip" = "Close Tip"; + +"DialogList.SearchSectionDialogs" = "Chats and Contacts"; +"DialogList.SearchSectionGlobal" = "Global Search"; +"DialogList.SearchSectionMessages" = "Messages"; + +"Username.LinkHint" = "This link opens a chat with you in Telegram:[\nhttps://t.me/%@]"; +"Username.LinkCopied" = "Copied link to clipboard"; + +"SharedMedia.DeleteItemsConfirmation_1" = "Delete media file?"; +"SharedMedia.DeleteItemsConfirmation_2" = "Delete 2 media files?"; +"SharedMedia.DeleteItemsConfirmation_3_10" = "Delete %@ media files?"; +"SharedMedia.DeleteItemsConfirmation_any" = "Delete %@ media files?"; +"SharedMedia.DeleteItemsConfirmation_many" = "Delete %@ media files?"; +"SharedMedia.DeleteItemsConfirmation_0" = "Delete %@ media files?"; + +"PrivacySettings.Passcode" = "Passcode Lock"; +"PasscodeSettings.Title" = "Passcode Lock"; +"PasscodeSettings.TurnPasscodeOn" = "Turn Passcode On"; +"PasscodeSettings.TurnPasscodeOff" = "Turn Passcode Off"; +"PasscodeSettings.ChangePasscode" = "Change Passcode"; +"PasscodeSettings.Help" = "When you set up an additional passcode, a lock icon will appear on the chats page. Tap it to lock and unlock the app.\n\nNote: if you forget the passcode, you'll need to delete and reinstall the app. All secret chats will be lost."; +"PasscodeSettings.UnlockWithTouchId" = "Unlock with Touch ID"; +"PasscodeSettings.SimplePasscode" = "Simple Passcode"; +"PasscodeSettings.SimplePasscodeHelp" = "A simple passcode is a 4 digit number."; +"PasscodeSettings.EncryptData" = "Encrypt Local Database"; +"PasscodeSettings.EncryptDataHelp" = "Experimental feature, use with caution. Encrypt your local Telegram data, using a derivative of your passcode as the key."; + +"EnterPasscode.EnterTitle" = "Enter your Telegram Passcode"; +"EnterPasscode.ChangeTitle" = "Change Passcode"; +"EnterPasscode.EnterPasscode" = "Enter your Telegram Passcode"; +"EnterPasscode.EnterNewPasscodeNew" = "Enter a passcode"; +"EnterPasscode.EnterNewPasscodeChange" = "Enter your new passcode"; +"EnterPasscode.RepeatNewPasscode" = "Re-enter your new passcode"; +"EnterPasscode.EnterCurrentPasscode" = "Enter your current passcode"; +"EnterPasscode.TouchId" = "Unlock Telegram"; + +"DialogList.PasscodeLockHelp" = "Tap to lock Telegram"; + +"PasscodeSettings.AutoLock" = "Auto-Lock"; +"PasscodeSettings.AutoLock.Disabled" = "Disabled"; +"PasscodeSettings.AutoLock.IfAwayFor_1minute" = "If away for 1 min"; +"PasscodeSettings.AutoLock.IfAwayFor_5minutes" = "If away for 5 min"; +"PasscodeSettings.AutoLock.IfAwayFor_1hour" = "If away for 1 hour"; +"PasscodeSettings.AutoLock.IfAwayFor_5hours" = "If away for 5 hours"; + +"PasscodeSettings.FailedAttempts_1" = "1 Failed Passcode Attempt"; +"PasscodeSettings.FailedAttempts_2" = "2 Failed Passcode Attempts"; +"PasscodeSettings.FailedAttempts_3_10" = "%@ Failed Passcode Attempts"; +"PasscodeSettings.FailedAttempts_any" = "%@ Failed Passcode Attempt"; +"PasscodeSettings.FailedAttempts_many" = "%@ Failed Passcode Attempts"; +"PasscodeSettings.FailedAttempts_0" = "%@ Failed Passcode Attempts"; +"PasscodeSettings.TryAgainIn1Minute" = "Try again in 1 minute"; + +"AccessDenied.Title" = "Please Allow Access"; + +"AccessDenied.Contacts" = "Telegram messaging is based on your existing contact list.\n\nPlease go to Settings > Privacy > Contacts and set Telegram to ON."; + +"AccessDenied.VoiceMicrophone" = "Telegram needs access to your microphone to send voice messages.\n\nPlease go to Settings > Privacy > Microphone and set Telegram to ON."; + +"AccessDenied.VideoMicrophone" = "Telegram needs access to your microphone to record sound in videos recording.\n\nPlease go to Settings > Privacy > Microphone and set Telegram to ON."; + +"AccessDenied.MicrophoneRestricted" = "Microphone access is restricted for Telegram.\n\nPlease go to Settings > General > Restrictions > Microphone and set Telegram to ON."; + + +"AccessDenied.Camera" = "Telegram needs access to your camera to take photos and videos.\n\nPlease go to Settings > Privacy > Camera and set Telegram to ON."; + +"AccessDenied.CameraRestricted" = "Camera access is restricted for Telegram.\n\nPlease go to Settings > General > Restrictions > Camera and set Telegram to ON."; + +"AccessDenied.CameraDisabled" = "Camera access is globally restricted on your phone.\n\nPlease go to Settings > General > Restrictions and set Camera to ON"; + +"AccessDenied.PhotosAndVideos" = "Telegram needs access to your photo library to send photos and videos.\n\nPlease go to Settings > Privacy > Photos and set Telegram to ON."; + +"AccessDenied.SaveMedia" = "Telegram needs access to your photo library to save photos and videos.\n\nPlease go to Settings > Privacy > Photos and set Telegram to ON."; + +"AccessDenied.PhotosRestricted" = "Photo access is restricted for Telegram.\n\nPlease go to Settings > General > Restrictions > Photos and set Telegram to ON."; + +"AccessDenied.LocationDenied" = "Telegram needs access to your location so that you can share it with your contacts.\n\nPlease go to Settings > Privacy > Location Services and set Telegram to ON."; + +"AccessDenied.LocationDisabled" = "Telegram needs access to your location so that you can share it with your contacts.\n\nPlease go to Settings > Privacy > Location Services and set it to ON."; + +"AccessDenied.LocationTracking" = "Telegram needs access to your location to show you on the map.\n\nPlease go to Settings > Privacy > Location Services and set it to ON."; + +"AccessDenied.Settings" = "Settings"; + +"WebSearch.RecentClearConfirmation" = "Are you sure you want to clear recent images?"; + +"FeatureDisabled.Oops" = "Oops"; + +"Conversation.ContextMenuReply" = "Reply"; + +"ForwardedMessages_1" = "Forwarded message"; +"ForwardedMessages_2" = "2 forwarded messages"; +"ForwardedMessages_3_10" = "%@ forwarded messages"; +"ForwardedMessages_any" = "%@ forwarded messages"; +"ForwardedMessages_many" = "%@ forwarded messages"; +"ForwardedMessages_0" = "%@ forwarded messages"; + +"ForwardedFiles_1" = "Forwarded file"; +"ForwardedFiles_2" = "2 forwarded files"; +"ForwardedFiles_3_10" = "%@ forwarded files"; +"ForwardedFiles_any" = "%@ forwarded files"; +"ForwardedFiles_many" = "%@ forwarded files"; +"ForwardedFiles_0" = "%@ forwarded files"; + +"ForwardedStickers_1" = "Forwarded sticker"; +"ForwardedStickers_2" = "2 forwarded stickers"; +"ForwardedStickers_3_10" = "%@ forwarded stickers"; +"ForwardedStickers_any" = "%@ forwarded stickers"; +"ForwardedStickers_many" = "%@ forwarded stickers"; +"ForwardedStickers_0" = "%@ forwarded stickers"; + +"ForwardedPhotos_1" = "Forwarded photo"; +"ForwardedPhotos_2" = "2 forwarded photos"; +"ForwardedPhotos_3_10" = "%@ forwarded photos"; +"ForwardedPhotos_any" = "%@ forwarded photos"; +"ForwardedPhotos_many" = "%@ forwarded photos"; +"ForwardedPhotos_0" = "%@ forwarded photos"; + +"ForwardedVideos_1" = "Forwarded video"; +"ForwardedVideos_2" = "2 forwarded videos"; +"ForwardedVideos_3_10" = "%@ forwarded videos"; +"ForwardedVideos_any" = "%@ forwarded videos"; +"ForwardedVideos_many" = "%@ forwarded videos"; +"ForwardedVideos_0" = "%@ forwarded videos"; + +"ForwardedAudios_1" = "Forwarded audio"; +"ForwardedAudios_2" = "2 forwarded audios"; +"ForwardedAudios_3_10" = "%@ forwarded audios"; +"ForwardedAudios_any" = "%@ forwarded audios"; +"ForwardedAudios_many" = "%@ forwarded audios"; +"ForwardedAudios_0" = "%@ forwarded audios"; + +"ForwardedLocations_1" = "Forwarded location"; +"ForwardedLocations_2" = "2 forwarded locations"; +"ForwardedLocations_3_10" = "%@ forwarded locations"; +"ForwardedLocations_any" = "%@ forwarded locations"; +"ForwardedLocations_many" = "%@ forwarded locations"; +"ForwardedLocations_0" = "%@ forwarded locations"; + +"ForwardedGifs_1" = "Forwarded GIF"; +"ForwardedGifs_2" = "2 forwarded GIFs"; +"ForwardedGifs_3_10" = "%@ forwarded GIFs"; +"ForwardedGifs_any" = "%@ forwarded GIFs"; +"ForwardedGifs_many" = "%@ forwarded GIFs"; +"ForwardedGifs_0" = "%@ forwarded GIFs"; + +"ForwardedContacts_1" = "Forwarded contact"; +"ForwardedContacts_2" = "2 forwarded contacts"; +"ForwardedContacts_3_10" = "%@ forwarded contacts"; +"ForwardedContacts_any" = "%@ forwarded contacts"; +"ForwardedContacts_many" = "%@ forwarded contacts"; +"ForwardedContacts_0" = "%@ forwarded contacts"; + +"ForwardedAuthors2" = "%@, %@"; +"ForwardedAuthorsOthers_1" = "%@ and 1 other"; +"ForwardedAuthorsOthers_2" = "%@ and 2 others"; +"ForwardedAuthorsOthers_3_10" = "%@ and %@ others"; +"ForwardedAuthorsOthers_any" = "%@ and %@ others"; +"ForwardedAuthorsOthers_many" = "%@ and %@ others"; +"ForwardedAuthorsOthers_0" = "%@ and %@ others"; + +"PrivacySettings.TwoStepAuth" = "Two-Step Verification"; +"TwoStepAuth.Title" = "Two-Step Verification"; +"TwoStepAuth.SetPassword" = "Set Additional Password"; +"TwoStepAuth.SetPasswordHelp" = "You can set a password that will be required when you log in on a new device in addition to the code you get in the SMS."; +"TwoStepAuth.SetupPasswordTitle" = "Your Password"; + +"TwoStepAuth.SetupHintTitle" = "Password Hint"; +"TwoStepAuth.SetupHint" = "Please create a hint for your password:"; + +"TwoStepAuth.ChangePassword" = "Change Password"; +"TwoStepAuth.RemovePassword" = "Turn Password Off"; +"TwoStepAuth.SetupEmail" = "Set Recovery E-Mail"; +"TwoStepAuth.ChangeEmail" = "Change Recovery E-Mail"; +"TwoStepAuth.PendingEmailHelp" = "Your recovery e-mail %@ is not yet active and pending confirmation."; +"TwoStepAuth.GenericHelp" = "You have enabled Two-Step verification.\nYou'll need the password you set up here to log in to your Telegram account."; + +"TwoStepAuth.ConfirmationTitle" = "Two-Step Verification"; +"TwoStepAuth.ConfirmationText" = "Please check your e-mail and click on the validation link to complete Two-Step Verification setup. Be sure to check the spam folder as well."; +"TwoStepAuth.ConfirmationAbort" = "Abort Two-Step Verification Setup"; + +"TwoStepAuth.SetupPasswordEnterPasswordNew" = "Enter a password:"; +"TwoStepAuth.SetupPasswordEnterPasswordChange" = "Please enter your new password:"; +"TwoStepAuth.SetupPasswordConfirmPassword" = "Please re-enter your password:"; +"TwoStepAuth.SetupPasswordConfirmFailed" = "Passwords don't match. Please try again."; + +"TwoStepAuth.EnterPasswordTitle" = "Password"; +"TwoStepAuth.EnterPasswordPassword" = "Password"; +"TwoStepAuth.EnterPasswordHint" = "Hint: %@"; +"TwoStepAuth.EnterPasswordHelp" = "You have enabled Two-Step Verification, so your account is protected with an additional password."; +"TwoStepAuth.EnterPasswordInvalid" = "Invalid password. Please try again."; +"TwoStepAuth.EnterPasswordForgot" = "Forgot password?"; + +"TwoStepAuth.EmailTitle" = "Recovery E-Mail"; +"TwoStepAuth.EmailSkip" = "Skip"; +"TwoStepAuth.EmailSkipAlert" = "No, seriously.\n\nIf you forget your password, you will lose access to your Telegram account. There will be no way to restore it."; +"TwoStepAuth.Email" = "E-Mail"; +"TwoStepAuth.EmailPlaceholder" = "Your E-Mail"; +"TwoStepAuth.EmailHelp" = "Please add your valid e-mail. It is the only way to recover a forgotten password."; +"TwoStepAuth.EmailInvalid" = "Invalid e-mail address. Please try again."; +"TwoStepAuth.EmailSent" = "We have sent you an e-mail to confirm your address."; +"TwoStepAuth.PasswordSet" = "Your password for Two-Step Verification is now active."; +"TwoStepAuth.PasswordRemoveConfirmation" = "Are you sure you want to disable your password?"; +"TwoStepAuth.EmailCodeExpired" = "This confirmation code has expired. Please try again."; + +"TwoStepAuth.RecoveryUnavailable" = "Since you haven't provided a recovery e-mail when setting up your password, your remaining options are either to remember your password or to reset your account."; +"TwoStepAuth.RecoveryFailed" = "Your remaining options are either to remember your password or to reset your account."; +"TwoStepAuth.ResetAccountHelp" = "You will lose all your chats and messages, along with any media and files you've shared, if you proceed with resetting your account."; +"TwoStepAuth.ResetAccountConfirmation" = "You will lose all your chats and messages, along with any media and files you've shared, if you proceed with resetting your account."; + +"TwoStepAuth.RecoveryTitle" = "E-Mail Code"; +"TwoStepAuth.RecoveryCode" = "Code"; +"TwoStepAuth.RecoveryCodeHelp" = "Please check your e-mail and enter the 6-digit code we've sent there to deactivate your cloud password."; +"TwoStepAuth.RecoveryCodeInvalid" = "Invalid code. Please try again."; +"TwoStepAuth.RecoveryCodeExpired" = "We have sent you a new 6-digit code."; +"TwoStepAuth.RecoveryEmailUnavailable" = "Having trouble accessing your e-mail %@?"; + +"TwoStepAuth.FloodError" = "Limit exceeded. Please try again later."; + +"Conversation.FilePhotoOrVideo" = "Photo or Video"; +"Conversation.FileICloudDrive" = "iCloud Drive"; +"Conversation.FileDropbox" = "Dropbox"; + +"Conversation.FileOpenIn" = "Open in..."; +"Conversation.FileHowToText" = "To share files of any type, open them on your %@ (e.g. in your browser), tap **Open in...** or the action button and choose Telegram."; + +"Map.LocationTitle" = "Location"; +"Map.OpenInMaps" = "Open in Maps"; +"Map.OpenInHereMaps" = "Open in HERE Maps"; +"Map.OpenInYandexMaps" = "Open in Yandex Maps"; +"Map.OpenInYandexNavigator" = "Open in Yandex Navigator"; +"Map.OpenIn" = "Open In"; + +"Map.SendThisLocation" = "Send This Location"; +"Map.SendMyCurrentLocation" = "Send My Current Location"; +"Map.Locating" = "Locating..."; +"Map.ChooseAPlace" = "Or choose a place"; +"Map.AccurateTo" = "Accurate to %@"; +"Map.Search" = "Search places nearby"; +"Map.ShowPlaces" = "Show places"; +"Map.LoadError" = "An error occurred. Please try again."; +"Map.LocatingError" = "Failed to locate"; +"Map.Unknown" = "Unknown location"; + +"Map.DistanceAway" = "%@ away"; +"Map.ETAMinutes_0" = "%@ min"; +"Map.ETAMinutes_1" = "%@ min"; +"Map.ETAMinutes_2" = "%@ min"; +"Map.ETAMinutes_3_10" = "%@ min"; +"Map.ETAMinutes_any" = "%@ min"; +"Map.ETAMinutes_many" = "%@ min"; +"Map.ETAMinutes_0" = "%@ min"; +"Map.ETAHours_1" = "%@ h"; +"Map.ETAHours_2" = "%@ h"; +"Map.ETAHours_3_10" = "%@ h"; +"Map.ETAHours_any" = "%@ h"; +"Map.ETAHours_many" = "%@ h"; + +"ChangePhone.ErrorOccupied" = "The number %@ is already connected to a Telegram account. Please delete that account before migrating to the new number."; + +"AccessDenied.LocationTracking" = "Telegram needs access to your location to show you on the map.\n\nPlease go to Settings > Privacy > Location Services and set it to ON."; + +"PrivacySettings.AuthSessions" = "Active Sessions"; +"AuthSessions.Title" = "Active Sessions"; +"AuthSessions.CurrentSession" = "CURRENT SESSION"; +"AuthSessions.TerminateOtherSessions" = "Terminate all other sessions"; +"AuthSessions.TerminateOtherSessionsHelp" = "Logs out all devices except for this one."; +"AuthSessions.TerminateSession" = "Terminate session"; +"AuthSessions.OtherSessions" = "ACTIVE SESSIONS"; +"AuthSessions.EmptyTitle" = "No other sessions"; +"AuthSessions.EmptyText" = "You can log in to Telegram from other mobile, tablet and desktop devices, using the same phone number. All your data will be instantly synchronized."; +"AuthSessions.AppUnofficial" = "(ID: %@)"; + +"WebPreview.GettingLinkInfo" = "Getting Link Info..."; + +"Preview.OpenInInstagram" = "Open in Instagram"; + +"MediaPicker.AddCaption" = "Add a caption..."; + +"GroupInfo.InviteByLink" = "Invite to Group via Link"; + +"GroupInfo.InviteLink.Title" = "Invite Link"; +"GroupInfo.InviteLink.LinkSection" = "LINK"; +"GroupInfo.InviteLink.Help" = "Anyone who has Telegram installed will be able to join your group by following this link."; +"GroupInfo.InviteLink.CopyLink" = "Copy Link"; +"GroupInfo.InviteLink.RevokeLink" = "Revoke Link"; +"GroupInfo.InviteLink.ShareLink" = "Share Link"; +"GroupInfo.InviteLink.RevokeAlert.Text" = "Are you sure you want to revoke this link? Once you do, no one will be able to join the group using it."; +"GroupInfo.InviteLink.RevokeAlert.Revoke" = "Revoke"; +"GroupInfo.InviteLink.RevokeAlert.Success" = "The previous invite link is now inactive. A new invite link has just been generated."; +"GroupInfo.InviteLink.CopyAlert.Success" = "Link copied to clipboard."; + +"UserInfo.ShareMyContactInfo" = "Share My Contact Info"; + +"GroupInfo.InvitationLinkAcceptChannel" = "Do you want to join the channel \"%@\"?"; +"GroupInfo.InvitationLinkDoesNotExist" = "Sorry, this group does not seem to exist."; +"GroupInfo.InvitationLinkGroupFull" = "Sorry, this group is already full."; + +"Core.ServiceUserStatus" = "Service Notifications"; + +"Notification.JoinedGroupByLink" = "%@ joined the group via invite link"; + +"ChatSettings.Other" = "OTHER"; +"ChatSettings.Stickers" = "Stickers"; + +"StickerPacksSettings.Title" = "Stickers"; +"StickerPacksSettings.ShowStickersButton" = "Show Stickers Tab"; +"StickerPacksSettings.ShowStickersButtonHelp" = "A sticker icon will appear in the input field."; + +"StickerPacksSettings.StickerPacksSection" = "STICKER SETS"; +"StickerPacksSettings.ManagingHelp" = "Artists are welcome to add their own sticker sets using our @stickers bot.\n\nTap on a sticker to view and add the whole set."; + +"StickerPack.BuiltinPackName" = "Great Minds"; +"StickerPack.StickerCount_1" = "1 sticker"; +"StickerPack.StickerCount_2" = "2 stickers"; +"StickerPack.StickerCount_3_10" = "%@ stickers"; +"StickerPack.StickerCount_any" = "%@ stickers"; +"StickerPack.StickerCount_many" = "%@ stickers"; +"StickerPack.StickerCount_0" = "%@ stickers"; + +"StickerPack.AddStickerCount_1" = "Add 1 Sticker"; +"StickerPack.AddStickerCount_2" = "Add 2 Stickers"; +"StickerPack.AddStickerCount_3_10" = "Add %@ Stickers"; +"StickerPack.AddStickerCount_any" = "Add %@ Stickers"; +"StickerPack.AddStickerCount_many" = "Add %@ Stickers"; +"StickerPack.AddStickerCount_0" = "Add %@ Stickers"; + +"Conversation.ContextMenuStickerPackAdd" = "Add Stickers"; +"Conversation.ContextMenuStickerPackInfo" = "Info"; + +"MediaPicker.Nof" = "%@ of"; + +"UserInfo.ShareBot" = "Share"; +"UserInfo.InviteBotToGroup" = "Add To Group"; +"Profile.BotInfo" = "about"; + +"Target.SelectGroup" = "Choose Group"; +"Target.InviteToGroupConfirmation" = "Add the bot to \"%@\"?"; +"Target.InviteToGroupErrorAlreadyInvited" = "The bot is already a member of the group."; +"Bot.GenericBotStatus" = "bot"; +"Bot.GenericSupportStatus" = "support"; +"Bot.DescriptionTitle" = "What can this bot do?"; +"Bot.GroupStatusReadsHistory" = "has access to messages"; +"Bot.GroupStatusDoesNotReadHistory" = "has no access to messages"; +"Bot.Start" = "Start"; +"UserInfo.BotSettings" = "Settings"; +"UserInfo.BotHelp" = "Help"; + +"Contacts.SearchLabel" = "Search for contacts or usernames"; +"ChatSearch.SearchPlaceholder" = "Search"; + +"WatchRemote.NotificationText" = "Open this notification on your phone to view the message from your Apple Watch"; +"WatchRemote.AlertTitle" = "Message from your Apple Watch"; +"WatchRemote.AlertText" = "Open the message here?"; +"WatchRemote.AlertOpen" = "Open"; + +"Conversation.SearchPlaceholder" = "Search this chat"; +"Conversation.SearchNoResults" = "No Results"; + +"GroupInfo.AddUserLeftError" = "Sorry, if a person left a group, only a mutual contact can bring them back (they need to have your phone number, and you need theirs)."; + +"DialogList.SearchSectionRecent" = "Recent"; + +"DialogList.DeleteBotConfirmation" = "Delete"; +"DialogList.DeleteBotConversationConfirmation" = "Delete and Stop"; +"Bot.Stop" = "Stop Bot"; +"Bot.Unblock" = "Restart Bot"; + +"Login.PhoneNumberHelp" = "Help"; +"Login.EmailPhoneSubject" = "Invalid number %@"; +"Login.EmailPhoneBody" = "I'm trying to use my mobile phone number: %@\nBut Telegram says it's invalid. Please help.\nAdditional Info: %@, %@."; + +"SharedMedia.TitleLink" = "Shared Links"; +"SharedMedia.EmptyLinksText" = "All links shared in this chat will appear here."; + +"SharedMedia.Link_1" = "1 link"; +"SharedMedia.Link_2" = "2 links"; +"SharedMedia.Link_3_10" = "%@ links"; +"SharedMedia.Link_any" = "%@ links"; +"SharedMedia.Link_many" = "%@ links"; +"SharedMedia.Link_0" = "%@ links"; + +"Compose.NewChannel" = "New Channel"; +"GroupInfo.ChannelListNamePlaceholder" = "Channel Name"; + +"Channel.MessagePhotoUpdated" = "Channel photo updated"; +"Channel.MessagePhotoRemoved" = "Channel photo removed"; +"Channel.MessageTitleUpdated" = "Channel renamed to \"%@\" "; +"Channel.TitleInfo" = "Channel Info"; + +"Channel.UpdatePhotoItem" = "Set Channel Photo"; + +"Channel.LinkItem" = "share link"; +"Channel.Edit.AboutItem" = "Description"; +"Channel.Edit.LinkItem" = "Link"; + +"Channel.Username.Title" = "Link"; +"Channel.Username.Help" = "You can choose a channel name on **Telegram**. If you do, other people will be able to find your channel by this name.\n\nYou can use **a-z**, **0-9** and underscores. Minimum length is **5** characters."; +"Channel.Username.LinkHint" = "This link opens your channel in Telegram:[\nhttps://t.me/%@]"; +"Channel.Username.InvalidTooShort" = "Channel names must have at least 5 characters."; +"Channel.Username.InvalidStartsWithNumber" = "Channel names can't start with a number."; +"Channel.Username.InvalidCharacters" = "Sorry, this name is invalid."; +"Channel.Username.InvalidTaken" = "Sorry, this name is already taken."; +"Channel.Username.CheckingUsername" = "Checking name..."; +"Channel.Username.UsernameIsAvailable" = "%@ is available."; + +"Channel.LeaveChannel" = "Leave Channel"; + +"Channel.About.Title" = "Description"; + +"Channel.About.Placeholder" = "Description (Optional)"; +"Channel.About.Help" = "You can provide an optional description for your channel."; +"Group.About.Help" = "You can provide an optional description for your group."; + +"Channel.Status" = "channel"; +"Group.Status" = "group"; + +"Compose.NewChannel.Members" = "MEMBERS"; + +"ChannelInfo.ConfirmLeave" = "Leave Channel"; +"Channel.JoinChannel" = "Join"; +"Forward.ChannelReadOnly" = "Sorry, you can't post to this channel."; + +"Channel.ErrorAccessDenied" = "Sorry, this channel is private."; +"Group.ErrorAccessDenied" = "Sorry, this group is private."; +"Conversation.InputTextBroadcastPlaceholder" = "Broadcast"; + +"Channel.NotificationLoading" = "Loading..."; + +"Compose.ChannelTokenListPlaceholder" = "Search for contacts or usernames"; +"Compose.GroupTokenListPlaceholder" = "Search for contacts or usernames"; + +"Compose.ChannelMembers" = "Members"; + +"Channel.Setup.TypeHeader" = "CHANNEL TYPE"; +"Channel.Setup.TypePrivate" = "Private"; +"Channel.Setup.TypePublic" = "Public"; +"Channel.Setup.TypePublicHelp" = "Public channels can be found in search, anyone can join them."; +"Channel.Setup.TypePrivateHelp" = "Private channels can only be joined via an invite link."; + +"Channel.Setup.Title" = "Channel"; + +"Channel.Username.CreatePublicLinkHelp" = "People can share this link with others and find your channel using Telegram search."; +"Channel.Username.CreatePrivateLinkHelp" = "People can join your channel by following this link. You can revoke the link at any time."; + +"Channel.Setup.PublicNoLink" = "Please choose a link for your public channel, so that people can find it in search and share with others.\n\nIf you're not interested, we suggest creating a private channel instead."; + +"Channel.Edit.PrivatePublicLinkAlert" = "Please note that if you choose a public link for your channel, anyone will be able to find it in search and join.\n\nDo not create this link if you want your channel to stay private."; + +"Channel.Info.Description" = "description"; + +"Channel.Info.Management" = "Admins"; +"Channel.Info.Banned" = "Blacklist"; +"Channel.Info.Members" = "Members"; + +"Channel.Members.AddMembers" = "Add Subscribers"; +"Channel.Members.AddMembersHelp" = "Only channel admins can see this list."; +"Channel.Members.Title" = "Members"; +"Channel.BlackList.Title" = "Blacklist"; +"Channel.Management.Title" = "Admins"; +"Channel.Management.LabelCreator" = "Creator"; +"Channel.Management.LabelEditor" = "Admin"; + +"Channel.Management.AddModerator" = "Add Admin"; +"Channel.Management.AddModeratorHelp" = "You can add admins to help you manage your channel."; + +"Channel.Members.InviteLink" = "Invite via Link"; + +"Channel.Management.ErrorNotMember" = "%@ hasn't joined the channel yet. Do you want to invite them?"; + +"Channel.Moderator.AccessLevelRevoke" = "Dismiss Admin"; + +"Channel.Moderator.Title" = "Admin"; + +"Notification.ChannelInviter" = "%@ invited you to this channel"; +"Notification.ChannelInviterSelf" = "You joined this channel"; + +"Notification.GroupInviter" = "%@ invited you to this group"; +"Notification.GroupInviterSelf" = "You joined this group"; + +"ChannelInfo.DeleteChannel" = "Delete Channel"; +"ChannelInfo.DeleteChannelConfirmation" = "Wait! Deleting this channel will remove all members and all messages will be lost. Delete the channel anyway?"; + +"ChannelInfo.ChannelForbidden" = "Sorry, the channel \"%@\" is no longer accessible."; +"ChannelInfo.AddParticipantConfirmation" = "Add %@ to the channel?"; + +"PhotoEditor.FadeTool" = "Fade"; +"PhotoEditor.TintTool" = "Tint"; +"PhotoEditor.ShadowsTint" = "Shadows"; +"PhotoEditor.HighlightsTint" = "Highlights"; +"PhotoEditor.CurvesTool" = "Curves"; +"PhotoEditor.CurvesAll" = "All"; +"PhotoEditor.CurvesRed" = "Red"; +"PhotoEditor.CurvesGreen" = "Green"; +"PhotoEditor.CurvesBlue" = "Blue"; + +"Channel.ErrorAddBlocked" = "Sorry, you can't add this user to channels."; +"Channel.ErrorAddTooMuch" = "Sorry, you can only add the first 200 members to a channel. Note that an unlimited number of people may join via the channel's link."; + +"ChannelIntro.Title" = "What is a Channel?"; +"ChannelIntro.Text" = "Channels are a new tool for\nbroadcasting your messages\nto large audiences."; +"ChannelIntro.CreateChannel" = "Create Channel"; + +"ShareMenu.Send" = "Send"; + +"Conversation.ReportSpam" = "Report Spam"; +"Conversation.ReportSpamAndLeave" = "Report Spam and Leave"; +"Conversation.ReportSpamConfirmation" = "Are you sure you want to report spam from this user?"; +"Conversation.ReportSpamGroupConfirmation" = "Are you sure you want to report spam from this group?"; +"Conversation.ReportSpamChannelConfirmation" = "Are you sure you want to report spam from this channel?"; +"SharedMedia.EmptyMusicText" = "All music shared in this chat will appear here."; + +"ChatSettings.AutoPlayAnimations" = "Autoplay GIFs"; + +"GroupInfo.ChatAdmins" = "Add Admins"; + +"ChatAdmins.Title" = "Chat Admins"; +"ChatAdmins.AllMembersAreAdmins" = "All Members Are Admins"; +"ChatAdmins.AllMembersAreAdminsOnHelp" = "All members can add new members, edit name and photo of the group."; +"ChatAdmins.AllMembersAreAdminsOffHelp" = "Only admins can add and remove members, edit name and photo of the group."; +"ChatAdmins.AdminLabel" = "admin"; + +"Group.MessagePhotoUpdated" = "Group photo updated"; +"Group.MessagePhotoRemoved" = "Group photo removed"; + +"Group.UpgradeNoticeHeader" = "MEMBERS LIMIT REACHED"; + +"Group.UpgradeNoticeText1" = "To go over the limit and get additional features, upgrade to a supergroup:"; +"Group.UpgradeNoticeText2" = "• Supergroups can get up to {supergroup_member_limit} members\n• New members see the entire chat history\n• Admins delete messages for everyone\n• Notifications are muted by default"; +"GroupInfo.UpgradeButton" = "Upgrade to supergroup"; +"Group.UpgradeConfirmation" = "Warning: this action is irreversible. It is not possible to downgrade a supergroup to a regular group."; + +"Notification.GroupActivated" = "Group deactivated"; + +"GroupInfo.DeactivatedStatus" = "Group Deactivated"; + +"Notification.RenamedGroup" = "Group renamed"; + +"Group.ErrorAddTooMuchBots" = "Sorry, you've reached the maximum number of bots for this group."; +"Group.ErrorAddTooMuchAdmins" = "Sorry, you've reached the maximum number of admins for this group."; +"Group.ErrorAddBlocked" = "Sorry, you can't add this user to groups."; +"Group.ErrorNotMutualContact" = "Sorry, you can only add mutual contacts to groups at the moment."; + +"Conversation.SendMessageErrorFlood" = "Sorry, you can only send messages to mutual contacts at the moment."; +"Generic.ErrorMoreInfo" = "More Info"; + +"ChannelInfo.DeleteGroup" = "Delete Group"; +"ChannelInfo.DeleteGroupConfirmation" = "Wait! Deleting this group will remove all members and all messages will be lost. Delete the group anyway?"; + +"ReportPeer.Report" = "Report"; + +"ReportPeer.ReasonSpam" = "Spam"; +"ReportPeer.ReasonViolence" = "Violence"; +"ReportPeer.ReasonPornography" = "Pornography"; +"ReportPeer.ReasonChildAbuse" = "Child Abuse"; +"ReportPeer.ReasonOther" = "Other"; + +"ReportPeer.AlertSuccess" = "Thank you!\nYour report will be reviewed by our team very soon."; + +"Login.TermsOfServiceLabel" = "By signing up,\nyou agree to the [Terms of Service]."; +"Login.TermsOfServiceHeader" = "Terms of Service"; + +"ReportPeer.ReasonOther.Placeholder" = "Description"; +"ReportPeer.ReasonOther.Title" = "Report"; +"ReportPeer.ReasonOther.Send" = "Send"; + +"Group.Management.AddModeratorHelp" = "You can add admins to help you manage your group."; + +"Watch.AppName" = "Telegram"; +"Watch.Compose.AddContact" = "Choose Contact"; +"Watch.Compose.CreateMessage" = "Create Message"; +"Watch.Compose.CurrentLocation" = "Current Location"; +"Watch.Compose.Send" = "Send"; +"Watch.Contacts.NoResults" = "No matching\ncontacts found"; +"Watch.ChatList.NoConversationsTitle" = "No Conversations"; +"Watch.ChatList.NoConversationsText" = "To start messaging,\npress firmly, then tap\nNew Message"; +"Watch.ChatList.Compose" = "New Message"; + +"Watch.Conversation.Reply" = "Reply"; +"Watch.Conversation.Unblock" = "Unblock"; +"Watch.Conversation.UserInfo" = "Info"; +"Watch.Conversation.GroupInfo" = "Group Info"; +"Watch.Bot.Restart" = "Restart"; + +"Watch.UserInfo.Title" = "Info"; +"Watch.UserInfo.Service" = "service notifications"; + +"Watch.UserInfo.Block" = "Block"; +"Watch.UserInfo.Unblock" = "Unblock"; +"Watch.UserInfo.Mute_1" = "Mute for 1 hour"; +"Watch.UserInfo.Mute_2" = "Mute for 2 hours"; +"Watch.UserInfo.Mute_3_10" = "Mute for %@ hours"; +"Watch.UserInfo.Mute_any" = "Mute for %@ hours"; +"Watch.UserInfo.Mute_many" = "Mute for %@ hours"; +"Watch.UserInfo.Mute_0" = "Mute for %@ hours"; +"Watch.UserInfo.MuteTitle" = "Mute"; +"Watch.UserInfo.Unmute" = "Unmute"; + +"Watch.GroupInfo.Title" = "Group Info"; +"Watch.ChannelInfo.Title" = "Channel Info"; + +"Watch.Message.ForwardedFrom" = "Forwarded from"; + +"Watch.Notification.Joined" = "Joined Telegram"; + +"Watch.MessageView.Title" = "Message"; +"Watch.MessageView.Forward" = "Forward"; +"Watch.MessageView.Reply" = "Reply"; +"Watch.MessageView.ViewOnPhone" = "View On Phone"; + +"Watch.PhotoView.Title" = "Photo"; + +"Watch.Stickers.Recents" = "Recents"; +"Watch.Stickers.RecentPlaceholder" = "Your most frequently used stickers will appear here"; +"Watch.Stickers.StickerPacks" = "Sticker Sets"; + +"Watch.Location.Current" = "Current Location"; +"Watch.Location.Access" = "Allow Telegram to access location on your phone"; + +"Watch.AuthRequired" = "Log in to Telegram on your phone to get started"; + +"Watch.NoConnection" = "No Connection"; +"Watch.ConnectionDescription" = "Your Watch needs to be connected for the app to work"; + +"Watch.Time.ShortTodayAt" = "Today %@"; +"Watch.Time.ShortYesterdayAt" = "Yesterday %@"; +"Watch.Time.ShortWeekdayAt" = "%1$@ %2$@"; +"Watch.Time.ShortFullAt" = "%1$@ %2$@"; + +"Watch.LastSeen.JustNow" = "just now"; +"Watch.LastSeen.MinutesAgo_1" = "1 minute ago"; +"Watch.LastSeen.MinutesAgo_2" = "2 minutes ago"; +"Watch.LastSeen.MinutesAgo_3_10" = "%@ minutes ago"; +"Watch.LastSeen.MinutesAgo_any" = "%@ minutes ago"; +"Watch.LastSeen.MinutesAgo_many" = "%@ minutes ago"; +"Watch.LastSeen.MinutesAgo_0" = "%@ minutes ago"; +"Watch.LastSeen.HoursAgo_1" = "1 hour ago"; +"Watch.LastSeen.HoursAgo_2" = "2 hours ago"; +"Watch.LastSeen.HoursAgo_3_10" = "%@ hours ago"; +"Watch.LastSeen.HoursAgo_any" = "%@ hours ago"; +"Watch.LastSeen.HoursAgo_many" = "%@ hours ago"; +"Watch.LastSeen.HoursAgo_0" = "%@ hours ago"; +"Watch.LastSeen.YesterdayAt" = "yesterday at %@"; +"Watch.LastSeen.AtDate" = "%@"; +"Watch.LastSeen.Lately" = "recently"; +"Watch.LastSeen.WithinAWeek" = "within a week"; +"Watch.LastSeen.WithinAMonth" = "within a month"; +"Watch.LastSeen.ALongTimeAgo" = "a long time ago"; + +"Watch.Suggestion.OK" = "OK"; +"Watch.Suggestion.Thanks" = "Thanks!"; +"Watch.Suggestion.WhatsUp" = "What's up?"; +"Watch.Suggestion.TalkLater" = "Talk later?"; +"Watch.Suggestion.CantTalk" = "Can't talk now..."; +"Watch.Suggestion.HoldOn" = "Hold on a sec..."; +"Watch.Suggestion.BRB" = "BRB"; +"Watch.Suggestion.OnMyWay" = "I'm on my way."; +"Cache.Photos" = "Photos"; +"Cache.Videos" = "Videos"; +"Cache.Music" = "Music"; +"Cache.Files" = "Files"; +"Cache.Clear" = "Clear (%@)"; +"Cache.ClearNone" = "Clear"; +"Cache.ClearProgress" = "Please Wait..."; +"Cache.ClearEmpty" = "Empty"; +"Cache.ByPeerHeader" = "CHATS"; +"Cache.Indexing" = "Telegram is calculating current cache size.\nThis can take a few minutes."; + +"ExplicitContent.AlertTitle" = "Sorry"; +"ExplicitContent.AlertChannel" = "You can't access this channel because it violates App Store rules."; + +"StickerSettings.ContextHide" = "Archive"; + +"Conversation.LinkDialogSave" = "Save"; +"Conversation.GifTooltip" = "Tap here to access saved GIFs"; + +"AttachmentMenu.PhotoOrVideo" = "Photo or Video"; +"AttachmentMenu.File" = "File"; + +"AttachmentMenu.SendPhoto_1" = "Send 1 Photo"; +"AttachmentMenu.SendPhoto_2" = "Send 2 Photos"; +"AttachmentMenu.SendPhoto_3_10" = "Send %@ Photos"; +"AttachmentMenu.SendPhoto_any" = "Send %@ Photos"; +"AttachmentMenu.SendPhoto_many" = "Send %@ Photos"; +"AttachmentMenu.SendPhoto_0" = "Send %@ Photos"; + +"AttachmentMenu.SendVideo_1" = "Send 1 Video"; +"AttachmentMenu.SendVideo_2" = "Send 2 Videos"; +"AttachmentMenu.SendVideo_3_10" = "Send %@ Videos"; +"AttachmentMenu.SendVideo_any" = "Send %@ Videos"; +"AttachmentMenu.SendVideo_many" = "Send %@ Videos"; +"AttachmentMenu.SendVideo_0" = "Send %@ Videos"; + +"AttachmentMenu.SendGif_1" = "Send 1 GIF"; +"AttachmentMenu.SendGif_2" = "Send 2 GIFs"; +"AttachmentMenu.SendGif_3_10" = "Send %@ GIFs"; +"AttachmentMenu.SendGif_any" = "Send %@ GIFs"; +"AttachmentMenu.SendGif_many" = "Send %@ GIFs"; +"AttachmentMenu.SendGif_0" = "Send %@ GIFs"; + +"AttachmentMenu.SendItem_1" = "Send 1 Item"; +"AttachmentMenu.SendItem_2" = "Send 2 Items"; +"AttachmentMenu.SendItem_3_10" = "Send %@ Items"; +"AttachmentMenu.SendItem_any" = "Send %@ Items"; +"AttachmentMenu.SendItem_many" = "Send %@ Items"; +"AttachmentMenu.SendItem_0" = "Send %@ Items"; + +"AttachmentMenu.SendAsFile" = "Send as File"; +"AttachmentMenu.SendAsFiles" = "Send as Files"; + +"Conversation.Processing" = "Processing..."; + +"Conversation.MessageViaUser" = "via %@"; + +"CreateGroup.SoftUserLimitAlert" = "You will be able to add more users after you finish creating the group and convert it to a supergroup."; + +"Privacy.GroupsAndChannels" = "Groups"; +"Privacy.GroupsAndChannels.WhoCanAddMe" = "WHO CAN ADD ME TO GROUP CHATS"; +"Privacy.GroupsAndChannels.CustomHelp" = "You can restrict who can add you to groups and channels with granular precision."; +"Privacy.GroupsAndChannels.AlwaysAllow" = "Always Allow"; +"Privacy.GroupsAndChannels.NeverAllow" = "Never Allow"; +"Privacy.GroupsAndChannels.CustomShareHelp" = "These users will or will not be able to add you to groups and channels regardless of the settings above."; + +"Privacy.GroupsAndChannels.AlwaysAllow.Title" = "Always Allow"; +"Privacy.GroupsAndChannels.AlwaysAllow.Placeholder" = "Always allow..."; +"Privacy.GroupsAndChannels.NeverAllow.Title" = "Never Allow"; +"Privacy.GroupsAndChannels.NeverAllow.Placeholder" = "Never allow..."; + +"Privacy.GroupsAndChannels.InviteToGroupError" = "Sorry, you cannot add %@ to groups because of %@'s privacy settings."; +"Privacy.GroupsAndChannels.InviteToChannelError" = "Sorry, you cannot add %@ to channels because of %@'s privacy settings."; +"Privacy.GroupsAndChannels.InviteToChannelMultipleError" = "Sorry, you can't create a group with these users due to their privacy settings."; + +"ChannelMembers.WhoCanAddMembers" = "Who can add members"; +"ChannelMembers.WhoCanAddMembers.AllMembers" = "All Members"; +"ChannelMembers.WhoCanAddMembers.Admins" = "Only Admins"; +"ChannelMembers.WhoCanAddMembersAllHelp" = "Everybody can add new members."; +"ChannelMembers.WhoCanAddMembersAdminsHelp" = "Only admins can add new members."; + +"ChannelMembers.GroupAdminsTitle" = "GROUP ADMINS"; +"ChannelMembers.ChannelAdminsTitle" = "CHANNEL ADMINS"; +"MusicPlayer.VoiceNote" = "Voice Message"; + +"PrivacyLastSeenSettings.WhoCanSeeMyTimestamp" = "WHO CAN SEE MY TIMESTAMP"; + +"PrivacyLastSeenSettings.GroupsAndChannelsHelp" = "Change who can add you to groups and channels."; +"MusicPlayer.VoiceNote" = "Voice Message"; + +"Watch.Microphone.Access" = "Allow Telegram to access the microphone on your phone"; + +"Settings.AppleWatch" = "Apple Watch"; +"AppleWatch.Title" = "Apple Watch"; +"AppleWatch.ReplyPresets" = "REPLY PRESETS"; +"AppleWatch.ReplyPresetsHelp" = "You can select one of these default replies when you compose or reply to a message, or you can change them to anything you like."; + +"KeyCommand.FocusOnInputField" = "Write Message"; +"KeyCommand.Find" = "Search"; +"KeyCommand.ScrollUp" = "Scroll Up"; +"KeyCommand.ScrollDown" = "Scroll Down"; +"KeyCommand.NewMessage" = "New Message"; +"KeyCommand.JumpToPreviousChat" = "Jump to Previous Chat"; +"KeyCommand.JumpToNextChat" = "Jump to Next Chat"; +"KeyCommand.JumpToPreviousUnreadChat" = "Jump to Previous Unread Chat"; +"KeyCommand.JumpToNextUnreadChat" = "Jump to Next Unread Chat"; +"KeyCommand.SendMessage" = "Send Message"; +"KeyCommand.ChatInfo" = "Chat Info"; + +"Conversation.SecretLinkPreviewAlert" = "Would you like to enable extended link previews in Secret Chats? Note that link previews are generated on Telegram servers."; +"Conversation.SecretChatContextBotAlert" = "Please note that inline bots are provided by third-party developers. For the bot to work, the symbols you type after the bot's username are sent to the respective developer."; + +"Map.OpenInWaze" = "Open in Waze"; + +"ShareMenu.CopyShareLink" = "Copy Link"; + +"Channel.SignMessages" = "Sign Messages"; +"Channel.SignMessages.Help" = "Add names of the admins to the messages they post."; + +"Channel.EditMessageErrorGeneric" = "Sorry, you can't edit this message."; + +"Conversation.InputTextSilentBroadcastPlaceholder" = "Silent Broadcast"; +"Conversation.SilentBroadcastTooltipOn" = "Members will be notified when you post"; +"Conversation.SilentBroadcastTooltipOff" = "Members will not be notified when you post"; + +"Settings.About" = "Bio"; +"GroupInfo.LabelAdmin" = "admin"; + +"Conversation.Pin" = "Pin"; +"Conversation.Unpin" = "Unpin"; +"Conversation.Report" = "Report Spam"; +"Conversation.PinnedMessage" = "Pinned Message"; + +"Conversation.Moderate.Delete" = "Delete Message"; +"Conversation.Moderate.Ban" = "Ban User"; +"Conversation.Moderate.Report" = "Report Spam"; +"Conversation.Moderate.DeleteAllMessages" = "Delete All From %@"; + +"Group.Username.InvalidTooShort" = "Group names must have at least 5 characters."; +"Group.Username.InvalidStartsWithNumber" = "Group names can't start with a number."; + +"Notification.PinnedTextMessage" = "%@ pinned \"%@\" "; +"Notification.PinnedPhotoMessage" = "%@ pinned a photo"; +"Notification.PinnedVideoMessage" = "%@ pinned a video"; +"Notification.PinnedRoundMessage" = "%@ pinned a video message"; +"Notification.PinnedAudioMessage" = "%@ pinned a voice message"; +"Notification.PinnedDocumentMessage" = "%@ pinned a file"; +"Notification.PinnedAnimationMessage" = "%@ pinned a GIF"; +"Notification.PinnedStickerMessage" = "%@ pinned a sticker"; +"Notification.PinnedLocationMessage" = "%@ pinned a map"; +"Notification.PinnedContactMessage" = "%@ pinned a contact"; +"Notification.PinnedDeletedMessage" = "%@ pinned deleted message"; +"Notification.PinnedPollMessage" = "%@ pinned a poll"; +"Notification.PinnedQuizMessage" = "%@ pinned a quiz"; + +"Message.PinnedTextMessage" = "pinned \"%@\" "; +"Message.PinnedPhotoMessage" = "pinned photo"; +"Message.PinnedVideoMessage" = "pinned video"; +"Message.PinnedAudioMessage" = "pinned voice message"; +"Message.PinnedDocumentMessage" = "pinned file"; +"Message.PinnedAnimationMessage" = "pinned GIF"; +"Message.PinnedStickerMessage" = "pinned sticker"; +"Message.PinnedLocationMessage" = "pinned location"; +"Message.PinnedContactMessage" = "pinned contact"; + +"Notification.PinnedMessage" = "pinned message"; + +"GroupInfo.ConvertToSupergroup" = "Convert to Supergroup"; + +"ConvertToSupergroup.Title" = "Supergroup"; +"ConvertToSupergroup.HelpTitle" = "**In supergroups:**"; +"ConvertToSupergroup.HelpText" = "• New members can see the full message history\n• Deleted messages will disappear for all members\n• Admins can pin important messages\n• Creator can set a public link for the group"; + +"ConvertToSupergroup.Note" = "**Note**: this action can't be undone."; + +"GroupInfo.GroupType" = "Group Type"; + +"Group.Setup.TypeHeader" = "GROUP TYPE"; +"Group.Setup.TypePublicHelp" = "Public groups can be found in search, chat history is available to everyone and anyone can join."; +"Group.Setup.TypePrivateHelp" = "Private groups can only be joined if you were invited or have an invite link."; + +"Group.Username.CreatePublicLinkHelp" = "People can share this link with others and find your group using Telegram search."; +"Group.Username.CreatePrivateLinkHelp" = "People can join your group by following this link. You can revoke the link at any time."; + +"Conversation.PinMessageAlertGroup" = "Pin this message and notify all members of the group?"; +"Conversation.PinMessageAlert.OnlyPin" = "Only Pin"; + +"Conversation.UnpinMessageAlert" = "Would you like to unpin this message?"; + +"Settings.About.Title" = "Bio"; +"Settings.About.Help" = "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco."; + +"Profile.About" = "bio"; + +"Conversation.StatusKickedFromChannel" = "you were removed from the channel"; + +"Generic.OpenHiddenLinkAlert" = "Open %@?"; + +"Resolve.ErrorNotFound" = "Sorry, this user doesn't seem to exist."; + +"StickerPack.Share" = "Share"; +"StickerPack.Send" = "Send Sticker"; + +"StickerPack.RemoveStickerCount_1" = "Remove 1 Sticker"; +"StickerPack.RemoveStickerCount_2" = "Remove 2 Stickers"; +"StickerPack.RemoveStickerCount_3_10" = "Remove %@ Stickers"; +"StickerPack.RemoveStickerCount_any" = "Remove %@ Stickers"; +"StickerPack.RemoveStickerCount_many" = "Remove %@ Stickers"; +"StickerPack.RemoveStickerCount_0" = "Remove %@ Stickers"; + +"StickerPack.HideStickers" = "Hide Stickers"; +"StickerPack.ShowStickers" = "Show Stickers"; + +"ShareMenu.ShareTo" = "Share to"; +"ShareMenu.SelectChats" = "Select chats"; +"ShareMenu.Comment" = "Add a comment..."; + +"MediaPicker.Videos" = "Videos"; + +"Coub.TapForSound" = "Tap for sound"; + +"Preview.SaveGif" = "Save GIF"; +"Preview.DeleteGif" = "Delete GIF"; +"Preview.CopyAddress" = "Copy Address"; + +"Conversation.ShareBotLocationConfirmationTitle" = "Share Your Location?"; +"Conversation.ShareBotLocationConfirmation" = "This will send your current location to the bot."; + +"Conversation.ShareBotContactConfirmationTitle" = "Share Your Phone Number?"; +"Conversation.ShareBotContactConfirmation" = "The bot will know your phone number. This can be useful for integration with other services."; + +"Conversation.ShareInlineBotLocationConfirmation" = "This bot would like to know your location each time you send it a request. This can be used to provide location-specific results."; + +"StickerPack.ErrorNotFound" = "Sorry, this sticker set doesn't seem to exist."; + +"Camera.TapAndHoldForVideo" = "Tap and hold for video"; + +"DialogList.RecentTitlePeople" = "People"; + +"Conversation.MessageEditedLabel" = "edited"; +"Conversation.EditingMessagePanelTitle" = "Edit Message"; + +"DialogList.Draft" = "Draft"; +"Embed.PlayingInPIP" = "This video is playing in Picture in Picture"; + +"StickerPacksSettings.FeaturedPacks" = "Trending Stickers"; +"FeaturedStickerPacks.Title" = "Trending Stickers"; + +"Invitation.JoinGroup" = "Join Group"; +"Invitation.Members_1" = "1 member:"; +"Invitation.Members_2" = "2 members:"; +"Invitation.Members_3_10" = "%@ members:"; +"Invitation.Members_any" = "%@ members:"; +"Invitation.Members_many" = "%@ members:"; +"Invitation.Members_0" = "%@ members:"; + +"StickerPacksSettings.ArchivedPacks" = "Archived Stickers"; +"StickerPacksSettings.ArchivedPacks.Info" = "You can have up to 200 sticker sets installed.\nUnused stickers are archived when you add more."; + +"Conversation.CloudStorageInfo.Title" = "Your Cloud Storage"; +"Conversation.ClousStorageInfo.Description1" = "• Forward messages here to save them"; +"Conversation.ClousStorageInfo.Description2" = "• Send media and files to store them"; +"Conversation.ClousStorageInfo.Description3" = "• Access this chat from any device"; +"Conversation.ClousStorageInfo.Description4" = "• Use search to quickly find things"; + +"Conversation.CloudStorage.ChatStatus" = "chat with yourself"; + +"ArchivedPacksAlert.Title" = "Some of your older sticker sets have been archived. You can reactivate them in the Sticker Settings."; + +"StickerSettings.ContextInfo" = "If you archive a sticker set, you can quickly restore it later from the Archived Stickers section."; + +"Contacts.TopSection" = "CONTACTS"; + +"Login.ResetAccountProtected.Title" = "Reset Account"; +"Login.ResetAccountProtected.Text" = "Since the account %@ is active and protected by a password, we will delete it in 1 week for security purposes.\n\nYou can cancel this process at any time."; +"Login.ResetAccountProtected.TimerTitle" = "You'll be able to reset your account in:"; +"Login.ResetAccountProtected.Reset" = "Reset"; +"Login.ResetAccountProtected.LimitExceeded" = "Your recent attempts to reset this account have been cancelled by its active user. Please try again in 7 days."; + +"Login.CodeSentCall" = "We are calling your phone to dictate a code."; + +"Login.WillSendSms" = "Telegram will send you an SMS in %@"; +"Login.SmsRequestState2" = "Requesting an SMS from Telegram..."; +"Login.SmsRequestState3" = "Telegram sent you an SMS\n[Didn't get the code?]"; + +"CancelResetAccount.Title" = "Cancel Account Reset"; +"CancelResetAccount.TextSMS" = "Somebody with access to your phone number %@ has requested to delete your Telegram account and reset your 2-Step Verification password.\n\nIf it wasn't you, please enter the code we've just sent you via SMS to your number."; + +"CancelResetAccount.Success" = "The deletion process was cancelled for your account %@."; +"MediaPicker.MomentsDateRangeSameMonthYearFormat" = "{month} {day1} – {day2}, {year}"; + +"Paint.Clear" = "Clear All"; +"Paint.ClearConfirm" = "Clear Painting"; +"Paint.Delete" = "Delete"; +"Paint.Edit" = "Edit"; +"Paint.Duplicate" = "Duplicate"; +"Paint.Stickers" = "Stickers"; +"Paint.RecentStickers" = "Recent"; +"Paint.Masks" = "Masks"; + +"Paint.Outlined" = "Outlined"; +"Paint.Regular" = "Regular"; + +"MediaPicker.VideoMuteDescription" = "Sound is now muted, so the video will autoplay and loop like a GIF."; + + +"Group.Username.RemoveExistingUsernamesInfo" = "Sorry, you have reserved too many public usernames. You can revoke the link from one of your older groups or channels, or create a private entity instead."; + +"ServiceMessage.GameScoreExtended_1" = "{name} scored %@ in {game}"; +"ServiceMessage.GameScoreExtended_2" = "{name} scored %@ in {game}"; +"ServiceMessage.GameScoreExtended_3_10" = "{name} scored %@ in {game}"; +"ServiceMessage.GameScoreExtended_any" = "{name} scored %@ in {game}"; +"ServiceMessage.GameScoreExtended_many" = "{name} scored %@ in {game}"; +"ServiceMessage.GameScoreExtended_0" = "{name} scored %@ in {game}"; + +"ServiceMessage.GameScoreSelfExtended_1" = "You scored %@ in {game}"; +"ServiceMessage.GameScoreSelfExtended_2" = "You scored %@ in {game}"; +"ServiceMessage.GameScoreSelfExtended_3_10" = "You scored %@ in {game}"; +"ServiceMessage.GameScoreSelfExtended_any" = "You scored %@ in {game}"; +"ServiceMessage.GameScoreSelfExtended_many" = "You scored %@ in {game}"; +"ServiceMessage.GameScoreSelfExtended_0" = "You scored %@ in {game}"; + +"ServiceMessage.GameScoreSimple_1" = "{name} scored %@"; +"ServiceMessage.GameScoreSimple_2" = "{name} scored %@"; +"ServiceMessage.GameScoreSimple_3_10" = "{name} scored %@"; +"ServiceMessage.GameScoreSimple_any" = "{name} scored %@"; +"ServiceMessage.GameScoreSimple_many" = "{name} scored %@"; +"ServiceMessage.GameScoreSimple_0" = "{name} scored %@"; + +"ServiceMessage.GameScoreSelfSimple_1" = "You scored %@"; +"ServiceMessage.GameScoreSelfSimple_2" = "You scored %@"; +"ServiceMessage.GameScoreSelfSimple_3_10" = "You scored %@"; +"ServiceMessage.GameScoreSelfSimple_any" = "You scored %@"; +"ServiceMessage.GameScoreSelfSimple_many" = "You scored %@"; +"ServiceMessage.GameScoreSelfSimple_0" = "You scored %@"; + +"Notification.GameScoreExtended_1" = "scored %@ in {game}"; +"Notification.GameScoreExtended_2" = "scored %@ in {game}"; +"Notification.GameScoreExtended_3_10" = "scored %@ in {game}"; +"Notification.GameScoreExtended_any" = "scored %@ in {game}"; +"Notification.GameScoreExtended_many" = "scored %@ in {game}"; +"Notification.GameScoreExtended_0" = "scored %@ in {game}"; + +"Notification.GameScoreSelfExtended_1" = "scored %@ in {game}"; +"Notification.GameScoreSelfExtended_2" = "scored %@ in {game}"; +"Notification.GameScoreSelfExtended_3_10" = "scored %@ in {game}"; +"Notification.GameScoreSelfExtended_any" = "scored %@ in {game}"; +"Notification.GameScoreSelfExtended_many" = "scored %@ in {game}"; +"Notification.GameScoreSelfExtended_0" = "scored %@ in {game}"; + +"Notification.GameScoreSimple_1" = "scored %@"; +"Notification.GameScoreSimple_2" = "scored %@"; +"Notification.GameScoreSimple_3_10" = "scored %@"; +"Notification.GameScoreSimple_any" = "scored %@"; +"Notification.GameScoreSimple_many" = "scored %@"; +"Notification.GameScoreSimple_0" = "scored %@"; + +"Notification.GameScoreSelfSimple_1" = "scored %@"; +"Notification.GameScoreSelfSimple_2" = "scored %@"; +"Notification.GameScoreSelfSimple_3_10" = "scored %@"; +"Notification.GameScoreSelfSimple_any" = "scored %@"; +"Notification.GameScoreSelfSimple_many" = "scored %@"; +"Notification.GameScoreSelfSimple_0" = "scored %@"; + +"Stickers.Install" = "ADD"; +"Stickers.Installed" = "ADDED"; + +"MaskStickerSettings.Title" = "Masks"; +"MaskStickerSettings.Info" = "You can add masks to photos and videos you send. To do this, open the photo editor before sending a photo or video."; + +"StickerPack.Add" = "Add"; +"StickerPack.AddMaskCount_1" = "Add 1 Mask"; +"StickerPack.AddMaskCount_2" = "Add 2 Masks"; +"StickerPack.AddMaskCount_3_10" = "Add %@ Masks"; +"StickerPack.AddMaskCount_any" = "Add %@ Masks"; +"StickerPack.AddMaskCount_many" = "Add %@ Masks"; +"StickerPack.AddMaskCount_0" = "Add %@ Masks"; + +"StickerPack.RemoveMaskCount_1" = "Remove 1 Mask"; +"StickerPack.RemoveMaskCount_2" = "Remove 2 Masks"; +"StickerPack.RemoveMaskCount_3_10" = "Remove %@ Masks"; +"StickerPack.RemoveMaskCount_any" = "Remove %@ Masks"; +"StickerPack.RemoveMaskCount_many" = "Remove %@ Masks"; +"StickerPack.RemoveMaskCount_0" = "Remove %@ Masks"; + +"Conversation.BotInteractiveUrlAlert" = "Allow %@ to pass your Telegram name and id (not your phone number) to pages you open with this bot?"; +"StickerPacksSettings.ArchivedMasks" = "Archived Masks"; +"StickerSettings.MaskContextInfo" = "If you archive a set of masks, you can quickly restore it later from the Archived Masks section."; +"StickerPacksSettings.ArchivedMasks.Info" = "You can have up to 200 sets of masks. +Unused sets are archived when you add more."; + +"CloudStorage.Title" = "Cloud Storage"; + +"Widget.AuthRequired" = "Log in to Telegram"; +"Widget.NoUsers" = "Start messaging to see your friends here"; + +"ShareMenu.CopyShareLinkGame" = "Copy link to game"; + +"Message.PinnedGame" = "pinned a game"; +"Message.AuthorPinnedGame" = "%@ pinned a game"; + +"Target.ShareGameConfirmationPrivate" = "Share the game with %@?"; +"Target.ShareGameConfirmationGroup" = "Share the game with \"%@\"?"; + +"Activity.PlayingGame" = "playing game"; +"Activity.UploadingVideoMessage" = "sending video"; + +"DialogList.SinglePlayingGameSuffix" = "%@ is playing a game"; + +"UserInfo.GroupsInCommon" = "Groups In Common"; +"Conversation.InstantPagePreview" = "INSTANT VIEW"; + +"StickerPack.ViewPack" = "View Sticker Set"; +"InstantPage.AuthorAndDateTitle" = "By %1$@ • %2$@"; +"InstantPage.FeedbackButton" = "Leave feedback about this preview"; +"Conversation.JumpToDate" = "Jump To Date"; +"Conversation.AddToReadingList" = "Add to Reading List"; + +"AccessDenied.CallMicrophone" = "Telegram needs access to your microphone for voice calls.\n\nPlease go to Settings > Privacy > Microphone and set Telegram to ON."; + +"Call.EncryptionKey.Title" = "Encryption Key"; + +"Application.Name" = "Telegram"; +"DialogList.Pin" = "Pin"; +"DialogList.Unpin" = "Unpin"; +"DialogList.PinLimitError" = "Sorry, you can pin no more than %@ chats to the top."; +"DialogList.UnknownPinLimitError" = "Sorry, you can't pin any more chats to the top."; + +"Conversation.DeleteMessagesForMe" = "Delete for me"; +"Conversation.DeleteMessagesFor" = "Delete for me and %@"; +"Conversation.DeleteMessagesForEveryone" = "Delete for everyone"; + +"NetworkUsageSettings.Title" = "Network Usage"; +"NetworkUsageSettings.Cellular" = "Cellular"; +"NetworkUsageSettings.Wifi" = "Wi-Fi"; + +"NetworkUsageSettings.GeneralDataSection" = "MESSAGES"; +"NetworkUsageSettings.MediaImageDataSection" = "PHOTOS"; +"NetworkUsageSettings.MediaVideoDataSection" = "VIDEOS"; +"NetworkUsageSettings.MediaAudioDataSection" = "AUDIO"; +"NetworkUsageSettings.MediaDocumentDataSection" = "DOCUMENTS"; +"NetworkUsageSettings.TotalSection" = "TOTAL BYTES"; +"NetworkUsageSettings.BytesSent" = "Bytes Sent"; +"NetworkUsageSettings.BytesReceived" = "Bytes Received"; + +"NetworkUsageSettings.ResetStats" = "Reset Statistics"; +"NetworkUsageSettings.ResetStatsConfirmation" = "Do you want to reset your usage statistics?"; +"NetworkUsageSettings.CellularUsageSince" = "Cellular usage since %@"; +"NetworkUsageSettings.WifiUsageSince" = "Wi-Fi usage since %@"; + +"Settings.CallSettings" = "Voice Calls"; + +"Calls.TabTitle" = "Calls"; +"Calls.All" = "All"; +"Calls.Missed" = "Missed"; + +"CallSettings.Title" = "Voice Calls"; +"CallSettings.RecentCalls" = "Recent Calls"; +"CallSettings.TabIcon" = "Show Calls Tab"; +"CallSettings.TabIconDescription" = "A call icon will appear in the tab bar."; +"CallSettings.UseLessData" = "Use Less Data"; +"CallSettings.Never" = "Never"; +"CallSettings.OnMobile" = "On Mobile Network"; +"CallSettings.Always" = "Always"; +"CallSettings.UseLessDataLongDescription" = "Using less data may improve your experience on bad networks, but will slightly decrease audio quality."; + +"Calls.CallTabTitle" = "Calls Tab"; +"Calls.CallTabDescription" = "You can add a Calls Tab to the tab bar."; +"Calls.NotNow" = "Not Now"; +"Calls.AddTab" = "Add Tab"; +"Calls.NewCall" = "New Call"; + +"Calls.RatingTitle" = "Please rate the quality\nof your Telegram call"; +"Calls.SubmitRating" = "Submit"; + +"Call.Seconds_1" = "%@ second"; +"Call.Seconds_2" = "%@ seconds"; +"Call.Seconds_3_10" = "%@ seconds"; +"Call.Seconds_any" = "%@ seconds"; +"Call.Seconds_many" = "%@ seconds"; +"Call.Seconds_0" = "%@ seconds"; +"Call.Minutes_1" = "%@ minute"; +"Call.Minutes_2" = "%@ minutes"; +"Call.Minutes_3_10" = "%@ minutes"; +"Call.Minutes_any" = "%@ minutes"; +"Call.Minutes_many" = "%@ minutes"; +"Call.Minutes_0" = "%@ minutes"; + +"Call.ShortSeconds_1" = "%@ sec"; +"Call.ShortSeconds_2" = "%@ sec"; +"Call.ShortSeconds_3_10" = "%@ sec"; +"Call.ShortSeconds_any" = "%@ sec"; +"Call.ShortSeconds_many" = "%@ sec"; +"Call.ShortSeconds_0" = "%@ sec"; +"Call.ShortMinutes_1" = "%@ min"; +"Call.ShortMinutes_2" = "%@ min"; +"Call.ShortMinutes_3_10" = "%@ min"; +"Call.ShortMinutes_any" = "%@ min"; +"Call.ShortMinutes_many" = "%@ min"; +"Call.ShortMinutes_0" = "%@ min"; + +"Notification.CallTimeFormat" = "%1$@ (%2$@)"; // 1 - type, 2 - duration +"Notification.CallOutgoing" = "Outgoing Call"; +"Notification.VideoCallOutgoing" = "Outgoing Video Call"; +"Notification.CallIncoming" = "Incoming Call"; +"Notification.VideoCallIncoming" = "Incoming Video Call"; +"Notification.CallMissed" = "Missed Call"; +"Notification.VideoCallMissed" = "Missed Video Call"; +"Notification.CallCanceled" = "Cancelled Call"; +"Notification.VideoCallCanceled" = "Cancelled Video Call"; +"Notification.CallOutgoingShort" = "Outgoing"; +"Notification.CallIncomingShort" = "Incoming"; +"Notification.CallMissedShort" = "Missed"; +"Notification.CallCanceledShort" = "Cancelled"; +"Notification.CallFormat" = "%1$@, %2$@"; // 1 - time, 2 - duration + + + +"Call.ConnectionErrorTitle" = "Unable to Call"; +"Call.ConnectionErrorMessage" = "Please check your internet connection and try again."; + +"Call.CallAgain" = "Call Again"; + +"Login.PhoneFloodError" = "Sorry, you have deleted and re-created your account too many times recently. Please wait for a few days before signing up again."; + +"Checkout.Title" = "Checkout"; +"Checkout.TotalAmount" = "Total"; +"Checkout.TotalPaidAmount" = "Total Paid"; +"Checkout.PaymentMethod" = "Payment Method"; +"Checkout.ShippingMethod" = "Shipping Method"; +"Checkout.ShippingAddress" = "Shipping Information"; +"Checkout.Name" = "Name"; +"Checkout.Email" = "E-Mail"; +"Checkout.Phone" = "Phone"; +"Checkout.PayPrice" = "Pay %@"; +"Checkout.PayNone" = "Pay"; + +"Checkout.PaymentMethod.Title" = "Payment Method"; +"Checkout.PaymentMethod.New" = "New Card..."; + +"Checkout.NewCard.Title" = "New Card"; +"Checkout.NewCard.PaymentCard" = "PAYMENT CARD"; +"Checkout.NewCard.SaveInfo" = "Save Payment Information"; +"Checkout.NewCard.SaveInfoEnableHelp" = "You can save your payment information for future use.\nPlease [turn on Two-Step Verification] to enable this."; +"Checkout.NewCard.SaveInfoHelp" = "You can save your payment information for future use."; +"Checkout.NewCard.CardholderNameTitle" = "CARDHOLDER"; +"Checkout.NewCard.CardholderNamePlaceholder" = "Cardholder Name"; +"Checkout.NewCard.PostcodeTitle" = "BILLING ADDRESS"; +"Checkout.NewCard.PostcodePlaceholder" = "Zip Code"; + +"Checkout.ShippingOption.Title" = "Shipping Method"; + +"Checkout.ErrorProviderAccountInvalid" = "This bot can't accept payments at the moment. Please try again later."; +"Checkout.ErrorProviderAccountTimeout" = "This bot can't process payments at the moment. Please try again later."; +"Checkout.ErrorInvoiceAlreadyPaid" = "You have already paid for this item."; + +"Checkout.ErrorGeneric" = "An error occurred while processing your payment. Your card has not been billed."; +"Checkout.ErrorPaymentFailed" = "Payment failed. Your card has not been billed."; +"Checkout.ErrorPrecheckoutFailed" = "The bot couldn't process your payment. Your card has not been billed."; + +"CheckoutInfo.Title" = "Shipping Information"; +"CheckoutInfo.ShippingInfoTitle" = "SHIPPING ADDRESS"; +"CheckoutInfo.ShippingInfoAddress1" = "Address 1"; +"CheckoutInfo.ShippingInfoAddress1Placeholder" = "Address"; +"CheckoutInfo.ShippingInfoAddress2" = "Address 2"; +"CheckoutInfo.ShippingInfoAddress2Placeholder" = "Address"; +"CheckoutInfo.ShippingInfoState" = "State"; +"CheckoutInfo.ShippingInfoStatePlaceholder" = "State"; +"CheckoutInfo.ShippingInfoCity" = "City"; +"CheckoutInfo.ShippingInfoCityPlaceholder" = "City"; +"CheckoutInfo.ShippingInfoCountry" = "Country"; +"CheckoutInfo.ShippingInfoCountryPlaceholder" = "Country"; +"CheckoutInfo.ShippingInfoPostcode" = "Postcode"; +"CheckoutInfo.ShippingInfoPostcodePlaceholder" = "Postcode"; +"CheckoutInfo.ReceiverInfoTitle" = "RECEIVER"; +"CheckoutInfo.ReceiverInfoName" = "Name"; +"CheckoutInfo.ReceiverInfoNamePlaceholder" = "Name Surname"; +"CheckoutInfo.ReceiverInfoEmail" = "Email"; +"CheckoutInfo.ReceiverInfoEmailPlaceholder" = "Email"; +"CheckoutInfo.ReceiverInfoPhone" = "Phone"; +"CheckoutInfo.SaveInfo" = "Save Info"; +"CheckoutInfo.SaveInfoHelp" = "You can save your shipping information for future use."; +"CheckoutInfo.Pay" = "Pay"; + +"Checkout.Receipt.Title" = "Receipt"; + +"Message.ReplyActionButtonShowReceipt" = "Show Receipt"; +"Message.InvoiceLabel" = "INVOICE"; + +"CheckoutInfo.ErrorShippingNotAvailable" = "Shipping to the selected country is not available."; +"CheckoutInfo.ErrorPostcodeInvalid" = "Please enter a valid postcode."; +"CheckoutInfo.ErrorStateInvalid" = "Please enter a valid state."; +"CheckoutInfo.ErrorCityInvalid" = "Please enter a valid city."; +"CheckoutInfo.ErrorNameInvalid" = "Please enter a valid name."; +"CheckoutInfo.ErrorEmailInvalid" = "Please enter a valid e-mail address."; +"CheckoutInfo.ErrorPhoneInvalid" = "Please enter a valid phone number."; + +"Checkout.WebConfirmation.Title" = "Complete Payment"; +"Checkout.PasswordEntry.Title" = "Payment Confirmation"; +"Checkout.PasswordEntry.Pay" = "Pay"; +"Checkout.PasswordEntry.Text" = "Your card %@ is on file. To pay with this card, please enter your 2-Step-Verification password."; + +"Checkout.SavePasswordTimeout" = "Would you like to save your password for %@?"; +"Checkout.SavePasswordTimeoutAndTouchId" = "Would you like to save your password for %@ and use Touch ID instead?"; +"Checkout.PayWithTouchId" = "Pay with Touch ID"; +"Checkout.EnterPassword" = "Enter Password"; + +"Your_card_has_expired" = "Your card has expired."; + +/* Error when the card was declined by the credit card networks */ +"Your_card_was_declined" = "Your card was declined."; + +/* Error when the card's expiration month is not valid */ +"Your_cards_expiration_month_is_invalid" ="You've entered an invalid expiration month."; + +/* Error when the card's expiration year is not valid */ +"Your_cards_expiration_year_is_invalid" ="You've entered an invalid expiration year."; + +/* Error when the card number is not valid */ +"Your_cards_number_is_invalid" = "You've entered an invalid card number."; + +/* Error when the card's CVC is not valid */ +"Your_cards_security_code_is_invalid" = "You've entered an invalid security code."; + +"MESSAGE_INVOICE" = "%1$@ sent you an invoice for %2$@"; +"CHAT_MESSAGE_INVOICE" = "%1$@ sent an invoice for %3$@ to the group %2$@"; +"PINNED_INVOICE" = "%1$@ pinned an invoice"; + +"Message.PinnedInvoice" = "pinned an invoice"; + +"User.DeletedAccount" = "Deleted Account"; + +"Settings.SaveEditedPhotos" = "Save Edited Photos"; + +"Message.PaymentSent" = "Payment: %@"; +"Notification.PaymentSent" = "You have just successfully transferred {amount} to {name} for {title}"; + +"Common.NotNow" = "Not Now"; + +"Calls.RatingFeedback" = "Write a comment..."; + +"Call.StatusIncoming" = "Telegram Audio..."; +"Call.IncomingVoiceCall" = "Incoming Voice Call"; +"Call.IncomingVideoCall" = "Incoming Video Call"; +"Call.StatusRequesting" = "Contacting..."; +"Call.StatusWaiting" = "Waiting..."; +"Call.StatusRinging" = "Ringing..."; +"Call.StatusConnecting" = "Connecting..."; +"Call.StatusOngoing" = "Telegram Audio %@"; +"Call.StatusEnded" = "Call Ended"; +"Call.StatusFailed" = "Call Failed"; +"Call.StatusBusy" = "Busy"; +"Call.Accept" = "Accept"; +"Call.Decline" = "Decline"; + +"Call.StatusBar" = "Touch to return to call %@"; + +"Call.ParticipantVersionOutdatedError" = "%@'s app does not support calls. They need to update their app before you can call them."; +"Call.ParticipantVideoVersionOutdatedError" = "%@'s app does not support video calls. They need to update their app before you can call them."; + +"Privacy.Calls" = "Voice Calls"; + +"Privacy.Calls.WhoCanCallMe" = "WHO CAN CALL ME"; +"Privacy.Calls.CustomHelp" = "You can restrict who can call you with granular precision."; +"Privacy.Calls.AlwaysAllow" = "Always Allow"; +"Privacy.Calls.NeverAllow" = "Never Allow"; +"Privacy.Calls.CustomShareHelp" = "These users will or will not be able to call you regardless of the settings above."; + +"Privacy.Calls.AlwaysAllow.Title" = "Always Allow"; +"Privacy.Calls.AlwaysAllow.Placeholder" = "Always allow..."; +"Privacy.Calls.NeverAllow.Title" = "Never Allow"; +"Privacy.Calls.NeverAllow.Placeholder" = "Never allow..."; + +"PhotoEditor.QualityTool" = "Quality"; +"PhotoEditor.QualityVeryLow" = "Very Low"; +"PhotoEditor.QualityLow" = "Low"; +"PhotoEditor.QualityMedium" = "Medium"; +"PhotoEditor.QualityHigh" = "High"; +"PhotoEditor.QualityVeryHigh" = "Very High"; + +"Settings.SaveEditedPhotos" = "Save Edited Photos"; + +"Calls.NoCallsPlaceholder" = "Your recent calls will appear here"; +"Calls.NoMissedCallsPlacehoder" = "You have no missed calls"; + +"Call.CallInProgressTitle" = "Call in Progress"; +"Call.CallInProgressMessage" = "Finish call with %1$@ and start a new one with %2$@?"; +"Call.ExternalCallInProgressMessage" = "Please finish the current call first."; + +"Call.Message" = "Message"; + +"UserInfo.TapToCall" = "Tap to make an end-to-end encrypted call"; +"Call.GroupFormat" = "%1$@ (%2$@)"; + +"NetworkUsageSettings.CallDataSection" = "CALLS"; + +"Call.PrivacyErrorMessage" = "Sorry, %@ doesn't accept calls."; + +"Notification.CallBack" = "Call Back"; + +"Call.AudioRouteSpeaker" = "Speaker"; +"Call.AudioRouteHeadphones" = "Headphones"; +"Call.AudioRouteHide" = "Hide"; + +"Call.PhoneCallInProgressMessage" = "You can’t place a Telegram call if you’re already on a phone call."; +"Call.RecordingDisabledMessage" = "Please end your call before recording a voice message."; + +"Call.EmojiDescription" = "If these emoji are the same on %@'s screen, this call is 100%% secure."; + +"Message.VideoMessage" = "Video Message"; + +"Conversation.HoldForAudio" = "Hold to record audio. Tap to switch to video."; +"Conversation.HoldForVideo" = "Hold to record video. Tap to switch to audio."; + +"UserInfo.TelegramCall" = "Telegram Call"; +"UserInfo.PhoneCall" = "Phone Call"; + +"SharedMedia.CategoryMedia" = "Media"; +"SharedMedia.CategoryDocs" = "Docs"; +"SharedMedia.CategoryLinks" = "Links"; +"SharedMedia.CategoryOther" = "Audio"; + +"AccessDenied.VideoMessageCamera" = "Telegram needs access to your camera to send video messages.\n\nPlease go to Settings > Privacy > Camera and set Telegram to ON."; +"AccessDenied.VideoMessageMicrophone" = "Telegram needs access to your microphone to send video messages.\n\nPlease go to Settings > Privacy > Microphone and set Telegram to ON."; + +"ChatSettings.AutomaticVideoMessageDownload" = "AUTOMATIC VIDEO MESSAGE DOWNLOAD"; + +"ForwardedVideoMessages_1" = "Forwarded video message"; +"ForwardedVideoMessages_2" = "2 forwarded video messages"; +"ForwardedVideoMessages_3_10" = "%@ forwarded video messages"; +"ForwardedVideoMessages_any" = "%@ forwarded video messages"; +"ForwardedVideoMessages_many" = "%@ forwarded video messages"; +"ForwardedVideoMessages_0" = "%@ forwarded video messages"; + +"Conversation.DiscardVoiceMessageTitle" = "Discard Voice Message"; +"Conversation.DiscardVoiceMessageDescription" = "Are you sure you want to stop recording and discard\nyour voice message?"; +"Conversation.DiscardVoiceMessageAction" = "Discard"; + +"Message.ForwardedMessageShort" = "Forwarded From\n%@"; + +"Checkout.LiabilityAlertTitle" = "Warning"; +"Checkout.LiabilityAlert" = "Neither Telegram, nor %1$@ will have access to your credit card information. Credit card details will be handled only by the payment system, %2$@.\n\nPayments will go directly to the developer of %1$@. Telegram cannot provide any guarantees, so proceed at your own risk. In case of problems, please contact the developer of %1$@ or your bank."; + +"Settings.AppLanguage" = "Language"; +"Settings.AppLanguage.Unofficial" = "UNOFFICIAL"; + +"InstantPage.AutoNightTheme" = "Auto-Night Theme"; + +"Privacy.PaymentsTitle" = "PAYMENTS"; +"Privacy.PaymentsClearInfo" = "Clear payment & shipping info"; +"Privacy.PaymentsClearInfoHelp" = "You can delete your shipping info and instruct all payment providers to remove your saved credit cards. Note that Telegram never stores your credit card data."; +"Privacy.PaymentsClear.PaymentInfo" = "Payment Info"; +"Privacy.PaymentsClear.ShippingInfo" = "Shipping Info"; + +"Channel.EditAdmin.PermissionsHeader" = "WHAT CAN THIS ADMIN DO?"; +"Channel.EditAdmin.PermissionChangeInfo" = "Change Channel Info"; +"Group.EditAdmin.PermissionChangeInfo" = "Change Group Info"; +"Channel.EditAdmin.PermissionPostMessages" = "Post Messages"; +"Channel.EditAdmin.PermissionEditMessages" = "Edit Messages"; +"Channel.EditAdmin.PermissionDeleteMessages" = "Delete Messages"; +"Channel.EditAdmin.PermissionBanUsers" = "Ban Users"; +"Channel.EditAdmin.PermissionInviteSubscribers" = "Add Subscribers"; +"Channel.EditAdmin.PermissionInviteMembers" = "Add Members"; +"Channel.EditAdmin.PermissionInviteViaLink" = "Invite Users via Link"; +"Channel.EditAdmin.PermissionPinMessages" = "Pin Messages"; +"Channel.EditAdmin.PermissionAddAdmins" = "Add New Admins"; + +"Channel.EditAdmin.PermissinAddAdminOn" = "This Admin will be able to add new admins with the same (or more limited) permissions."; +"Channel.EditAdmin.PermissinAddAdminOff" = "This Admin will not be able to add new admins."; + +"Login.ContinueWithLocalization" = "Continue with English"; +"Localization.LanguageName" = "English"; +"Localization.ChooseLanguage" = "Choose Your Language"; +"Localization.EnglishLanguageName" = "English"; +"Localization.LanguageOther" = "Other"; +"Localization.LanguageCustom" = "Custom"; + +"Channel.BanUser.Title" = "Ban User"; +"Channel.BanUser.PermissionsHeader" = "User Restrictions"; +"Channel.BanUser.PermissionReadMessages" = "Can Read Messages"; +"Channel.BanUser.PermissionSendMessages" = "Can Send Messages"; +"Channel.BanUser.PermissionSendMedia" = "Can Send Media"; +"Channel.BanUser.PermissionSendStickersAndGifs" = "Can Send Stickers & GIFs"; +"Channel.BanUser.PermissionEmbedLinks" = "Can Embed Links"; +"Channel.BanUser.PermissionSendPolls" = "Send Polls"; +"Channel.BanUser.PermissionChangeGroupInfo" = "Change Group Info"; +"Channel.BanUser.PermissionAddMembers" = "Add Members"; +"Channel.BanUser.Unban" = "Unban"; + +"Channel.BanUser.BlockFor" = "Block For"; + +"Channel.BanList.BlockedTitle" = "BLOCKED"; +"Channel.BanList.RestrictedTitle" = "RESTRICTED"; + +"Group.Info.AdminLog" = "Recent Actions"; +"Channel.AdminLog.InfoPanelTitle" = "What Is This?"; +"Channel.AdminLog.InfoPanelAlertTitle" = "What is the event log?"; +"Channel.AdminLog.InfoPanelAlertText" = "This is a list of all service actions taken by the group's members and admins in the last 48 hours."; +"Channel.AdminLog.InfoPanelChannelAlertText" = "This is a list of all service actions taken by the channel's admins in the last 48 hours."; + +"Channel.AdminLog.BanReadMessages" = "Read Messages"; +"Channel.AdminLog.BanSendMessages" = "Send Messages"; +"Channel.AdminLog.BanSendMedia" = "Send Media"; +"Channel.AdminLog.BanSendStickersAndGifs" = "Send Stickers & GIFs"; +"Channel.AdminLog.BanEmbedLinks" = "Embed Links"; +"Channel.AdminLog.MessageRestricted" = "%@ changed restrictions for %@ (%@)"; +"Channel.AdminLog.MessageAdmin" = "%@ changed privileges for %@ (%@)"; +"Channel.AdminLog.ChangeInfo" = "Change Info"; +"Channel.AdminLog.PinMessages" = "Pin Messages"; +"Channel.AdminLog.AddMembers" = "Add Members"; +"Channel.AdminLog.SendPolls" = "Send Polls"; + +"Channel.AdminLog.CanChangeInfo" = "Change Info"; +"Channel.AdminLog.CanSendMessages" = "Post Messages"; +"Channel.AdminLog.CanDeleteMessages" = "Delete Messages"; +"Channel.AdminLog.CanBanUsers" = "Ban Users"; +"Channel.AdminLog.CanInviteUsers" = "Add Users"; +"Channel.AdminLog.CanPinMessages" = "Pin Messages"; +"Channel.AdminLog.CanAddAdmins" = "Add New Admins"; +"Channel.AdminLog.CanBeAnonymous" = "Remain Anonymous"; +"Channel.AdminLog.CanEditMessages" = "Edit Messages"; + +"Channel.AdminLog.MessageToggleInvitesOn" = "%@ enabled group invites"; +"Channel.AdminLog.MessageToggleInvitesOff" = "%@ disabled group invites"; + +"Channel.AdminLog.MessageUnpinned" = "%@ unpinned message"; + +"Channel.AdminLog.MessageToggleSignaturesOn" = "%@ enabled signatures"; +"Channel.AdminLog.MessageToggleSignaturesOff" = "%@ disabled signatures"; + +"Channel.AdminLog.MessageChangedGroupUsername" = "%@ changed group link:"; +"Channel.AdminLog.MessageChangedChannelUsername" = "%@ changed channel link:"; +"Channel.AdminLog.MessageRemovedGroupUsername" = "%@ removed group link"; +"Channel.AdminLog.MessageRemovedChannelUsername" = "%@ removed channel link"; + +"Channel.AdminLog.MessageChangedGroupAbout" = "%@ edited group description"; +"Channel.AdminLog.MessageChangedChannelAbout" = "%@ edited channel description"; + +"Channel.AdminLog.MessageEdited" = "%@ edited message:"; +"Channel.AdminLog.CaptionEdited" = "%@ edited caption:"; +"Channel.AdminLog.MessageDeleted" = "%@ deleted message:"; +"Channel.AdminLog.MessagePinned" = "%@ pinned message:"; + +"Channel.AdminLog.MessageInvitedName" = "invited %1$@"; +"Channel.AdminLog.MessageInvitedNameUsername" = "invited %1$@ (%2$@)"; +"Channel.AdminLog.MessageKickedName" = "banned %1$@"; +"Channel.AdminLog.MessageKickedNameUsername" = "banned %1$@ (%2$@)"; +"Channel.AdminLog.MessageUnkickedName" = "unbanned %1$@"; +"Channel.AdminLog.MessageUnkickedNameUsername" = "unbanned %1$@ (%2$@)"; +"Channel.AdminLog.MessageRestrictedName" = "changed restrictions for %1$@"; +"Channel.AdminLog.MessageRestrictedNameUsername" = "changed restrictions for %1$@ (%2$@)"; +"Channel.AdminLog.MessagePromotedName" = "changed privileges for %1$@"; +"Channel.AdminLog.MessagePromotedNameUsername" = "changed privileges for %1$@ (%2$@)"; +"Channel.AdminLog.MessageRestrictedUntil" = "until %@"; +"Channel.AdminLog.MessageRestrictedForever" = "indefinitely"; +"Channel.AdminLog.MessageRestrictedNewSetting" = "now: %@"; + +"Channel.AdminLog.MessagePreviousMessage" = "Original message"; +"Channel.AdminLog.MessagePreviousCaption" = "Original caption"; +"Channel.AdminLog.MessagePreviousLink" = "Previous link"; +"Channel.AdminLog.MessagePreviousDescription" = "Previous description"; + +"Contacts.MemberSearchSectionTitleGroup" = "Group Members"; + +"Channel.AdminLog.TitleAllEvents" = "All Actions"; +"Channel.AdminLog.TitleSelectedEvents" = "Selected Actions"; +"Channel.AdminLogFilter.Title" = "Filter"; +"Channel.AdminLogFilter.EventsTitle" = "ACTIONS"; +"Channel.AdminLogFilter.EventsAll" = "All Actions"; +"Channel.AdminLogFilter.EventsRestrictions" = "New Restrictions"; +"Channel.AdminLogFilter.EventsAdmins" = "New Admins"; +"Channel.AdminLogFilter.EventsNewMembers" = "New Members"; +"Channel.AdminLogFilter.EventsInfo" = "Group Info"; +"Channel.AdminLogFilter.ChannelEventsInfo" = "Channel Info"; +"Channel.AdminLogFilter.EventsDeletedMessages" = "Deleted Messages"; +"Channel.AdminLogFilter.EventsEditedMessages" = "Edited Messages"; +"Channel.AdminLogFilter.EventsPinned" = "Pinned Messages"; +"Channel.AdminLogFilter.EventsLeaving" = "Members Removed"; +"Channel.AdminLogFilter.AdminsTitle" = "ADMINS"; +"Channel.AdminLogFilter.AdminsAll" = "All Admins"; + +"Group.ErrorSendRestrictedStickers" = "Sorry, the admins of this group have restricted you from sending stickers."; +"Group.ErrorSendRestrictedMedia" = "Sorry, the admins of this group have restricted you from sending media."; + +"SharedMedia.ViewInChat" = "View in Chat"; + +"Channel.Info.BlackList" = "Blacklist"; + +"Channel.Management.PromotedBy" = "Promoted by %@"; +"DialogList.LanguageTooltip" = "You can change the language later in Settings"; + +"Contacts.PhoneNumber" = "Phone Number"; +"Contacts.AddPhoneNumber" = "Add %@"; +"Contacts.ShareTelegram" = "Share Telegram"; + +"Conversation.ViewChannel" = "VIEW CHANNEL"; +"Conversation.ViewGroup" = "VIEW GROUP"; + +"GroupInfo.ActionPromote" = "Promote"; +"GroupInfo.ActionRestrict" = "Restrict"; + +"Conversation.RestrictedTextTimed" = "The admins of this group have restricted you from writing here until %@."; +"Conversation.RestrictedText" = "The admins of this group have restricted you from writing here."; +"Conversation.DefaultRestrictedText" = "Writing messages isn’t allowed in this group."; + +"Conversation.RestrictedInlineTimed" = "The admins of this group have restricted you from posting inline content here until %@."; +"Conversation.RestrictedInline" = "The admins of this group have restricted you from posting inline content here."; +"Conversation.DefaultRestrictedInline" = "Posting inline content isn’t allowed in this group."; + +"Conversation.RestrictedMediaTimed" = "The admins of this group have restricted you from posting media content here until %@."; +"Conversation.RestrictedMedia" = "The admins of this group have restricted you from posting media content here."; +"Conversation.DefaultRestrictedMedia" = "Posting media content isn’t allowed in this group."; + +"Conversation.RestrictedStickersTimed" = "The admins of this group have restricted you from posting stickers here until %@."; +"Conversation.RestrictedStickers" = "The admins of this group have restricted you from posting stickers here."; +"Conversation.DefaultRestrictedStickers" = "Posting stickers isn’t allowed in this group."; + +"ChatSettings.ConnectionType.Title" = "CONNECTION TYPE"; +"ChatSettings.ConnectionType.UseProxy" = "Use Proxy"; +"ChatSettings.ConnectionType.UseSocks5" = "SOCKS5"; + +"SocksProxySetup.Title" = "Proxy"; + +"SocksProxySetup.TypeNone" = "Disabled"; +"SocksProxySetup.TypeSocks" = "SOCKS5"; + +"SocksProxySetup.Connection" = "CONNECTION"; +"SocksProxySetup.Hostname" = "Server"; +"SocksProxySetup.Port" = "Port"; + +"SocksProxySetup.Credentials" = "CREDENTIALS (OPTIONAL)"; +"SocksProxySetup.Username" = "Username"; +"SocksProxySetup.Password" = "Password"; + +"Channel.AdminLog.EmptyTitle" = "No actions here yet"; +"Channel.AdminLog.EmptyText" = "No service actions were taken by the channel members and admins in the last 48 hours."; +"Group.AdminLog.EmptyText" = "No service actions were taken by the group's members and admins in the last 48 hours."; +"Broadcast.AdminLog.EmptyText" = "No service actions were taken by the channel's admins in the last 48 hours."; + +"Channel.AdminLog.EmptyFilterTitle" = "No actions found"; +"Channel.AdminLog.EmptyFilterQueryText" = "No recent actions that contain '%@' have been found."; +"Channel.AdminLog.EmptyFilterText" = "No recent actions that match your query have been found."; + +"Channel.AdminLog.EmptyMessageText" = "Empty"; + +"Camera.Title" = "Take Photo or Video"; + +"Channel.Members.AddAdminErrorNotAMember" = "Sorry, you can't add this user as an admin because they are not a member of this group and you are not allowed to invite them."; + +"Channel.Members.AddAdminErrorBlacklisted" = "Sorry, you can't add this user as an admin because they are in the blacklist and you can't unban them."; + +"Channel.Members.AddBannedErrorAdmin" = "Sorry, you can't ban this user because they are an admin in this group and you are not allowed to demote them."; + +"Group.Members.AddMemberBotErrorNotAllowed" = "Sorry, you don't have the necessary permissions to add bots to this group."; + +"Privacy.Calls.P2P" = "Peer-to-Peer"; +"Privacy.Calls.P2PHelp" = "Disabling peer-to-peer will relay all calls through Telegram servers to avoid revealing your IP address, but will slightly decrease audio quality."; + +"Privacy.Calls.Integration" = "iOS Call Integration"; +"Privacy.Calls.IntegrationHelp" = "iOS Call Integration shows Telegram calls on the lock screen and in the system's call history. If iCloud sync is enabled, your call history is shared with Apple."; + +"Call.ReportPlaceholder" = "What went wrong?"; +"Call.ReportIncludeLog" = "Send technical information"; +"Call.ReportIncludeLogDescription" = "This won't reveal the contents of your conversation, but will help us fix the issue sooner."; +"Call.ReportSkip" = "Skip"; +"Call.ReportSend" = "Send"; + +"Channel.EditAdmin.CannotEdit" = "You cannot edit the rights of this admin."; +"Call.RateCall" = "Rate This Call"; +"Call.ShareStats" = "Share Statistics"; + +"Settings.ApplyProxyAlert" = "Are you sure you want to enable this proxy?\nServer: %1$@\nPort: %2$@\n\nYou can change your proxy server later it in the Settings (Data and Storage)."; +"Settings.ApplyProxyAlertCredentials" = "Are you sure you want to enable this proxy?\nServer: %1$@\nPort: %2$@\nUsername: %3$@\nPassword: %4$@\n\nYou can change your proxy server later it in the Settings (Data and Storage)."; +"Settings.ApplyProxyAlertEnable" = "Enable"; + +"Channel.Management.RestrictedBy" = "Restricted by %@"; + +"Stickers.FrequentlyUsed" = "Recently Used"; + +"Contacts.ImportersCount_1" = "1 contact on Telegram"; +"Contacts.ImportersCount_2" = "2 contacts on Telegram"; +"Contacts.ImportersCount_3_10" = "%@ contacts on Telegram"; +"Contacts.ImportersCount_any" = "%@ contacts on Telegram"; +"Contacts.ImportersCount_many" = "%@ contacts on Telegram"; +"Contacts.ImportersCount_0" = "%@ contacts on Telegram"; + +"Conversation.ContextMenuBan" = "Restrict"; + +"SocksProxySetup.UseForCalls" = "Use for calls"; +"SocksProxySetup.UseForCallsHelp" = "Proxy servers may degrade the quality of your calls."; + +"InviteText.URL" = "https://telegram.org/dl"; +"InviteText.SingleContact" = "Hey, I'm using Telegram to chat. Join me! Download it here: %@"; +"InviteText.ContactsCountText_1" = "Hey, I'm using Telegram to chat. Join me! Download it here: {url}"; +"InviteText.ContactsCountText_2" = "Hey, I'm using Telegram to chat – and so are 2 of our other contacts. Join us! Download it here: {url}"; +"InviteText.ContactsCountText_3_10" = "Hey, I'm using Telegram to chat – and so are %@ of our other contacts. Join us! Download it here: {url}"; +"InviteText.ContactsCountText_any" = "Hey, I'm using Telegram to chat – and so are %@ of our other contacts. Join us! Download it here: {url}"; +"InviteText.ContactsCountText_many" = "Hey, I'm using Telegram to chat – and so are %@ of our other contacts. Join us! Download it here: {url}"; +"InviteText.ContactsCountText_0" = "Hey, I'm using Telegram to chat. Join me! Download it here: {url}"; + +"Invite.LargeRecipientsCountWarning" = "Please note that it may take some time for your device to send all of these invitations"; + +"Contacts.InviteSearchLabel" = "Search for contacts"; + +"Message.ImageExpired" = "Photo has expired"; +"Message.VideoExpired" = "Video has expired"; + +"SecretImage.Title" = "Disappearing Photo"; +"SecretVideo.Title" = "Disappearing Video"; +"SecretGif.Title" = "Disappearing GIF"; +"SecretTimer.ImageDescription" = "If you set a timer, the photo will self-destruct after it was viewed."; +"SecretTimer.VideoDescription" = "If you set a timer, the video will self-destruct after it was viewed."; + +"PhotoEditor.TiltShift" = "Tilt Shift"; + +"Notification.SecretChatMessageScreenshotSelf" = "You took a screenshot!"; + +"Settings.AboutEmpty" = "Add"; + +"SecretImage.NotViewedYet" = "%@ hasn't opened this photo yet"; +"SecretVideo.NotViewedYet" = "%@ hasn't played this video yet"; +"SecretGIF.NotViewedYet" = "%@ hasn't played this GIF yet"; + +"UserInfo.About.Placeholder" = "Bio"; + +"Call.StatusNoAnswer" = "No Answer"; + +"Conversation.SearchByName.Prefix" = "from: "; +"Conversation.SearchByName.Placeholder" = "Search Members"; + +"Login.PhoneBannedError" = "Your phone was banned."; + +"Clipboard.SendPhoto" = "Send Photo"; + +"HashtagSearch.AllChats" = "All Chats"; + +"Stickers.AddToFavorites" = "Add to Favorites"; +"Stickers.RemoveFromFavorites" = "Remove from Favorites"; + +"Channel.Info.Stickers" = "Group Sticker Set"; +"Channel.Stickers.Placeholder" = "stickerset"; +"Channel.Stickers.YourStickers" = "CHOOSE FROM YOUR STICKERS"; + +"Stickers.FavoriteStickers" = "Favorite Stickers"; +"Stickers.GroupStickers" = "Group Stickers"; +"Stickers.GroupChooseStickerPack" = "CHOOSE STICKER SET"; +"Stickers.GroupStickersHelp" = "You can choose a set that will be available to all group members when they are chatting in this group."; + +"Channel.AdminLog.MessageChangedGroupStickerPack" = "%@ changed group sticker set"; +"Channel.AdminLog.MessageRemovedGroupStickerPack" = "%@ removed group sticker set"; + +"Conversation.ContextMenuCopyLink" = "Copy Link"; + +"Channel.Stickers.Searching" = "Searching..."; +"Channel.Stickers.NotFound" = "No such sticker set found"; +"Channel.Stickers.NotFoundHelp" = "Try again or choose from the list below"; +"Channel.Stickers.CreateYourOwn" = "You can create your own custom sticker set using @stickers bot."; + +"MediaPicker.TimerTooltip" = "You can now set a self-destruct timer"; + +"UserInfo.BlockConfirmation" = "Block %@?"; + +"FastTwoStepSetup.Title" = "Password & Email"; +"FastTwoStepSetup.PasswordSection" = "PASSWORD"; +"FastTwoStepSetup.PasswordPlaceholder" = "Enter a password"; +"FastTwoStepSetup.PasswordConfirmationPlaceholder" = "Re-enter your password"; +"FastTwoStepSetup.PasswordHelp" = "Please create a password to protect your payment info. You'll be asked to enter it when you log in."; +"FastTwoStepSetup.EmailSection" = "RECOVERY E-MAIL"; +"FastTwoStepSetup.EmailPlaceholder" = "Your E-Mail"; +"FastTwoStepSetup.EmailHelp" = "Please add your valid e-mail. It is the only way to recover a forgotten password."; + +"Conversation.ViewMessage" = "VIEW MESSAGE"; + +"GroupInfo.GroupHistory" = "History For New Members"; +"GroupInfo.GroupHistoryVisible" = "Visible"; +"GroupInfo.GroupHistoryHidden" = "Hidden"; + +"Group.Setup.HistoryTitle" = "Chat History Settings"; +"Group.Setup.HistoryHeader" = "HISTORY FOR NEW MEMBERS"; +"Group.Setup.HistoryVisible" = "Visible"; +"Group.Setup.HistoryHidden" = "Hidden"; + +"Group.Setup.HistoryVisibleHelp" = "New members will see messages that were sent before they joined."; +"Group.Setup.HistoryHiddenHelp" = "New members won't see earlier messages."; +"Group.Setup.BasicHistoryHiddenHelp" = "New members won't see more than 100 previous messages."; + + +"Channel.AdminLog.MessageGroupPreHistoryVisible" = "%@ made the group history visible for new members"; +"Channel.AdminLog.MessageGroupPreHistoryHidden" = "%@ made the group history hidden from new members"; + +"Map.PullUpForPlaces" = "PULL UP TO SEE PLACES NEARBY"; +"Map.ShareLiveLocation" = "Share My Live Location for..."; +"Map.ShareLiveLocationHelp" = "Updated in real time as you move"; +"Map.StopLiveLocation" = "Stop Sharing Location"; +"Map.Directions" = "Directions"; +"Map.DirectionsDriveEta" = "%@ drive"; +"Map.Location" = "Location"; +"Map.YouAreHere" = "you are here"; +"Map.LiveLocationShowAll" = "Show All"; + +"Map.LiveLocationTitle" = "Live Location"; +"Map.LiveLocationPrivateDescription" = "Choose for how long %@ will see your accurate location."; +"Map.LiveLocationGroupDescription" = "Choose for how long people in this chat will see your accurate location."; +"Map.LiveLocationFor15Minutes" = "for 15 minutes"; +"Map.LiveLocationFor1Hour" = "for 1 hour"; +"Map.LiveLocationFor8Hours" = "for 8 hours"; +"Map.LiveLocationShortHour" = "%@h"; + +"Message.LiveLocation" = "Live Location"; +"Conversation.LiveLocation" = "Live Location"; + +"Conversation.LiveLocationYou" = "You"; +"Conversation.LiveLocationYouAnd" = "*You* and %@"; +"Conversation.LiveLocationMembersCount_1" = "1 member"; +"Conversation.LiveLocationMembersCount_2" = "2 members"; +"Conversation.LiveLocationMembersCount_3_10" = "%@ members"; +"Conversation.LiveLocationMembersCount_any" = "%@ members"; +"Conversation.LiveLocationMembersCount_many" = "%@ members"; +"Conversation.LiveLocationMembersCount_0" = "%@ members"; + +"Conversation.Admin" = "admin"; + +"LiveLocationUpdated.JustNow" = "updated just now"; +"LiveLocationUpdated.MinutesAgo_0" = "updated %@ minutes ago"; //three to ten +"LiveLocationUpdated.MinutesAgo_1" = "updated 1 minute ago"; //one +"LiveLocationUpdated.MinutesAgo_2" = "updated 2 minutes ago"; //two +"LiveLocationUpdated.MinutesAgo_3_10" = "updated %@ minutes ago"; //three to ten +"LiveLocationUpdated.MinutesAgo_many" = "updated %@ minutes ago"; // more than ten +"LiveLocationUpdated.MinutesAgo_any" = "updated %@ minutes ago"; // more than ten +"LiveLocationUpdated.TodayAt" = "updated at %@"; +"LiveLocationUpdated.YesterdayAt" = "updated yesterday at %@"; + +"LiveLocation.MenuChatsCount_1" = "You are sharing Live Location with 1 chat."; +"LiveLocation.MenuChatsCount_2" = "You are sharing Live Location with 2 chats."; +"LiveLocation.MenuChatsCount_3_10" = "You are sharing Live Location with %@ chats."; +"LiveLocation.MenuChatsCount_any" = "You are sharing Live Location with %@ chats."; +"LiveLocation.MenuChatsCount_many" = "You are sharing Live Location with %@ chats."; +"LiveLocation.MenuChatsCount_0" = "You are sharing Live Location with %@ chats."; +"LiveLocation.MenuStopAll" = "Stop All"; + +"DialogList.LiveLocationSharingTo" = "sharing with %@"; +"DialogList.LiveLocationChatsCount_1" = "sharing with 1 chat"; +"DialogList.LiveLocationChatsCount_2" = "sharing with 2 chats"; +"DialogList.LiveLocationChatsCount_3_10" = "sharing with %@ chats"; +"DialogList.LiveLocationChatsCount_any" = "sharing with %@ chats"; +"DialogList.LiveLocationChatsCount_many" = "sharing with %@ chats"; +"DialogList.LiveLocationChatsCount_0" = "sharing with %@ chats"; + +"Notification.PinnedLiveLocationMessage" = "%@ pinned a live location"; +"Message.PinnedLiveLocationMessage" = "pinned live location"; + +"NotificationSettings.ContactJoined" = "New Contacts"; + +"AccessDenied.LocationAlwaysDenied" = "If you'd like to share your Live Location with friends, Telegram needs location access when the app is in the background.\n\nPlease go to Settings > Privacy > Location Services and set Telegram to Always."; + +"UserInfo.UnblockConfirmation" = "Unblock %@?"; + +"Login.BannedPhoneSubject" = "Banned phone number: %@"; +"Login.BannedPhoneBody" = "I'm trying to use my mobile phone number: %@\nBut Telegram says it's banned. Please help."; + +"Conversation.StopLiveLocation" = "Stop Sharing"; + +"Settings.SavedMessages" = "Saved Messages"; +"Conversation.SavedMessages" = "Saved Messages"; +"DialogList.SavedMessages" = "Saved Messages"; + +"MediaPicker.TapToUngroupDescription" = "Tap to send media separately"; +"MediaPicker.GroupDescription" = "Group media into one message"; +"MediaPicker.UngroupDescription" = "Show media as separate messages"; + +"EditProfile.Title" = "Edit Profile"; +"EditProfile.NameAndPhotoHelp" = "Enter your name and add an optional profile photo."; + +"Settings.SetUsername" = "Set Username"; + +"DialogList.SearchSubtitleFormat" = "%1$@, %2$@"; + +"Media.ShareThisPhoto" = "This Photo"; +"Media.SharePhoto_1" = "%@ Photo"; +"Media.SharePhoto_2" = "All %@ Photos"; +"Media.SharePhoto_3_10" = "All %@ Photos"; +"Media.SharePhoto_any" = "All %@ Photos"; +"Media.SharePhoto_many" = "All %@ Photos"; +"Media.SharePhoto_0" = "All %@ Photos"; + +"Media.ShareThisVideo" = "This Video"; +"Media.ShareVideo_1" = "%@ Video"; +"Media.ShareVideo_2" = "All %@ Videos"; +"Media.ShareVideo_3_10" = "All %@ Videos"; +"Media.ShareVideo_any" = "All %@ Videos"; +"Media.ShareVideo_many" = "All %@ Videos"; +"Media.ShareVideo_0" = "All %@ Videos"; + +"Media.ShareItem_1" = "%@ Item"; +"Media.ShareItem_2" = "All %@ Items"; +"Media.ShareItem_3_10" = "All %@ Items"; +"Media.ShareItem_any" = "All %@ Items"; +"Media.ShareItem_many" = "All %@ Items"; +"Media.ShareItem_0" = "All %@ Items"; + +"Settings.ViewPhoto" = "View Photo"; + +"DialogList.SavedMessagesTooltip" = "You can find your Saved Messages in Settings"; + +"PasscodeSettings.UnlockWithFaceId" = "Unlock with Face ID"; +"Checkout.SavePasswordTimeoutAndFaceId" = "Would you like to save your password for %@ and use Face ID instead?"; +"Checkout.PayWithFaceId" = "Pay with Face ID"; + +"Conversation.StatusSubscribers_0" = "%@ subscribers"; +"Conversation.StatusSubscribers_1" = "%@ subscriber"; +"Conversation.StatusSubscribers_2" = "%@ subscribers"; +"Conversation.StatusSubscribers_3_10" = "%@ subscribers"; +"Conversation.StatusSubscribers_many" = "%@ subscribers"; +"Conversation.StatusSubscribers_any" = "%@ subscribers"; + +"DialogList.SavedMessagesHelp" = "Forward messages here for quick access"; + +"PrivacySettings.PasscodeAndTouchId" = "Passcode & Touch ID"; +"PrivacySettings.PasscodeAndFaceId" = "Passcode & Face ID"; + +"TwoStepAuth.AdditionalPassword" = "Additional Password"; + +"PasscodeSettings.HelpTop" = "When you set up an additional passcode, a lock icon will appear on the chats page. Tap it to lock and unlock the app."; +"PasscodeSettings.HelpBottom" = "Note: if you forget the passcode, you'll need to delete and reinstall the app. All secret chats will be lost."; + +"Channel.Setup.TypePublicHelp" = "Public channels can be found in search, channel history is available to everyone and anyone can join."; +"Channel.Setup.TypePrivateHelp" = "Private channels can only be joined if you were invited or have an invite link."; +"Group.Username.InvalidTooShort" = "Group names must have at least 5 characters."; +"Group.Username.InvalidStartsWithNumber" = "Group names can't start with a number."; +"Group.Username.CreatePublicLinkHelp" = "People can share this link with others and find your group using Telegram search."; +"Channel.TypeSetup.Title" = "Channel Type"; + +"Group.Setup.TypePrivate" = "Private"; +"Group.Setup.TypePublic" = "Public"; + +"Channel.Info.Subscribers" = "Subscribers"; +"Channel.Subscribers.Title" = "Subscribers"; +"Conversation.InfoGroup" = "Group"; + +"Privacy.PaymentsClearInfoDoneHelp" = "Payment & shipping info cleared."; + +"InfoPlist.NSContactsUsageDescription" = "Telegram will continuously upload your contacts to its heavily encrypted cloud servers to let you connect with your friends across all your devices."; +"InfoPlist.NSLocationWhenInUseUsageDescription" = "When you send your location to your friends, Telegram needs access to show them a map."; +"InfoPlist.NSCameraUsageDescription" = "We need this so that you can take and share photos and videos, as well as make video calls."; +"InfoPlist.NSPhotoLibraryUsageDescription" = "We need this so that you can share photos and videos from your photo library."; +"InfoPlist.NSPhotoLibraryAddUsageDescription" = "We need this so that you can save photos and videos to your photo library."; +"InfoPlist.NSMicrophoneUsageDescription" = "We need this so that you can record and share voice messages and videos with sound."; +"InfoPlist.NSSiriUsageDescription" = "You can use Siri to send messages."; +"InfoPlist.NSLocationAlwaysAndWhenInUseUsageDescription" = "When you choose to share your Live Location with friends in a chat, Telegram needs background access to your location to keep them updated for the duration of the live sharing."; +"InfoPlist.NSLocationAlwaysUsageDescription" = "When you choose to share your live location with friends in a chat, Telegram needs background access to your location to keep them updated for the duration of the live sharing. You also need this to send locations from an Apple Watch."; +"InfoPlist.NSLocationWhenInUseUsageDescription" = "When you send your location to your friends, Telegram needs access to show them a map."; +"InfoPlist.NSFaceIDUsageDescription" = "You can use Face ID to unlock the app."; + +"Privacy.Calls.P2PNever" = "Never"; +"Privacy.Calls.P2PContacts" = "My Contacts"; +"Privacy.Calls.P2PAlways" = "Always"; + +"ChatSettings.AutoDownloadTitle" = "AUTO-DOWNLOAD MEDIA"; +"ChatSettings.AutoDownloadEnabled" = "Auto-Download Media"; +"ChatSettings.AutoDownloadPhotos" = "Photos"; +"ChatSettings.AutoDownloadVideos" = "Videos"; +"ChatSettings.AutoDownloadDocuments" = "Documents"; +"ChatSettings.AutoDownloadVoiceMessages" = "Voice Messages"; +"ChatSettings.AutoDownloadVideoMessages" = "Video Messages"; +"ChatSettings.AutoDownloadReset" = "Reset Auto-Download Settings"; + +"AutoDownloadSettings.Title" = "Auto-Download"; + +"AutoDownloadSettings.PhotosTitle" = "Photos"; +"AutoDownloadSettings.VideosTitle" = "Videos"; +"AutoDownloadSettings.DocumentsTitle" = "Documents"; +"AutoDownloadSettings.VoiceMessagesTitle" = "Voice Messages"; +"AutoDownloadSettings.VideoMessagesTitle" = "Video Messages"; + +"AutoDownloadSettings.Cellular" = "CELLULAR"; +"AutoDownloadSettings.WiFi" = "WI-FI"; +"AutoDownloadSettings.Contacts" = "Contacts"; +"AutoDownloadSettings.PrivateChats" = "Other Private Chats"; +"AutoDownloadSettings.GroupChats" = "Group Chats"; +"AutoDownloadSettings.Channels" = "Channels"; +"AutoDownloadSettings.LimitBySize" = "LIMIT BY SIZE"; +"AutoDownloadSettings.UpTo" = "up to %@"; +"AutoDownloadSettings.Unlimited" = "unlimited"; + +"AutoDownloadSettings.Reset" = "Reset"; +"AutoDownloadSettings.ResetHelp" = "Undo all custom auto-download settings."; + +"SaveIncomingPhotosSettings.Title" = "Save Incoming Photos"; +"SaveIncomingPhotosSettings.From" = "SAVE INCOMING PHOTOS FROM"; + +"Channel.AdminLog.ChannelEmptyText" = "No service actions were taken by the channel's subscribers and admins in the last 48 hours."; +"Channel.AdminLogFilter.EventsNewSubscribers" = "New Subscribers"; +"Channel.AdminLogFilter.EventsLeavingSubscribers" = "Subscribers Removed"; + +"Conversation.ClearPrivateHistory" = "This will delete all messages and media in this chat from your Telegram cloud. Your chat partner will still have them."; +"Conversation.ClearGroupHistory" = "This will delete all messages and media in this chat from your Telegram cloud. Other members of the group will still have them."; +"Conversation.ClearSecretHistory" = "This will delete all messages and media in this chat for both you and your chat partner."; +"Conversation.ClearSelfHistory" = "This will delete all messages and media in this chat from your Telegram cloud."; + +"MediaPicker.LivePhotoDescription" = "The live photo will be sent as a GIF."; + +"Settings.Appearance" = "Appearance"; +"Appearance.Title" = "Appearance"; +"Appearance.Preview" = "CHAT PREVIEW"; +"Appearance.ColorTheme" = "COLOR THEME"; +"Appearance.ThemeDayClassic" = "Day Classic"; +"Appearance.ThemeDay" = "Day"; +"Appearance.ThemeNight" = "Night"; +"Appearance.ThemeNightBlue" = "Night Blue"; +"Appearance.PreviewReplyAuthor" = "Lucio"; +"Appearance.PreviewReplyText" = "Reinhardt, we need to find you some..."; +"Appearance.PreviewIncomingText" = "Ah you kids today with techno music! Enjoy the classics, like Hasselhoff!"; +"Appearance.PreviewOutgoingText" = "I can't take you seriously right now. Sorry.."; +"Appearance.AccentColor" = "Accent Color"; +"Appearance.PickAccentColor" = "Pick an Accent Color"; + +"Appearance.AutoNightTheme" = "Auto-Night Theme"; +"Appearance.AutoNightThemeDisabled" = "Disabled"; + +"AutoNightTheme.Title" = "Auto-Night Theme"; +"AutoNightTheme.Disabled" = "Disabled"; +"AutoNightTheme.Scheduled" = "Scheduled"; +"AutoNightTheme.Automatic" = "Automatic"; + +"AutoNightTheme.ScheduleSection" = "SCHEDULE"; +"AutoNightTheme.UseSunsetSunrise" = "Use Location Sunset & Sunrise"; +"AutoNightTheme.ScheduledFrom" = "From"; +"AutoNightTheme.ScheduledTo" = "To"; + +"AutoNightTheme.UpdateLocation" = "Update Location"; +"AutoNightTheme.LocationHelp" = "Calculating sunset & sunrise times requires a one-time check of your approximate location. Note that this location is stored locally on your device only.\n\nSunset: %@\nSunrise: %@"; +"AutoNightTheme.NotAvailable" = "N/A"; + +"AutoNightTheme.AutomaticSection" = "BRIGHTNESS THRESHOLD"; +"AutoNightTheme.AutomaticHelp" = "Switch to night theme when brightness is %@%% or less. Auto-brightness should be enabled for this feature to work correctly."; + +"AutoNightTheme.PreferredTheme" = "PREFERRED THEME"; + +"AuthSessions.Sessions" = "Sessions"; +"AuthSessions.LoggedIn" = "Websites"; +"AuthSessions.LogOutApplications" = "Disconnect All Websites"; +"AuthSessions.LogOutApplicationsHelp" = "You can log in on websites that support signing in with Telegram."; +"AuthSessions.LoggedInWithTelegram" = "CONNECTED WEBSITES"; +"AuthSessions.LogOut" = "Disconnect"; +"AuthSessions.Message" = "You allowed this bot to message you when you logged in on %@."; + +"Conversation.ContextMenuReport" = "Report"; + +"Stickers.Search" = "Search Stickers"; +"Stickers.NoStickersFound" = "No Stickers Found"; + +"Camera.Discard" = "Discard All"; + +"Stickers.SuggestStickers" = "Suggest Stickers by Emoji"; +"Stickers.SuggestAll" = "All Sets"; +"Stickers.SuggestAdded" = "My Sets"; +"Stickers.SuggestNone" = "None"; + +"Settings.Proxy" = "Proxy"; +"Settings.ProxyDisabled" = "Disabled"; +"Settings.ProxyConnecting" = "Connecting..."; +"Settings.ProxyConnected" = "Connected"; + +"SocksProxySetup.UseProxy" = "Use Proxy"; +"SocksProxySetup.SavedProxies" = "SAVED PROXIES"; +"SocksProxySetup.AddProxy" = "Add Proxy"; +"SocksProxySetup.SaveProxy" = "Save Proxy"; +"SocksProxySetup.ConnectAndSave" = "Connect Proxy"; +"SocksProxySetup.AddProxyTitle" = "Add Proxy"; +"SocksProxySetup.ProxyDetailsTitle" = "Proxy Details"; +"SocksProxySetup.ProxyStatusChecking" = "checking..."; +"SocksProxySetup.ProxyStatusPing" = "%@ ms ping"; +"SocksProxySetup.ProxyStatusUnavailable" = "unavailable"; +"SocksProxySetup.ProxyStatusConnecting" = "connecting"; +"SocksProxySetup.ProxyStatusConnected" = "connected"; + +"SocksProxySetup.ProxyType" = "TYPE"; +"SocksProxySetup.ProxySocks5" = "SOCKS5"; +"SocksProxySetup.ProxyTelegram" = "MTProto"; +"SocksProxySetup.HostnamePlaceholder" = "Server"; +"SocksProxySetup.PortPlaceholder" = "Port"; +"SocksProxySetup.UsernamePlaceholder" = "Username"; +"SocksProxySetup.PasswordPlaceholder" = "Password"; +"SocksProxySetup.Secret" = "Secret"; +"SocksProxySetup.SecretPlaceholder" = "Secret"; +"SocksProxySetup.RequiredCredentials" = "CREDENTIALS"; + +"SocksProxySetup.Connecting" = "Connecting..."; +"SocksProxySetup.FailedToConnect" = "Failed to connect"; + +"SocksProxySetup.ProxyEnabled" = "Proxy\nEnabled"; + +"DialogList.AdLabel" = "Proxy Sponsor"; +"DialogList.AdNoticeAlert" = "The proxy you are using displays a sponsored channel in your chat list."; +"SocksProxySetup.AdNoticeHelp" = "This proxy may display a sponsored channel in your chat list. This doesn't reveal your Telegram traffic."; + +"SocksProxySetup.ShareProxyList" = "Share Proxy List"; + +"Privacy.SecretChatsTitle" = "SECRET CHATS"; +"Privacy.SecretChatsLinkPreviews" = "Link Previews"; +"Privacy.SecretChatsLinkPreviewsHelp" = "Link previews will be generated on Telegram servers. We do not store data about the links you send."; + +"Privacy.ContactsTitle" = "CONTACTS"; +"Privacy.ContactsSync" = "Sync Contacts"; +"Privacy.ContactsSyncHelp" = "Turn on to continuously sync contacts from this device with your account."; +"Privacy.ContactsReset" = "Delete Synced Contacts"; +"Privacy.ContactsResetConfirmation" = "This will remove your contacts from the Telegram servers.\nIf 'Sync Contacts' is enabled, contacts will be re-synced."; + +"Login.TermsOfServiceDecline" = "Decline"; +"Login.TermsOfServiceAgree" = "Agree & Continue"; + +"Login.TermsOfService.ProceedBot" = "Please agree and proceed to %@."; + +"Login.TermsOfServiceSignupDecline" = "We're very sorry, but this means you can't sign up for Telegram.\n\nUnlike others, we don't use your data for ad targeting or other commercial purposes. Telegram only stores the information it needs to function as a feature-rich cloud service. You can adjust how we use your data (e.g., delete synced contacts) in Privacy & Security settings.\n\nBut if you're generally not OK with Telegram's modest needs, it won't be possible for us to provide this service."; + +"UserInfo.BotPrivacy" = "Privacy Policy"; + +"PrivacyPolicy.Title" = "Privacy Policy and Terms of Service"; +"PrivacyPolicy.Decline" = "Decline"; +"PrivacyPolicy.Accept" = "Agree & Continue"; + +"PrivacyPolicy.AgeVerificationTitle" = "Age Verification"; +"PrivacyPolicy.AgeVerificationMessage" = "Tap Agree to confirm that you are %@ or over."; +"PrivacyPolicy.AgeVerificationAgree" = "Agree"; + +"PrivacyPolicy.DeclineTitle" = "Decline"; +"PrivacyPolicy.DeclineMessage" = "We're very sorry, but this means we must part ways here. Unlike others, we don't use your data for ad targeting or other commercial purposes. Telegram only stores the information it needs to function as a feature-rich cloud service. You can adjust how we use your data (e.g., delete synced contacts) in Privacy & Security settings.\n\nBut if you're generally not OK with Telegram's modest needs, it won't be possible for us to provide this service."; +"PrivacyPolicy.DeclineDeclineAndDelete" = "Decline and Delete"; + +"PrivacyPolicy.DeclineLastWarning" = "Warning, this will irreversibly delete your Telegram account along with all the data you store in the Telegram cloud.\n\nWe will provide a tool to download your data before June, 23 – so you may want to wait a little before deleting."; +"PrivacyPolicy.DeclineDeleteNow" = "Delete Now"; + +"Settings.Passport" = "Telegram Passport"; + +"Passport.Title" = "Passport"; + +"Passport.RequestHeader" = "%@ requests access to your personal data to sign you up for their services."; + +"Passport.InfoTitle" = "What is Telegram Passport?"; +"Passport.InfoText" = "With **Telegram Passport** you can easily sign up for websites and services that require identity verification.\n\nYour information, personal data, and documents are protected by end-to-end encryption. Nobody, including Telegram, can access them without your permission."; +"Passport.InfoLearnMore" = "Learn More"; +"Passport.InfoFAQ_URL" = "https://telegram.org/faq#passport"; + +"Passport.PassportInformation" = "PASSPORT INFORMATION"; +"Passport.RequestedInformation" = "REQUESTED INFORMATION"; +"Passport.FieldIdentity" = "Identity Document"; +"Passport.FieldIdentityDetailsHelp" = "Fill in your personal details"; +"Passport.FieldIdentityUploadHelp" = "Upload a scan of your passport or other ID"; +"Passport.FieldIdentitySelfieHelp" = "Take a selfie with your document"; +"Passport.FieldAddress" = "Residential Address"; +"Passport.FieldAddressHelp" = "Please provide your address"; +"Passport.FieldAddressUploadHelp" = "Upload proof of your address"; +"Passport.FieldPhone" = "Phone Number"; +"Passport.FieldPhoneHelp" = "Provide your contact phone number"; +"Passport.FieldEmail" = "Email Address"; +"Passport.FieldEmailHelp" = "Provide your contact email address"; +"Passport.PrivacyPolicy" = "You accept the [%1$@ Privacy Policy] and allow their @%2$@ to send you messages."; +"Passport.AcceptHelp" = "You are sending your documents directly to %1$@ and allowing their @%2$@ to send you messages."; +"Passport.Authorize" = "Authorize"; + +"Passport.DeletePassport" = "Delete Telegram Passport"; +"Passport.DeletePassportConfirmation" = "Are you sure you want to delete your Telegram Passport? All details will be lost."; + +"Passport.PasswordHelp" = "Please enter your Telegram Password\nto decrypt your data"; +"Passport.PasswordPlaceholder" = "Enter your password"; +"Passport.InvalidPasswordError" = "Invalid password. Please try again."; +"Passport.FloodError" = "Limit exceeded. Please try again later."; +"Passport.UpdateRequiredError" = "Sorry, your Telegram app is out of date and can’t handle this request. Please update Telegram."; + +"Passport.ForgottenPassword" = "Forgotten Password"; +"Passport.PasswordReset" = "All documents uploaded to your Telegram Passport will be lost. You will be able to upload new documents."; + +"Passport.PasswordDescription" = "Please create a password to secure your personal data with end-to-end encryption.\n\nThis password will also be required whenever you log in to Telegram on a new device."; +"Passport.PasswordCreate" = "Create a Password"; +"Passport.PasswordCompleteSetup" = "Complete Password Setup"; +"Passport.PasswordNext" = "Next"; + +"Passport.DeletePersonalDetails" = "Delete Personal Details"; +"Passport.DeletePersonalDetailsConfirmation" = "Are you sure you want to delete personal details?"; + +"Passport.DeleteAddress" = "Delete Address"; +"Passport.DeleteAddressConfirmation" = "Are you sure you want to delete address?"; + +"Passport.DeleteDocument" = "Delete Document"; +"Passport.DeleteDocumentConfirmation" = "Are you sure you want to delete this document? All details will be lost."; + +"Passport.Scans" = "SCANS"; +"Passport.Scans.Upload" = "Upload Scan"; +"Passport.Scans.UploadNew" = "Upload Additional Scan"; +"Passport.Scans.ScanIndex" = "Scan %@"; + +"Passport.Identity.TypePersonalDetails" = "Personal Details"; +"Passport.Identity.TypePassport" = "Passport"; +"Passport.Identity.TypePassportUploadScan" = "Upload a scan of your passport"; +"Passport.Identity.TypeInternalPassport" = "Internal Passport"; +"Passport.Identity.TypeInternalPassportUploadScan" = "Upload a scan of your internal passport"; +"Passport.Identity.TypeIdentityCard" = "Identity Card"; +"Passport.Identity.TypeIdentityCardUploadScan" = "Upload a scan of your identity card"; +"Passport.Identity.TypeDriversLicense" = "Driver's License"; +"Passport.Identity.TypeDriversLicenseUploadScan" = "Upload a scan of your driver's license"; + +"Passport.Identity.AddPersonalDetails" = "Add Personal Details"; +"Passport.Identity.AddPassport" = "Add Passport"; +"Passport.Identity.AddInternalPassport" = "Add Internal Passport"; +"Passport.Identity.AddIdentityCard" = "Add Identity Card"; +"Passport.Identity.AddDriversLicense" = "Add Driver's License"; + +"Passport.Identity.EditPersonalDetails" = "Edit Personal Details"; +"Passport.Identity.EditPassport" = "Edit Passport"; +"Passport.Identity.EditInternalPassport" = "Edit Internal Passport"; +"Passport.Identity.EditIdentityCard" = "Edit Identity Card"; +"Passport.Identity.EditDriversLicense" = "Edit Driver's License"; + +"Passport.Identity.DocumentDetails" = "DOCUMENT DETAILS"; +"Passport.Identity.Name" = "First Name"; +"Passport.Identity.NamePlaceholder" = "First Name"; +"Passport.Identity.MiddleName" = "Middle Name"; +"Passport.Identity.MiddleNamePlaceholder" = "Middle Name"; +"Passport.Identity.Surname" = "Last Name"; +"Passport.Identity.SurnamePlaceholder" = "Last Name"; +"Passport.Identity.DateOfBirth" = "Date of Birth"; +"Passport.Identity.DateOfBirthPlaceholder" = "Date of Birth"; +"Passport.Identity.Gender" = "Gender"; +"Passport.Identity.GenderPlaceholder" = "Gender"; +"Passport.Identity.GenderMale" = "Male"; +"Passport.Identity.GenderFemale" = "Female"; +"Passport.Identity.Country" = "Citizenship"; +"Passport.Identity.CountryPlaceholder" = "Citizenship"; +"Passport.Identity.ResidenceCountry" = "Residence"; +"Passport.Identity.ResidenceCountryPlaceholder" = "Residence"; +"Passport.Identity.DocumentNumber" = "Document #"; +"Passport.Identity.DocumentNumberPlaceholder" = "Document Number"; +"Passport.Identity.IssueDate" = "Issue Date"; +"Passport.Identity.IssueDatePlaceholder" = "Issue Date"; +"Passport.Identity.ExpiryDate" = "Expiry Date"; +"Passport.Identity.ExpiryDatePlaceholder" = "Expiry Date"; +"Passport.Identity.ExpiryDateNone" = "None"; +"Passport.Identity.DoesNotExpire" = "Does Not Expire"; + +"Passport.Identity.FilesTitle" = "REQUESTED FILES"; +"Passport.Identity.ScansHelp" = "The document must contain your photograph, first and last name, date of birth, document number, country of issue, and expiry date."; +"Passport.Identity.FilesView" = "View"; +"Passport.Identity.FilesUploadNew" = "Upload New"; +"Passport.Identity.MainPage" = "Main Page"; +"Passport.Identity.MainPageHelp" = "Upload a main page photo of the document"; +"Passport.Identity.FrontSide" = "Front Side"; +"Passport.Identity.FrontSideHelp" = "Upload a front side photo of the document"; +"Passport.Identity.ReverseSide" = "Reverse Side"; +"Passport.Identity.ReverseSideHelp" = "Upload a reverse side photo of the document"; +"Passport.Identity.Selfie" = "Selfie"; +"Passport.Identity.SelfieHelp" = "Upload a selfie holding this document"; +"Passport.Identity.Translation" = "Translation"; +"Passport.Identity.TranslationHelp" = "Upload a translation of this document"; + +"Passport.Address.TypeResidentialAddress" = "Residential Address"; +"Passport.Address.TypePassportRegistration" = "Passport Registration"; +"Passport.Address.TypeUtilityBill" = "Utility Bill"; +"Passport.Address.TypeBankStatement" = "Bank Statement"; +"Passport.Address.TypeRentalAgreement" = "Tenancy Agreement"; +"Passport.Address.TypeTemporaryRegistration" = "Temporary Registration"; + +"Passport.Address.AddResidentialAddress" = "Add Residential Address"; +"Passport.Address.AddPassportRegistration" = "Add Passport Registration"; +"Passport.Address.AddUtilityBill" = "Add Utility Bill"; +"Passport.Address.AddBankStatement" = "Add Bank Statement"; +"Passport.Address.AddRentalAgreement" = "Add Tenancy Agreement"; +"Passport.Address.AddTemporaryRegistration" = "Add Temporary Registration"; + +"Passport.Address.EditResidentialAddress" = "Edit Residential Address"; +"Passport.Address.EditPassportRegistration" = "Edit Passport Registration"; +"Passport.Address.EditUtilityBill" = "Edit Utility Bill"; +"Passport.Address.EditBankStatement" = "Edit Bank Statement"; +"Passport.Address.EditRentalAgreement" = "Edit Tenancy Agreement"; +"Passport.Address.EditTemporaryRegistration" = "Edit Temporary Registration"; + +"Passport.Address.Address" = "ADDRESS"; +"Passport.Address.Street" = "Street"; +"Passport.Address.Street1Placeholder" = "Street and number, P.O. box"; +"Passport.Address.Street2Placeholder" = "Apt., suite, unit, building, floor"; +"Passport.Address.Postcode" = "Postcode"; +"Passport.Address.PostcodePlaceholder" = "Postcode"; +"Passport.Address.City" = "City"; +"Passport.Address.CityPlaceholder" = "City"; +"Passport.Address.Region" = "Region"; +"Passport.Address.RegionPlaceholder" = "State / Province / Region"; +"Passport.Address.Country" = "Country"; +"Passport.Address.CountryPlaceholder" = "Country"; + +"Passport.Address.ScansHelp" = "The document must contain your first and last name, your residential address, a stamp / barcode / QR code / logo, and issue date, no more than 3 months ago."; + +"Passport.Phone.Title" = "Phone Number"; +"Passport.Phone.UseTelegramNumber" = "Use %@"; +"Passport.Phone.UseTelegramNumberHelp" = "Use the same phone number as on Telegram."; +"Passport.Phone.EnterOtherNumber" = "OR ENTER NEW PHONE NUMBER"; +"Passport.Phone.Help" = "Note: You will receive a confirmation code on the phone number you provide."; +"Passport.Phone.Delete" = "Delete Phone Number"; + +"Passport.Email.Title" = "Email"; +"Passport.Email.UseTelegramEmail" = "Use %@"; +"Passport.Email.UseTelegramEmailHelp" = "Use the same address as on Telegram."; +"Passport.Email.EnterOtherEmail" = "OR ENTER NEW EMAIL ADDRESS"; +"Passport.Email.EmailPlaceholder" = "Enter your email address"; +"Passport.Email.Help" = "Note: You will receive a confirmation code to the email address you provide."; +"Passport.Email.Delete" = "Delete Email Address"; +"Passport.Email.CodeHelp" = "Please enter the confirmation code we've just sent to %@"; + +"Notification.PassportValuesSentMessage" = "%1$@ received the following documents: %2$@"; +"Notification.PassportValuePersonalDetails" = "personal details"; +"Notification.PassportValueProofOfIdentity" = "proof of identity"; +"Notification.PassportValueAddress" = "your address"; +"Notification.PassportValueProofOfAddress" = "proof of address"; +"Notification.PassportValuePhone" = "phone number"; +"Notification.PassportValueEmail" = "email address"; + +"FastTwoStepSetup.HintSection" = "HINT"; +"FastTwoStepSetup.HintPlaceholder" = "Enter a hint"; +"FastTwoStepSetup.HintHelp" = "Please create an optional hint for your password."; + +"Passport.DiscardMessageTitle" = "Discard Changes"; +"Passport.DiscardMessageDescription" = "Are you sure you want to discard all changes?"; +"Passport.DiscardMessageAction" = "Discard"; + +"Passport.ScanPassport" = "Scan Your Passport"; +"Passport.ScanPassportHelp" = "Scan your passport or identity card with machine-readable zone to fill personal details automatically."; + +"TwoStepAuth.PasswordRemovePassportConfirmation" = "Are you sure you want to disable your password?\n\nWarning! All data saved in your Telegram Passport will be lost!"; + +"Application.Update" = "Update"; + +"Conversation.EditingMessagePanelMedia" = "Tap to edit media"; +"Conversation.EditingMessageMediaChange" = "Change Photo or Video"; +"Conversation.EditingMessageMediaEditCurrentPhoto" = "Edit Current Photo"; +"Conversation.EditingMessageMediaEditCurrentVideo" = "Edit Current Video"; + +"Conversation.InputTextCaptionPlaceholder" = "Caption"; + +"Conversation.ViewContactDetails" = "VIEW CONTACT"; + +"DialogList.Read" = "Read"; +"DialogList.Unread" = "Unread"; + +"ContactInfo.Title" = "Contact Info"; +"ContactInfo.PhoneLabelHome" = "home"; +"ContactInfo.PhoneLabelWork" = "work"; +"ContactInfo.PhoneLabelMobile" = "mobile"; +"ContactInfo.PhoneLabelMain" = "main"; +"ContactInfo.PhoneLabelHomeFax" = "home fax"; +"ContactInfo.PhoneLabelWorkFax" = "work fax"; +"ContactInfo.PhoneLabelPager" = "pager"; +"ContactInfo.PhoneLabelOther" = "other"; +"ContactInfo.URLLabelHomepage" = "homepage"; +"ContactInfo.BirthdayLabel" = "birthday"; +"ContactInfo.Job" = "job"; + +"UserInfo.NotificationsDefault" = "Default"; +"UserInfo.NotificationsDefaultSound" = "Default (%@)"; + +"DialogList.ProxyConnectionIssuesTooltip" = "Can’t connect to your preferred proxy.\nTap to change settings."; + +"Conversation.TapAndHoldToRecord" = "Tap and hold to record"; + +"Privacy.TopPeers" = "Suggest Frequent Contacts"; +"Privacy.TopPeersHelp" = "Display people you message frequently at the top of the search section for quick access."; +"Privacy.TopPeersWarning" = "This will delete all data about the people you message frequently as well the inline bots you are likely to use."; +"Privacy.TopPeersDelete" = "Delete"; + +"Conversation.EditingCaptionPanelTitle" = "Edit Caption"; + +"Passport.CorrectErrors" = "Tap to correct errors"; + +"Passport.NotLoggedInMessage" = "Please log in to your account to use Telegram Passport"; + +"Update.Title" = "Telegram Update"; +"Update.AppVersion" = "Telegram %@"; +"Update.UpdateApp" = "Update Telegram"; +"Update.Skip" = "Skip"; + +"ReportPeer.ReasonCopyright" = "Copyright"; + +"PrivacySettings.DataSettings" = "Data Settings"; +"PrivacySettings.DataSettingsHelp" = "Control which of your data is stored in the cloud and used by Telegram to enable advanced features."; + +"PrivateDataSettings.Title" = "Data Settings"; +"Privacy.ChatsTitle" = "CHATS"; +"Privacy.DeleteDrafts" = "Delete All Cloud Drafts"; + +"UserInfo.NotificationsDefaultEnabled" = "Default (Enabled)"; +"UserInfo.NotificationsDefaultDisabled" = "Default (Disabled)"; + +"Notifications.MessageNotificationsExceptions" = "Exceptions"; +"Notifications.GroupNotificationsExceptions" = "Exceptions"; + +"Notifications.ExceptionsNone" = "None"; +"Notifications.Exceptions_1" = "%@ chat"; +"Notifications.Exceptions_2" = "%@ chats"; +"Notifications.Exceptions_3_10" = "%@ chats"; +"Notifications.Exceptions_any" = "%@ chats"; +"Notifications.Exceptions_many" = "%@ chats"; +"Notifications.Exceptions_0" = "%@ chats"; + +"Notifications.ExceptionMuteExpires.Minutes_1" = "In 1 minute"; +"Notifications.ExceptionMuteExpires.Minutes_2" = "In 2 minutes"; +"Notifications.ExceptionMuteExpires.Minutes_3_10" = "In %@ minutes"; +"Notifications.ExceptionMuteExpires.Minutes_any" = "In %@ minutes"; +"Notifications.ExceptionMuteExpires.Minutes_many" = "In %@ minutes"; +"Notifications.ExceptionMuteExpires.Minutes_0" = "In %@ minutes"; + +"Notifications.ExceptionMuteExpires.Hours_1" = "In 1 hour"; +"Notifications.ExceptionMuteExpires.Hours_2" = "In 2 hours"; +"Notifications.ExceptionMuteExpires.Hours_3_10" = "In %@ hours"; +"Notifications.ExceptionMuteExpires.Hours_any" = "In %@ hours"; +"Notifications.ExceptionMuteExpires.Hours_many" = "In %@ hours"; +"Notifications.ExceptionMuteExpires.Hours_0" = "In %@ hours"; + +"Notifications.ExceptionMuteExpires.Days_1" = "In 1 day"; +"Notifications.ExceptionMuteExpires.Days_2" = "In 2 days"; +"Notifications.ExceptionMuteExpires.Days_3_10" = "In %@ days"; +"Notifications.ExceptionMuteExpires.Days_any" = "In %@ days"; +"Notifications.ExceptionMuteExpires.Days_many" = "In %@ days"; +"Notifications.ExceptionMuteExpires.Days_0" = "In %@ days"; + +"Notifications.ExceptionsTitle" = "Exceptions"; +"Notifications.ExceptionsChangeSound" = "Change Sound (%@)"; +"Notifications.ExceptionsDefaultSound" = "Default"; +"Notifications.ExceptionsMuted" = "Muted"; +"Notifications.ExceptionsUnmuted" = "Unmuted"; +"Notifications.AddExceptionTitle" = "Add Exception"; + +"Notifications.ExceptionsMessagePlaceholder" = "This section will list all private chats with non-default notification settings."; +"Notifications.ExceptionsGroupPlaceholder" = "This section will list all groups and channels with non-default notification settings."; + +"Passport.Identity.LatinNameHelp" = "Enter your name using the Latin alphabet"; +"Passport.Identity.NativeNameTitle" = "YOUR NAME IN %@"; +"Passport.Identity.NativeNameGenericTitle" = "NAME IN DOCUMENT LANGUAGE"; +"Passport.Identity.NativeNameHelp" = "Your name in the language of the country that issued the document."; +"Passport.Identity.NativeNameGenericHelp" = "Your name in the language of the country (%@) that issued the document."; + +"Passport.Identity.Translations" = "TRANSLATION"; +"Passport.Identity.TranslationsHelp" = "Upload scans of verified translation of the document."; +"Passport.FieldIdentityTranslationHelp" = "Upload a translation of your document"; +"Passport.FieldAddressTranslationHelp" = "Upload a translation of your document"; + +"Passport.FieldOneOf.Or" = "%1$@ or %2$@"; +"Passport.Identity.UploadOneOfScan" = "Upload a scan of your %@"; +"Passport.Address.UploadOneOfScan" = "Upload a scan of your %@"; + +"Passport.Address.TypeUtilityBillUploadScan" = "Upload a scan of your utiliity bill"; +"Passport.Address.TypeBankStatementUploadScan" = "Upload a scan of your bank statement"; +"Passport.Address.TypeRentalAgreementUploadScan" = "Upload a scan of your tenancy agreement"; +"Passport.Address.TypePassportRegistrationUploadScan" = "Upload a scan of your passport registration"; +"Passport.Address.TypeTemporaryRegistrationUploadScan" = "Upload a scan of your temporary registration"; + +"Passport.Identity.OneOfTypePassport" = "passport"; +"Passport.Identity.OneOfTypeInternalPassport" = "internal passport"; +"Passport.Identity.OneOfTypeIdentityCard" = "identity card"; +"Passport.Identity.OneOfTypeDriversLicense" = "driver's license"; + +"Passport.Address.OneOfTypePassportRegistration" = "passport registration"; +"Passport.Address.OneOfTypeUtilityBill" = "utility bill"; +"Passport.Address.OneOfTypeBankStatement" = "bank statement"; +"Passport.Address.OneOfTypeRentalAgreement" = "tenancy agreement"; +"Passport.Address.OneOfTypeTemporaryRegistration" = "temporary registration"; + +"Passport.FieldOneOf.Delimeter" = ", "; +"Passport.FieldOneOf.FinalDelimeter" = " or "; + +"Passport.Scans_1" = "%@ scan"; +"Passport.Scans_2" = "%@ scans"; +"Passport.Scans_3_10" = "%@ scans"; +"Passport.Scans_any" = "%@ scans"; +"Passport.Scans_many" = "%@ scans"; +"Passport.Scans_0" = "%@ scans"; + +"NotificationsSound.None" = "None"; +"NotificationsSound.Note" = "Note"; +"NotificationsSound.Aurora" = "Aurora"; +"NotificationsSound.Bamboo" = "Bamboo"; +"NotificationsSound.Chord" = "Chord"; +"NotificationsSound.Circles" = "Circles"; +"NotificationsSound.Complete" = "Complete"; +"NotificationsSound.Hello" = "Hello"; +"NotificationsSound.Input" = "Input"; +"NotificationsSound.Keys" = "Keys"; +"NotificationsSound.Popcorn" = "Popcorn"; +"NotificationsSound.Pulse" = "Pulse"; +"NotificationsSound.Synth" = "Synth"; + +"NotificationsSound.Tritone" = "Tri-tone"; +"NotificationsSound.Tremolo" = "Tremolo"; +"NotificationsSound.Alert" = "Alert"; +"NotificationsSound.Bell" = "Bell"; +"NotificationsSound.Calypso" = "Calypso"; +"NotificationsSound.Chime" = "Chime"; +"NotificationsSound.Glass" = "Glass"; +"NotificationsSound.Telegraph" = "Telegraph"; + +"Settings.CopyPhoneNumber" = "Copy Phone Number"; +"Settings.CopyUsername" = "Copy Username"; + +"Passport.Language.ar" = "Arabic"; +"Passport.Language.az" = "Azerbaijani"; +"Passport.Language.bg" = "Bulgarian"; +"Passport.Language.bn" = "Bangla"; +"Passport.Language.cs" = "Czech"; +"Passport.Language.da" = "Danish"; +"Passport.Language.de" = "German"; +"Passport.Language.dv" = "Divehi"; +"Passport.Language.dz" = "Dzongkha"; +"Passport.Language.el" = "Greek"; +"Passport.Language.en" = "English"; +"Passport.Language.es" = "Spanish"; +"Passport.Language.et" = "Estonian"; +"Passport.Language.fa" = "Persian"; +"Passport.Language.fr" = "French"; +"Passport.Language.he" = "Hebrew"; +"Passport.Language.hr" = "Croatian"; +"Passport.Language.hu" = "Hungarian"; +"Passport.Language.hy" = "Armenian"; +"Passport.Language.id" = "Indonesian"; +"Passport.Language.is" = "Icelandic"; +"Passport.Language.it" = "Italian"; +"Passport.Language.ja" = "Japanese"; +"Passport.Language.ka" = "Georgian"; +"Passport.Language.km" = "Khmer"; +"Passport.Language.ko" = "Korean"; +"Passport.Language.lo" = "Lao"; +"Passport.Language.lt" = "Lithuanian"; +"Passport.Language.lv" = "Latvian"; +"Passport.Language.mk" = "Macedonian"; +"Passport.Language.mn" = "Mongolian"; +"Passport.Language.ms" = "Malay"; +"Passport.Language.my" = "Burmese"; +"Passport.Language.ne" = "Nepali"; +"Passport.Language.nl" = "Dutch"; +"Passport.Language.pl" = "Polish"; +"Passport.Language.pt" = "Portuguese"; +"Passport.Language.ro" = "Romanian"; +"Passport.Language.ru" = "Russian"; +"Passport.Language.sk" = "Slovak"; +"Passport.Language.sl" = "Slovenian"; +"Passport.Language.th" = "Thai"; +"Passport.Language.tk" = "Turkmen"; +"Passport.Language.tr" = "Turkish"; +"Passport.Language.uk" = "Ukrainian"; +"Passport.Language.uz" = "Uzbek"; +"Passport.Language.vi" = "Vietnamese"; + +"Conversation.EmptyGifPanelPlaceholder" = "You have no saved GIFs yet.\nEnter @gif to search."; +"DialogList.MultipleTyping" = "%@ and %@"; +"Contacts.NotRegisteredSection" = "Phonebook"; + +"SocksProxySetup.PasteFromClipboard" = "Paste From Clipboard"; + +"Share.AuthTitle" = "Log in to Telegram"; +"Share.AuthDescription" = "Open Telegram and log in to share."; + +"Notifications.DisplayNamesOnLockScreen" = "Names on lock-screen"; +"Notifications.DisplayNamesOnLockScreenInfoWithLink" = "Display names in notifications when the device is locked. To disable, make sure that \"Show Previews\" is also set to \"When Unlocked\" or \"Never\" in [iOS Settings]"; + +"Notifications.Badge" = "BADGE COUNTER"; +"Notifications.Badge.IncludeMutedChats" = "Include Muted Chats"; +"Notifications.Badge.IncludePublicGroups" = "Include Public Groups"; +"Notifications.Badge.IncludeChannels" = "Include Channels"; +"Notifications.Badge.CountUnreadMessages" = "Count Unread Messages"; +"Notifications.Badge.CountUnreadMessages.InfoOff" = "Switch on to show the number of unread messages instead of chats."; +"Notifications.Badge.CountUnreadMessages.InfoOn" = "Switch off to show the number of unread chats instead of messages."; + +"Appearance.ReduceMotion" = "Reduce Motion"; +"Appearance.ReduceMotionInfo" = "Disable animations in message bubbles and in the chats list."; + +"Appearance.Animations" = "ANIMATIONS"; + +"Weekday.Monday" = "Monday"; +"Weekday.Tuesday" = "Tuesday"; +"Weekday.Wednesday" = "Wednesday"; +"Weekday.Thursday" = "Thursday"; +"Weekday.Friday" = "Friday"; +"Weekday.Saturday" = "Saturday"; +"Weekday.Sunday" = "Sunday"; + +"Watch.Message.Call" = "Call"; +"Watch.Message.Game" = "Game"; +"Watch.Message.Invoice" = "Invoice"; +"Watch.Message.Poll" = "Poll"; +"Watch.Message.Unsupported" = "Unsupported Message"; + +"Notifications.ExceptionsResetToDefaults" = "Reset to Defaults"; + +"AuthSessions.IncompleteAttempts" = "INCOMPLETE LOGIN ATTEMPTS"; +"AuthSessions.IncompleteAttemptsInfo" = "These devices have no access to your account. The code was entered correctly, but no correct password was given."; + +"AuthSessions.Terminate" = "Terminate"; + +"ApplyLanguage.ChangeLanguageAlreadyActive" = "The language %1$@ is already active."; +"ApplyLanguage.ChangeLanguageTitle" = "Change Language?"; +"ApplyLanguage.ChangeLanguageUnofficialText" = "You are about to apply a custom language pack **%1$@** that is %2$@% complete.\n\nThis will translate the entire interface. You can suggest corrections in the [translation panel]().\n\nYou can change your language back at any time in Settings."; +"ApplyLanguage.ChangeLanguageOfficialText" = "You are about to apply a language pack **%1$@**.\n\nThis will translate the entire interface. You can suggest corrections in the [translation panel]().\n\nYou can change your language back at any time in Settings."; +"ApplyLanguage.ChangeLanguageAction" = "Change"; +"ApplyLanguage.ApplyLanguageAction" = "Change"; +"ApplyLanguage.UnsufficientDataTitle" = "Insufficient Data"; +"ApplyLanguage.UnsufficientDataText" = "Unfortunately, this custom language pack (%1$@) doesn't contain data for Telegram iOS. You can contribute to this language pack using the [translations platform]()"; +"ApplyLanguage.LanguageNotSupportedError" = "Sorry, this language doesn't seem to exist."; +"ApplyLanguage.ApplySuccess" = "Language changed"; + +"TextFormat.Bold" = "Bold"; +"TextFormat.Italic" = "Italic"; +"TextFormat.Monospace" = "Monospace"; + +"TwoStepAuth.SetupPasswordTitle" = "Create a Password"; +"TwoStepAuth.SetupPasswordDescription" = "Please create a password which will be used to protect your data."; +"TwoStepAuth.ChangePassword" = "Change Password"; +"TwoStepAuth.ChangePasswordDescription" = "Please enter a new password which will be used to protect your data."; +"TwoStepAuth.ReEnterPasswordTitle" = "Re-enter your Password"; +"TwoStepAuth.ReEnterPasswordDescription" = "Please confirm your password."; +"TwoStepAuth.AddHintTitle" = "Add a Hint"; +"TwoStepAuth.AddHintDescription" = "You can create an optional hint for your password."; +"TwoStepAuth.HintPlaceholder" = "Hint"; +"TwoStepAuth.RecoveryEmailTitle" = "Recovery Email"; +"TwoStepAuth.RecoveryEmailAddDescription" = "Please add your valid e-mail. It is the only way to recover a forgotten password."; +"TwoStepAuth.RecoveryEmailChangeDescription" = "Please enter your new recovery email. It is the only way to recover a forgotten password."; +"TwoStepAuth.ChangeEmail" = "Change Email"; +"TwoStepAuth.ConfirmEmailDescription" = "Please enter the code we've just emailed at %1$@."; +"TwoStepAuth.ConfirmEmailCodePlaceholder" = "Code"; +"TwoStepAuth.ConfirmEmailResendCode" = "Resend Code"; + +"TwoStepAuth.SetupPendingEmail" = "Your recovery email %@ needs to be confirmed and is not yet active.\n\nPlease check your email and enter the confirmation code to complete Two-Step Verification setup. Be sure to check the spam folder as well."; +"TwoStepAuth.SetupResendEmailCode" = "Resend Code"; +"TwoStepAuth.SetupResendEmailCodeAlert" = "The code has been sent. Please check your e-mail. Be sure to check the spam folder as well."; +"TwoStepAuth.EnterEmailCode" = "Enter Code"; + +"TwoStepAuth.EnabledSuccess" = "Two-Step verification\nis enabled."; +"TwoStepAuth.DisableSuccess" = "Two-Step verification\nis disabled."; +"TwoStepAuth.PasswordChangeSuccess" = "Your password\nhas been changed."; +"TwoStepAuth.EmailAddSuccess" = "Your recovery e-mail\nhas been added."; +"TwoStepAuth.EmailChangeSuccess" = "Your recovery e-mail\nhas been changed."; + +"Conversation.SendMessageErrorGroupRestricted" = "Sorry, you are currently restricted from posting to public groups."; + +"InstantPage.TapToOpenLink" = "Tap to open the link:"; +"InstantPage.RelatedArticleAuthorAndDateTitle" = "%1$@ • %2$@"; + +"AuthCode.Alert" = "Your login code is %@. Enter it in the Telegram app where you are trying to log in.\n\nDo not give this code to anyone."; +"Login.CheckOtherSessionMessages" = "Check your Telegram messages"; +"Login.SendCodeViaSms" = "Send the code as an SMS"; +"Login.CancelPhoneVerification" = "Do you want to stop the phone number verification process?"; +"Login.CancelPhoneVerificationStop" = "Stop"; +"Login.CancelPhoneVerificationContinue" = "Continue"; +"Login.CodeExpired" = "Code expired, please login again."; +"Login.CancelSignUpConfirmation" = "Do you want to stop the registration process?"; + +"Passcode.AppLockedAlert" = "Telegram\nLocked"; + +"ChatList.ReadAll" = "Read All"; +"ChatList.Read" = "Read"; +"ChatList.DeleteConfirmation_1" = "Delete"; +"ChatList.DeleteConfirmation_2" = "Delete 2 Chats"; +"ChatList.DeleteConfirmation_3_10" = "Delete %@ Chats"; +"ChatList.DeleteConfirmation_any" = "Delete %@ Chats"; +"ChatList.DeleteConfirmation_many" = "Delete %@ Chats"; +"ChatList.DeleteConfirmation_0" = "Delete %@ Chats"; + +"Username.TooManyPublicUsernamesError" = "Sorry, you have reserved too many public usernames."; +"Group.Username.RevokeExistingUsernamesInfo" = "You can revoke the link from one of your older groups or channels, or create a private group instead."; +"Channel.Username.RevokeExistingUsernamesInfo" = "You can revoke the link from one of your older groups or channels, or create a private channel instead."; + +"InstantPage.Reference" = "Reference"; + +"Permissions.Skip" = "Skip"; + +"Permissions.ContactsTitle.v0" = "Sync Your Contacts"; +"Permissions.ContactsText.v0" = "See who's on Telegram and switch seamlessly, without having to \"add\" your friends."; +"Permissions.ContactsAllow.v0" = "Allow Access"; +"Permissions.ContactsAllowInSettings.v0" = "Allow in Settings"; + +"Permissions.NotificationsTitle.v0" = "Turn ON Notifications"; +"Permissions.NotificationsText.v0" = "Don't miss important messages from your friends and coworkers."; +"Permissions.NotificationsUnreachableText.v0" = "Please note that you partly disabled message notifications in your Settings."; +"Permissions.NotificationsAllow.v0" = "Turn Notifications ON"; +"Permissions.NotificationsAllowInSettings.v0" = "Turn ON in Settings"; + +"Permissions.CellularDataTitle.v0" = "Enable Cellular Data"; +"Permissions.CellularDataText.v0" = "Don't worry, Telegram keeps network usage to a minimum. You can further control this in Settings > Data and Storage."; +"Permissions.CellularDataAllowInSettings.v0" = "Turn ON in Settings"; + +"Permissions.SiriTitle.v0" = "Turn ON Siri"; +"Permissions.SiriText.v0" = "Use Siri to send messages and make calls."; +"Permissions.SiriAllow.v0" = "Turn Siri ON"; +"Permissions.SiriAllowInSettings.v0" = "Turn ON in Settings"; + +"Permissions.PrivacyPolicy" = "Privacy Policy"; + +"Contacts.PermissionsTitle" = "Access to Contacts"; +"Contacts.PermissionsText" = "Please allow Telegram access to your phonebook to seamlessly find all your friends."; +"Contacts.PermissionsAllow" = "Allow Access"; +"Contacts.PermissionsAllowInSettings" = "Allow in Settings"; +"Contacts.PermissionsSuppressWarningTitle" = "Keep contacts disabled?"; +"Contacts.PermissionsSuppressWarningText" = "You won't know when your friends join Telegram and become available to chat. We recommend enabling access to contacts in Settings."; +"Contacts.PermissionsKeepDisabled" = "Keep Disabled"; +"Contacts.PermissionsEnable" = "Enable"; + +"Notifications.PermissionsTitle" = "Turn ON Notifications"; +"Notifications.PermissionsText" = "Don't miss important messages from your friends and coworkers."; +"Notifications.PermissionsUnreachableTitle" = "Check Notification Settings"; +"Notifications.PermissionsUnreachableText" = "Please note that you partly disabled message notifications in your Settings."; +"Notifications.PermissionsAllow" = "Turn Notifications ON"; +"Notifications.PermissionsAllowInSettings" = "Turn ON in Settings"; +"Notifications.PermissionsOpenSettings" = "Open Settings"; +"Notifications.PermissionsSuppressWarningTitle" = "Keep notifications disabled?"; +"Notifications.PermissionsSuppressWarningText" = "You may miss important messages on Telegram due to your current settings.\n\nFor better results, enable alerts or banners and try muting certain chats or chat types in Telegram settings."; +"Notifications.PermissionsKeepDisabled" = "Keep Disabled"; +"Notifications.PermissionsEnable" = "Enable"; + +"ChatSettings.DownloadInBackground" = "Background Download"; +"ChatSettings.DownloadInBackgroundInfo" = "The app will continue downloading media files for a limited time."; + +"Cache.ServiceFiles" = "Service Files"; + +"SharedMedia.SearchNoResults" = "No Results"; +"SharedMedia.SearchNoResultsDescription" = "There were no results for \"%@\".\nTry a new search."; + +"MessagePoll.LabelAnonymous" = "Anonymous Poll"; +"MessagePoll.LabelClosed" = "Final Results"; +"MessagePoll.NoVotes" = "No votes"; +"MessagePoll.VotedCount_0" = "%@ votes"; +"MessagePoll.VotedCount_1" = "1 vote"; +"MessagePoll.VotedCount_2" = "2 votes"; +"MessagePoll.VotedCount_3_10" = "%@ votes"; +"MessagePoll.VotedCount_many" = "%@ votes"; +"MessagePoll.VotedCount_any" = "%@ votes"; +"AttachmentMenu.Poll" = "Poll"; +"Conversation.PinnedPoll" = "Pinned Poll"; +"Conversation.PinnedQuiz" = "Pinned Quiz"; + +"CreatePoll.Title" = "New Poll"; +"CreatePoll.Create" = "Send"; +"CreatePoll.TextHeader" = "QUESTION"; +"CreatePoll.TextPlaceholder" = "Ask a question"; +"CreatePoll.OptionsHeader" = "POLL OPTIONS"; +"CreatePoll.OptionPlaceholder" = "Option"; +"CreatePoll.AddOption" = "Add an Option"; + +"CreatePoll.AddMoreOptions_0" = "You can add %@ more options."; +"CreatePoll.AddMoreOptions_1" = "You can add 1 more option."; +"CreatePoll.AddMoreOptions_2" = "You can add 2 more options."; +"CreatePoll.AddMoreOptions_3_10" = "You can add %@ more options."; +"CreatePoll.AddMoreOptions_many" = "You can add %@ more options."; +"CreatePoll.AddMoreOptions_any" = "You can add %@ more options."; +"CreatePoll.AllOptionsAdded" = "You have added the maximum number of options."; + +"CreatePoll.CancelConfirmation" = "Are you sure you want to discard this poll?"; + +"ForwardedPolls_1" = "Forwarded poll"; +"ForwardedPolls_2" = "2 forwarded polls"; +"ForwardedPolls_3_10" = "%@ forwarded polls"; +"ForwardedPolls_any" = "%@ forwarded polls"; +"ForwardedPolls_many" = "%@ forwarded polls"; +"ForwardedPolls_0" = "%@ forwarded polls"; + +"Conversation.UnvotePoll" = "Retract Vote"; +"Conversation.StopPoll" = "Stop Poll"; +"Conversation.StopPollConfirmationTitle" = "If you stop this poll now, nobody will be able to vote in it anymore. This action cannot be undone."; +"Conversation.StopPollConfirmation" = "Stop Poll"; + +"AttachmentMenu.WebSearch" = "Web Search"; + +"Conversation.UnsupportedMediaPlaceholder" = "This message is not supported on your version of Telegram. Please update to the latest version."; +"Conversation.UpdateTelegram" = "UPDATE TELEGRAM"; + +"Cache.LowDiskSpaceText" = "Your phone has run out of available storage. Please free some space to download or upload media."; + +"Contacts.SortBy" = "Sort by:"; +"Contacts.SortByName" = "Name"; +"Contacts.SortByPresence" = "Last Seen Time"; +"Contacts.SortedByName" = "Sorted by Name"; +"Contacts.SortedByPresence" = "Sorted by Last Seen Time"; + +"NotificationSettings.ContactJoinedInfo" = "Receive push notifications when one of your contacts becomes available on Telegram."; + +"GroupInfo.Permissions" = "Permissions"; +"GroupInfo.Permissions.Title" = "Permissions"; +"GroupInfo.Permissions.SectionTitle" = "WHAT CAN MEMBERS OF THIS GROUP DO?"; +"GroupInfo.Permissions.Removed" = "Removed Users"; +"GroupInfo.Permissions.Exceptions" = "EXCEPTIONS"; +"GroupInfo.Permissions.AddException" = "Add Exception"; +"GroupInfo.Permissions.SearchPlaceholder" = "Search Exceptions"; + +"GroupInfo.Administrators" = "Administrators"; +"GroupInfo.Administrators.Title" = "Administrators"; + +"GroupPermission.NoSendMessages" = "no messages"; +"GroupPermission.NoSendMedia" = "no media"; +"GroupPermission.NoSendGifs" = "no GIFs"; +"GroupPermission.NoSendPolls" = "no polls"; +"GroupPermission.NoSendLinks" = "no links"; +"GroupPermission.NoChangeInfo" = "no info"; +"GroupPermission.NoAddMembers" = "no add"; +"GroupPermission.NoPinMessages" = "no pin"; + +"GroupPermission.Title" = "Exception"; +"GroupPermission.NewTitle" = "New Exception"; +"GroupPermission.SectionTitle" = "WHAT CAN THIS MEMBER DO?"; +"GroupPermission.Duration" = "Duration"; +"GroupPermission.AddedInfo" = "Exception added by %1$@ %2$@"; +"GroupPermission.Delete" = "Delete Exception"; +"GroupPermission.ApplyAlertText" = "You have changed this user's rights in %@.\nApply Changes?"; +"GroupPermission.ApplyAlertAction" = "Apply"; +"GroupPermission.AddSuccess" = "Exception Added"; +"GroupPermission.NotAvailableInPublicGroups" = "This permission is not available in public groups."; +"GroupPermission.AddMembersNotAvailable" = "You don't have persmission to add members."; + +"Channel.EditAdmin.PermissionEnabledByDefault" = "This option is permitted for all members in Group Permissions."; + +"GroupPermission.EditingDisabled" = "You cannot edit restrictions of this user."; +"GroupPermission.PermissionDisabledByDefault" = "This option is disabled for all members in Group Permissions."; + +"Channel.Management.RemovedBy" = "Removed by %@"; + +"GroupRemoved.Title" = "Removed Users"; +"GroupRemoved.Remove" = "Remove User"; +"GroupRemoved.RemoveInfo" = "Users removed from the group by admins cannot rejoin it via invite links."; +"ChannelRemoved.RemoveInfo" = "Users removed from the channel by admins cannot rejoin it via invite links."; +"GroupRemoved.UsersSectionTitle" = "REMOVED USERS"; +"GroupRemoved.ViewUserInfo" = "View User Info"; +"GroupRemoved.AddToGroup" = "Add To Group"; +"GroupRemoved.DeleteUser" = "Delete"; + +"EmptyGroupInfo.Title" = "You have created a group"; +"EmptyGroupInfo.Subtitle" = "Groups can have:"; +"EmptyGroupInfo.Line1" = "Up to %@ members"; +"EmptyGroupInfo.Line2" = "Persistent chat history"; +"EmptyGroupInfo.Line3" = "Public links such as t.me/title"; +"EmptyGroupInfo.Line4" = "Admins with different rights"; + +"WallpaperPreview.Title" = "Background Preview"; +"WallpaperPreview.PreviewTopText" = "Press Set to apply the background"; +"WallpaperPreview.PreviewBottomText" = "Enjoy the view"; +"WallpaperPreview.SwipeTopText" = "Swipe left or right to preview more backgrounds"; +"WallpaperPreview.SwipeBottomText" = "Backgrounds for the god of backgrounds!"; +"WallpaperPreview.SwipeColorsTopText" = "Swipe left or right to see more colors"; +"WallpaperPreview.SwipeColorsBottomText" = "Salmon is a fish, not a color"; +"WallpaperPreview.CustomColorTopText" = "Use sliders to adjust color"; +"WallpaperPreview.CustomColorBottomText" = "Something to match your curtains"; +"WallpaperPreview.CropTopText" = "Pinch and pan to adjust background"; +"WallpaperPreview.CropBottomText" = "Pinch me, I'm dreaming"; +"WallpaperPreview.Motion" = "Motion"; +"WallpaperPreview.Blurred" = "Blurred"; +"WallpaperPreview.Pattern" = "Pattern"; + +"Wallpaper.Search" = "Search Backgrounds"; +"Wallpaper.SearchShort" = "Search"; +"Wallpaper.SetColor" = "Set a Color"; +"Wallpaper.SetCustomBackground" = "Choose from Gallery"; +"Wallpaper.SetCustomBackgroundInfo" = "You can set a custom background image and share it with your friends."; + +"Wallpaper.DeleteConfirmation_1" = "Delete Background"; +"Wallpaper.DeleteConfirmation_2" = "Delete 2 Backgrounds"; +"Wallpaper.DeleteConfirmation_3_10" = "Delete %@ Backgrounds"; +"Wallpaper.DeleteConfirmation_any" = "Delete %@ Backgrounds"; +"Wallpaper.DeleteConfirmation_many" = "Delete %@ Backgrounds"; +"Wallpaper.DeleteConfirmation_0" = "Delete %@ Backgrounds"; + +"WallpaperColors.Title" = "Set a Color"; +"WallpaperColors.SetCustomColor" = "Set Custom Color"; + +"WallpaperSearch.ColorTitle" = "SEARCH BY COLOR"; +"WallpaperSearch.Recent" = "RECENT"; +"WallpaperSearch.ColorPrefix" = "color: "; +"WallpaperSearch.ColorBlue" = "Blue"; +"WallpaperSearch.ColorRed" = "Red"; +"WallpaperSearch.ColorOrange" = "Orange"; +"WallpaperSearch.ColorYellow" = "Yellow"; +"WallpaperSearch.ColorGreen" = "Green"; +"WallpaperSearch.ColorTeal" = "Teal"; +"WallpaperSearch.ColorPurple" = "Purple"; +"WallpaperSearch.ColorPink" = "Pink"; +"WallpaperSearch.ColorBrown" = "Brown"; +"WallpaperSearch.ColorBlack" = "Black"; +"WallpaperSearch.ColorGray" = "Gray"; +"WallpaperSearch.ColorWhite" = "White"; + +"Channel.AdminLog.DefaultRestrictionsUpdated" = "changed default permissions"; +"Channel.AdminLog.PollStopped" = "%@ stopped poll"; + +"ChatList.DeleteChat" = "Delete Chat"; +"ChatList.DeleteChatConfirmation" = "Are you sure you want to delete chat\nwith %@?"; +"ChatList.DeleteSecretChatConfirmation" = "Are you sure you want to delete secret chat\nwith %@?"; +"ChatList.LeaveGroupConfirmation" = "Are you sure you want to leave %@?"; +"ChatList.DeleteSavedMessagesConfirmation" = "Are you sure you want to delete\nSaved Messages?"; + +"Undo.Undo" = "Undo"; +"Undo.ChatDeleted" = "Chat deleted"; +"Undo.ChatCleared" = "Chat cleared"; +"Undo.ChatClearedForBothSides" = "Chat cleared for both sides"; +"Undo.SecretChatDeleted" = "Secret Chat deleted"; +"Undo.LeftChannel" = "Left channel"; +"Undo.LeftGroup" = "Left group"; +"Undo.DeletedChannel" = "Deleted channel"; +"Undo.DeletedGroup" = "Deleted group"; + +"AccessDenied.Wallpapers" = "Telegram needs access to your photo library to set a custom chat background.\n\nPlease go to Settings > Privacy > Photos and set Telegram to ON."; + +"Conversation.ChatBackground" = "Chat Background"; +"Conversation.ViewBackground" = "VIEW BACKGROUND"; + +"SocksProxySetup.ShareQRCodeInfo" = "Your friends can add this proxy by scanning this code with phone or in-app camera."; +"SocksProxySetup.ShareQRCode" = "Share QR Code"; +"SocksProxySetup.ShareLink" = "Share Lisnk"; + +"CallFeedback.Title" = "Call Feedback"; +"CallFeedback.WhatWentWrong" = "WHAT WENT WRONG?"; +"CallFeedback.ReasonEcho" = "I heard my own voice"; +"CallFeedback.ReasonNoise" = "I heard background noise"; +"CallFeedback.ReasonInterruption" = "The other side kept disappearing"; +"CallFeedback.ReasonDistortedSpeech" = "Speech was distorted"; +"CallFeedback.ReasonSilentLocal" = "I couldn't hear the other side"; +"CallFeedback.ReasonSilentRemote" = "The other side couldn't hear me"; +"CallFeedback.ReasonDropped" = "Call ended unexpectedly"; +"CallFeedback.VideoReasonDistorted" = "Video was distorted"; +"CallFeedback.VideoReasonLowQuality" = "Video was pixelated"; +"CallFeedback.AddComment" = "Add an optional comment"; +"CallFeedback.IncludeLogs" = "Include technical information"; +"CallFeedback.IncludeLogsInfo" = "This won't reveal the contents of your conversation, but will help us fix the issue sooner."; +"CallFeedback.Send" = "Send"; +"CallFeedback.Success" = "Thanks for\nyour feedback"; + +"Settings.AddAccount" = "Add Account"; +"WebSearch.SearchNoResults" = "No Results"; +"WebSearch.SearchNoResultsDescription" = "There were no results for \"%@\".\nTry a new search."; + +"WallpaperPreview.PatternIntensity" = "Pattern Intensity"; + +"Message.Wallpaper" = "Chat Background"; + +"Wallpaper.ResetWallpapers" = "Reset Chat Backgrounds"; +"Wallpaper.ResetWallpapersInfo" = "Remove all uploaded chat backgrounds and restore pre-installed backgrounds for all themes."; +"Wallpaper.ResetWallpapersConfirmation" = "Reset Chat Backgrounds"; + +"Proxy.TooltipUnavailable" = "The proxy may be unavailable. Try selecting another one."; + +"SocksProxySetup.Status" = "Status"; +"Login.PhoneNumberAlreadyAuthorized" = "This account is already logged in from this app."; + +"Login.PhoneNumberAlreadyAuthorizedSwitch" = "Switch"; + +"Call.AnsweringWithAccount" = "Answering as %@"; + +"AutoDownloadSettings.CellularTitle" = "Using Cellular"; +"AutoDownloadSettings.WifiTitle" = "Using Wi-Fi"; +"AutoDownloadSettings.AutoDownload" = "Auto-Download Media"; +"AutoDownloadSettings.MediaTypes" = "TYPES OF MEDIA"; +"AutoDownloadSettings.Photos" = "Photos"; +"AutoDownloadSettings.Videos" = "Videos"; +"AutoDownloadSettings.Files" = "Files"; +"AutoDownloadSettings.VoiceMessagesInfo" = "Voice messages are tiny and always downloaded automatically."; +"AutoDownloadSettings.ResetSettings" = "Reset Auto-Download Settings"; +"AutoDownloadSettings.AutodownloadPhotos" = "AUTO-DOWNLOAD PHOTOS"; +"AutoDownloadSettings.AutodownloadVideos" = "AUTO-DOWNLOAD VIDEOS AND GIFS"; +"AutoDownloadSettings.AutodownloadFiles" = "AUTO-DOWNLOAD FILES AND MUSIC"; +"AutoDownloadSettings.MaxVideoSize" = "MAXIMUM VIDEO SIZE"; +"AutoDownloadSettings.MaxFileSize" = "MAXIMUM FILE SIZE"; +"AutoDownloadSettings.DataUsage" = "DATA USAGE"; +"AutoDownloadSettings.DataUsageLow" = "Low"; +"AutoDownloadSettings.DataUsageMedium" = "Medium"; +"AutoDownloadSettings.DataUsageHigh" = "High"; +"AutoDownloadSettings.DataUsageCustom" = "Custom"; +"AutoDownloadSettings.OnForAll" = "On for all chats"; +"AutoDownloadSettings.OnFor" = "On for %@"; +"AutoDownloadSettings.TypeContacts" = "Contacts"; +"AutoDownloadSettings.TypePrivateChats" = "PM"; +"AutoDownloadSettings.TypeGroupChats" = "Groups"; +"AutoDownloadSettings.TypeChannels" = "Channels"; +"AutoDownloadSettings.UpToForAll" = "Up to %@ for all chats"; +"AutoDownloadSettings.UpToFor" = "Up to %1$@ for %2$@"; +"AutoDownloadSettings.OffForAll" = "Off for all chats"; +"AutoDownloadSettings.Delimeter" = ", "; +"AutoDownloadSettings.LastDelimeter" = " and "; +"AutoDownloadSettings.PreloadVideo" = "Preload Larger Videos"; +"AutoDownloadSettings.PreloadVideoInfo" = "Preload first seconds of videos larger than %@ for instant playback."; + +"ChatSettings.AutoDownloadUsingCellular" = "Using Cellular"; +"ChatSettings.AutoDownloadUsingWiFi" = "Using Wi-Fi"; +"ChatSettings.AutoPlayTitle" = "AUTO-PLAY MEDIA"; +"ChatSettings.AutoPlayGifs" = "GIFs"; +"ChatSettings.AutoPlayVideos" = "Videos"; + +"ChatSettings.AutoDownloadSettings.TypePhoto" = "Photos"; +"ChatSettings.AutoDownloadSettings.TypeVideo" = "Videos (%@)"; +"ChatSettings.AutoDownloadSettings.TypeFile" = "Files (%@)"; +"ChatSettings.AutoDownloadSettings.OffForAll" = "Disabled"; +"ChatSettings.AutoDownloadSettings.Delimeter" = ", "; + +"LogoutOptions.Title" = "Log out"; +"LogoutOptions.AlternativeOptionsSection" = "ALTERNATIVE OPTIONS"; +"LogoutOptions.AddAccountTitle" = "Add another account"; +"LogoutOptions.AddAccountText" = "Set up multiple phone numbers and easily switch between them."; +"LogoutOptions.SetPasscodeTitle" = "Set a Passcode"; +"LogoutOptions.SetPasscodeText" = "Lock the app with a passcode so that others can't open it."; +"LogoutOptions.ClearCacheTitle" = "Clear Cache"; +"LogoutOptions.ClearCacheText" = "Free up disk space on your device; your media will stay in the cloud."; +"LogoutOptions.ChangePhoneNumberTitle" = "Change Phone Number"; +"LogoutOptions.ChangePhoneNumberText" = "Move your contacts, groups, messages and media to a new number."; +"LogoutOptions.ContactSupportTitle" = "Contact Support"; +"LogoutOptions.ContactSupportText" = "Tell us about any issues; logging out doesn't usually help."; +"LogoutOptions.LogOut" = "Log Out"; +"LogoutOptions.LogOutInfo" = "Remember, logging out kills all your Secret Chats."; +"LogoutOptions.LogOutWalletInfo" = "Logging out will cancel all your secret chats and unlink your Gram wallet from the app."; + +"GroupPermission.PermissionGloballyDisabled" = "This permission is disabled in this group."; + +"ChannelInfo.Stats" = "Statistics"; + +"Conversation.PressVolumeButtonForSound" = "Press volume button\nto unmute the video"; + +"ChatList.SelectedChats_1" = "%@ Chat Selected"; +"ChatList.SelectedChats_2" = "%@ Chats Selected"; +"ChatList.SelectedChats_3_10" = "%@ Chats Selected"; +"ChatList.SelectedChats_any" = "%@ Chats Selected"; +"ChatList.SelectedChats_many" = "%@ Chats Selected"; +"ChatList.SelectedChats_0" = "%@ Chats Selected"; + +"NotificationSettings.ShowNotificationsFromAccountsSection" = "SHOW NOTIFICATIONS FROM"; +"NotificationSettings.ShowNotificationsAllAccounts" = "All Accounts"; +"NotificationSettings.ShowNotificationsAllAccountsInfoOn" = "Turn this off if you want to receive notifications only from your active account."; +"NotificationSettings.ShowNotificationsAllAccountsInfoOff" = "Turn this on if you want to receive notifications from all your accounts."; + +"Gif.Search" = "Search GIFs"; +"Gif.NoGifsFound" = "No GIFs Found"; +"Gif.NoGifsPlaceholder" = "You have no saved GIFs yet."; + +"Privacy.ProfilePhoto" = "Profile Photo"; +"Privacy.Forwards" = "Forwarded Messages"; + +"Privacy.ProfilePhoto.WhoCanSeeMyPhoto" = "WHO CAN SEE MY PROFILE PHOTO"; +"Privacy.ProfilePhoto.CustomHelp" = "You can restrict who can see your profile photo with granular precision."; +"Privacy.ProfilePhoto.AlwaysShareWith.Title" = "Always Share With"; +"Privacy.ProfilePhoto.NeverShareWith.Title" = "Never Share With"; + +"Privacy.Forwards.WhoCanForward" = "WHO CAN ADD LINK TO MY ACCOUNT WHEN FORWARDING MY MESSAGES"; +"Privacy.Forwards.CustomHelp" = "When forwarded to other chats, messages you send will not link back to your account."; +"Privacy.Forwards.AlwaysAllow.Title" = "Always Allow"; +"Privacy.Forwards.NeverAllow.Title" = "Never Allow"; + +"Conversation.ContextMenuCancelSending" = "Cancel Sending"; + +"Conversation.ForwardAuthorHiddenTooltip" = "The account was hidden by the user"; + +"Privacy.Forwards.Preview" = "PREVIEW"; +"Privacy.Forwards.PreviewMessageText" = "Reinhardt, we need to find you some new music."; +"Privacy.Forwards.AlwaysLink" = "Link to your account"; +"Privacy.Forwards.LinkIfAllowed" = "Link if allowed by settings below"; +"Privacy.Forwards.NeverLink" = "Not a link to your account"; + +"Chat.UnsendMyMessagesAlertTitle" = "Unsending will also delete messages you sent on %@'s side."; +"Chat.UnsendMyMessages" = "Unsend My Messages"; + +"Chat.DeleteMessagesConfirmation_1" = "Delete message"; +"Chat.DeleteMessagesConfirmation_any" = "Delete %@ messages"; + +"Settings.Search" = "Search Settings"; + +"SettingsSearch.FAQ" = "FAQ"; + +"SettingsSearch.Synonyms.EditProfile.Title" = " "; +"SettingsSearch.Synonyms.EditProfile.Bio" = " "; +"SettingsSearch.Synonyms.EditProfile.PhoneNumber" = " "; +"SettingsSearch.Synonyms.EditProfile.Username" = " "; +"SettingsSearch.Synonyms.EditProfile.AddAccount" = " "; +"SettingsSearch.Synonyms.EditProfile.Logout" = " "; + +"SettingsSearch.Synonyms.Calls.Title" = " "; +"SettingsSearch.Synonyms.Calls.CallTab" = " "; + +"SettingsSearch.Synonyms.Stickers.Title" = " "; +"SettingsSearch.Synonyms.Stickers.SuggestStickers" = " "; +"SettingsSearch.Synonyms.Stickers.FeaturedPacks" = " "; +"SettingsSearch.Synonyms.Stickers.ArchivedPacks" = " "; +"SettingsSearch.Synonyms.Stickers.Masks" = " "; + +"SettingsSearch.Synonyms.Notifications.Title" = " "; +"SettingsSearch.Synonyms.Notifications.MessageNotificationsAlert" = " "; +"SettingsSearch.Synonyms.Notifications.MessageNotificationsPreview" = " "; +"SettingsSearch.Synonyms.Notifications.MessageNotificationsSound" = " "; +"SettingsSearch.Synonyms.Notifications.MessageNotificationsExceptions" = " "; +"SettingsSearch.Synonyms.Notifications.GroupNotificationsAlert" = " "; +"SettingsSearch.Synonyms.Notifications.GroupNotificationsPreview" = " "; +"SettingsSearch.Synonyms.Notifications.GroupNotificationsSound" = " "; +"SettingsSearch.Synonyms.Notifications.GroupNotificationsExceptions" = " "; +"SettingsSearch.Synonyms.Notifications.ChannelNotificationsAlert" = " "; +"SettingsSearch.Synonyms.Notifications.ChannelNotificationsPreview" = " "; +"SettingsSearch.Synonyms.Notifications.ChannelNotificationsSound" = " "; +"SettingsSearch.Synonyms.Notifications.ChannelNotificationsExceptions" = " "; +"SettingsSearch.Synonyms.Notifications.InAppNotificationsSound" = " "; +"SettingsSearch.Synonyms.Notifications.InAppNotificationsVibrate" = " "; +"SettingsSearch.Synonyms.Notifications.InAppNotificationsPreview" = " "; +"SettingsSearch.Synonyms.Notifications.DisplayNamesOnLockScreen" = " "; +"SettingsSearch.Synonyms.Notifications.BadgeIncludeMutedChats" = " "; +"SettingsSearch.Synonyms.Notifications.BadgeIncludeMutedPublicGroups" = " "; +"SettingsSearch.Synonyms.Notifications.BadgeIncludeMutedChannels" = " "; +"SettingsSearch.Synonyms.Notifications.BadgeCountUnreadMessages" = " "; +"SettingsSearch.Synonyms.Notifications.ContactJoined" = " "; +"SettingsSearch.Synonyms.Notifications.ResetAllNotifications" = " "; + +"SettingsSearch.Synonyms.Privacy.Title" = " "; +"SettingsSearch.Synonyms.Privacy.BlockedUsers" = " "; +"SettingsSearch.Synonyms.Privacy.LastSeen" = " "; +"SettingsSearch.Synonyms.Privacy.ProfilePhoto" = " "; +"SettingsSearch.Synonyms.Privacy.Forwards" = " "; +"SettingsSearch.Synonyms.Privacy.Calls" = " "; +"SettingsSearch.Synonyms.Privacy.GroupsAndChannels" = " "; +"SettingsSearch.Synonyms.Privacy.Passcode" = " "; +"SettingsSearch.Synonyms.Privacy.PasscodeAndTouchId" = " "; +"SettingsSearch.Synonyms.Privacy.PasscodeAndFaceId" = " "; +"SettingsSearch.Synonyms.Privacy.TwoStepAuth" = "Password"; +"SettingsSearch.Synonyms.Privacy.AuthSessions" = " "; +"SettingsSearch.Synonyms.Privacy.DeleteAccountIfAwayFor" = " "; + +"SettingsSearch.Synonyms.Privacy.Data.Title" = " "; +"SettingsSearch.Synonyms.Privacy.Data.ContactsReset" = " "; +"SettingsSearch.Synonyms.Privacy.Data.ContactsSync" = " "; +"SettingsSearch.Synonyms.Privacy.Data.TopPeers" = " "; +"SettingsSearch.Synonyms.Privacy.Data.DeleteDrafts" = " "; +"SettingsSearch.Synonyms.Privacy.Data.ClearPaymentsInfo" = " "; +"SettingsSearch.Synonyms.Privacy.Data.SecretChatLinkPreview" = " "; + +"SettingsSearch.Synonyms.Data.Title" = " "; +"SettingsSearch.Synonyms.Data.Storage.Title" = "Cache"; +"SettingsSearch.Synonyms.Data.Storage.KeepMedia" = " "; +"SettingsSearch.Synonyms.Data.Storage.ClearCache" = " "; +"SettingsSearch.Synonyms.Data.NetworkUsage" = " "; +"SettingsSearch.Synonyms.Data.AutoDownloadUsingCellular" = " "; +"SettingsSearch.Synonyms.Data.AutoDownloadUsingWifi" = " "; +"SettingsSearch.Synonyms.Data.AutoDownloadReset" = " "; +"SettingsSearch.Synonyms.Data.AutoplayGifs" = " "; +"SettingsSearch.Synonyms.Data.AutoplayVideos" = " "; +"SettingsSearch.Synonyms.Data.CallsUseLessData" = " "; +"SettingsSearch.Synonyms.Data.SaveIncomingPhotos" = " "; +"SettingsSearch.Synonyms.Data.SaveEditedPhotos" = " "; +"SettingsSearch.Synonyms.Data.DownloadInBackground" = " "; + +"SettingsSearch.Synonyms.Proxy.Title" = "SOCKS5\nMTProto"; +"SettingsSearch.Synonyms.Proxy.AddProxy" = " "; +"SettingsSearch.Synonyms.Proxy.UseForCalls" = " "; + +"SettingsSearch.Synonyms.Appearance.Title" = " "; +"SettingsSearch.Synonyms.Appearance.TextSize" = " "; +"SettingsSearch.Synonyms.Appearance.ChatBackground" = "Wallpaper"; +"SettingsSearch.Synonyms.Appearance.ChatBackground.SetColor" = " "; +"SettingsSearch.Synonyms.Appearance.ChatBackground.Custom" = " "; +"SettingsSearch.Synonyms.Appearance.AutoNightTheme" = " "; +"SettingsSearch.Synonyms.Appearance.ColorTheme" = " "; +"SettingsSearch.Synonyms.Appearance.LargeEmoji" = " "; +"SettingsSearch.Synonyms.Appearance.Animations" = "Animations"; + +"SettingsSearch.Synonyms.SavedMessages" = " "; +"SettingsSearch.Synonyms.AppLanguage" = " "; +"SettingsSearch.Synonyms.Passport" = " "; +"SettingsSearch.Synonyms.Watch" = "Apple Watch"; +"SettingsSearch.Synonyms.Support" = "Support"; +"SettingsSearch.Synonyms.FAQ" = " "; + +"ChatList.DeleteForCurrentUser" = "Delete just for me"; +"ChatList.DeleteForEveryone" = "Delete for me and %@"; +"ChatList.DeleteForEveryoneConfirmationTitle" = "Warning!"; +"ChatList.DeleteForEveryoneConfirmationText" = "This will **delete all messages** in this chat for **both participants**."; +"ChatList.DeleteForEveryoneConfirmationAction" = "Delete All"; + +"ChatList.DeleteSavedMessagesConfirmationTitle" = "Warning!"; +"ChatList.DeleteSavedMessagesConfirmationText" = "This will **delete all messages** in this chat."; +"ChatList.DeleteSavedMessagesConfirmationAction" = "Delete All"; + +"ChatList.ClearChatConfirmation" = "Are you sure you want to delete all\nmessages in the chat with %@?"; + +"Settings.CheckPhoneNumberTitle" = "Is %@ still your number?"; +"Settings.CheckPhoneNumberText" = "Keep your number up to date to ensure you can always log in to Telegram. [Learn more]()"; +"Settings.KeepPhoneNumber" = "Keep %@"; +"Settings.ChangePhoneNumber" = "Change Number"; + +"Undo.ChatDeletedForBothSides" = "Chat deleted for both sides"; + +"AppUpgrade.Running" = "Optimizing Telegram... +This may take a while, depending on the size of the database. Please keep the app open until the process is finished. + +Sorry for the inconvenience."; + +"Call.Mute" = "mute"; +"Call.Camera" = "camera"; +"Call.Flip" = "flip"; +"Call.End" = "end"; +"Call.Speaker" = "speaker"; + +"MemberSearch.BotSection" = "BOTS"; + +"Conversation.PrivateMessageLinkCopied" = "This link will only work for members of this chat."; +"Conversation.ErrorInaccessibleMessage" = "Unfortunately, you can't access this message. You are not a member of the chat where it was posted."; + +"Stickers.ClearRecent" = "Clear Recent Stickers"; + +"Appearance.Other" = "Other"; +"Appearance.LargeEmoji" = "Large Emoji"; + +"ChatList.ArchiveAction" = "Archive"; +"ChatList.UnarchiveAction" = "Unarchive"; +"ChatList.HideAction" = "Hide"; +"ChatList.UnhideAction" = "Pin"; + +"ChatList.UndoArchiveTitle" = "Chat archived"; +"ChatList.UndoArchiveMultipleTitle" = "Chats archived"; +"ChatList.UndoArchiveText1" = "Hide the archive by swiping left on it."; +"ChatList.UndoArchiveHiddenTitle" = "Archive hidden"; +"ChatList.UndoArchiveHiddenText" = "Swipe down to see archive."; +"ChatList.UndoArchiveRevealedTitle" = "Archive pinned"; +"ChatList.UndoArchiveRevealedText" = "Swipe left on the archive to hide it."; +"ChatList.ArchivedChatsTitle" = "Archived Chats"; + +"PasscodeSettings.PasscodeOptions" = "Passcode Options"; +"PasscodeSettings.DoNotMatch" = "Passcodes don't match. Please try again."; + +"Conversation.PrivateChannelTooltip" = "This channel is private"; + +"PasscodeSettings.PasscodeOptions" = "Passcode Options"; +"PasscodeSettings.AlphanumericCode" = "Custom Alphanumeric Code"; +"PasscodeSettings.4DigitCode" = "4-Digit Numeric Code"; +"PasscodeSettings.6DigitCode" = "6-Digit Numeric Code"; + +"Conversation.ScamWarning" = "⚠️ Warning: Many users reported this account as a scam. Please be careful, especially if it asks you for money."; + +"Conversation.ClearChatConfirmation" = "Warning, this will delete your **entire chat history** with %@."; + +"ArchivedChats.IntroTitle1" = "This is your archive"; +"ArchivedChats.IntroText1" = "Chats with enabled notifications get unarchived when new notifications arrive."; +"ArchivedChats.IntroTitle2" = "Muted Chats"; +"ArchivedChats.IntroText2" = "Muted chats stay archived when new messages arrive."; +"ArchivedChats.IntroTitle3" = "Pinned Chats"; +"ArchivedChats.IntroText3" = "You can pin up to 100 archived chats to the top."; + +"UserInfo.ScamUserWarning" = "⚠️ Warning: Many users reported this user as a scam. Please be careful, especially if it asks you for money."; +"UserInfo.ScamBotWarning" = "⚠️ Warning: Many users reported this user as a scam. Please be careful, especially if it asks you for money."; +"ChannelInfo.ScamChannelWarning" = "⚠️ Warning: Many users reported this channel as a scam. Please be careful, especially if it asks you for money."; +"GroupInfo.ScamGroupWarning" = "⚠️ Warning: Many users reported this group as a scam. Please be careful, especially if it asks you for money."; + +"Privacy.AddNewPeer" = "Add Users or Groups"; +"PrivacyPhoneNumberSettings.WhoCanSeeMyPhoneNumber" = "WHO CAN SEE MY PHONE NUMBER"; +"PrivacyPhoneNumberSettings.CustomHelp" = "Users who already have your number saved in the contacts will also see it on Telegram."; +"PrivacyPhoneNumberSettings.CustomDisabledHelp" = "Users who add your number to their contacts will see it on Telegram only if they are your contacts."; + +"PrivacyPhoneNumberSettings.DiscoveryHeader" = "WHO CAN FIND ME BY MY NUMBER"; + +"Privacy.PhoneNumber" = "Phone Number"; +"PrivacySettings.PhoneNumber" = "Phone Number"; +"Contacts.SearchUsersAndGroupsLabel" = "Search for users and groups"; + +"PrivacySettings.PasscodeOff" = "Off"; +"PrivacySettings.PasscodeOn" = "On"; + +"UserInfo.BlockConfirmationTitle" = "Do you want to block %@ from messaging and calling you on Telegram?"; +"UserInfo.BlockActionTitle" = "Block %@"; +"ReportSpam.DeleteThisChat" = "Delete this Chat"; + +"PrivacySettings.BlockedPeersEmpty" = "None"; + +"Channel.DiscussionGroup" = "Discussion"; +"Group.LinkedChannel" = "Linked Channel"; +"Channel.DiscussionGroupAdd" = "Add"; +"Channel.DiscussionGroupInfo" = "Add group chat for comments."; +"Channel.DiscussionGroup.Header" = "Select a group chat for discussion that will be displayed in your channel."; +"Channel.DiscussionGroup.HeaderSet" = "A link to %@ is shown to all subscribers in the bottom panel."; +"Channel.DiscussionGroup.HeaderGroupSet" = "%@ is linking the group as it's discussion board."; +"Channel.DiscussionGroup.HeaderLabel" = "Discuss"; +"Channel.DiscussionGroup.Create" = "Create New Group"; +"Channel.DiscussionGroup.PrivateGroup" = "private group"; +"Channel.DiscussionGroup.Info" = "Everything you post in the channel will be forwarded to this group."; +"Channel.DiscussionGroup.LinkGroup" = "Link Group"; +"Channel.DiscussionGroup.UnlinkGroup" = "Unlink Group"; +"Channel.DiscussionGroup.UnlinkChannel" = "Unlink Channel"; +"Channel.DiscussionGroup.PublicChannelLink" = "Do you want to make %1$@ the discussion board for %2$@?"; +"Channel.DiscussionGroup.PrivateChannelLink" = "Do you want to make %1$@ the discussion board for %2$@? + +Any member of this group will be able to see messages in the channel."; +"Channel.DiscussionGroup.MakeHistoryPublic" = "Warning: If you set this private group as the disccussion group for your channel, all channel subscribers will be able to access the group. \"Chat history for new members\" will be switched to Visible."; +"Channel.DiscussionGroup.MakeHistoryPublicProceed" = "Proceed"; + +"Channel.DiscussionGroup.SearchPlaceholder" = "Search"; + +"Channel.AdminLog.MessageChangedLinkedGroup" = "%1$@ made %2$@ the discussion group for this channel."; +"Channel.AdminLog.MessageChangedLinkedChannel" = "%1$@ linked this group to %2$@"; +"Channel.AdminLog.MessageChangedUnlinkedGroup" = "%1$@ removed the discussion group %2$@"; +"Channel.AdminLog.MessageChangedUnlinkedChannel" = "%1$@ unlinked this group from %2$@"; + +"Conversation.OpenBotLinkTitle" = "Open Link"; +"Conversation.OpenBotLinkText" = "Do you want to open\n**%@**?"; +"Conversation.OpenBotLinkLogin" = "Log in to **%1$@** as %2$@"; +"Conversation.OpenBotLinkAllowMessages" = "Allow **%@** to send me messages"; +"Conversation.OpenBotLinkOpen" = "Open"; + +"TextFormat.Link" = "Link"; +"TextFormat.Strikethrough" = "Strikethrough"; +"TextFormat.Underline" = "Underline"; + +"TextFormat.AddLinkTitle" = "Add Link"; +"TextFormat.AddLinkText" = "The link will be displayed as \"%@\"."; +"TextFormat.AddLinkPlaceholder" = "URL"; + +"Channel.AddBotErrorHaveRights" = "Bots can only be added as administrators."; +"Channel.AddBotAsAdmin" = "Make Admin"; +"Channel.AddBotErrorNoRights" = "Sorry, bots can only be added to channels as administrators."; + +"Appearance.AppIcon" = "App Icon"; +"Appearance.AppIconDefault" = "Default"; +"Appearance.AppIconDefaultX" = "Default X"; +"Appearance.AppIconClassic" = "Classic"; +"Appearance.AppIconClassicX" = "Classic X"; +"Appearance.AppIconFilled" = "Filled"; +"Appearance.AppIconFilledX" = "Filled X"; + +"Appearance.ThemeCarouselClassic" = "Classic"; +"Appearance.ThemeCarouselDay" = "Day"; +"Appearance.ThemeCarouselNightBlue" = "Night Blue"; +"Appearance.ThemeCarouselNight" = "Monochrome"; + +"Notification.Exceptions.DeleteAll" = "Delete All"; +"Notification.Exceptions.DeleteAllConfirmation" = "Are you sure you want to delete all exceptions?"; +"Notification.Exceptions.Add" = "Add"; +"Exceptions.AddToExceptions" = "ADD TO EXCEPTIONS"; + +"Notification.Exceptions.NewException.MessagePreviewHeader" = "MESSAGE PREVIEW"; +"Notification.Exceptions.PreviewAlwaysOn" = "Show Preview"; +"Notification.Exceptions.PreviewAlwaysOff" = "Hide Preview"; +"Notification.Exceptions.RemoveFromExceptions" = "Remove from Exceptions"; +"Conversation.Block" = "Block"; +"Conversation.BlockUser" = "Block User"; +"Conversation.ShareMyPhoneNumber" = "Share My Phone Number"; +"Conversation.ShareMyPhoneNumberConfirmation" = "Are you sure you want to share your phone number %1$@ with %2$@?"; +"Conversation.AddToContacts" = "Add to Contacts"; +"Conversation.AddNameToContacts" = "Add %@ to Contacts"; + +"AddContact.ContactWillBeSharedAfterMutual" = "Phone number will be visible once %1$@ adds you as a contact."; +"AddContact.SharedContactException" = "Share My Phone Number"; +"AddContact.SharedContactExceptionInfo" = "You can make your phone visible to %@."; +"AddContact.StatusSuccess" = "%@ is now in your contacts list."; +"Conversation.ShareMyPhoneNumber.StatusSuccess" = "%@ can now see your phone number."; + +"Group.EditAdmin.TransferOwnership" = "Transfer Group Ownership"; +"Channel.EditAdmin.TransferOwnership" = "Transfer Channel Ownership"; + +"OwnershipTransfer.SecurityCheck" = "Security Check"; +"OwnershipTransfer.SecurityRequirements" = "Ownership transfers are available if:\n\n• 2-Step verification was enabled for your account more than **7 days** ago.\n\n• You have logged in on this device more than **24 hours** ago."; +"OwnershipTransfer.ComeBackLater" = "\n\nPlease come back later."; +"OwnershipTransfer.SetupTwoStepAuth" = "Enable 2-Step Verification"; + +"Channel.OwnershipTransfer.Title" = "Transfer Channel Ownership"; +"Channel.OwnershipTransfer.DescriptionInfo" = "This will transfer the full **owner rights** for **%1$@** to **%2$@**.\n\nYou will no longer be considered the creator of the channel. The new owner will be free to remove any of your admin privileges or even ban you."; +"Group.OwnershipTransfer.Title" = "Transfer Group Ownership"; +"Group.OwnershipTransfer.DescriptionInfo" = "This will transfer the full **owner rights** for **%1$@** to **%2$@**.\n\nYou will no longer be considered the creator of the group. The new owner will be free to remove any of your admin privileges or even ban you."; +"Channel.OwnershipTransfer.ChangeOwner" = "Change Owner"; + +"Channel.OwnershipTransfer.ErrorPublicChannelsTooMuch" = "Sorry, the target user has too many public groups or channels already. Please ask them to make one of their existing groups or channels private first."; +"Group.OwnershipTransfer.ErrorLocatedGroupsTooMuch" = "Sorry, the target user has too many location-based groups already. Please ask them to delete or transfer one of their existing ones first."; + +"Group.OwnershipTransfer.ErrorAdminsTooMuch" = "Sorry, this group has too many admins and the new owner can't be added. Please remove one of the existing admins first."; +"Channel.OwnershipTransfer.ErrorAdminsTooMuch" = "Sorry, this channel has too many admins and the new owner can't be added. Please remove one of the existing admins first."; + +"Group.OwnershipTransfer.ErrorPrivacyRestricted" = "Sorry, this user is not a member of this group and their privacy settings prevent you from adding them manually."; +"Channel.OwnershipTransfer.ErrorPrivacyRestricted" = "Sorry, this user is not a member of this channel and their privacy settings prevent you from adding them manually."; + +"Channel.OwnershipTransfer.EnterPassword" = "Enter Password"; +"Channel.OwnershipTransfer.EnterPasswordText" = "Please enter your 2-Step Verification password to complete the transfer."; +"Channel.OwnershipTransfer.PasswordPlaceholder" = "Password"; + +"Channel.OwnershipTransfer.TransferCompleted" = "**%1$@** is now the owner of **%2$@**"; + +"Contacts.AddPeopleNearby" = "Add People Nearby"; + +"PeopleNearby.Title" = "People Nearby"; +"PeopleNearby.Description" = "Ask your friend nearby to open this page to exchange phone numbers."; +"PeopleNearby.Users" = "People Nearby"; +"PeopleNearby.UsersEmpty" = "Looking for users around you..."; +"PeopleNearby.Groups" = "Groups Nearby"; +"PeopleNearby.CreateGroup" = "Create a Group Here"; +"PeopleNearby.NoMembers" = "no members"; + +"Channel.Management.LabelOwner" = "Owner"; +"Channel.Management.LabelAdministrator" = "Administrator"; +"ContactInfo.PhoneNumberHidden" = "Hidden"; + +"Common.ActionNotAllowedError" = "Sorry, you are not allowed to do this."; + +"Group.Location.Title" = "Location"; +"Group.Location.ChangeLocation" = "Change Location"; +"Group.Location.Info" = "People can find your group using People Nearby section."; + +"Channel.AdminLog.MessageTransferedName" = "transferred ownership to %1$@"; +"Channel.AdminLog.MessageTransferedNameUsername" = "transferred ownership to %1$@ (%2$@)"; + +"Channel.AdminLog.MessageChangedGroupGeoLocation" = "changed group location to \"%@\""; + +"Map.SetThisLocation" = "Set This Location"; + +"Permissions.PeopleNearbyTitle.v0" = "People Nearby"; +"Permissions.PeopleNearbyText.v0" = "Use this section to quickly add people near you and discover nearby group chats.\n\nPlease allow location access\nto start using this feature."; +"Permissions.PeopleNearbyAllow.v0" = "Allow Access"; +"Permissions.PeopleNearbyAllowInSettings.v0" = "Allow in Settings"; + +"Conversation.ReportGroupLocation" = "Group unrelated to location?"; +"ReportGroupLocation.Title" = "Report Unrelated Group"; +"ReportGroupLocation.Text" = "Please tell us if this group is not related to this location."; +"ReportGroupLocation.Report" = "Report"; + +"LocalGroup.Title" = "Create a Local Group"; +"LocalGroup.Text" = "Anyone close to this location (neighbors, co-workers, fellow students, event attendees, visitors of a venue) will see your group in the People Nearby section."; +"LocalGroup.ButtonTitle" = "Start Group"; +"LocalGroup.IrrelevantWarning" = "If you start an unrelated group at this location, you may get restricted in creating new location-based groups."; + +"GroupInfo.Location" = "Location"; +"GroupInfo.PublicLink" = "Public Link"; +"GroupInfo.PublicLinkAdd" = "Add"; + +"Group.PublicLink.Title" = "Public Link"; +"Group.PublicLink.Placeholder" = "link"; +"Group.PublicLink.Info" = "People can share this link with others and find your group using Telegram search.\n\nYou can use **a-z**, **0-9** and underscores. Minimum length is **5** characters."; + +"CreateGroup.ErrorLocatedGroupsTooMuch" = "Sorry, you have too many location-based groups already. Please delete one of your existing ones first."; + +"GroupInfo.LabelOwner" = "owner"; + +"Activity.RemindAboutGroup" = "Send message to %@"; +"Activity.RemindAboutUser" = "Send message to %@"; +"Activity.RemindAboutChannel" = "Read %@"; + +"CreateGroup.ChannelsTooMuch" = "Sorry, you are a member of too many groups and channels. Please leave some before creating a new one."; +"Join.ChannelsTooMuch" = "Sorry, you are a member of too many groups and channels. Please leave some before joining one."; +"Invite.ChannelsTooMuch" = "Sorry, the target user is a member of too many groups and channels. Please ask them to leave some first."; + +"Appearance.TintAllColors" = "Tint All Colors"; + +"Contacts.DeselectAll" = "Deselect All"; + +"Channel.TooMuchBots" = "Sorry, there are already too many bots in this group. Please remove some of the bots you're not using first."; +"Channel.BotDoesntSupportGroups" = "Sorry, this bot is telling us it doesn't want to be added to groups. You can't add this bot unless its developers change their mind."; + +"StickerPacksSettings.AnimatedStickers" = "Loop Animated Stickers"; +"StickerPacksSettings.AnimatedStickersInfo" = "Animated stickers will play in chat continuously."; +"GroupInfo.Permissions.SlowmodeHeader" = "SLOWMODE"; +"GroupInfo.Permissions.SlowmodeInfo" = "Members will be restricted to send one message per this interval."; +"Channel.AdminLog.DisabledSlowmode" = "%@ disabled slowmode"; +"Channel.AdminLog.SetSlowmode" = "%1$@ set slowmode to %2$@"; + +"GroupInfo.Permissions.EditingDisabled" = "You cannot edit this permission."; + +"Chat.SlowmodeTooltip" = "Slowmode is enabled. You can send\nyour next message in %@."; +"Chat.SlowmodeTooltipPending" = "Slowmode is enabled. You can't send more than one message at once."; +"Chat.AttachmentLimitReached" = "You can't select more items."; +"Chat.SlowmodeAttachmentLimitReached" = "Slowmode is enabled. You can't select more items."; +"Chat.AttachmentMultipleFilesDisabled" = "Slowmode is enabled. You can't send multiple files at once."; +"Chat.AttachmentMultipleForwardDisabled" = "Slowmode is enabled. You can't forward multiple messages at once."; +"Chat.MultipleTextMessagesDisabled" = "Slowmode is enabled. You can't send multiple messages at once."; +"Share.MultipleMessagesDisabled" = "Slowmode is enabled. You can't send multiple messages at once."; +"Chat.SlowmodeSendError" = "Slowmode is enabled."; +"StickerPacksSettings.AnimatedStickersInfo" = "Animated stickers in a chat will play continuously."; + +"Conversation.Owner" = "owner"; + +"Group.EditAdmin.RankTitle" = "CUSTOM TITLE"; +"Group.EditAdmin.RankInfo" = "A title that will be shown instead of '%@'."; +"Group.EditAdmin.RankOwnerPlaceholder" = "owner"; +"Group.EditAdmin.RankAdminPlaceholder" = "admin"; + +"Conversation.SendMessage.SendSilently" = "Send Without Sound"; +"Conversation.SendMessage.ScheduleMessage" = "Schedule Message"; + +"Appearance.ThemeCarouselTintedNight" = "Tinted Night"; +"Appearance.ThemeCarouselNewNight" = "Night"; + +"Channel.AdminLog.MessageRankName" = "changed custom title for %1$@:\n%2$@"; +"Channel.AdminLog.MessageRankUsername" = "changed custom title for %1$@ (%2$@):\n%3$@"; +"Channel.AdminLog.MessageRank" = "changed custom title:\n%1$@"; + +"VoiceOver.Editing.ClearText" = "Clear text"; +"VoiceOver.Recording.StopAndPreview" = "Stop and preview"; +"VoiceOver.Media.PlaybackRate" = "Playback rate"; +"VoiceOver.Media.PlaybackRateNormal" = "Normal"; +"VoiceOver.Media.PlaybackRateFast" = "Fast"; +"VoiceOver.Media.PlaybackRateChange" = "Double tap to change"; +"VoiceOver.Media.PlaybackStop" = "Stop playback"; +"VoiceOver.Media.PlaybackPlay" = "Play"; +"VoiceOver.Media.PlaybackPause" = "Pause"; +"VoiceOver.Navigation.Compose" = "Compose"; +"VoiceOver.Navigation.Search" = "Search"; +"VoiceOver.Navigation.ProxySettings" = "Proxy settings"; +"VoiceOver.DiscardPreparedContent" = "Discard"; +"VoiceOver.AttachMedia" = "Send media"; +"VoiceOver.Chat.RecordPreviewVoiceMessage" = "Preview voice message"; +"VoiceOver.Chat.RecordModeVoiceMessage" = "Voice message"; +"VoiceOver.Chat.RecordModeVoiceMessageInfo" = "Double tap and hold to record voice message. Slide up to pin recording, slide left to cancel. Double tap to switch to video."; +"VoiceOver.Chat.RecordModeVideoMessage" = "Video message"; +"VoiceOver.Chat.RecordModeVideoMessageInfo" = "Double tap and hold to record video message. Slide up to pin recording, slide left to cancel. Double tap to switch to audio."; +"VoiceOver.Chat.Message" = "Message"; +"VoiceOver.Chat.YourMessage" = "Your message"; +"VoiceOver.Chat.ReplyFrom" = "Reply to message from: %@"; +"VoiceOver.Chat.Reply" = "Reply to message"; +"VoiceOver.Chat.ReplyToYourMessage" = "Reply to your message"; +"VoiceOver.Chat.ForwardedFrom" = "Forwarded from: %@"; +"VoiceOver.Chat.ForwardedFromYou" = "Forwarded from you"; +"VoiceOver.Chat.PhotoFrom" = "Photo, from: %@"; +"VoiceOver.Chat.Photo" = "Photo"; +"VoiceOver.Chat.YourPhoto" = "Your photo"; +"VoiceOver.Chat.VoiceMessageFrom" = "Voice message, from: %@"; +"VoiceOver.Chat.VoiceMessage" = "Voice message"; +"VoiceOver.Chat.YourVoiceMessage" = "Your voice message"; +"VoiceOver.Chat.MusicFrom" = "Music file, from: %@"; +"VoiceOver.Chat.Music" = "Music message"; +"VoiceOver.Chat.YourMusic" = "Your music message"; +"VoiceOver.Chat.VideoFrom" = "Video, from: %@"; +"VoiceOver.Chat.Video" = "Video"; +"VoiceOver.Chat.YourVideo" = "Your video"; +"VoiceOver.Chat.VideoMessageFrom" = "Video message, from: %@"; +"VoiceOver.Chat.VideoMessage" = "Video message"; +"VoiceOver.Chat.YourVideoMessage" = "Your video message"; +"VoiceOver.Chat.FileFrom" = "File, from: %@"; +"VoiceOver.Chat.File" = "File"; +"VoiceOver.Chat.YourFile" = "Your file"; +"VoiceOver.Chat.ContactFrom" = "Shared contact, from: %@"; +"VoiceOver.Chat.Contact" = "Shared contact"; +"VoiceOver.Chat.ContactPhoneNumberCount_1" = "%@ phone number"; +"VoiceOver.Chat.ContactPhoneNumberCount_any" = "%@ phone numbers"; +"VoiceOver.Chat.ContactPhoneNumber" = "Phone number"; +"VoiceOver.Chat.ContactEmailCount_1" = "%@ email address"; +"VoiceOver.Chat.ContactEmailCount_any" = "%@ email addresses"; +"VoiceOver.Chat.ContactEmail" = "Email"; +"VoiceOver.Chat.ContactOrganization" = "Organization: %@"; +"VoiceOver.Chat.YourContact" = "Your shared contact"; +"VoiceOver.Chat.AnonymousPollFrom" = "Anonymous poll, from: %@"; +"VoiceOver.Chat.AnonymousPoll" = "Anonymous poll"; +"VoiceOver.Chat.YourAnonymousPoll" = "Your Anonymous poll"; +"VoiceOver.Chat.PollOptionCount_1" = "%@ option:"; +"VoiceOver.Chat.PollOptionCount_any" = "%@ options:"; +"VoiceOver.Chat.PollVotes_1" = "%@ vote"; +"VoiceOver.Chat.PollVotes_any" = "%@ votes"; +"VoiceOver.Chat.PollNoVotes" = "No votes"; +"VoiceOver.Chat.PollFinalResults" = "Final results"; +"VoiceOver.Chat.OptionSelected" = "selected"; +"VoiceOver.Chat.PagePreview" = "Page preview"; +"VoiceOver.Chat.Title" = "Title: %@"; +"VoiceOver.Chat.Caption" = "Caption: %@"; +"VoiceOver.Chat.Duration" = "Duration: %@"; +"VoiceOver.Chat.Size" = "Size: %@"; +"VoiceOver.Chat.MusicTitle" = "%1$@, by %2$@"; +"VoiceOver.Chat.PlayHint" = "Double tap to play"; +"VoiceOver.Chat.OpenHint" = "Double tap to open"; +"VoiceOver.Chat.OpenLinkHint" = "Double tap to open link"; +"VoiceOver.Chat.SeenByRecipient" = "Seen by recipient"; +"VoiceOver.Chat.SeenByRecipients" = "Seen by recipients"; +"VoiceOver.Chat.Selected" = "Selected"; +"VoiceOver.MessageContextDelete" = "Delete"; +"VoiceOver.MessageContextReport" = "Report"; +"VoiceOver.MessageContextForward" = "Forward"; +"VoiceOver.MessageContextShare" = "Share"; +"VoiceOver.MessageContextSend" = "Send"; +"VoiceOver.MessageContextReply" = "Reply"; +"VoiceOver.MessageContextOpenMessageMenu" = "Open message menu"; + +"ProxyServer.VoiceOver.Active" = "Active"; + +"Conversation.ScheduleMessage.Title" = "Schedule Message"; +"Conversation.ScheduleMessage.SendToday" = "Send today at %@"; +"Conversation.ScheduleMessage.SendTomorrow" = "Send tomorrow at %@"; +"Conversation.ScheduleMessage.SendOn" = "Send on %@ at %@"; + +"Conversation.SetReminder.Title" = "Set a Reminder"; +"Conversation.SetReminder.RemindToday" = "Remind today at %@"; +"Conversation.SetReminder.RemindTomorrow" = "Remind tomorrow at %@"; +"Conversation.SetReminder.RemindOn" = "Remind on %@ at %@"; + +"ScheduledMessages.Title" = "Scheduled Messages"; +"ScheduledMessages.RemindersTitle" = "Reminders"; +"ScheduledMessages.ScheduledDate" = "Scheduled for %@"; +"ScheduledMessages.ScheduledToday" = "Scheduled for today"; +"ScheduledMessages.SendNow" = "Send Now"; +"ScheduledMessages.EditTime" = "Reschedule"; +"ScheduledMessages.ClearAll" = "Clear All"; +"ScheduledMessages.ClearAllConfirmation" = "Clear Scheduled Messages"; +"ScheduledMessages.Delete" = "Delete Scheduled Message"; +"ScheduledMessages.DeleteMany" = "Delete Scheduled Messages"; +"ScheduledMessages.EmptyPlaceholder" = "No scheduled messages here yet..."; +"ScheduledMessages.BotActionUnavailable" = "This action will become available after the message is published."; +"ScheduledMessages.PollUnavailable" = "Voting will become available after the message is published."; +"ScheduledMessages.ReminderNotification" = "📅 Reminder"; + +"Conversation.SendMessage.SetReminder" = "Set a Reminder"; + +"Conversation.SelectedMessages_1" = "%@ Selected"; +"Conversation.SelectedMessages_2" = "%@ Selected"; +"Conversation.SelectedMessages_3_10" = "%@ Selected"; +"Conversation.SelectedMessages_any" = "%@ Selected"; +"Conversation.SelectedMessages_many" = "%@ Selected"; +"Conversation.SelectedMessages_0" = "%@ Selected"; + +"AccentColor.Title" = "Accent Color"; + +"Appearance.ThemePreview.ChatList.1.Name" = "Alicia Torreaux"; +"Appearance.ThemePreview.ChatList.1.Text" = "Bob says hi. 😊 ❤️ 😱"; +"Appearance.ThemePreview.ChatList.2.Name" = "Roberto"; +"Appearance.ThemePreview.ChatList.2.Text" = "Say hello to Alice 👋"; +"Appearance.ThemePreview.ChatList.3.Name" = "Campus Public Chat"; +"Appearance.ThemePreview.ChatList.3.AuthorName" = "Jennie Alpha"; +"Appearance.ThemePreview.ChatList.3.Text" = "We just reached 2,500 members! WOO!"; +"Appearance.ThemePreview.ChatList.4.Name" = "Veronica"; +"Appearance.ThemePreview.ChatList.4.Text" = "Table for four, 2PM. Be there."; +"Appearance.ThemePreview.ChatList.5.Name" = "Animal Videos"; +"Appearance.ThemePreview.ChatList.5.Text" = "Vote now! Moar cat videos in this channel?"; +"Appearance.ThemePreview.ChatList.6.Name" = "Little Sister"; +"Appearance.ThemePreview.ChatList.6.Text" = "Don't tell mom yet, but I got the job! I'm going to ROME!"; +"Appearance.ThemePreview.ChatList.7.Name" = "Jennie Alpha"; +"Appearance.ThemePreview.ChatList.7.Text" = "🖼 Check these out"; + +"Appearance.ThemePreview.Chat.1.Text" = "Does he want me to, to turn from the right or turn from the left? 🤔"; +"Appearance.ThemePreview.Chat.2.ReplyName" = "Bob Harris"; +"Appearance.ThemePreview.Chat.2.Text" = "Right side. And, uh, with intensity."; +"Appearance.ThemePreview.Chat.3.Text" = "Is that everything? It seemed like he said quite a bit more than that. 😯"; +"Appearance.ThemePreview.Chat.3.TextWithLink" = "Is that everything? It seemed like he said [quite a bit more] than that. 😯"; + +"Appearance.ThemePreview.Chat.4.Text" = "For relaxing times, make it Suntory time. 😎"; +"Appearance.ThemePreview.Chat.5.Text" = "He wants you to turn, look in camera. O.K.?"; +"Appearance.ThemePreview.Chat.6.Text" = "That’s all he said?"; +"Appearance.ThemePreview.Chat.7.Text" = "Yes, turn to camera."; + +"GroupInfo.Permissions.SlowmodeValue.Off" = "Off"; + +"Undo.ScheduledMessagesCleared" = "Scheduled messages cleared"; + +"Appearance.CreateTheme" = "Create New Theme"; +"Appearance.EditTheme" = "Edit Theme"; +"Appearance.ShareTheme" = "Share"; +"Appearance.RemoveTheme" = "Remove"; +"Appearance.RemoveThemeConfirmation" = "Remove Theme"; + +"Conversation.Theme" = "Color Theme"; +"Conversation.ViewTheme" = "VIEW THEME"; + +"Message.Theme" = "Color Theme"; + +"EditTheme.CreateTitle" = "Create Theme"; +"EditTheme.EditTitle" = "Edit Theme"; +"EditTheme.Title" = "Theme Name"; +"EditTheme.ShortLink" = "link"; +"EditTheme.Preview" = "CHAT PREVIEW"; +"EditTheme.UploadNewTheme" = "Create from File..."; +"EditTheme.UploadEditedTheme" = "Update from File..."; +"EditTheme.ThemeTemplateAlertTitle" = "New Theme Added"; +"EditTheme.ThemeTemplateAlertText" = "Press and hold on your theme to edit it or get a sharing link. Users who install your theme will get automatic updates each time you change it.\n\nFor advanced editing purposes, you can find a file with your theme in Saved Messages."; +"EditTheme.FileReadError" = "Invalid theme file"; + +"EditTheme.Create.TopInfo" = "The theme will be based on your currently selected colors and wallpaper."; +"EditTheme.Create.BottomInfo" = "You can also use a manually edited custom theme file."; + +"EditTheme.Expand.TopInfo" = "The theme will be based on your currently selected colors and wallpaper."; +"EditTheme.Expand.BottomInfo" = "You can also use a manually edited custom theme file."; + +"EditTheme.Edit.TopInfo" = "Your theme will be updated for all users each time you change it. Anyone can install it using this link.\n\nTheme links must be at least **5** characters long and can use **a-z**, **0-9** and underscores."; +"EditTheme.Edit.BottomInfo" = "You can select a new file to update the theme. It will be updated for all users."; + +"EditTheme.Create.Preview.IncomingReplyName" = "Bob"; +"EditTheme.Create.Preview.IncomingReplyText" = "How does it work?"; +"EditTheme.Create.Preview.IncomingText" = "Use your current colors"; +"EditTheme.Create.Preview.OutgoingText" = "Or upload a theme file"; + +"EditTheme.Expand.Preview.IncomingReplyName" = "Bob"; +"EditTheme.Expand.Preview.IncomingReplyText" = "How does it work?"; +"EditTheme.Expand.Preview.IncomingText" = "Use your current colors"; +"EditTheme.Expand.Preview.OutgoingText" = "Or upload a theme file"; + +"EditTheme.Edit.Preview.IncomingReplyName" = "Bob"; +"EditTheme.Edit.Preview.IncomingReplyText" = "How does it work?"; +"EditTheme.Edit.Preview.IncomingText" = "Use your current colors"; +"EditTheme.Edit.Preview.OutgoingText" = "Or upload a theme file"; + +"EditTheme.ErrorLinkTaken" = "Sorry, this link is already taken"; +"EditTheme.ErrorInvalidCharacters" = "Sorry, this link is invalid."; + +"Wallpaper.ErrorNotFound" = "Sorry, this chat background doesn't seem to exist."; +"Theme.ErrorNotFound" = "Sorry, this color theme doesn't seem to exist."; +"Theme.Unsupported" = "Sorry, this color theme doesn't support your device yet."; + +"Theme.UsersCount_1" = "%@ person is using this theme"; +"Theme.UsersCount_2" = "%@ people are using this theme"; +"Theme.UsersCount_3_10" = "%@ people are using this theme"; +"Theme.UsersCount_any" = "%@ people are using this theme"; +"Theme.UsersCount_many" = "%@ people are using this theme"; +"Theme.UsersCount_0" = "%@ people are using this theme"; + +"Conversation.SendMessageErrorTooMuchScheduled" = "Sorry, you can not schedule more than 100 messages."; + +"ChatList.Context.MarkAllAsRead" = "Mark All as Read"; +"ChatList.Context.HideArchive" = "Hide Above the List"; +"ChatList.Context.UnhideArchive" = "Pin in the list"; +"ChatList.Context.RemoveFromRecents" = "Clear from Recents"; +"ChatList.Context.AddToContacts" = "Add to Contacts"; +"ChatList.Context.MarkAsRead" = "Mark as Read"; +"ChatList.Context.MarkAsUnread" = "Mark as Unread"; +"ChatList.Context.Archive" = "Archive"; +"ChatList.Context.Unarchive" = "Unarchive"; +"ChatList.Context.Pin" = "Pin"; +"ChatList.Context.Unpin" = "Unpin"; +"ChatList.Context.Mute" = "Mute"; +"ChatList.Context.Unmute" = "Unmute"; +"ChatList.Context.JoinChannel" = "Join Channel"; +"ChatList.Context.Delete" = "Delete"; + +"ContactList.Context.SendMessage" = "Send Message"; +"ContactList.Context.StartSecretChat" = "Start Secret Chat"; +"ContactList.Context.Call" = "Call"; +"ContactList.Context.VideoCall" = "Video Call"; + +"Theme.Context.Apply" = "Apply"; + +"Settings.Context.Logout" = "Logout"; + +"Channel.EditAdmin.PermissionDeleteMessagesOfOthers" = "Delete Messages of Others"; +"Channel.AdminLog.CanDeleteMessagesOfOthers" = "Delete Messages of Others"; + +"ChatSearch.ResultsTooltip" = "Tap to view as a list."; + +"Wallet.Updated.JustNow" = "updated just now"; +"Wallet.Updated.MinutesAgo_0" = "%@ minutes ago"; //three to ten +"Wallet.Updated.MinutesAgo_1" = "1 minute ago"; //one +"Wallet.Updated.MinutesAgo_2" = "2 minutes ago"; //two +"Wallet.Updated.MinutesAgo_3_10" = "%@ minutes ago"; //three to ten +"Wallet.Updated.MinutesAgo_many" = "%@ minutes ago"; // more than ten +"Wallet.Updated.MinutesAgo_any" = "%@ minutes ago"; // more than ten +"Wallet.Updated.HoursAgo_0" = "%@ hours ago"; +"Wallet.Updated.HoursAgo_1" = "1 hour ago"; +"Wallet.Updated.HoursAgo_2" = "2 hours ago"; +"Wallet.Updated.HoursAgo_3_10" = "%@ hours ago"; +"Wallet.Updated.HoursAgo_any" = "%@ hours ago"; +"Wallet.Updated.HoursAgo_many" = "%@ hours ago"; +"Wallet.Updated.HoursAgo_0" = "%@ hours ago"; +"Wallet.Updated.YesterdayAt" = "yesterday at %@"; +"Wallet.Updated.AtDate" = "%@"; +"Wallet.Updated.TodayAt" = "today at %@"; + +"Wallet.Info.WalletCreated" = "Wallet Created"; +"Wallet.Info.Address" = "Your wallet address"; +"Wallet.Info.YourBalance" = "your balance"; +"Wallet.Info.Receive" = "Receive"; +"Wallet.Info.ReceiveGrams" = "Receive Grams"; +"Wallet.Info.Send" = "Send"; +"Wallet.Info.RefreshErrorTitle" = "No network"; +"Wallet.Info.RefreshErrorText" = "Couldn't refresh balance. Please make sure your internet connection is working and try again."; +"Wallet.Info.RefreshErrorNetworkText" = "Wallet state can not be retrieved at this time. Please try again later."; +"Wallet.Info.UnknownTransaction" = "Empty Transaction"; +"Wallet.Info.TransactionTo" = "to"; +"Wallet.Info.TransactionFrom" = "from"; +"Wallet.Info.Updating" = "updating"; +"Wallet.Info.TransactionBlockchainFee" = "%@ blockchain fees"; +"Wallet.Info.TransactionPendingHeader" = "Pending"; +"Wallet.Qr.ScanCode" = "Scan QR Code"; +"Wallet.Qr.Title" = "QR Code"; +"Wallet.Receive.Title" = "Receive Grams"; +"Wallet.Receive.AddressHeader" = "YOUR WALLET ADDRESS"; +"Wallet.Receive.InvoiceUrlHeader" = "INVOICE URL"; +"Wallet.Receive.CopyAddress" = "Copy Wallet Address"; +"Wallet.Receive.CopyInvoiceUrl" = "Copy Invoice URL"; +"Wallet.Receive.ShareAddress" = "Share Wallet Address"; +"Wallet.Receive.ShareInvoiceUrl" = "Share Invoice URL"; +"Wallet.Receive.ShareUrlInfo" = "Share this link with other Gram wallet owners to receive Grams from them. Note: this link won't work for real Grams."; +"Wallet.Receive.AmountHeader" = "AMOUNT"; +"Wallet.Receive.AmountText" = "Grams to receive"; +"Wallet.Receive.AmountInfo" = "You can specify the amount and purpose of the payment to save the sender some time."; +"Wallet.Receive.CommentHeader" = "COMMENT (OPTIONAL)"; +"Wallet.Receive.CommentInfo" = "Description of the payment"; +"Wallet.Receive.AddressCopied" = "Address copied to clipboard."; +"Wallet.Receive.InvoiceUrlCopied" = "Invoice URL copied to clipboard."; +"Wallet.Send.Title" = "Send Grams"; +"Wallet.Send.AddressHeader" = "RECIPIENT WALLET ADDRESS"; +"Wallet.Send.AddressText" = "Enter wallet address..."; +"Wallet.Send.AddressInfo" = "Paste the 48-letter address of the recipient here or ask them to send you a ton:// link."; +"Wallet.Send.Balance" = "Balance: %@"; +"Wallet.Send.AmountText" = "Grams to send"; +"Wallet.Send.Confirmation" = "Confirmation"; +"Wallet.Send.ConfirmationText" = "Do you want to send **%1$@** Grams to\n\n%2$@?\n\nBlockchain fees: ~%3$@ grams"; +"Wallet.Send.ConfirmationConfirm" = "Confirm"; +"Wallet.Send.Send" = "Send"; +"Wallet.Send.OwnAddressAlertTitle" = "Warning"; +"Wallet.Send.OwnAddressAlertText" = "Sending Grams from a wallet to the same wallet doesn't make sense, you will simply waste a portion of the value on blockchain fees."; +"Wallet.Send.OwnAddressAlertProceed" = "Proceed"; +"Wallet.Send.TransactionInProgress" = "Please wait until the current transaction is completed."; +"Wallet.Send.SyncInProgress" = "Please wait while the wallet finishes syncing with the TON Blockchain."; +"Wallet.Send.EncryptComment" = "Encrypt Text"; +"Wallet.Settings.Title" = "Settings"; +"Wallet.Settings.Configuration" = "Server Settings"; +"Wallet.Settings.ConfigurationInfo" = "Advanced Settings"; +"Wallet.Settings.BackupWallet" = "Backup Wallet"; +"Wallet.Settings.DeleteWallet" = "Delete Wallet"; +"Wallet.Settings.DeleteWalletInfo" = "This will disconnect the wallet from this app. You will be able to restore your wallet using 24 secret words – or import another wallet.\n\nGram Wallets are located in the decentralized TON Blockchain. If you want a wallet to be deleted, simply transfer all the grams from it and leave it empty."; +"Wallet.Intro.NotNow" = "Not Now"; +"Wallet.Intro.ImportExisting" = "Import existing wallet"; +"Wallet.Intro.CreateErrorTitle" = "An Error Occurred"; +"Wallet.Intro.CreateErrorText" = "Sorry. Please try again."; +"Wallet.Intro.Title" = "Gram Wallet"; +"AppWallet.Intro.Text" = "The gram wallet allows you to make fast and secure blockchain-based payments without intermediaries."; +"Wallet.Intro.Text" = "Gram wallet allows you to make fast and secure blockchain-based payments without intermediaries."; +"Wallet.Intro.CreateWallet" = "Create My Wallet"; +"Wallet.Intro.Terms" = "By creating a wallet you accept the\n[Terms of Conditions]()."; +"TelegramWallet.Intro.TermsUrl" = "https://telegram.org/tos/wallet"; +"Wallet.Created.Title" = "Congratulations"; +"Wallet.Created.Text" = "Your Gram wallet has just been created. Only you control it.\n\nTo be able to always have access to it, please write down your secret words and\nset up a secure passcode."; +"Wallet.Created.Proceed" = "Proceed"; +"Wallet.Created.ExportErrorTitle" = "Error"; +"Wallet.Created.ExportErrorText" = "Encryption error. Please make sure you have enabled a device passcode in iOS settings and try again."; +"Wallet.Completed.Title" = "Ready to go!"; +"Wallet.Completed.Text" = "You’re all set. Now you have a wallet that only you control - directly, without middlemen or bankers."; +"Wallet.Completed.ViewWallet" = "View My Wallet"; +"Wallet.RestoreFailed.Title" = "Too Bad"; +"Wallet.RestoreFailed.Text" = "Without the secret words, you can't\nrestore access to your wallet."; +"Wallet.RestoreFailed.CreateWallet" = "Create a New Wallet"; +"Wallet.RestoreFailed.EnterWords" = "Enter 24 words"; +"Wallet.Sending.Title" = "Sending Grams"; +"Wallet.Sending.Text" = "Please wait a few seconds for your transaction to be processed..."; +"Wallet.Sending.Title" = "Sending Grams"; +"Wallet.Sent.Title" = "Done!"; +"Wallet.Sent.Text" = "**%@ Grams** have been sent."; +"Wallet.Sent.ViewWallet" = "View My Wallet"; +"Wallet.SecureStorageNotAvailable.Title" = "Set a Passcode"; +"Wallet.SecureStorageNotAvailable.Text" = "Please set up a Passcode on your device to enable secure payments with your Gram wallet."; +"Wallet.SecureStorageReset.Title" = "Security Settings Have Changed"; +"Wallet.SecureStorageReset.BiometryTouchId" = "Touch ID"; +"Wallet.SecureStorageReset.BiometryFaceId" = "Face ID"; +"Wallet.SecureStorageReset.BiometryText" = "Unfortunately, your wallet is no longer available because your system Passcode or %@ has been turned off. Please enable them before proceeding."; +"Wallet.SecureStorageReset.PasscodeText" = "Unfortunately, your wallet is no longer available because your system Passcode has been turned off. Please enable it before proceeding."; +"Wallet.SecureStorageChanged.BiometryText" = "Unfortunately, your wallet is no longer available due to the change in your system security settings (Passcode/%@). To restore your wallet, tap \"Import existing wallet\"."; +"Wallet.SecureStorageChanged.PasscodeText" = "Unfortunately, your wallet is no longer available due to the change in your system security settings (Passcode). To restore your wallet, tap \"Import existing wallet\"."; +"Wallet.SecureStorageChanged.ImportWallet" = "Import Existing Wallet"; +"Wallet.SecureStorageChanged.CreateWallet" = "Create New Wallet"; +"Wallet.TransactionInfo.Title" = "Transaction"; +"Wallet.TransactionInfo.NoAddress" = "No Address"; +"Wallet.TransactionInfo.RecipientHeader" = "RECIPIENT"; +"Wallet.TransactionInfo.SenderHeader" = "SENDER"; +"Wallet.TransactionInfo.CopyAddress" = "Copy Wallet Address"; +"Wallet.TransactionInfo.AddressCopied" = "Address copied to clipboard."; +"Wallet.TransactionInfo.SendGrams" = "Send Grams to This Address"; +"Wallet.TransactionInfo.CommentHeader" = "COMMENT"; +"Wallet.TransactionInfo.StorageFeeHeader" = "STORAGE FEE"; +"Wallet.TransactionInfo.OtherFeeHeader" = "TRANSACTION FEE"; +"Wallet.TransactionInfo.StorageFeeInfo" = "Blockchain validators collect a tiny fee for storing information about your decentralized wallet and processing your transactions."; +"Wallet.TransactionInfo.StorageFeeInfoUrl" = "Blockchain validators collect a tiny fee for storing information about your decentralized wallet and processing your transactions. [More info]()"; +"Wallet.TransactionInfo.OtherFeeInfo" = "Blockchain validators collect a tiny fee for processing your decentralized transactions."; +"Wallet.TransactionInfo.OtherFeeInfoUrl" = "Blockchain validators collect a tiny fee for processing your decentralized transactions. [More info]()"; +"AppWallet.TransactionInfo.FeeInfoURL" = "https://telegram.org/wallet/fee"; +"Wallet.WordCheck.Title" = "Test Time!"; +"Wallet.WordCheck.Text" = "Let’s check that you wrote them down correctly. Please enter the words\n**%1$@**, **%2$@** and **%3$@**"; +"Wallet.WordCheck.Continue" = "Continue"; +"Wallet.WordCheck.IncorrectHeader" = "Incorrect words!"; +"Wallet.WordCheck.IncorrectText" = "The secret words you have entered do not match the ones in the list."; +"Wallet.WordCheck.TryAgain" = "Try Again"; +"Wallet.WordCheck.ViewWords" = "View Words"; +"Wallet.WordImport.Title" = "24 Secret Words"; +"Wallet.WordImport.Text" = "Please restore access to your wallet by\nentering the 24 secret words you wrote down when creating the wallet."; +"Wallet.WordImport.Continue" = "Continue"; +"Wallet.WordImport.CanNotRemember" = "I don't have them"; +"Wallet.WordImport.IncorrectTitle" = "Incorrect words"; +"Wallet.WordImport.IncorrectText" = "Sorry, you have entered incorrect secret words. Please double check and try again."; +"Wallet.Words.Title" = "24 Secret Words"; +"Wallet.Words.Text" = "Write down these 24 words in the correct order and store them in a secret place.\n\nUse these secret words to restore access to your wallet if you lose your passcode or device."; +"Wallet.Words.Done" = "Done"; +"Wallet.Words.NotDoneTitle" = "Sure Done?"; +"Wallet.Words.NotDoneText" = "You didn't have enough time to write those words down."; +"Wallet.Words.NotDoneOk" = "OK, Sorry"; +"Wallet.Words.NotDoneResponse" = "Apologies Accepted"; +"Wallet.Send.NetworkErrorTitle" = "No network"; +"Wallet.Send.NetworkErrorText" = "Couldn't send grams. Please make sure your internet connection is working and try again."; +"Wallet.Send.ErrorNotEnoughFundsTitle" = "Insufficient Grams"; +"Wallet.Send.ErrorNotEnoughFundsText" = "Unfortunately, your transfer couldn't be completed. You don't have enough grams."; +"Wallet.Send.ErrorInvalidAddress" = "Invalid wallet address. Please correct and try again."; +"Wallet.Send.ErrorDecryptionFailed" = "Please make sure that your device has a passcode set in iOS Settings and try again."; +"Wallet.Send.UninitializedTitle" = "Warning"; +"Wallet.Send.UninitializedText" = "This address belongs to an empty wallet. Are you sure you want to transfer grams to it?"; +"Wallet.Send.SendAnyway" = "Send Anyway"; +"Wallet.Receive.CreateInvoice" = "Create Invoice"; +"Wallet.Receive.CreateInvoiceInfo" = "You can specify the amount and purpose of the payment to save the sender some time."; + +"Conversation.WalletRequiredTitle" = "Gram Wallet Required"; +"Conversation.WalletRequiredText" = "This link can be used to send money on the TON Blockchain. To do this, you need to set up a Gram wallet first."; +"Conversation.WalletRequiredNotNow" = "Not Now"; +"Conversation.WalletRequiredSetup" = "Set Up"; + +"Wallet.Configuration.Title" = "Server Settings"; +"Wallet.Configuration.Apply" = "Save"; +"Wallet.Configuration.SourceHeader" = "SOURCE"; +"Wallet.Configuration.SourceURL" = "URL"; +"Wallet.Configuration.SourceJSON" = "JSON"; +"Wallet.Configuration.SourceInfo" = "Using a different configuration allows you to change Lite Server addresses."; +"Wallet.Configuration.BlockchainIdHeader" = "BLOCKCHAIN ID"; +"Wallet.Configuration.BlockchainIdPlaceholder" = "Blockchain ID"; +"Wallet.Configuration.BlockchainIdInfo" = "This setting is for developers. Change it only if you are working on creating your own TON network."; +"Wallet.Configuration.ApplyErrorTitle" = "Error"; +"Wallet.Configuration.ApplyErrorTextURLInvalid" = "The URL you have entered is invalid. Please try again."; +"Wallet.Configuration.ApplyErrorTextURLUnreachable" = "There was an error while downloading configuration from %@\nPlease try again."; +"Wallet.Configuration.ApplyErrorTextURLInvalidData" = "This blockchain configuration is invalid. Please try again."; +"Wallet.Configuration.ApplyErrorTextJSONInvalidData" = "This blockchain configuration is invalid. Please try again."; +"Wallet.Configuration.BlockchainNameChangedTitle" = "Warning"; +"Wallet.Configuration.BlockchainNameChangedText" = "Are you sure you want to change the blockchain ID? You don't need this unless you're testing your own TON network.\n\nIf you proceed, you will need to reconnect your wallet using 24 secret words."; +"Wallet.Configuration.BlockchainNameChangedProceed" = "Proceed"; + +"Wallet.CreateInvoice.Title" = "Create Invoice"; + +"Wallet.Navigation.Close" = "Close"; +"Wallet.Navigation.Back" = "Back"; +"Wallet.Navigation.Done" = "Done"; +"Wallet.Navigation.Cancel" = "Cancel"; +"Wallet.Alert.OK" = "OK"; +"Wallet.Alert.Cancel" = "Cancel"; + +"Wallet.Month.GenJanuary" = "January"; +"Wallet.Month.GenFebruary" = "February"; +"Wallet.Month.GenMarch" = "March"; +"Wallet.Month.GenApril" = "April"; +"Wallet.Month.GenMay" = "May"; +"Wallet.Month.GenJune" = "June"; +"Wallet.Month.GenJuly" = "July"; +"Wallet.Month.GenAugust" = "August"; +"Wallet.Month.GenSeptember" = "September"; +"Wallet.Month.GenOctober" = "October"; +"Wallet.Month.GenNovember" = "November"; +"Wallet.Month.GenDecember" = "December"; +"Wallet.Month.ShortJanuary" = "Jan"; +"Wallet.Month.ShortFebruary" = "Feb"; +"Wallet.Month.ShortMarch" = "Mar"; +"Wallet.Month.ShortApril" = "Apr"; +"Wallet.Month.ShortMay" = "May"; +"Wallet.Month.ShortJune" = "Jun"; +"Wallet.Month.ShortJuly" = "Jul"; +"Wallet.Month.ShortAugust" = "Aug"; +"Wallet.Month.ShortSeptember" = "Sep"; +"Wallet.Month.ShortOctober" = "Oct"; +"Wallet.Month.ShortNovember" = "Nov"; +"Wallet.Month.ShortDecember" = "Dec"; + +"Wallet.Weekday.Today" = "Today"; +"Wallet.Weekday.Yesterday" = "Yesterday"; + +"Wallet.Info.TransactionDateHeader" = "%1$@ %2$@"; +"Wallet.Info.TransactionDateHeaderYear" = "%1$@ %2$@, %3$@"; + +"Wallet.UnknownError" = "An error occurred. Please try again later."; + +"Wallet.ContextMenuCopy" = "Copy"; + +"Wallet.Time.PreciseDate_m1" = "Jan %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m2" = "Feb %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m3" = "Mar %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m4" = "Apr %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m5" = "May %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m6" = "Jun %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m7" = "Jul %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m8" = "Aug %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m9" = "Sep %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m10" = "Oct %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m11" = "Nov %1$@, %2$@ at %3$@"; +"Wallet.Time.PreciseDate_m12" = "Dec %1$@, %2$@ at %3$@"; + +"Wallet.VoiceOver.Editing.ClearText" = "Clear text"; + +"Wallet.Receive.ShareInvoiceUrlInfo" = "Share this link with other Gram wallet owners to receive %@ Grams from them."; + +"Settings.Wallet" = "Gram Wallet"; +"SettingsSearch.Synonyms.Wallet" = "TON Telegram Open Network Crypto"; + +"Conversation.ClearCache" = "Clear Cache"; +"ClearCache.Description" = "Media files will be deleted from your phone, but available for re-downloading when necessary."; +"ClearCache.FreeSpaceDescription" = "If you want to save space on your device, you don't need to delete anything.\n\nYou can use cache settings to remove unnecessary media — and re-download files if you need them again."; +"ClearCache.FreeSpace" = "Free Space"; +"ClearCache.Success" = "**%@** freed on your %@!"; +"ClearCache.StorageUsage" = "Storage Usage"; + +"Conversation.ScheduleMessage.SendWhenOnline" = "Send When Online"; +"ScheduledMessages.ScheduledOnline" = "Scheduled until online"; + +"Conversation.SwipeToReplyHintTitle" = "Swipe To Reply"; +"Conversation.SwipeToReplyHintText" = "Swipe left on any message to reply to it."; + +"TwoFactorSetup.Intro.Title" = "Additional Password"; +"TwoFactorSetup.Intro.Text" = "You can set a password that will be\nrequired when you log in on a new device in addition to the code you get via SMS."; +"TwoFactorSetup.Intro.Action" = "Set Additional Password"; + +"TwoFactorSetup.Password.Title" = "Create Password"; +"TwoFactorSetup.Password.PlaceholderPassword" = "Password"; +"TwoFactorSetup.Password.PlaceholderConfirmPassword" = "Re-enter Password"; +"TwoFactorSetup.Password.Action" = "Create Password"; + +"TwoFactorSetup.Email.Title" = "Recovery Email"; +"TwoFactorSetup.Email.Text" = "You can set a recovery email to be able to reset you password and restore access to your Telegram account."; +"TwoFactorSetup.Email.Placeholder" = "Your email address"; +"TwoFactorSetup.Email.Action" = "Continue"; +"TwoFactorSetup.Email.SkipAction" = "Skip setting email"; +"TwoFactorSetup.Email.SkipConfirmationTitle" = "No, seriously."; +"TwoFactorSetup.Email.SkipConfirmationText" = "If you forget your password, you will lose access to your Telegram account. There will be no way to restore it."; +"TwoFactorSetup.Email.SkipConfirmationSkip" = "Skip"; + +"TwoFactorSetup.EmailVerification.Title" = "Recovery Email"; +"TwoFactorSetup.EmailVerification.Text" = "Please enter code we've just emailed at %@"; +"TwoFactorSetup.EmailVerification.Placeholder" = "Code"; +"TwoFactorSetup.EmailVerification.Action" = "Continue"; +"TwoFactorSetup.EmailVerification.ChangeAction" = "Change Email"; +"TwoFactorSetup.EmailVerification.ResendAction" = "Re-send Code"; + +"TwoFactorSetup.Hint.Title" = "Hint"; +"TwoFactorSetup.Hint.Text" = "You can create an optional hint for\nyour password."; +"TwoFactorSetup.Hint.Placeholder" = "Hint (optional)"; +"TwoFactorSetup.Hint.Action" = "Continue"; +"TwoFactorSetup.Hint.SkipAction" = "Skip setting hint"; + +"TwoFactorSetup.Done.Title" = "Password Set!"; +"TwoFactorSetup.Done.Text" = "This password will be required when you log in on a new device in addition to the code you get via SMS."; +"TwoFactorSetup.Done.Action" = "Return to Settings"; + +"AutoNightTheme.System" = "System"; + +"ChatSettings.OpenLinksIn" = "Open Links in"; +"SettingsSearch.Synonyms.ChatSettings.OpenLinksIn" = "Browser"; + +"WebBrowser.Title" = "Web Browser"; +"WebBrowser.DefaultBrowser" = "DEFAULT WEB BROWSER"; +"WebBrowser.InAppSafari" = "In-App Safari"; + +"Widget.ApplicationLocked" = "Unlock the app to use the widget"; + +"Group.ErrorSupergroupConversionNotPossible" = "Sorry, you are a member of too many groups and channels. Please leave some before creating a new one."; + +"ClearCache.StorageTitle" = "%@ STORAGE"; +"ClearCache.StorageCache" = "Telegram Cache"; +"ClearCache.StorageServiceFiles" = "Telegram Service Files"; +"ClearCache.StorageOtherApps" = "Other Apps"; +"ClearCache.StorageFree" = "Free"; +"ClearCache.ClearCache" = "Clear Telegram Cache"; +"ClearCache.Clear" = "Clear"; +"ClearCache.Forever" = "Forever"; + +"ChatList.DeletedChats_1" = "Deleted 1 chat"; +"ChatList.DeletedChats_any" = "Deleted %@ chats"; + +"Appearance.ColorThemeNight" = "COLOR THEME — AUTO-NIGHT MODE"; + +"UserInfo.StartSecretChatConfirmation" = "Are you sure you want to start a secret chat with\n%@?"; +"UserInfo.StartSecretChatStart" = "Start"; + +"Wallet.AccessDenied.Title" = "Please Allow Access"; +"Wallet.AccessDenied.Camera" = "TON Wallet needs access to your camera to take photos and videos.\n\nPlease go to Settings > Privacy > Camera and set TON Wallet to ON."; +"Wallet.AccessDenied.Settings" = "Settings"; + +"GroupInfo.ShowMoreMembers_0" = "%@ more"; +"GroupInfo.ShowMoreMembers_1" = "%@ more"; +"GroupInfo.ShowMoreMembers_2" = "%@ more"; +"GroupInfo.ShowMoreMembers_3_10" = "%@ more"; +"GroupInfo.ShowMoreMembers_many" = "%@ more"; +"GroupInfo.ShowMoreMembers_any" = "%@ more"; + +"ContactInfo.Note" = "note"; + +"Group.Location.CreateInThisPlace" = "Create a group in this place"; + +"Theme.Colors.Accent" = "Accent"; +"Theme.Colors.Background" = "Background"; +"Theme.Colors.Messages" = "Messages"; +"Theme.Colors.ColorWallpaperWarning" = "Are you sure you want to change your chat wallpaper to a color?"; +"Theme.Colors.ColorWallpaperWarningProceed" = "Proceed"; + +"ChatSettings.IntentsSettings" = "Share Sheet"; +"IntentsSettings.Title" = "Share Sheet"; +"IntentsSettings.MainAccount" = "Main Account"; +"IntentsSettings.MainAccountInfo" = "Choose an account for Siri and share suggestions."; +"IntentsSettings.SuggestedChats" = "Suggested Chats"; +"IntentsSettings.SuggestedChatsContacts" = "Contacts"; +"IntentsSettings.SuggestedChatsSavedMessages" = "Saved Messages"; +"IntentsSettings.SuggestedChatsPrivateChats" = "Private Chats"; +"IntentsSettings.SuggestedChatsGroups" = "Groups"; +"IntentsSettings.SuggestedChatsInfo" = "Archived chats will not be suggested."; +"IntentsSettings.SuggestedAndSpotlightChatsInfo" = "Suggestions will appear in the Share Sheet and Spotlight search results. Archived chats will not be suggested."; +"IntentsSettings.SuggestBy" = "Suggest By"; +"IntentsSettings.SuggestByAll" = "All Sent Messages"; +"IntentsSettings.SuggestByShare" = "Only Shared Messages"; +"IntentsSettings.ResetAll" = "Reset All Share Suggestions"; +"IntentsSettings.Reset" = "Reset"; + +"Conversation.SendingOptionsTooltip" = "Hold this button to schedule your message\nor send it without sound."; + +"Appearance.TextSizeSetting" = "Text Size"; +"Appearance.TextSize.Automatic" = "System"; +"Appearance.TextSize.Title" = "Text Size"; +"Appearance.TextSize.UseSystem" = "User System Text Size"; +"Appearance.TextSize.Apply" = "Set"; + +"Shortcut.SwitchAccount" = "Switch Account"; + +"Settings.Devices" = "Devices"; +"Settings.AddDevice" = "Scan QR"; +"AuthSessions.DevicesTitle" = "Devices"; +"AuthSessions.AddDevice" = "Scan QR"; +"AuthSessions.AddDevice.ScanInfo" = "Scan a QR code to log into\nthis account on another device."; +"AuthSessions.AddDevice.ScanTitle" = "Scan QR Code"; +"AuthSessions.AddDevice.InvalidQRCode" = "Invalid QR Code"; +"AuthSessions.AddDeviceIntro.Title" = "Log in by QR Code"; +"AuthSessions.AddDeviceIntro.Text1" = "Download Telegram on your computer from [desktop.telegram.org]()"; +"AuthSessions.AddDeviceIntro.Text2" = "Run Telegram on your computer to get the QR code"; +"AuthSessions.AddDeviceIntro.Text3" = "Scan the QR code to connect your account"; +"AuthSessions.AddDeviceIntro.Action" = "Scan QR Code"; +"AuthSessions.AddedDeviceTitle" = "Login Successful"; +"AuthSessions.AddedDeviceTerminate" = "Terminate"; + +"Map.SendThisPlace" = "Send This Place"; +"Map.SetThisPlace" = "Set This Place"; +"Map.AddressOnMap" = "Address On Map"; +"Map.PlacesNearby" = "Places Nearby"; +"Map.Home" = "Home"; +"Map.Work" = "Work"; +"Map.HomeAndWorkTitle" = "Home & Work Addresses"; +"Map.HomeAndWorkInfo" = "Telegram uses the Home and Work addresses from your Contact Card.\n\nKeep your Contact Card up to date for quick access to sending Home and Work addresses."; +"Map.SearchNoResultsDescription" = "There were no results for \"%@\".\nTry a new search."; + +"ChatList.Search.ShowMore" = "Show more"; +"ChatList.Search.ShowLess" = "Show less"; + +"AuthSessions.OtherDevices" = "The official Telegram App is available for iPhone, iPad, Android, macOS, Windows and Linux. [Learn More]()"; + +"MediaPlayer.UnknownArtist" = "Unknown Artist"; +"MediaPlayer.UnknownTrack" = "Unknown Track"; + +"Contacts.InviteContacts_1" = "Invite %@ Contact"; +"Contacts.InviteContacts_2" = "Invite %@ Contacts"; +"Contacts.InviteContacts_3_10" = "Invite %@ Contacts"; +"Contacts.InviteContacts_any" = "Invite %@ Contacts"; +"Contacts.InviteContacts_many" = "Invite %@ Contacts"; +"Contacts.InviteContacts_0" = "Invite %@ Contacts"; + +"Theme.Context.ChangeColors" = "Change Colors"; + +"EditTheme.ChangeColors" = "Change Colors"; + +"Theme.Colors.Proceed" = "Proceed"; + +"AuthSessions.AddDevice.UrlLoginHint" = "This code can be used to allow someone to log in to your Telegram account.\n\nTo confirm Telegram login, please go to Settings > Devices > Scan QR and scan the code."; + +"Appearance.RemoveThemeColor" = "Remove"; +"Appearance.RemoveThemeColorConfirmation" = "Remove Color"; + +"WallpaperPreview.PatternTitle" = "Choose Pattern"; +"WallpaperPreview.PatternPaternDiscard" = "Discard"; +"WallpaperPreview.PatternPaternApply" = "Apply"; + +"ChatContextMenu.TextSelectionTip" = "Hold a word, then move cursor to select more| text to copy."; + +"OldChannels.Title" = "Limit Reached"; +"OldChannels.NoticeTitle" = "Too Many Groups and Channels"; +"OldChannels.NoticeText" = "Sorry, you are member of too many groups and channels.\nPlease leave some before joining new one."; +"OldChannels.NoticeCreateText" = "Sorry, you are member of too many groups and channels.\nPlease leave some before creating a new one."; +"OldChannels.NoticeUpgradeText" = "Sorry, you are a member of too many groups and channels.\nFor technical reasons, you need to leave some first before changing this setting in your groups."; +"OldChannels.ChannelsHeader" = "MOST INACTIVE"; +"OldChannels.Leave_1" = "Leave %@ Chat"; +"OldChannels.Leave_any" = "Leave %@ Chats"; + +"OldChannels.ChannelFormat" = "channel, "; +"OldChannels.GroupEmptyFormat" = "group, "; +"OldChannels.GroupFormat_1" = "%@ member "; +"OldChannels.GroupFormat_any" = "%@ members "; + +"OldChannels.InactiveWeek_1" = "inactive %@ week"; +"OldChannels.InactiveWeek_any" = "inactive %@ weeks"; + +"OldChannels.InactiveMonth_1" = "inactive %@ month"; +"OldChannels.InactiveMonth_any" = "inactive %@ months"; + +"OldChannels.InactiveYear_1" = "inactive %@ year"; +"OldChannels.InactiveYear_any" = "inactive %@ years"; + +"PrivacySettings.WebSessions" = "Active Websites"; + +"Appearance.ShareThemeColor" = "Share"; + +"Theme.ThemeChanged" = "Color Theme Changed"; +"Theme.ThemeChangedText" = "You can change it back in\n[Settings > Appearance]()."; + +"StickerPackActionInfo.AddedTitle" = "Stickers Added"; +"StickerPackActionInfo.AddedText" = "%@ has been added to your stickers."; +"StickerPackActionInfo.RemovedTitle" = "Stickers Removed"; +"StickerPackActionInfo.ArchivedTitle" = "Stickers Archived"; +"StickerPackActionInfo.RemovedText" = "%@ is no longer in your stickers."; + +"Conversation.ContextMenuCancelEditing" = "Cancel Editing"; + +"Map.NoPlacesNearby" = "There are no known places nearby.\nTry a different location."; + +"CreatePoll.QuizTitle" = "New Quiz"; +"CreatePoll.QuizOptionsHeader" = "QUIZ ANSWERS"; +"CreatePoll.Anonymous" = "Anonymous Voting"; +"CreatePoll.MultipleChoice" = "Multiple Choice"; +"CreatePoll.MultipleChoiceQuizAlert" = "A quiz has one correct answer."; +"CreatePoll.Quiz" = "Quiz Mode"; +"CreatePoll.QuizInfo" = "Polls in Quiz Mode have one correct answer. Users can't revoke their answers."; +"CreatePoll.QuizTip" = "Tap to choose the correct answer"; + +"MessagePoll.LabelPoll" = "Public Poll"; +"MessagePoll.LabelAnonymousQuiz" = "Anonymous Quiz"; +"MessagePoll.LabelQuiz" = "Quiz"; +"MessagePoll.SubmitVote" = "Vote"; +"MessagePoll.ViewResults" = "View Results"; +"MessagePoll.QuizNoUsers" = "Nobody answered yet"; +"MessagePoll.QuizCount_0" = "%@ answered"; +"MessagePoll.QuizCount_1" = "1 answered"; +"MessagePoll.QuizCount_2" = "2 answered"; +"MessagePoll.QuizCount_3_10" = "%@ answered"; +"MessagePoll.QuizCount_many" = "%@ answered"; +"MessagePoll.QuizCount_any" = "%@ answered"; + +"PollResults.Title" = "Poll Results"; +"PollResults.Collapse" = "COLLAPSE"; +"PollResults.ShowMore_1" = "Show More (%@)"; +"PollResults.ShowMore_any" = "Show More (%@)"; + +"Conversation.StopQuiz" = "Stop Quiz"; +"Conversation.StopQuizConfirmationTitle" = "If you stop this quiz now, nobody will be able to submit answers. This action cannot be undone."; +"Conversation.StopQuizConfirmation" = "Stop Quiz"; + +"Forward.ErrorDisabledForChat" = "Sorry, you can't forward messages to this chat."; +"Forward.ErrorPublicPollDisabledInChannels" = "Sorry, public polls can’t be forwarded to channels."; +"Forward.ErrorPublicQuizDisabledInChannels" = "Sorry, public polls can’t be forwarded to channels."; + +"Map.PlacesInThisArea" = "Places In This Area"; + +"Appearance.BubbleCornersSetting" = "Message Corners"; +"Appearance.BubbleCorners.Title" = "Message Corners"; +"Appearance.BubbleCorners.AdjustAdjacent" = "Adjust Adjacent Corners"; +"Appearance.BubbleCorners.Apply" = "Set"; + +"Conversation.LiveLocationYouAndOther" = "**You** and %@"; + +"PeopleNearby.MakeVisible" = "Make Myself Visible"; +"PeopleNearby.MakeInvisible" = "Stop Showing Me"; + +"PeopleNearby.ShowMorePeople_0" = "Show %@ More People"; +"PeopleNearby.ShowMorePeople_1" = "Show %@ More People"; +"PeopleNearby.ShowMorePeople_2" = "Show %@ More People"; +"PeopleNearby.ShowMorePeople_3_10" = "Show %@ More People"; +"PeopleNearby.ShowMorePeople_many" = "Show %@ More People"; +"PeopleNearby.ShowMorePeople_any" = "Show %@ More People"; + +"PeopleNearby.VisibleUntil" = "visible until %@"; + +"PeopleNearby.MakeVisibleTitle" = "Make Myself Visible"; +"PeopleNearby.MakeVisibleDescription" = "Users nearby will be able to view your profile and send you messages. This may help you find new friends, but could also attract excessive attention. You can stop sharing your profile at any time.\n\nYour phone number will remain hidden."; + +"PeopleNearby.DiscoverDescription" = "Exchange contact info with people nearby\nand find new friends."; + +"Time.TomorrowAt" = "tomorrow at %@"; + +"PeerInfo.ButtonMessage" = "Message"; +"PeerInfo.ButtonDiscuss" = "Discuss"; +"PeerInfo.ButtonCall" = "Call"; +"PeerInfo.ButtonVideoCall" = "Video"; +"PeerInfo.ButtonMute" = "Mute"; +"PeerInfo.ButtonUnmute" = "Unmute"; +"PeerInfo.ButtonMore" = "More"; +"PeerInfo.ButtonAddMember" = "Add Members"; +"PeerInfo.ButtonSearch" = "Search"; +"PeerInfo.ButtonLeave" = "Leave"; + +"PeerInfo.PaneMedia" = "Media"; +"PeerInfo.PaneFiles" = "Files"; +"PeerInfo.PaneLinks" = "Links"; +"PeerInfo.PaneVoiceAndVideo" = "Voice"; +"PeerInfo.PaneAudio" = "Audio"; +"PeerInfo.PaneGroups" = "Groups"; +"PeerInfo.PaneMembers" = "Members"; +"PeerInfo.PaneGifs" = "GIFs"; + +"PeerInfo.AddToContacts" = "Add to Contacts"; + +"PeerInfo.BioExpand" = "more"; + +"External.OpenIn" = "Open in %@"; + +"ChatList.EmptyChatList" = "You have no\nconversations yet."; +"ChatList.EmptyChatListFilterTitle" = "Folder is empty."; +"ChatList.EmptyChatListFilterText" = "No chats currently match this folder."; + +"ChatList.EmptyChatListNewMessage" = "New Message"; +"ChatList.EmptyChatListEditFilter" = "Edit Folder"; + +"ChatListFilter.AddChatsTitle" = "Add Chats..."; + +"Stats.Overview" = "OVERVIEW"; +"Stats.Followers" = "Followers"; +"Stats.EnabledNotifications" = "Enabled Notifications"; +"Stats.ViewsPerPost" = "Views Per Post"; +"Stats.SharesPerPost" = "Shares Per Post"; + +"Stats.GrowthTitle" = "GROWTH"; +"Stats.FollowersTitle" = "FOLLOWERS"; +"Stats.NotificationsTitle" = "NOTIFICATIONS"; +"Stats.InteractionsTitle" = "INTERACTIONS"; +"Stats.InstantViewInteractionsTitle" = "INSTANT VIEW INTERACTIONS"; +"Stats.ViewsBySourceTitle" = "VIEWS BY SOURCE"; +"Stats.ViewsByHoursTitle" = "VIEWS BY HOURS"; +"Stats.FollowersBySourceTitle" = "FOLLOWERS BY SOURCE"; +"Stats.LanguagesTitle" = "LANGUAGES"; +"Stats.PostsTitle" = "RECENT POSTS"; + +"Stats.MessageViews_0" = "%@ views"; +"Stats.MessageViews_1" = "%@ view"; +"Stats.MessageViews_2" = "%@ views"; +"Stats.MessageViews_3_10" = "%@ views"; +"Stats.MessageViews_many" = "%@ views"; +"Stats.MessageViews_any" = "%@ views"; + +"Stats.MessageForwards_0" = "%@ forwards"; +"Stats.MessageForwards_1" = "%@ forward"; +"Stats.MessageForwards_2" = "%@ forwards"; +"Stats.MessageForwards_3_10" = "%@ forwards"; +"Stats.MessageForwards_many" = "%@ forwards"; +"Stats.MessageForwards_any" = "%@ forwards"; + +"Stats.LoadingTitle" = "Preparing stats"; +"Stats.LoadingText" = "Please wait a few moments while\nwe generate your stats"; + +"Stats.ZoomOut" = "Zoom Out"; +"Stats.Total" = "Total"; + +"InstantPage.Views_0" = "%@ views"; +"InstantPage.Views_1" = "%@ view"; +"InstantPage.Views_2" = "%@ views"; +"InstantPage.Views_3_10" = "%@ views"; +"InstantPage.Views_many" = "%@ views"; +"InstantPage.Views_any" = "%@ views"; +"InstantPage.FeedbackButtonShort" = "Wrong layout?"; + +"ChatList.EditFolder" = "Edit Folder"; +"ChatList.AddChatsToFolder" = "Add Chats"; +"ChatList.RemoveFolderConfirmation" = "This will remove the folder, your chats will not be deleted."; +"ChatList.RemoveFolderAction" = "Remove"; +"ChatList.RemoveFolder" = "Remove"; +"ChatList.ReorderTabs" = "Reorder Tabs"; +"ChatList.TabIconFoldersTooltipNonEmptyFolders" = "Hold on 'Chats' to edit folders and switch between views."; +"ChatList.TabIconFoldersTooltipEmptyFolders" = "Hold to organize your chats with folders."; +"ChatList.AddFolder" = "Add Folder"; +"ChatList.EditFolders" = "Edit Folders"; +"ChatList.FolderAllChats" = "All Chats"; +"ChatList.Tabs.AllChats" = "All Chats"; +"ChatList.Tabs.All" = "All"; +"Settings.ChatFolders" = "Chat Folders"; +"ChatList.ChatTypesSection" = "CHAT TYPES"; +"ChatList.PeerTypeNonContact" = "user"; +"ChatList.PeerTypeContact" = "contact"; +"ChatList.PeerTypeBot" = "bot"; +"ChatList.PeerTypeGroup" = "group"; +"ChatList.PeerTypeChannel" = "channel"; +"ChatListFolderSettings.Info" = "Create folders for different groups of chats and\nquickly switch between them."; + +"ChatListFolderSettings.Title" = "Folders"; +"ChatListFolderSettings.FoldersSection" = "FOLDERS"; +"ChatListFolderSettings.NewFolder" = "Create New Folder"; +"ChatListFolderSettings.EditFoldersInfo" = "Tap \"Edit\" to change the order or delete folders."; +"ChatListFolderSettings.RecommendedFoldersSection" = "RECOMMENDED FOLDERS"; +"ChatListFolderSettings.RecommendedNewFolder" = "Add Custom Folder"; + +"ChatListFolder.TitleCreate" = "New Folder"; +"ChatListFolder.TitleEdit" = "Edit Folder"; +"ChatListFolder.CategoryContacts" = "Contacts"; +"ChatListFolder.CategoryNonContacts" = "Non-Contacts"; +"ChatListFolder.CategoryBots" = "Bots"; +"ChatListFolder.CategoryGroups" = "Groups"; +"ChatListFolder.CategoryChannels" = "Channels"; +"ChatListFolder.CategoryMuted" = "Muted"; +"ChatListFolder.CategoryRead" = "Read"; +"ChatListFolder.CategoryArchived" = "Archived"; +"ChatListFolder.NameSectionHeader" = "FOLDER NAME"; +"ChatListFolder.NamePlaceholder" = "Folder Name"; +"ChatListFolder.IncludedSectionHeader" = "INCLUDED CHATS"; +"ChatListFolder.AddChats" = "Add Chats"; +"ChatListFolder.IncludeSectionInfo" = "Choose chats and types of chats that will appear in this folder."; +"ChatListFolder.ExcludedSectionHeader" = "EXCLUDED CHATS"; +"ChatListFolder.ExcludeSectionInfo" = "Choose chats and types of chats that will never appear in this folder."; +"ChatListFolder.NameNonMuted" = "Not Muted"; +"ChatListFolder.NameUnread" = "Unread"; +"ChatListFolder.NameChannels" = "Channels"; +"ChatListFolder.NameContacts" = "Contacts"; +"ChatListFolder.NameNonContacts" = "Non-Contacts"; +"ChatListFolder.NameBots" = "Bots"; +"ChatListFolder.NameGroups" = "Groups"; +"ChatListFolder.DiscardConfirmation" = "You have changed the filter. Discard changes?"; +"ChatListFolder.DiscardDiscard" = "Discard"; +"ChatListFolder.DiscardCancel" = "No"; +"ChatListFolder.IncludeChatsTitle" = "Include Chats"; +"ChatListFolder.ExcludeChatsTitle" = "Exclude Chats"; + +"ChatListFolderSettings.AddRecommended" = "ADD"; + +"ChatListFilter.ShowMoreChats_0" = "Show %@ More Chats"; +"ChatListFilter.ShowMoreChats_1" = "Show %@ More Chat"; +"ChatListFilter.ShowMoreChats_2" = "Show %@ More Chats"; +"ChatListFilter.ShowMoreChats_3_10" = "Show %@ More Chats"; +"ChatListFilter.ShowMoreChats_many" = "Show %@ More Chats"; +"ChatListFilter.ShowMoreChats_any" = "Show %@ More Chats"; + +"MuteFor.Forever" = "Mute Forever"; + +"Conversation.Dice.u1F3B2" = "Send a dice emoji to any chat to roll a die."; +"Conversation.Dice.u1F3AF" = "Send a dart emoji to try your luck."; +"Conversation.SendDice" = "Send"; + +"Conversation.ContextMenuDiscuss" = "Discuss"; + +"CreatePoll.ExplanationHeader" = "EXPLANATION"; +"CreatePoll.Explanation" = "Add a Comment (Optional)"; +"CreatePoll.ExplanationInfo" = "Users will see this comment after choosing a wrong answer, good for educational purposes."; + +"FeaturedStickers.OtherSection" = "OTHER STICKERS"; + +"ChatList.GenericPsaAlert" = "This provides public service announcements in your chat list."; +"ChatList.PsaAlert.covid" = "This message provides you with a public service announcement in relation to the undergoing pandemics. Learn more about this initiative at https://telegram.org/blog/coronavirus"; +"ChatList.GenericPsaLabel" = "PSA"; +"ChatList.PsaLabel.covid" = "Covid-19"; +"Chat.GenericPsaTooltip" = "This is a public service announcement"; +"Chat.PsaTooltip.covid" = "This message provides you with a public service announcement in relation to the undergoing pandemics. Learn more about this initiative at https://telegram.org/blog/coronavirus"; + +"Message.GenericForwardedPsa" = "Public Service Announcement\nFrom: %@"; +"Message.ForwardedPsa.covid" = "Covid-19 Notification\nFrom: %@"; + +"Channel.AboutItem" = "about"; +"PeerInfo.GroupAboutItem" = "about"; + +"Widget.ApplicationStartRequired" = "Open the app to use the widget"; + +"ChatList.Context.AddToFolder" = "Add to Folder"; +"ChatList.Context.RemoveFromFolder" = "Remove from Folder"; +"ChatList.Context.Back" = "Back"; +"ChatList.AddedToFolderTooltip" = "%1$@ has been added to folder %2$@"; +"ChatList.RemovedFromFolderTooltip" = "%1$@ has been removed from folder %2$@"; + +"OwnershipTransfer.Transfer" = "Transfer"; + +"TwoStepAuth.Disable" = "Disable"; + +"Chat.Gifs.TrendingSectionHeader" = "TRENDING GIFS"; +"Chat.Gifs.SavedSectionHeader" = "MY GIFS"; + +"Paint.Framed" = "Framed"; + +"Media.SendingOptionsTooltip" = "Hold this button to send your message with a self-destruct timer."; +"Media.SendWithTimer" = "Send With Timer"; + +"Conversation.Timer.Title" = "Send With Timer"; +"Conversation.Timer.Send" = "Send With Timer"; + +"Paint.Pen" = "Pen"; +"Paint.Marker" = "Marker"; +"Paint.Neon" = "Neon"; +"Paint.Arrow" = "Arrow"; + +"Conversation.NoticeInvitedByInChannel" = "%@ invited you to this channel"; +"Conversation.NoticeInvitedByInGroup" = "%@ invited you to this group"; + +"ChatList.MessagePhotos_1" = "1 Photo"; +"ChatList.MessagePhotos_any" = "%@ Photos"; +"ChatList.MessageVideos_1" = "1 Videos"; +"ChatList.MessageVideos_any" = "%@ Videos"; + +"Conversation.PrivateChannelTimeLimitedAlertTitle" = "Join Channel"; +"Conversation.PrivateChannelTimeLimitedAlertText" = "This channel is private. Please join it to continue viewing its content."; +"Conversation.PrivateChannelTimeLimitedAlertJoin" = "Join"; + +"KeyCommand.SearchInChat" = "Search in Chat"; + +"PhotoEditor.SkinTool" = "Soften Skin"; +"PhotoEditor.BlurToolPortrait" = "Portrait"; +"PhotoEditor.SelectCoverFrame" = "Choose a cover for your profile video"; + +"Conversation.PeerNearbyTitle" = "%1$@ is %2$@ away"; +"Conversation.PeerNearbyText" = "Send a message or tap on the greeting below to show that you are ready to chat."; +"Conversation.PeerNearbyDistance" = "%1$@ is %2$@ away"; + +"ProfilePhoto.MainPhoto" = "Main Photo"; +"ProfilePhoto.SetMainPhoto" = "Set as Main Photo"; + +"ProfilePhoto.MainVideo" = "Main Video"; +"ProfilePhoto.SetMainVideo" = "Set as Main Video"; + +"Stats.GroupOverview" = "OVERVIEW"; +"Stats.GroupMembers" = "Members"; +"Stats.GroupMessages" = "Messages"; +"Stats.GroupViewers" = "Viewing Members"; +"Stats.GroupPosters" = "Posting Members"; + +"Stats.GroupGrowthTitle" = "GROWTH"; +"Stats.GroupMembersTitle" = "GROUP MEMBERS"; +"Stats.GroupNewMembersBySourceTitle" = "NEW MEMBERS BY SOURCE"; +"Stats.GroupLanguagesTitle" = "MEMBERS' PRIMARY LANGUAGE"; +"Stats.GroupMessagesTitle" = "MESSAGES"; +"Stats.GroupActionsTitle" = "ACTIONS"; +"Stats.GroupTopHoursTitle" = "TOP HOURS"; +"Stats.GroupTopWeekdaysTitle" = "TOP DAYS OF WEEK"; +"Stats.GroupTopPostersTitle" = "TOP MEMBERS"; +"Stats.GroupTopAdminsTitle" = "TOP ADMINS"; +"Stats.GroupTopInvitersTitle" = "TOP INVITERS"; + +"Stats.GroupTopPosterMessages_0" = "%@ messages"; +"Stats.GroupTopPosterMessages_1" = "%@ message"; +"Stats.GroupTopPosterMessages_2" = "%@ messages"; +"Stats.GroupTopPosterMessages_3_10" = "%@ messages"; +"Stats.GroupTopPosterMessages_many" = "%@ messages"; +"Stats.GroupTopPosterMessages_any" = "%@ messages"; + +"Stats.GroupTopPosterChars_0" = "%@ symbols per message"; +"Stats.GroupTopPosterChars_1" = "%@ symbol per message"; +"Stats.GroupTopPosterChars_2" = "%@ symbols per message"; +"Stats.GroupTopPosterChars_3_10" = "%@ symbols per message"; +"Stats.GroupTopPosterChars_many" = "%@ symbols per message"; +"Stats.GroupTopPosterChars_any" = "%@ symbols per message"; + +"Stats.GroupTopPoster.History" = "History"; +"Stats.GroupTopPoster.Promote" = "Promote"; + +"Stats.GroupTopAdminDeletions_0" = "%@ deletions"; +"Stats.GroupTopAdminDeletions_1" = "%@ deletion"; +"Stats.GroupTopAdminDeletions_2" = "%@ deletions"; +"Stats.GroupTopAdminDeletions_3_10" = "%@ deletions"; +"Stats.GroupTopAdminDeletions_many" = "%@ deletions"; +"Stats.GroupTopAdminDeletions_any" = "%@ deletions"; + +"Stats.GroupTopAdminKicks_0" = "%@ kicks"; +"Stats.GroupTopAdminKicks_1" = "%@ kick"; +"Stats.GroupTopAdminKicks_2" = "%@ kicks"; +"Stats.GroupTopAdminKicks_3_10" = "%@ kicks"; +"Stats.GroupTopAdminKicks_many" = "%@ kicks"; +"Stats.GroupTopAdminKicks_any" = "%@ kicks"; + +"Stats.GroupTopAdminBans_0" = "%@ bans"; +"Stats.GroupTopAdminBans_1" = "%@ ban"; +"Stats.GroupTopAdminBans_2" = "%@ bans"; +"Stats.GroupTopAdminBans_3_10" = "%@ bans"; +"Stats.GroupTopAdminBans_many" = "%@ bans"; +"Stats.GroupTopAdminBans_any" = "%@ bans"; + +"Stats.GroupTopAdmin.Actions" = "Actions"; +"Stats.GroupTopAdmin.Promote" = "Promote"; + +"Stats.GroupTopInviterInvites_0" = "%@ invitations"; +"Stats.GroupTopInviterInvites_1" = "%@ invitation"; +"Stats.GroupTopInviterInvites_2" = "%@ invitations"; +"Stats.GroupTopInviterInvites_3_10" = "%@ invitations"; +"Stats.GroupTopInviterInvites_many" = "%@ invitations"; +"Stats.GroupTopInviterInvites_any" = "%@ invitations"; + +"Stats.GroupTopInviter.History" = "History"; +"Stats.GroupTopInviter.Promote" = "Promote"; + +"PrivacySettings.AutoArchiveTitle" = "NEW CHATS FROM UNKNOWN USERS"; +"PrivacySettings.AutoArchive" = "Archive and Mute"; +"PrivacySettings.AutoArchiveInfo" = "Automatically archive and mute new chats, groups and channels from non-contacts."; + +"Call.RemoteVideoPaused" = "%@'s video is paused"; + +"Settings.SetProfilePhotoOrVideo" = "Set Photo or Video"; +"Settings.SetNewProfilePhotoOrVideo" = "Set New Photo or Video"; +"Settings.ViewVideo" = "View Video"; +"Settings.RemoveVideo" = "Remove Video"; + +"Conversation.Unarchive" = "Unarchive"; +"Conversation.UnarchiveDone" = "The chat was moved to your main list."; + +"ChatList.AutoarchiveSuggestion.Title" = "Hide new chats?"; +"ChatList.AutoarchiveSuggestion.Text" = "You are receiving lots of new chats from users who are not in your Contact List. Do you want to have such chats **automatically muted** and **archived**?"; +"ChatList.AutoarchiveSuggestion.OpenSettings" = "Go to Settings"; + +"SettingsSearch.Synonyms.ChatSettings.IntentsSettings" = "Siri Suggestions"; + +"Stats.GroupShowMoreTopPosters_0" = "Show %@ More"; +"Stats.GroupShowMoreTopPosters_1" = "Show %@ More"; +"Stats.GroupShowMoreTopPosters_2" = "Show %@ More"; +"Stats.GroupShowMoreTopPosters_3_10" = "Show %@ More"; +"Stats.GroupShowMoreTopPosters_many" = "Show %@ More"; +"Stats.GroupShowMoreTopPosters_any" = "Show %@ More"; + +"Stats.GroupShowMoreTopAdmins_0" = "Show %@ More"; +"Stats.GroupShowMoreTopAdmins_1" = "Show %@ More"; +"Stats.GroupShowMoreTopAdmins_2" = "Show %@ More"; +"Stats.GroupShowMoreTopAdmins_3_10" = "Show %@ More"; +"Stats.GroupShowMoreTopAdmins_many" = "Show %@ More"; +"Stats.GroupShowMoreTopAdmins_any" = "Show %@ More"; + +"Stats.GroupShowMoreTopInviters_0" = "Show %@ More"; +"Stats.GroupShowMoreTopInviters_1" = "Show %@ More"; +"Stats.GroupShowMoreTopInviters_2" = "Show %@ More"; +"Stats.GroupShowMoreTopInviters_3_10" = "Show %@ More"; +"Stats.GroupShowMoreTopInviters_many" = "Show %@ More"; +"Stats.GroupShowMoreTopInviters_any" = "Show %@ More"; + +"Settings.AddAnotherAccount" = "Add Another Account"; +"Settings.AddAnotherAccount.Help" = "You can add up to three accounts with different phone numbers."; + +"ProfilePhoto.OpenGallery" = "Open Gallery"; +"ProfilePhoto.SearchWeb" = "Search Web"; +"ProfilePhoto.OpenInEditor" = "Open in Editor"; + +"Settings.EditAccount" = "Edit Account"; +"Settings.EditPhoto" = "Edit"; +"Settings.EditVideo" = "Edit"; +"Settings.CancelUpload" = "Cancel Upload"; + +"Settings.FrequentlyAskedQuestions" = "Frequently Asked Questions"; + +"Notification.ChangedGroupVideo" = "%@ changed group video"; +"Group.MessageVideoUpdated" = "Group video updated"; +"Channel.MessageVideoUpdated" = "Channel video updated"; + +"Conversation.Dice.u1F3C0" = "Send a basketball emoji to try your luck."; +"Conversation.Dice.u26BD" = "Send a football emoji to try your luck."; + +"SettingsSearch_Synonyms_ChatFolders" = ""; + +"EditProfile.NameAndPhotoOrVideoHelp" = "Enter your name and add an optional profile photo or video."; + +"Settings.RemoveConfirmation" = "Remove"; + +"Conversation.ContextMenuOpenProfile" = "Open Profile"; +"Conversation.ContextMenuSendMessage" = "Send Message"; +"Conversation.ContextMenuMention" = "Mention"; + +"Conversation.ContextMenuOpenChannelProfile" = "Open Profile"; +"Conversation.ContextMenuOpenChannel" = "Open Channel"; + +"Cache.KeepMediaHelp" = "Photos, videos and other files from cloud chats that you have **not accessed** during this period will be removed from this device to save disk space."; + + +"Cache.MaximumCacheSize" = "Maximum Cache Size"; +"Cache.NoLimit" = "No Limit"; +"Cache.MaximumCacheSizeHelp" = "If your cache size exceeds this limit, the oldest media will be deleted.\n\nAll media will stay in the Telegram cloud and can be re-downloaded if you need it again."; + +"Stats.MessageTitle" = "Message Statistics"; +"Stats.MessageOverview" = "Overview"; +"Stats.MessageInteractionsTitle" = "Interactions"; +"Stats.MessagePublicForwardsTitle" = "Public Shares"; + +"Call.CameraTooltip" = "Tap here to turn on your camera"; +"Call.CameraConfirmationText" = "Switch to video call?"; +"Call.CameraConfirmationConfirm" = "Switch"; + +"Call.YourMicrophoneOff" = "Your microphone is off"; +"Call.MicrophoneOff" = "%@'s microphone is off"; +"Call.CameraOff" = "%@'s camera is off"; +"Call.BatteryLow" = "%@'s battery level is low"; + +"Call.Audio" = "audio"; +"Call.AudioRouteMute" = "Mute Yourself"; + +"AccessDenied.VideoCallCamera" = "Telegram needs access to your camera to make video calls.\n\nPlease go to Settings > Privacy > Camera and set Telegram to ON."; + +"Call.AccountIsLoggedOnCurrentDevice" = "Sorry, you can't call %@ because that account is logged in to Telegram on the device you're using for the call."; + +"ChatList.Search.FilterMedia" = "Media"; +"ChatList.Search.FilterPhotos" = "Photos"; +"ChatList.Search.FilterVideos" = "Video"; +"ChatList.Search.FilterLinks" = "Links"; +"ChatList.Search.FilterFiles" = "Files"; +"ChatList.Search.FilterMusic" = "Audio"; + +"ChatList.Search.NoResults" = "No Results"; +"ChatList.Search.NoResultsDescription" = "There were no results for \"%@\".\nTry a new search."; + +<<<<<<< HEAD +"ChatList.Search.Messages_0" = "%@ messages"; +"ChatList.Search.Messages_1" = "%@ message"; +"ChatList.Search.Messages_2" = "%@ messages"; +"ChatList.Search.Messages_3_10" = "%@ messages"; +"ChatList.Search.Messages_many" = "%@ messages"; +"ChatList.Search.Messages_any" = "%@ messages"; + +"ChatList.Search.Photos_0" = "%@ photos"; +"ChatList.Search.Photos_1" = "%@ photo"; +"ChatList.Search.Photos_2" = "%@ photos"; +"ChatList.Search.Photos_3_10" = "%@ photos"; +"ChatList.Search.Photos_many" = "%@ photos"; +"ChatList.Search.Photos_any" = "%@ photos"; + +"ChatList.Search.Links_0" = "%@ links"; +"ChatList.Search.Links_1" = "%@ link"; +"ChatList.Search.Links_2" = "%@ links"; +"ChatList.Search.Links_3_10" = "%@ links"; +"ChatList.Search.Links_many" = "%@ links"; +"ChatList.Search.Links_any" = "%@ links"; + +"ChatList.Search.Files_0" = "%@ files"; +"ChatList.Search.Files_1" = "%@ file"; +"ChatList.Search.Files_2" = "%@ files"; +"ChatList.Search.Files_3_10" = "%@ files"; +"ChatList.Search.Files_many" = "%@ files"; +"ChatList.Search.Files_any" = "%@ files"; + +"ChatList.Search.Music_0" = "%@ audio files"; +"ChatList.Search.Music_1" = "%@ audio file"; +"ChatList.Search.Music_2" = "%@ audio files"; +"ChatList.Search.Music_3_10" = "%@ audio files"; +"ChatList.Search.Music_many" = "%@ audio files"; +"ChatList.Search.Music_any" = "%@ audio files"; +======= +"Conversation.InputTextAnonymousPlaceholder" = "Send anonymously"; +>>>>>>> features/comments diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 4bc3fe02d3..07d3a40d74 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -170,6 +170,7 @@ public enum ResolvedUrl { case botStart(peerId: PeerId, payload: String) case groupBotStart(peerId: PeerId, payload: String) case channelMessage(peerId: PeerId, messageId: MessageId) + case replyThreadMessage(replyThreadMessageId: MessageId, isChannelPost: Bool, maxReadMessageId: MessageId?, messageId: MessageId) case stickerPack(name: String) case instantView(TelegramMediaWebpage, String?) case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?) @@ -249,6 +250,12 @@ public enum ChatSearchDomain: Equatable { } } } + +public enum ChatLocation: Equatable { + case peer(PeerId) + case replyThread(threadMessageId: MessageId, isChannelPost: Bool, maxReadMessageId: MessageId?) +} + public final class NavigateToChatControllerParams { public let navigationController: NavigationController public let chatController: ChatController? @@ -522,8 +529,8 @@ public protocol SharedAccountContext: class { func handleTextLinkAction(context: AccountContext, peerId: PeerId?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?) func openChatMessage(_ params: OpenChatMessageParams) -> Bool - func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError> - func makeOverlayAudioPlayerController(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController + func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError> + func makeOverlayAudioPlayerController(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, isGlobalSearch: Bool, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController func makePeerInfoController(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool) -> ViewController? func makeChannelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant) -> ViewController? func makeDeviceContactInfoController(context: AccountContext, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController @@ -632,6 +639,9 @@ public final class TonContext { #endif +public protocol ChatLocationContextHolder: class { +} + public protocol AccountContext: class { var sharedContext: SharedAccountContext { get } var account: Account { get } @@ -659,4 +669,7 @@ public protocol AccountContext: class { func storeSecureIdPassword(password: String) func getStoredSecureIdPassword() -> String? + + func chatLocationInput(for location: ChatLocation, contextHolder: Atomic) -> ChatLocationInput + func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic, messageIndex: MessageIndex) } diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index e0fe69e795..03bd0b2a26 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -10,6 +10,93 @@ import SwiftSignalKit import TelegramPresentationData import TelegramUIPreferences +public final class ChatMessageItemAssociatedData: Equatable { + public enum ChannelDiscussionGroupStatus: Equatable { + case unknown + case known(PeerId?) + } + + public let automaticDownloadPeerType: MediaAutoDownloadPeerType + public let automaticDownloadNetworkType: MediaAutoDownloadNetworkType + public let isRecentActions: Bool + public let isScheduledMessages: Bool + public let contactsPeerIds: Set + public let channelDiscussionGroup: ChannelDiscussionGroupStatus + public let animatedEmojiStickers: [String: [StickerPackItem]] + public let forcedResourceStatus: FileMediaResourceStatus? + + public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, isScheduledMessages: Bool = false, contactsPeerIds: Set = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil) { + self.automaticDownloadPeerType = automaticDownloadPeerType + self.automaticDownloadNetworkType = automaticDownloadNetworkType + self.isRecentActions = isRecentActions + self.isScheduledMessages = isScheduledMessages + self.contactsPeerIds = contactsPeerIds + self.channelDiscussionGroup = channelDiscussionGroup + self.animatedEmojiStickers = animatedEmojiStickers + self.forcedResourceStatus = forcedResourceStatus + } + + public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool { + if lhs.automaticDownloadPeerType != rhs.automaticDownloadPeerType { + return false + } + if lhs.automaticDownloadNetworkType != rhs.automaticDownloadNetworkType { + return false + } + if lhs.isRecentActions != rhs.isRecentActions { + return false + } + if lhs.isScheduledMessages != rhs.isScheduledMessages { + return false + } + if lhs.contactsPeerIds != rhs.contactsPeerIds { + return false + } + if lhs.channelDiscussionGroup != rhs.channelDiscussionGroup { + return false + } + if lhs.animatedEmojiStickers != rhs.animatedEmojiStickers { + return false + } + if lhs.forcedResourceStatus != rhs.forcedResourceStatus { + return false + } + return true + } +} + +public enum ChatControllerInteractionLongTapAction { + case url(String) + case mention(String) + case peerMention(PeerId, String) + case command(String) + case hashtag(String) + case timecode(Double, String) + case bankCard(String) +} + +public enum ChatHistoryMessageSelection: Equatable { + case none + case selectable(selected: Bool) + + public static func ==(lhs: ChatHistoryMessageSelection, rhs: ChatHistoryMessageSelection) -> Bool { + switch lhs { + case .none: + if case .none = rhs { + return true + } else { + return false + } + case let .selectable(selected): + if case .selectable(selected) = rhs { + return true + } else { + return false + } + } + } +} + public enum ChatControllerInitialBotStartBehavior { case interactive case automatic(returnToPeerId: PeerId, scheduled: Bool) diff --git a/submodules/AccountContext/Sources/ChatHistoryLocation.swift b/submodules/AccountContext/Sources/ChatHistoryLocation.swift index 5541df6c25..e4e59fd224 100644 --- a/submodules/AccountContext/Sources/ChatHistoryLocation.swift +++ b/submodules/AccountContext/Sources/ChatHistoryLocation.swift @@ -15,8 +15,8 @@ public enum ChatHistoryLocation: Equatable { } public struct ChatHistoryLocationInput: Equatable { - public let content: ChatHistoryLocation - public let id: Int32 + public var content: ChatHistoryLocation + public var id: Int32 public init(content: ChatHistoryLocation, id: Int32) { self.content = content diff --git a/submodules/AccountContext/Sources/GalleryController.swift b/submodules/AccountContext/Sources/GalleryController.swift index c88a5fdafb..24d7acda1f 100644 --- a/submodules/AccountContext/Sources/GalleryController.swift +++ b/submodules/AccountContext/Sources/GalleryController.swift @@ -1,5 +1,13 @@ import Foundation import Postbox +import SwiftSignalKit +import TelegramCore + +public enum GalleryControllerItemSource { + case peerMessagesAtId(messageId: MessageId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic) + case standaloneMessage(Message) + case custom(messages: Signal<([Message], Int32, Bool), NoError>, messageId: MessageId, loadMore: (() -> Void)?) +} public final class GalleryControllerActionInteraction { public let openUrl: (String, Bool) -> Void diff --git a/submodules/AccountContext/Sources/MediaManager.swift b/submodules/AccountContext/Sources/MediaManager.swift index ba033b358d..8169ca55dc 100644 --- a/submodules/AccountContext/Sources/MediaManager.swift +++ b/submodules/AccountContext/Sources/MediaManager.swift @@ -8,6 +8,120 @@ import AsyncDisplayKit import TelegramAudio import UniversalMediaPlayer +public enum PeerMessagesMediaPlaylistId: Equatable, SharedMediaPlaylistId { + case peer(PeerId) + case recentActions(PeerId) + case custom + + public func isEqual(to: SharedMediaPlaylistId) -> Bool { + if let to = to as? PeerMessagesMediaPlaylistId { + return self == to + } + return false + } +} + +public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation { + case messages(peerId: PeerId, tagMask: MessageTags, at: MessageId) + case singleMessage(MessageId) + case recentActions(Message) + case custom(messages: Signal<([Message], Int32, Bool), NoError>, at: MessageId, loadMore: (() -> Void)?) + + public var playlistId: PeerMessagesMediaPlaylistId { + switch self { + case let .messages(peerId, _, _): + return .peer(peerId) + case let .singleMessage(id): + return .peer(id.peerId) + case let .recentActions(message): + return .recentActions(message.id.peerId) + case .custom: + return .custom + } + } + + public var messageId: MessageId? { + switch self { + case let .messages(_, _, messageId), let .singleMessage(messageId), let .custom(_, messageId, _): + return messageId + default: + return nil + } + } + + public func isEqual(to: SharedMediaPlaylistLocation) -> Bool { + if let to = to as? PeerMessagesPlaylistLocation { + return self == to + } else { + return false + } + } + + public static func ==(lhs: PeerMessagesPlaylistLocation, rhs: PeerMessagesPlaylistLocation) -> Bool { + switch lhs { + case let .messages(peerId, tagMask, at): + if case .messages(peerId, tagMask, at) = rhs { + return true + } else { + return false + } + case let .singleMessage(messageId): + if case .singleMessage(messageId) = rhs { + return true + } else { + return false + } + case let .recentActions(lhsMessage): + if case let .recentActions(rhsMessage) = rhs, lhsMessage.id == rhsMessage.id { + return true + } else { + return false + } + case let .custom(_, lhsAt, _): + if case let .custom(_, rhsAt, _) = rhs, lhsAt == rhsAt { + return true + } else { + return false + } + } + } +} + +public func peerMessageMediaPlayerType(_ message: Message) -> MediaManagerPlayerType? { + func extractFileMedia(_ message: Message) -> TelegramMediaFile? { + var file: TelegramMediaFile? + for media in message.media { + if let media = media as? TelegramMediaFile { + file = media + break + } else if let media = media as? TelegramMediaWebpage, case let .Loaded(content) = media.content, let f = content.file { + file = f + break + } + } + return file + } + + if let file = extractFileMedia(message) { + if file.isVoice || file.isInstantVideo { + return .voice + } else if file.isMusic { + return .music + } + } + return nil +} + +public func peerMessagesMediaPlaylistAndItemId(_ message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? { + if isGlobalSearch { + return (PeerMessagesMediaPlaylistId.custom, PeerMessagesMediaPlaylistItemId(messageId: message.id)) + } else if isRecentActions { + return (PeerMessagesMediaPlaylistId.recentActions(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id)) + } else { + return (PeerMessagesMediaPlaylistId.peer(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id)) + } +} + public enum MediaManagerPlayerType { case voice case music diff --git a/submodules/AccountContext/Sources/OpenChatMessage.swift b/submodules/AccountContext/Sources/OpenChatMessage.swift index 00f353051c..be3fcfd330 100644 --- a/submodules/AccountContext/Sources/OpenChatMessage.swift +++ b/submodules/AccountContext/Sources/OpenChatMessage.swift @@ -6,6 +6,7 @@ import SyncCore import SwiftSignalKit import Display import AsyncDisplayKit +import UniversalMediaPlayer public enum ChatControllerInteractionOpenMessageMode { case `default` @@ -18,6 +19,8 @@ public enum ChatControllerInteractionOpenMessageMode { public final class OpenChatMessageParams { public let context: AccountContext + public let chatLocation: ChatLocation? + public let chatLocationContextHolder: Atomic? public let message: Message public let standalone: Bool public let reverseMessageGalleryOrder: Bool @@ -36,9 +39,13 @@ public final class OpenChatMessageParams { public let setupTemporaryHiddenMedia: (Signal, Int, Media) -> Void public let chatAvatarHiddenMedia: (Signal, Media) -> Void public let actionInteraction: GalleryControllerActionInteraction? + public let playlistLocation: PeerMessagesPlaylistLocation? + public let gallerySource: GalleryControllerItemSource? public init( context: AccountContext, + chatLocation: ChatLocation?, + chatLocationContextHolder: Atomic?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, @@ -56,9 +63,13 @@ public final class OpenChatMessageParams { sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, setupTemporaryHiddenMedia: @escaping (Signal, Int, Media) -> Void, chatAvatarHiddenMedia: @escaping (Signal, Media) -> Void, - actionInteraction: GalleryControllerActionInteraction? = nil + actionInteraction: GalleryControllerActionInteraction? = nil, + playlistLocation: PeerMessagesPlaylistLocation? = nil, + gallerySource: GalleryControllerItemSource? = nil ) { self.context = context + self.chatLocation = chatLocation + self.chatLocationContextHolder = chatLocationContextHolder self.message = message self.standalone = standalone self.reverseMessageGalleryOrder = reverseMessageGalleryOrder @@ -77,5 +88,7 @@ public final class OpenChatMessageParams { self.setupTemporaryHiddenMedia = setupTemporaryHiddenMedia self.chatAvatarHiddenMedia = chatAvatarHiddenMedia self.actionInteraction = actionInteraction + self.playlistLocation = playlistLocation + self.gallerySource = gallerySource } } diff --git a/submodules/AccountContext/Sources/StoredMessageFromSearchPeer.swift b/submodules/AccountContext/Sources/StoredMessageFromSearchPeer.swift index a085442e95..845e350199 100644 --- a/submodules/AccountContext/Sources/StoredMessageFromSearchPeer.swift +++ b/submodules/AccountContext/Sources/StoredMessageFromSearchPeer.swift @@ -29,7 +29,7 @@ public func storedMessageFromSearch(account: Account, message: Message) -> Signa } } - let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media) + let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media) let _ = transaction.addMessages([storeMessage], location: .Random) } @@ -46,7 +46,7 @@ public func storeMessageFromSearch(transaction: Transaction, message: Message) { } } - let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media) + let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media) let _ = transaction.addMessages([storeMessage], location: .Random) } diff --git a/submodules/AvatarNode/Sources/AvatarNode.swift b/submodules/AvatarNode/Sources/AvatarNode.swift index 4a84a6bc84..9ce01b26a9 100644 --- a/submodules/AvatarNode/Sources/AvatarNode.swift +++ b/submodules/AvatarNode/Sources/AvatarNode.swift @@ -15,6 +15,7 @@ import Emoji private let deletedIcon = UIImage(bundleImageName: "Avatar/DeletedIcon")?.precomposed() private let savedMessagesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/SavedMessagesIcon"), color: .white) private let archivedChatsIcon = UIImage(bundleImageName: "Avatar/ArchiveAvatarIcon")?.precomposed() +private let repliesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/RepliesMessagesIcon"), color: .white) public func avatarPlaceholderFont(size: CGFloat) -> UIFont { return Font.with(size: size, design: .round, traits: [.bold]) @@ -100,6 +101,7 @@ private func ==(lhs: AvatarNodeState, rhs: AvatarNodeState) -> Bool { private enum AvatarNodeIcon: Equatable { case none case savedMessagesIcon + case repliesIcon case archivedChatsIcon(hiddenByDefault: Bool) case editAvatarIcon case deletedIcon @@ -109,6 +111,7 @@ public enum AvatarNodeImageOverride: Equatable { case none case image(TelegramMediaImageRepresentation) case savedMessagesIcon + case repliesIcon case archivedChatsIcon(hiddenByDefault: Bool) case editAvatarIcon case deletedIcon @@ -308,6 +311,9 @@ public final class AvatarNode: ASDisplayNode { case .savedMessagesIcon: representation = nil icon = .savedMessagesIcon + case .repliesIcon: + representation = nil + icon = .repliesIcon case let .archivedChatsIcon(hiddenByDefault): representation = nil icon = .archivedChatsIcon(hiddenByDefault: hiddenByDefault) @@ -452,6 +458,8 @@ public final class AvatarNode: ASDisplayNode { colorsArray = grayscaleColors } else if case .savedMessagesIcon = parameters.icon { colorsArray = savedMessagesColors + } else if case .repliesIcon = parameters.icon { + colorsArray = savedMessagesColors } else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme { colorsArray = [theme.list.itemAccentColor.withAlphaComponent(0.1).cgColor, theme.list.itemAccentColor.withAlphaComponent(0.1).cgColor] } else if case let .archivedChatsIcon(hiddenByDefault) = parameters.icon, let theme = parameters.theme { @@ -506,6 +514,15 @@ public final class AvatarNode: ASDisplayNode { if let savedMessagesIcon = savedMessagesIcon { context.draw(savedMessagesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - savedMessagesIcon.size.width) / 2.0), y: floor((bounds.size.height - savedMessagesIcon.size.height) / 2.0)), size: savedMessagesIcon.size)) } + } else if case .repliesIcon = parameters.icon { + let factor = bounds.size.width / 60.0 + context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0) + context.scaleBy(x: factor, y: -factor) + context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0) + + if let repliesIcon = repliesIcon { + context.draw(repliesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - repliesIcon.size.width) / 2.0), y: floor((bounds.size.height - repliesIcon.size.height) / 2.0)), size: repliesIcon.size)) + } } else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme, !parameters.hasImage { context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0) context.scaleBy(x: 1.0, y: -1.0) diff --git a/submodules/ChatInterfaceState/BUCK b/submodules/ChatInterfaceState/BUCK new file mode 100644 index 0000000000..13b14d8c7b --- /dev/null +++ b/submodules/ChatInterfaceState/BUCK @@ -0,0 +1,22 @@ +load("//Config:buck_rule_macros.bzl", "static_library") + +static_library( + name = "ChatInterfaceState", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", + "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", + "//submodules/Display:Display#shared", + "//submodules/Postbox:Postbox#shared", + "//submodules/TelegramCore:TelegramCore#shared", + "//submodules/SyncCore:SyncCore#shared", + "//submodules/TextFormat:TextFormat", + "//submodules/AccountContext:AccountContext", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + ], +) diff --git a/submodules/ChatInterfaceState/BUILD b/submodules/ChatInterfaceState/BUILD new file mode 100644 index 0000000000..08daa544b5 --- /dev/null +++ b/submodules/ChatInterfaceState/BUILD @@ -0,0 +1,22 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatInterfaceState", + module_name = "ChatInterfaceState", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/TextFormat:TextFormat", + "//submodules/AccountContext:AccountContext", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceState.swift b/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift similarity index 84% rename from submodules/TelegramUI/Sources/ChatInterfaceState.swift rename to submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift index 2de35faf20..bcc798afd9 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceState.swift +++ b/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift @@ -6,18 +6,23 @@ import SyncCore import TextFormat import AccountContext -struct ChatInterfaceSelectionState: PostboxCoding, Equatable { - let selectedIds: Set +public enum ChatTextInputMediaRecordingButtonMode: Int32 { + case audio = 0 + case video = 1 +} + +public struct ChatInterfaceSelectionState: PostboxCoding, Equatable { + public let selectedIds: Set - static func ==(lhs: ChatInterfaceSelectionState, rhs: ChatInterfaceSelectionState) -> Bool { + public static func ==(lhs: ChatInterfaceSelectionState, rhs: ChatInterfaceSelectionState) -> Bool { return lhs.selectedIds == rhs.selectedIds } - init(selectedIds: Set) { + public init(selectedIds: Set) { self.selectedIds = selectedIds } - init(decoder: PostboxDecoder) { + public init(decoder: PostboxDecoder) { if let data = decoder.decodeBytesForKeyNoCopy("i") { self.selectedIds = Set(MessageId.decodeArrayFromBuffer(data)) } else { @@ -25,27 +30,27 @@ struct ChatInterfaceSelectionState: PostboxCoding, Equatable { } } - func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: PostboxEncoder) { let buffer = WriteBuffer() MessageId.encodeArrayToBuffer(Array(selectedIds), buffer: buffer) encoder.encodeBytes(buffer, forKey: "i") } } -struct ChatEditMessageState: PostboxCoding, Equatable { - let messageId: MessageId - let inputState: ChatTextInputState - let disableUrlPreview: String? - let inputTextMaxLength: Int32? +public struct ChatEditMessageState: PostboxCoding, Equatable { + public let messageId: MessageId + public let inputState: ChatTextInputState + public let disableUrlPreview: String? + public let inputTextMaxLength: Int32? - init(messageId: MessageId, inputState: ChatTextInputState, disableUrlPreview: String?, inputTextMaxLength: Int32?) { + public init(messageId: MessageId, inputState: ChatTextInputState, disableUrlPreview: String?, inputTextMaxLength: Int32?) { self.messageId = messageId self.inputState = inputState self.disableUrlPreview = disableUrlPreview self.inputTextMaxLength = inputTextMaxLength } - init(decoder: PostboxDecoder) { + public init(decoder: PostboxDecoder) { self.messageId = MessageId(peerId: PeerId(decoder.decodeInt64ForKey("mp", orElse: 0)), namespace: decoder.decodeInt32ForKey("mn", orElse: 0), id: decoder.decodeInt32ForKey("mi", orElse: 0)) if let inputState = decoder.decodeObjectForKey("is", decoder: { return ChatTextInputState(decoder: $0) }) as? ChatTextInputState { self.inputState = inputState @@ -56,7 +61,7 @@ struct ChatEditMessageState: PostboxCoding, Equatable { self.inputTextMaxLength = decoder.decodeOptionalInt32ForKey("tl") } - func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt64(self.messageId.peerId.toInt64(), forKey: "mp") encoder.encodeInt32(self.messageId.namespace, forKey: "mn") encoder.encodeInt32(self.messageId.id, forKey: "mi") @@ -73,31 +78,31 @@ struct ChatEditMessageState: PostboxCoding, Equatable { } } - static func ==(lhs: ChatEditMessageState, rhs: ChatEditMessageState) -> Bool { + public static func ==(lhs: ChatEditMessageState, rhs: ChatEditMessageState) -> Bool { return lhs.messageId == rhs.messageId && lhs.inputState == rhs.inputState && lhs.disableUrlPreview == rhs.disableUrlPreview && lhs.inputTextMaxLength == rhs.inputTextMaxLength } - func withUpdatedInputState(_ inputState: ChatTextInputState) -> ChatEditMessageState { + public func withUpdatedInputState(_ inputState: ChatTextInputState) -> ChatEditMessageState { return ChatEditMessageState(messageId: self.messageId, inputState: inputState, disableUrlPreview: self.disableUrlPreview, inputTextMaxLength: self.inputTextMaxLength) } - func withUpdatedDisableUrlPreview(_ disableUrlPreview: String?) -> ChatEditMessageState { + public func withUpdatedDisableUrlPreview(_ disableUrlPreview: String?) -> ChatEditMessageState { return ChatEditMessageState(messageId: self.messageId, inputState: self.inputState, disableUrlPreview: disableUrlPreview, inputTextMaxLength: self.inputTextMaxLength) } } -struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { - var closedButtonKeyboardMessageId: MessageId? - var processedSetupReplyMessageId: MessageId? - var closedPinnedMessageId: MessageId? - var closedPeerSpecificPackSetup: Bool = false - var dismissedAddContactPhoneNumber: String? +public struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { + public var closedButtonKeyboardMessageId: MessageId? + public var processedSetupReplyMessageId: MessageId? + public var closedPinnedMessageId: MessageId? + public var closedPeerSpecificPackSetup: Bool = false + public var dismissedAddContactPhoneNumber: String? - var isEmpty: Bool { + public var isEmpty: Bool { return self.closedButtonKeyboardMessageId == nil && self.processedSetupReplyMessageId == nil && self.closedPinnedMessageId == nil && self.closedPeerSpecificPackSetup == false && self.dismissedAddContactPhoneNumber == nil } - init() { + public init() { self.closedButtonKeyboardMessageId = nil self.processedSetupReplyMessageId = nil self.closedPinnedMessageId = nil @@ -105,7 +110,7 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { self.dismissedAddContactPhoneNumber = nil } - init(closedButtonKeyboardMessageId: MessageId?, processedSetupReplyMessageId: MessageId?, closedPinnedMessageId: MessageId?, closedPeerSpecificPackSetup: Bool, dismissedAddContactPhoneNumber: String?) { + public init(closedButtonKeyboardMessageId: MessageId?, processedSetupReplyMessageId: MessageId?, closedPinnedMessageId: MessageId?, closedPeerSpecificPackSetup: Bool, dismissedAddContactPhoneNumber: String?) { self.closedButtonKeyboardMessageId = closedButtonKeyboardMessageId self.processedSetupReplyMessageId = processedSetupReplyMessageId self.closedPinnedMessageId = closedPinnedMessageId @@ -113,7 +118,7 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { self.dismissedAddContactPhoneNumber = dismissedAddContactPhoneNumber } - init(decoder: PostboxDecoder) { + public init(decoder: PostboxDecoder) { if let closedMessageIdPeerId = decoder.decodeOptionalInt64ForKey("cb.p"), let closedMessageIdNamespace = decoder.decodeOptionalInt32ForKey("cb.n"), let closedMessageIdId = decoder.decodeOptionalInt32ForKey("cb.i") { self.closedButtonKeyboardMessageId = MessageId(peerId: PeerId(closedMessageIdPeerId), namespace: closedMessageIdNamespace, id: closedMessageIdId) } else { @@ -135,7 +140,7 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { self.closedPeerSpecificPackSetup = decoder.decodeInt32ForKey("cpss", orElse: 0) != 0 } - func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: PostboxEncoder) { if let closedButtonKeyboardMessageId = self.closedButtonKeyboardMessageId { encoder.encodeInt64(closedButtonKeyboardMessageId.peerId.toInt64(), forKey: "cb.p") encoder.encodeInt32(closedButtonKeyboardMessageId.namespace, forKey: "cb.n") @@ -176,21 +181,21 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { } } -struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable { - let messageIndex: MessageIndex - let relativeOffset: Double +public struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable { + public let messageIndex: MessageIndex + public let relativeOffset: Double - init(messageIndex: MessageIndex, relativeOffset: Double) { + public init(messageIndex: MessageIndex, relativeOffset: Double) { self.messageIndex = messageIndex self.relativeOffset = relativeOffset } - init(decoder: PostboxDecoder) { + public init(decoder: PostboxDecoder) { self.messageIndex = MessageIndex(id: MessageId(peerId: PeerId(decoder.decodeInt64ForKey("m.p", orElse: 0)), namespace: decoder.decodeInt32ForKey("m.n", orElse: 0), id: decoder.decodeInt32ForKey("m.i", orElse: 0)), timestamp: decoder.decodeInt32ForKey("m.t", orElse: 0)) self.relativeOffset = decoder.decodeDoubleForKey("ro", orElse: 0.0) } - func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.messageIndex.timestamp, forKey: "m.t") encoder.encodeInt64(self.messageIndex.id.peerId.toInt64(), forKey: "m.p") encoder.encodeInt32(self.messageIndex.id.namespace, forKey: "m.n") @@ -198,7 +203,7 @@ struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable { encoder.encodeDouble(self.relativeOffset, forKey: "ro") } - static func ==(lhs: ChatInterfaceHistoryScrollState, rhs: ChatInterfaceHistoryScrollState) -> Bool { + public static func ==(lhs: ChatInterfaceHistoryScrollState, rhs: ChatInterfaceHistoryScrollState) -> Bool { if lhs.messageIndex != rhs.messageIndex { return false } @@ -210,18 +215,18 @@ struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable { } public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable { - let timestamp: Int32 - let composeInputState: ChatTextInputState - let composeDisableUrlPreview: String? - let replyMessageId: MessageId? - let forwardMessageIds: [MessageId]? - let editMessage: ChatEditMessageState? - let selectionState: ChatInterfaceSelectionState? - let messageActionsState: ChatInterfaceMessageActionsState - let historyScrollState: ChatInterfaceHistoryScrollState? - let mediaRecordingMode: ChatTextInputMediaRecordingButtonMode - let silentPosting: Bool - let inputLanguage: String? + public let timestamp: Int32 + public let composeInputState: ChatTextInputState + public let composeDisableUrlPreview: String? + public let replyMessageId: MessageId? + public let forwardMessageIds: [MessageId]? + public let editMessage: ChatEditMessageState? + public let selectionState: ChatInterfaceSelectionState? + public let messageActionsState: ChatInterfaceMessageActionsState + public let historyScrollState: ChatInterfaceHistoryScrollState? + public let mediaRecordingMode: ChatTextInputMediaRecordingButtonMode + public let silentPosting: Bool + public let inputLanguage: String? public var associatedMessageIds: [MessageId] { var ids: [MessageId] = [] @@ -259,7 +264,7 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata return result } - var effectiveInputState: ChatTextInputState { + public var effectiveInputState: ChatTextInputState { if let editMessage = self.editMessage { return editMessage.inputState } else { @@ -282,7 +287,7 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata self.inputLanguage = nil } - init(timestamp: Int32, composeInputState: ChatTextInputState, composeDisableUrlPreview: String?, replyMessageId: MessageId?, forwardMessageIds: [MessageId]?, editMessage: ChatEditMessageState?, selectionState: ChatInterfaceSelectionState?, messageActionsState: ChatInterfaceMessageActionsState, historyScrollState: ChatInterfaceHistoryScrollState?, mediaRecordingMode: ChatTextInputMediaRecordingButtonMode, silentPosting: Bool, inputLanguage: String?) { + public init(timestamp: Int32, composeInputState: ChatTextInputState, composeDisableUrlPreview: String?, replyMessageId: MessageId?, forwardMessageIds: [MessageId]?, editMessage: ChatEditMessageState?, selectionState: ChatInterfaceSelectionState?, messageActionsState: ChatInterfaceMessageActionsState, historyScrollState: ChatInterfaceHistoryScrollState?, mediaRecordingMode: ChatTextInputMediaRecordingButtonMode, silentPosting: Bool, inputLanguage: String?) { self.timestamp = timestamp self.composeInputState = composeInputState self.composeDisableUrlPreview = composeDisableUrlPreview @@ -437,17 +442,17 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata return lhs.composeInputState == rhs.composeInputState && lhs.replyMessageId == rhs.replyMessageId && lhs.selectionState == rhs.selectionState && lhs.editMessage == rhs.editMessage } - func withUpdatedComposeInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { + public func withUpdatedComposeInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { let updatedComposeInputState = inputState return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedComposeDisableUrlPreview(_ disableUrlPreview: String?) -> ChatInterfaceState { + public func withUpdatedComposeDisableUrlPreview(_ disableUrlPreview: String?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: disableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedEffectiveInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { + public func withUpdatedEffectiveInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { var updatedEditMessage = self.editMessage var updatedComposeInputState = self.composeInputState if let editMessage = self.editMessage { @@ -459,15 +464,15 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: updatedEditMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedReplyMessageId(_ replyMessageId: MessageId?) -> ChatInterfaceState { + public func withUpdatedReplyMessageId(_ replyMessageId: MessageId?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedForwardMessageIds(_ forwardMessageIds: [MessageId]?) -> ChatInterfaceState { + public func withUpdatedForwardMessageIds(_ forwardMessageIds: [MessageId]?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedSelectedMessages(_ messageIds: [MessageId]) -> ChatInterfaceState { + public func withUpdatedSelectedMessages(_ messageIds: [MessageId]) -> ChatInterfaceState { var selectedIds = Set() if let selectionState = self.selectionState { selectedIds.formUnion(selectionState.selectedIds) @@ -478,7 +483,7 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withToggledSelectedMessages(_ messageIds: [MessageId], value: Bool) -> ChatInterfaceState { + public func withToggledSelectedMessages(_ messageIds: [MessageId], value: Bool) -> ChatInterfaceState { var selectedIds = Set() if let selectionState = self.selectionState { selectedIds.formUnion(selectionState.selectedIds) @@ -493,27 +498,27 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withoutSelectionState() -> ChatInterfaceState { + public func withoutSelectionState() -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: nil, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedTimestamp(_ timestamp: Int32) -> ChatInterfaceState { + public func withUpdatedTimestamp(_ timestamp: Int32) -> ChatInterfaceState { return ChatInterfaceState(timestamp: timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedEditMessage(_ editMessage: ChatEditMessageState?) -> ChatInterfaceState { + public func withUpdatedEditMessage(_ editMessage: ChatEditMessageState?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedMessageActionsState(_ f: (ChatInterfaceMessageActionsState) -> ChatInterfaceMessageActionsState) -> ChatInterfaceState { + public func withUpdatedMessageActionsState(_ f: (ChatInterfaceMessageActionsState) -> ChatInterfaceMessageActionsState) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: f(self.messageActionsState), historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedHistoryScrollState(_ historyScrollState: ChatInterfaceHistoryScrollState?) -> ChatInterfaceState { + public func withUpdatedHistoryScrollState(_ historyScrollState: ChatInterfaceHistoryScrollState?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } - func withUpdatedMediaRecordingMode(_ mediaRecordingMode: ChatTextInputMediaRecordingButtonMode) -> ChatInterfaceState { + public func withUpdatedMediaRecordingMode(_ mediaRecordingMode: ChatTextInputMediaRecordingButtonMode) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage) } diff --git a/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift b/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift index 1cb70fd606..4fcfeebe60 100644 --- a/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift +++ b/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift @@ -4,7 +4,7 @@ import Display import TelegramPresentationData import ListSectionHeaderNode -public enum ChatListSearchItemHeaderType: Int32 { +public enum ChatListSearchItemHeaderType { case localPeers case members case contacts @@ -13,7 +13,6 @@ public enum ChatListSearchItemHeaderType: Int32 { case globalPeers case deviceContacts case recentPeers - case messages case phoneNumber case exceptions case addToExceptions @@ -22,6 +21,109 @@ public enum ChatListSearchItemHeaderType: Int32 { case chats case chatTypes case faq + case messages(Int32) + + fileprivate func title(strings: PresentationStrings) -> String { + switch self { + case .localPeers: + return strings.DialogList_SearchSectionDialogs + case .members: + return strings.Channel_Info_Members + case .contacts: + return strings.Contacts_TopSection + case .bots: + return strings.MemberSearch_BotSection + case .admins: + return strings.Channel_Management_Title + case .globalPeers: + return strings.DialogList_SearchSectionGlobal + case .deviceContacts: + return strings.Contacts_NotRegisteredSection + case .recentPeers: + return strings.DialogList_SearchSectionRecent + case .phoneNumber: + return strings.Contacts_PhoneNumber + case .exceptions: + return strings.GroupInfo_Permissions_Exceptions + case .addToExceptions: + return strings.Exceptions_AddToExceptions + case .mapAddress: + return strings.Map_AddressOnMap + case .nearbyVenues: + return strings.Map_PlacesNearby + case .chats: + return strings.Cache_ByPeerHeader + case .chatTypes: + return strings.ChatList_ChatTypesSection + case .faq: + return strings.Settings_FrequentlyAskedQuestions + case let .messages(count): + return strings.ChatList_Search_Messages(count) + } + } + + fileprivate var id: ChatListSearchItemHeaderId { + switch self { + case .localPeers: + return .localPeers + case .members: + return .members + case .contacts: + return .contacts + case .bots: + return .bots + case .admins: + return .admins + case .globalPeers: + return .globalPeers + case .deviceContacts: + return .deviceContacts + case .recentPeers: + return .recentPeers + case .phoneNumber: + return .phoneNumber + case .exceptions: + return .exceptions + case .addToExceptions: + return .addToExceptions + case .mapAddress: + return .mapAddress + case .nearbyVenues: + return .nearbyVenues + case .chats: + return .chats + case .chatTypes: + return .chatTypes + case .faq: + return .faq + case .messages: + return .messages + } + } +} + +private enum ChatListSearchItemHeaderId: Int32 { + case localPeers + case members + case contacts + case bots + case admins + case globalPeers + case deviceContacts + case recentPeers + case phoneNumber + case exceptions + case addToExceptions + case mapAddress + case nearbyVenues + case chats + case chatTypes + case faq + case messages + case photos + case links + case files + case music } public final class ChatListSearchItemHeader: ListViewItemHeader { @@ -37,7 +139,7 @@ public final class ChatListSearchItemHeader: ListViewItemHeader { public init(type: ChatListSearchItemHeaderType, theme: PresentationTheme, strings: PresentationStrings, actionTitle: String? = nil, action: (() -> Void)? = nil) { self.type = type - self.id = Int64(self.type.rawValue) + self.id = Int64(self.type.id.rawValue) self.theme = theme self.strings = strings self.actionTitle = actionTitle @@ -75,43 +177,7 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode { super.init() - switch type { - case .localPeers: - self.sectionHeaderNode.title = strings.DialogList_SearchSectionDialogs.uppercased() - case .members: - self.sectionHeaderNode.title = strings.Channel_Info_Members.uppercased() - case .contacts: - self.sectionHeaderNode.title = strings.Contacts_TopSection.uppercased() - case .bots: - self.sectionHeaderNode.title = strings.MemberSearch_BotSection.uppercased() - case .admins: - self.sectionHeaderNode.title = strings.Channel_Management_Title.uppercased() - case .globalPeers: - self.sectionHeaderNode.title = strings.DialogList_SearchSectionGlobal.uppercased() - case .deviceContacts: - self.sectionHeaderNode.title = strings.Contacts_NotRegisteredSection.uppercased() - case .messages: - 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() - case .exceptions: - self.sectionHeaderNode.title = strings.GroupInfo_Permissions_Exceptions.uppercased() - case .addToExceptions: - self.sectionHeaderNode.title = strings.Exceptions_AddToExceptions.uppercased() - case .mapAddress: - self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased() - case .nearbyVenues: - self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased() - case .chats: - self.sectionHeaderNode.title = strings.Cache_ByPeerHeader.uppercased() - case .chatTypes: - self.sectionHeaderNode.title = strings.ChatList_ChatTypesSection.uppercased() - case .faq: - self.sectionHeaderNode.title = strings.Settings_FrequentlyAskedQuestions.uppercased() - } - + self.sectionHeaderNode.title = type.title(strings: strings).uppercased() self.sectionHeaderNode.action = actionTitle self.sectionHeaderNode.activateAction = action @@ -127,43 +193,7 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode { self.actionTitle = actionTitle self.action = action - switch type { - case .localPeers: - self.sectionHeaderNode.title = strings.DialogList_SearchSectionDialogs.uppercased() - case .members: - self.sectionHeaderNode.title = strings.Channel_Info_Members.uppercased() - case .contacts: - self.sectionHeaderNode.title = strings.Contacts_TopSection.uppercased() - case .bots: - self.sectionHeaderNode.title = strings.MemberSearch_BotSection.uppercased() - case .admins: - self.sectionHeaderNode.title = strings.Channel_Management_Title.uppercased() - case .globalPeers: - self.sectionHeaderNode.title = strings.DialogList_SearchSectionGlobal.uppercased() - case .deviceContacts: - self.sectionHeaderNode.title = strings.Contacts_NotRegisteredSection.uppercased() - case .messages: - 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() - case .exceptions: - self.sectionHeaderNode.title = strings.GroupInfo_Permissions_Exceptions.uppercased() - case .addToExceptions: - self.sectionHeaderNode.title = strings.Exceptions_AddToExceptions.uppercased() - case .mapAddress: - self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased() - case .nearbyVenues: - self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased() - case .chats: - self.sectionHeaderNode.title = strings.Cache_ByPeerHeader.uppercased() - case .chatTypes: - self.sectionHeaderNode.title = strings.ChatList_ChatTypesSection.uppercased() - case .faq: - self.sectionHeaderNode.title = strings.Settings_FrequentlyAskedQuestions.uppercased() - } - + self.sectionHeaderNode.title = type.title(strings: strings).uppercased() self.sectionHeaderNode.action = actionTitle self.sectionHeaderNode.activateAction = action diff --git a/submodules/ChatListUI/BUCK b/submodules/ChatListUI/BUCK index 2694f6a128..3a99a42a00 100644 --- a/submodules/ChatListUI/BUCK +++ b/submodules/ChatListUI/BUCK @@ -46,6 +46,15 @@ static_library( "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/TooltipUI:TooltipUI", + "//submodules/ListMessageItem:ListMessageItem", + "//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge", + "//submodules/MediaPlayer:UniversalMediaPlayer", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/GalleryData:GalleryData", + "//submodules/InstantPageUI:InstantPageUI", + "//submodules/ListSectionHeaderNode:ListSectionHeaderNode", + "//submodules/ChatInterfaceState:ChatInterfaceState", + "//submodules/GridMessageSelectionNode:GridMessageSelectionNode", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index 0eb8212ebd..e0756763e3 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -46,6 +46,15 @@ swift_library( "//submodules/ItemListPeerActionItem:ItemListPeerActionItem", "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/TooltipUI:TooltipUI", + "//submodules/ListMessageItem:ListMessageItem", + "//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge", + "//submodules/MediaPlayer:UniversalMediaPlayer", + "//submodules/GalleryData:GalleryData", + "//submodules/InstantPageUI:InstantPageUI", + "//submodules/ListSectionHeaderNode:ListSectionHeaderNode", + "//submodules/ChatInterfaceState:ChatInterfaceState", + "//submodules/ShareController:ShareController", + "//submodules/GridMessageSelectionNode:GridMessageSelectionNode", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 44e06dca1e..3c596ca0b3 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1548,7 +1548,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, let isEmpty = resolvedItems.count <= 1 || displayTabsAtBottom - if wasEmpty != isEmpty { + if wasEmpty != isEmpty, strongSelf.displayNavigationBar { strongSelf.navigationBar?.setSecondaryContentNode(isEmpty ? nil : strongSelf.tabContainerNode) if let parentController = strongSelf.parent as? TabBarController { parentController.navigationBar?.setSecondaryContentNode(isEmpty ? nil : strongSelf.tabContainerNode) @@ -1681,20 +1681,79 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, if let scrollToTop = strongSelf.scrollToTop { scrollToTop() } + if let searchContentNode = strongSelf.searchContentNode { - strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode) + var updatedSearchOptionsImpl: ((ChatListSearchOptions?, Bool) -> Void)? + + if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, navigationController: strongSelf.navigationController as? NavigationController, updatedSearchOptions: { options, hasDate in + updatedSearchOptionsImpl?(options, hasDate) + }) { + let (filterContainerNode, activate) = filterContainerNodeAndActivate + strongSelf.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: false) + if let parentController = strongSelf.parent as? TabBarController { + parentController.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: true) + } + activate() + + var currentHasSuggestions = true + updatedSearchOptionsImpl = { [weak self, weak filterContainerNode] options, hasSuggestions in + guard let strongSelf = self, let strongFilterContainerNode = filterContainerNode else { + return + } + if currentHasSuggestions != hasSuggestions { + currentHasSuggestions = hasSuggestions + + var node: ASDisplayNode? + if let options = options, options.messageTags != nil && !hasSuggestions { + } else { + node = strongFilterContainerNode + } + + strongSelf.navigationBar?.setSecondaryContentNode(node, animated: false) + if let parentController = strongSelf.parent as? TabBarController { + parentController.navigationBar?.setSecondaryContentNode(node, animated: true) + } + + let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) + if let layout = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, transition: transition) + (strongSelf.parent as? TabBarController)?.updateLayout(transition: transition) + } + } + } + } } - strongSelf.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring)) + + let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) + strongSelf.setDisplayNavigationBar(false, transition: transition) + + (strongSelf.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.4, curve: .spring)) }) } } public func deactivateSearch(animated: Bool) { if !self.displayNavigationBar { - self.setDisplayNavigationBar(true, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate) if let searchContentNode = self.searchContentNode { self.chatListDisplayNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated) } + + let filtersIsEmpty: Bool + if let (resolvedItems, displayTabsAtBottom) = tabContainerData { + filtersIsEmpty = resolvedItems.count <= 1 || displayTabsAtBottom + } else { + filtersIsEmpty = true + } + + self.navigationBar?.setSecondaryContentNode(filtersIsEmpty ? nil : self.tabContainerNode, animated: false) + if let parentController = self.parent as? TabBarController { + parentController.navigationBar?.setSecondaryContentNode(filtersIsEmpty ? nil : self.tabContainerNode, animated: animated) + } + + let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate + self.setDisplayNavigationBar(true, transition: transition) + + (self.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.4, curve: .spring)) } } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index d336726443..f66e5f0318 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -200,7 +200,7 @@ private final class ChatListShimmerNode: ASDisplayNode { }, present: { _ in }) let items = (0 ..< 2).map { _ -> ChatListItem in - return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } var itemNodes: [ChatListItemNode] = [] @@ -1147,12 +1147,12 @@ final class ChatListControllerNode: ASDisplayNode { } } - func activateSearch(placeholderNode: SearchBarPlaceholderNode) { + func activateSearch(placeholderNode: SearchBarPlaceholderNode, navigationController: NavigationController?, updatedSearchOptions: ((ChatListSearchOptions?, Bool) -> Void)?) -> (ASDisplayNode, () -> Void)? { guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else { - return + return nil } - self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ChatListSearchContainerNode(context: self.context, filter: [], groupId: self.groupId, openPeer: { [weak self] peer, dismissSearch in + let contentNode = ChatListSearchContainerNode(context: self.context, filter: [], groupId: self.groupId, openPeer: { [weak self] peer, dismissSearch in self?.requestOpenPeerFromSearch?(peer, dismissSearch) }, openDisabledPeer: { _ in }, openRecentPeerOptions: { [weak self] peer in @@ -1165,25 +1165,36 @@ final class ChatListControllerNode: ASDisplayNode { if let requestAddContact = self?.requestAddContact { requestAddContact(phoneNumber) } - }, peerContextAction: self.peerContextAction, present: { [weak self] c in - self?.controller?.present(c, in: .window(.root)) - }), cancel: { [weak self] in + }, peerContextAction: self.peerContextAction, present: { [weak self] c, a in + self?.controller?.present(c, in: .window(.root), with: a) + }, presentInGlobalOverlay: { [weak self] c, a in + self?.controller?.presentInGlobalOverlay(c, with: a) + }, navigationController: navigationController, updatedSearchOptions: { options, hasDate in + updatedSearchOptions?(options, hasDate) + }) + + self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: contentNode, cancel: { [weak self] in if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() } }) self.containerNode.accessibilityElementsHidden = true - - self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: cleanNavigationBarHeight, transition: .immediate) - self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in - if let strongSelf = self, let strongPlaceholderNode = placeholderNode { - if isSearchBar { - strongPlaceholderNode.supernode?.insertSubnode(subnode, aboveSubnode: strongPlaceholderNode) - } else { - strongSelf.insertSubnode(subnode, belowSubnode: navigationBar) - } + + return (contentNode.filterContainerNode, { [weak self] in + guard let strongSelf = self else { + return } - }, placeholder: placeholderNode) + strongSelf.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: cleanNavigationBarHeight, transition: .immediate) + strongSelf.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in + if let strongSelf = self, let strongPlaceholderNode = placeholderNode { + if isSearchBar { + strongPlaceholderNode.supernode?.insertSubnode(subnode, aboveSubnode: strongPlaceholderNode) + } else { + strongSelf.insertSubnode(subnode, belowSubnode: navigationBar) + } + } + }, placeholder: placeholderNode) + }) } func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) { diff --git a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift index d7c12d1c90..5424516ee6 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift @@ -625,7 +625,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode { let previousContentWidth = self.scrollNode.view.contentSize.width if self.currentParams?.presentationData.theme !== presentationData.theme { - self.selectedLineNode.image = generateImage(CGSize(width: 7.0, height: 4.0), rotatedContext: { size, context in + self.selectedLineNode.image = generateImage(CGSize(width: 8.0, height: 4.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(presentationData.theme.list.itemAccentColor.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.width))) diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 7e9b5bf567..0a5714d4d3 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -17,12 +17,43 @@ import ContactListUI import ContextUI import PhoneNumberFormat import ItemListUI +import SearchBarNode +import ListMessageItem +import TelegramBaseController +import OverlayStatusController +import UniversalMediaPlayer +import PresentationDataUtils +import AnimatedStickerNode +import AppBundle +import GalleryData +import InstantPageUI +import ChatInterfaceState +import ShareController + +private final class PassthroughContainerNode: ASDisplayNode { + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let subnodes = self.subnodes { + for subnode in subnodes { + if let result = subnode.view.hitTest(self.view.convert(point, to: subnode.view), with: event) { + return result + } + } + } + return nil + } +} private enum ChatListRecentEntryStableId: Hashable { case topPeers case peerId(PeerId) } +private enum ChatListTokenId: Int32 { + case filter + case peer + case date +} + private enum ChatListRecentEntry: Comparable, Identifiable { case topPeers([Peer], PresentationTheme, PresentationStrings) case peer(index: Int, peer: RecentlySearchedPeer, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool) @@ -250,7 +281,7 @@ public enum ChatListSearchSectionExpandType { public enum ChatListSearchEntry: Comparable, Identifiable { case localPeer(Peer, Peer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType) case globalPeer(FoundPeer, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType) - case message(Message, RenderedPeer, CombinedPeerReadState?, ChatListPresentationData) + case message(Message, RenderedPeer, CombinedPeerReadState?, ChatListPresentationData, Int32, Bool?, Bool) case addContact(String, PresentationTheme, PresentationStrings) public var stableId: ChatListSearchEntryStableId { @@ -259,7 +290,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { return .localPeerId(peer.id) case let .globalPeer(peer, _, _, _, _, _, _, _): return .globalPeerId(peer.peer.id) - case let .message(message, _, _, _): + case let .message(message, _, _, _, _, _, _): return .messageId(message.id) case .addContact: return .addContact @@ -280,8 +311,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable { } else { return false } - case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData): - if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData) = rhs { + case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData, lhsTotalCount, lhsSelected, lhsDisplayCustomHeader): + if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData, rhsTotalCount, rhsSelected, rhsDisplayCustomHeader) = rhs { if lhsMessage.id != rhsMessage.id { return false } @@ -297,6 +328,15 @@ public enum ChatListSearchEntry: Comparable, Identifiable { if lhsCombinedPeerReadState != rhsCombinedPeerReadState { return false } + if lhsTotalCount != rhsTotalCount { + return false + } + if lhsSelected != rhsSelected { + return false + } + if lhsDisplayCustomHeader != rhsDisplayCustomHeader { + return false + } return true } else { return false @@ -336,8 +376,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable { case .message, .addContact: return true } - case let .message(lhsMessage, _, _, _): - if case let .message(rhsMessage, _, _, _) = rhs { + case let .message(lhsMessage, _, _, _, _, _, _): + if case let .message(rhsMessage, _, _, _, _, _, _) = rhs { return lhsMessage.index < rhsMessage.index } else if case .addContact = rhs { return true @@ -349,7 +389,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { } } - public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void) -> ListViewItem { + public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (Peer) -> Void, searchResults: [Message], searchOptions: ChatListSearchOptions?, messageContextAction: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?) -> ListViewItem { switch self { case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType): let primaryPeer: Peer @@ -424,7 +464,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { gesture?.cancel() } } - }) + }, arrowAction: nil) case let .globalPeer(peer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType): var enabled = true if filter.contains(.onlyWriteable) { @@ -484,8 +524,14 @@ public enum ChatListSearchEntry: Comparable, Identifiable { peerContextAction(peer.peer, .search, node, gesture) } }) - case let .message(message, peer, readState, presentationData): - return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(messages: [message], peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + case let .message(message, peer, readState, presentationData, totalCount, selected, displayCustomHeader): + let header = ChatListSearchItemHeader(type: .messages(totalCount), theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) + let selection: ChatHistoryMessageSelection = selected.flatMap { .selectable(selected: $0) } ?? .none + if let tags = searchOptions?.messageTags, tags != .photoOrVideo { + return ListMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .builtin(WallpaperSettings())), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(peer.peerId), interaction: listInteraction, message: message, selection: selection, displayHeader: enableHeaders && !displayCustomHeader, customHeader: nil, hintIsLink: tags == .webPage, isGlobalSearchResult: true) + } else { + return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(messages: [message], peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: header, enableContextActions: false, hiddenOffset: 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) @@ -505,12 +551,20 @@ public struct ChatListSearchContainerTransition { public let insertions: [ListViewInsertItem] public let updates: [ListViewUpdateItem] public let displayingResults: Bool + public let isEmpty: Bool + public let isLoading: Bool + public let query: String + public let animated: Bool - public init(deletions: [ListViewDeleteItem], insertions: [ListViewInsertItem], updates: [ListViewUpdateItem], displayingResults: Bool) { + public init(deletions: [ListViewDeleteItem], insertions: [ListViewInsertItem], updates: [ListViewUpdateItem], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, query: String, animated: Bool) { self.deletions = deletions self.insertions = insertions self.updates = updates self.displayingResults = displayingResults + self.isEmpty = isEmpty + self.isLoading = isLoading + self.query = query + self.animated = animated } } @@ -524,19 +578,20 @@ private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates) } -public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void) -> ChatListSearchContainerTransition { +public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, searchQuery: String, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (Peer) -> Void, searchResults: [Message], searchOptions: ChatListSearchOptions?, messageContextAction: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?) -> ChatListSearchContainerTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, interaction: interaction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, interaction: interaction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchResults: searchResults, searchOptions: searchOptions, messageContextAction: messageContextAction), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchResults: searchResults, searchOptions: searchOptions, messageContextAction: messageContextAction), directionHint: nil) } - return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults) + return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, isLoading: isLoading, query: searchQuery, animated: animated) } private struct ChatListSearchContainerNodeState: Equatable { let peerIdWithRevealedOptions: PeerId? + init(peerIdWithRevealedOptions: PeerId? = nil) { self.peerIdWithRevealedOptions = peerIdWithRevealedOptions } @@ -556,6 +611,11 @@ private struct ChatListSearchContainerNodeState: Equatable { private struct ChatListSearchContainerNodeSearchState: Equatable { var expandLocalSearch: Bool = false var expandGlobalSearch: Bool = false + var selectedMessageIds: Set? + + func withUpdatedSelectedMessageIds(_ selectedMessageIds: Set?) -> ChatListSearchContainerNodeSearchState { + return ChatListSearchContainerNodeSearchState(expandLocalSearch: self.expandLocalSearch, expandGlobalSearch: self.expandGlobalSearch, selectedMessageIds: selectedMessageIds) + } } private func doesPeerMatchFilter(peer: Peer, filter: ChatListNodePeersFilter) -> Bool { @@ -581,6 +641,7 @@ private struct ChatListSearchMessagesResult { let messages: [Message] let readStates: [PeerId: CombinedPeerReadState] let hasMore: Bool + let totalCount: Int32 let state: SearchMessagesState } @@ -595,21 +656,59 @@ public enum ChatListSearchContextActionSource { case search } +public struct ChatListSearchOptions { + let peer: (PeerId, String)? + let minDate: (Int32, String)? + let maxDate: (Int32, String)? + let messageTags: MessageTags? + + func withUpdatedPeer(_ peerIdAndName: (PeerId, String)?) -> ChatListSearchOptions { + return ChatListSearchOptions(peer: peerIdAndName, minDate: self.minDate, maxDate: self.maxDate, messageTags: self.messageTags) + } + + func withUpdatedMinDate(_ minDateAndTitle: (Int32, String)?) -> ChatListSearchOptions { + return ChatListSearchOptions(peer: self.peer, minDate: minDateAndTitle, maxDate: self.maxDate, messageTags: self.messageTags) + } + + func withUpdatedMaxDate(_ maxDateAndTitle: (Int32, String)?) -> ChatListSearchOptions { + return ChatListSearchOptions(peer: self.peer, minDate: self.minDate, maxDate: maxDateAndTitle, messageTags: self.messageTags) + } + + func withUpdatedMessageTags(_ messageTags: MessageTags?) -> ChatListSearchOptions { + return ChatListSearchOptions(peer: self.peer, minDate: self.minDate, maxDate: self.maxDate, messageTags: messageTags) + } +} + public final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { private let context: AccountContext + private let peersFilter: ChatListNodePeersFilter private var interaction: ChatListNodeInteraction? + private let openMessage: (Peer, MessageId) -> Void + private let navigationController: NavigationController? + let filterContainerNode: ChatListSearchFiltersContainerNode + private var selectionPanelNode: ChatListSearchMessageSelectionPanelNode? private let recentListNode: ListView + private let loadingNode: ASImageNode private let listNode: ListView + private let mediaNode: ChatListSearchMediaNode private let dimNode: ASDisplayNode private var enqueuedRecentTransitions: [(ChatListSearchContainerRecentTransition, Bool)] = [] private var enqueuedTransitions: [(ChatListSearchContainerTransition, Bool)] = [] - private var validLayout: ContainerViewLayout? + private var validLayout: (ContainerViewLayout, CGFloat)? + + private var present: ((ViewController, Any?) -> Void)? + private var presentInGlobalOverlay: ((ViewController, Any?) -> Void)? + + private let activeActionDisposable = MetaDisposable() private let recentDisposable = MetaDisposable() private let updatedRecentPeersDisposable = MetaDisposable() - private let searchQuery = Promise() + private var searchQueryValue: String? + private let searchQuery = Promise(nil) + private var searchOptionsValue: ChatListSearchOptions? + private let searchOptions = Promise(nil) private let searchDisposable = MetaDisposable() private var presentationData: PresentationData @@ -620,48 +719,95 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo private let statePromise: ValuePromise private var searchStateValue = ChatListSearchContainerNodeSearchState() private let searchStatePromise: ValuePromise + private let searchContextValue = Atomic(value: nil) private let _isSearching = ValuePromise(false, ignoreRepeated: true) override public var isSearching: Signal { return self._isSearching.get() } - private let filter: ChatListNodePeersFilter + private var mediaStatusDisposable: Disposable? + private var playlistPreloadDisposable: Disposable? - public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController) -> Void) { + private var playlistStateAndType: (SharedMediaPlaylistItem, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, MusicPlaybackSettingsOrder, MediaManagerPlayerType, Account)? + private var mediaAccessoryPanelContainer: PassthroughContainerNode + private var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)? + private var dismissingPanel: ASDisplayNode? + + private let updatedSearchOptions: ((ChatListSearchOptions?, Bool) -> Void)? + + private let emptyResultsTitleNode: ImmediateTextNode + private let emptyResultsTextNode: ImmediateTextNode + private let emptyResultsAnimationNode: AnimatedStickerNode + private var animationSize: CGSize = CGSize() + + public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?, updatedSearchOptions: ((ChatListSearchOptions?, Bool) -> Void)? = nil) { self.context = context - self.filter = filter + self.peersFilter = filter self.dimNode = ASDisplayNode() + self.navigationController = navigationController + self.updatedSearchOptions = updatedSearchOptions - let openPeer: (Peer, Bool) -> Void = { peer, value in - originalOpenPeer(peer, value) - - if peer.id.namespace != Namespaces.Peer.SecretChat { - addAppLogEvent(postbox: context.account.postbox, time: Date().timeIntervalSince1970, type: "search_global_open_peer", peerId: peer.id, data: .dictionary([:])) - } - } - - let openMessage: (Peer, MessageId) -> Void = { peer, messageId in - originalOpenMessage(peer, messageId) - - if peer.id.namespace != Namespaces.Peer.SecretChat { - addAppLogEvent(postbox: context.account.postbox, time: Date().timeIntervalSince1970, type: "search_global_open_message", peerId: peer.id, data: .dictionary(["msg_id": .number(Double(messageId.id))])) - } - } + self.present = present + self.presentInGlobalOverlay = presentInGlobalOverlay + self.openMessage = originalOpenMessage + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationDataPromise = Promise(ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations)) + self.filterContainerNode = ChatListSearchFiltersContainerNode() + self.recentListNode = ListView() self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor + + var openMediaMessageImpl: ((Message, ChatControllerInteractionOpenMessageMode) -> Void)? + var messageContextActionImpl: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)? + var toggleMessageSelectionImpl: ((MessageId, Bool) -> Void)? + var transitionNodeImpl: ((MessageId, Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?)? + var addToTransitionSurfaceImpl: ((UIView) -> Void)? + + self.mediaNode = ChatListSearchMediaNode(context: self.context, contentType: .photoOrVideo, openMessage: { message, mode in + openMediaMessageImpl?(message, mode) + }, messageContextAction: { message, sourceNode, sourceRect, gesture in + messageContextActionImpl?(message, sourceNode, sourceRect, gesture) + }, toggleMessageSelection: { messageId, selected in + toggleMessageSelectionImpl?(messageId, selected) + }) + + self.loadingNode = ASImageNode() + self.listNode = ListView() self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor self.statePromise = ValuePromise(self.stateValue, ignoreRepeated: true) self.searchStatePromise = ValuePromise(self.searchStateValue, ignoreRepeated: true) + self.mediaAccessoryPanelContainer = PassthroughContainerNode() + self.mediaAccessoryPanelContainer.clipsToBounds = true + + self.emptyResultsTitleNode = ImmediateTextNode() + self.emptyResultsTitleNode.displaysAsynchronously = false + self.emptyResultsTitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.ChatList_Search_NoResults, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.freeTextColor) + self.emptyResultsTitleNode.textAlignment = .center + self.emptyResultsTitleNode.isHidden = true + + self.emptyResultsTextNode = ImmediateTextNode() + self.emptyResultsTextNode.displaysAsynchronously = false + self.emptyResultsTextNode.maximumNumberOfLines = 0 + self.emptyResultsTextNode.textAlignment = .center + self.emptyResultsTextNode.isHidden = true + + self.emptyResultsAnimationNode = AnimatedStickerNode() + self.emptyResultsAnimationNode.isHidden = true + super.init() + if let path = getAppBundle().path(forResource: "ChatListNoResults", ofType: "tgs") { + self.emptyResultsAnimationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 248, height: 248, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) + self.animationSize = CGSize(width: 124.0, height: 124.0) + } + self.dimNode.backgroundColor = filter.contains(.excludeRecent) ? UIColor.black.withAlphaComponent(0.5) : self.presentationData.theme.chatList.backgroundColor self.backgroundColor = filter.contains(.excludeRecent) ? nil : self.presentationData.theme.chatList.backgroundColor @@ -669,9 +815,17 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.addSubnode(self.dimNode) self.addSubnode(self.recentListNode) self.addSubnode(self.listNode) + self.addSubnode(self.loadingNode) + self.addSubnode(self.mediaNode) + + self.addSubnode(self.mediaAccessoryPanelContainer) + + self.addSubnode(self.emptyResultsAnimationNode) + self.addSubnode(self.emptyResultsTitleNode) + self.addSubnode(self.emptyResultsTextNode) let searchContext = Promise(nil) - let searchContextValue = Atomic(value: nil) + let searchContextValue = self.searchContextValue let updateSearchContext: ((ChatListSearchMessagesContext?) -> (ChatListSearchMessagesContext?, Bool)) -> Void = { f in var shouldUpdate = false let updated = searchContextValue.modify { current in @@ -689,32 +843,15 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } self.listNode.isHidden = true - self.listNode.visibleBottomContentOffsetChanged = { offset in - guard case let .known(value) = offset, value < 100.0 else { - return - } - updateSearchContext { previous in - guard let previous = previous else { - return (nil, false) - } - if previous.loadMoreIndex != nil { - return (previous, false) - } - guard let last = previous.result.messages.last else { - return (previous, false) - } - return (ChatListSearchMessagesContext(result: previous.result, loadMoreIndex: last.index), true) - } - } + self.mediaNode.isHidden = true self.recentListNode.isHidden = filter.contains(.excludeRecent) - + let currentRemotePeers = Atomic<([FoundPeer], [FoundPeer])?>(value: nil) - let presentationDataPromise = self.presentationDataPromise let searchStatePromise = self.searchStatePromise - let foundItems = self.searchQuery.get() - |> mapToSignal { query -> Signal<([ChatListSearchEntry], Bool)?, NoError> in - guard let query = query, !query.isEmpty else { + let foundItems = combineLatest(self.searchQuery.get(), self.searchOptions.get()) + |> mapToSignal { query, options -> Signal<([ChatListSearchEntry], Bool)?, NoError> in + if query == nil && options == nil { let _ = currentRemotePeers.swap(nil) return .single(nil) } @@ -722,71 +859,68 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> take(1) - let foundLocalPeers = context.account.postbox.searchPeers(query: query.lowercased()) - |> mapToSignal { local -> Signal<([PeerView], [RenderedPeer]), NoError> in - return combineLatest(local.map { context.account.postbox.peerView(id: $0.peerId) }) |> map { views in - return (views, local) + let foundLocalPeers: Signal<(peers: [RenderedPeer], unread: [PeerId: (Int32, Bool)]), NoError> + + if let query = query { + foundLocalPeers = context.account.postbox.searchPeers(query: query.lowercased()) + |> mapToSignal { local -> Signal<([PeerView], [RenderedPeer]), NoError> in + return combineLatest(local.map { context.account.postbox.peerView(id: $0.peerId) }) |> map { views in + return (views, local) + } } - } - |> mapToSignal { viewsAndPeers -> Signal<(peers: [RenderedPeer], unread: [PeerId: (Int32, Bool)]), NoError> in - return context.account.postbox.unreadMessageCountsView(items: viewsAndPeers.0.map {.peer($0.peerId)}) |> map { values in - var unread: [PeerId: (Int32, Bool)] = [:] - for peerView in viewsAndPeers.0 { - var isMuted: Bool = false - if let nofiticationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - switch nofiticationSettings.muteState { - case .muted: - isMuted = true - default: - break + |> mapToSignal { viewsAndPeers -> Signal<(peers: [RenderedPeer], unread: [PeerId: (Int32, Bool)]), NoError> in + return context.account.postbox.unreadMessageCountsView(items: viewsAndPeers.0.map {.peer($0.peerId)}) |> map { values in + var unread: [PeerId: (Int32, Bool)] = [:] + for peerView in viewsAndPeers.0 { + var isMuted: Bool = false + if let nofiticationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { + switch nofiticationSettings.muteState { + case .muted: + isMuted = true + default: + break + } + } + + let unreadCount = values.count(for: .peer(peerView.peerId)) + if let unreadCount = unreadCount, unreadCount > 0 { + unread[peerView.peerId] = (unreadCount, isMuted) } } - - let unreadCount = values.count(for: .peer(peerView.peerId)) - if let unreadCount = unreadCount, unreadCount > 0 { - unread[peerView.peerId] = (unreadCount, isMuted) - } + return (peers: viewsAndPeers.1, unread: unread) } - return (peers: viewsAndPeers.1, unread: unread) } + } else { + foundLocalPeers = .single((peers: [], unread: [:])) } let foundRemotePeers: Signal<([FoundPeer], [FoundPeer], Bool), NoError> let currentRemotePeersValue = currentRemotePeers.with { $0 } ?? ([], []) - foundRemotePeers = ( - .single((currentRemotePeersValue.0, currentRemotePeersValue.1, true)) - |> then( - searchPeers(account: context.account, query: query) - |> map { ($0.0, $0.1, false) } - |> delay(0.2, queue: Queue.concurrentDefaultQueue()) + if let query = query { + foundRemotePeers = ( + .single((currentRemotePeersValue.0, currentRemotePeersValue.1, true)) + |> then( + searchPeers(account: context.account, query: query) + |> map { ($0.0, $0.1, false) } + |> delay(0.2, queue: Queue.concurrentDefaultQueue()) + ) ) - ) - let location: SearchMessagesLocation - let messageTags: MessageTags? - if query.hasPrefix("%media ") { - messageTags = .photoOrVideo - } else if query.hasPrefix("%photo ") { - messageTags = .photo - } else if query.hasPrefix("%video ") { - messageTags = .video - } else if query.hasPrefix("%file ") { - messageTags = .file - } else if query.hasPrefix("%music ") { - messageTags = .music - } else if query.hasPrefix("%link ") { - messageTags = .webPage - } else if query.hasPrefix("%gif ") { - messageTags = .gif } else { - messageTags = nil + foundRemotePeers = .single(([], [], false)) } - location = .general(tags: messageTags) - - var finalQuery = query - if let _ = messageTags, let index = finalQuery.firstIndex(of: " ") { - finalQuery = String(finalQuery.suffix(from: finalQuery.index(after: index))) + let location: SearchMessagesLocation + if let options = options { + if let (peerId, _) = options.peer { + location = .peer(peerId: peerId, fromId: nil, tags: options.messageTags, topMsgId: nil, minDate: options.minDate?.0, maxDate: options.maxDate?.0) + } else { + + location = .general(tags: options.messageTags, minDate: options.minDate?.0, maxDate: options.maxDate?.0) + } + } else { + location = .general(tags: nil, minDate: nil, maxDate: nil) } + let finalQuery = query ?? "" updateSearchContext { _ in return (nil, true) } @@ -795,21 +929,21 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo foundRemoteMessages = .single((([], [:], 0), false)) } else { if !finalQuery.isEmpty { - addAppLogEvent(postbox: context.account.postbox, time: Date().timeIntervalSince1970, type: "search_global_query", peerId: nil, data: .dictionary([:])) + addAppLogEvent(postbox: context.account.postbox, type: "search_global_query") } let searchSignal = searchMessages(account: context.account, location: location, query: finalQuery, state: nil, limit: 50) |> map { result, updatedState -> ChatListSearchMessagesResult in - return ChatListSearchMessagesResult(query: finalQuery, messages: result.messages.sorted(by: { $0.index > $1.index }), readStates: result.readStates, hasMore: !result.completed, state: updatedState) + return ChatListSearchMessagesResult(query: finalQuery, messages: result.messages.sorted(by: { $0.index > $1.index }), readStates: result.readStates, hasMore: !result.completed, totalCount: result.totalCount, state: updatedState) } let loadMore = searchContext.get() |> mapToSignal { searchContext -> Signal<(([Message], [PeerId: CombinedPeerReadState], Int32), Bool), NoError> in - if let searchContext = searchContext { + if let searchContext = searchContext, searchContext.result.hasMore { if let _ = searchContext.loadMoreIndex { return searchMessages(account: context.account, location: location, query: finalQuery, state: searchContext.result.state, limit: 80) |> map { result, updatedState -> ChatListSearchMessagesResult in - return ChatListSearchMessagesResult(query: finalQuery, messages: result.messages.sorted(by: { $0.index > $1.index }), readStates: result.readStates, hasMore: !result.completed, state: updatedState) + return ChatListSearchMessagesResult(query: finalQuery, messages: result.messages.sorted(by: { $0.index > $1.index }), readStates: result.readStates, hasMore: !result.completed, totalCount: result.totalCount, state: updatedState) } |> mapToSignal { foundMessages -> Signal<(([Message], [PeerId: CombinedPeerReadState], Int32), Bool), NoError> in updateSearchContext { previous in @@ -819,7 +953,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo return .complete() } } else { - return .single(((searchContext.result.messages, searchContext.result.readStates, 0), false)) + return .single(((searchContext.result.messages, searchContext.result.readStates, searchContext.result.totalCount), false)) } } else { return .complete() @@ -833,7 +967,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo updateSearchContext { _ in return (ChatListSearchMessagesContext(result: foundMessages, loadMoreIndex: nil), true) } - return ((foundMessages.messages, foundMessages.readStates, 0), false) + return ((foundMessages.messages, foundMessages.readStates, foundMessages.totalCount), false) } |> delay(0.2, queue: Queue.concurrentDefaultQueue()) |> then(loadMore) @@ -843,7 +977,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo let resolvedMessage = .single(nil) |> then(context.sharedContext.resolveUrl(account: context.account, url: finalQuery) |> mapToSignal { resolvedUrl -> Signal in - if case let .channelMessage(peerId, messageId) = resolvedUrl { + if case let .channelMessage(_, messageId) = resolvedUrl { return downloadMessage(postbox: context.account.postbox, network: context.account.network, messageId: messageId) } else { return .single(nil) @@ -852,8 +986,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo return combineLatest(accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, presentationDataPromise.get(), searchStatePromise.get(), resolvedMessage) |> map { accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, presentationData, searchState, resolvedMessage -> ([ChatListSearchEntry], Bool)? in - var entries: [ChatListSearchEntry] = [] let isSearching = foundRemotePeers.2 || foundRemoteMessages.1 + var entries: [ChatListSearchEntry] = [] var index = 0 let _ = currentRemotePeers.swap((foundRemotePeers.0, foundRemotePeers.1)) @@ -919,7 +1053,16 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo existingPeerIds.removeAll() - let localExpandType: ChatListSearchSectionExpandType = .none + let localExpandType: ChatListSearchSectionExpandType + if let _ = options?.messageTags { + if totalNumberOfLocalPeers > 3 { + localExpandType = searchState.expandLocalSearch ? .collapse : .expand + } else { + localExpandType = .none + } + } else { + localExpandType = .none + } let globalExpandType: ChatListSearchSectionExpandType if totalNumberOfGlobalPeers > 3 { globalExpandType = searchState.expandGlobalSearch ? .collapse : .expand @@ -927,52 +1070,52 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo globalExpandType = .none } - if let _ = messageTags { - } else { - let lowercasedQuery = finalQuery.lowercased() - if presentationData.strings.DialogList_SavedMessages.lowercased().hasPrefix(lowercasedQuery) || "saved messages".hasPrefix(lowercasedQuery) { - if !existingPeerIds.contains(accountPeer.id), filteredPeer(accountPeer, accountPeer) { - existingPeerIds.insert(accountPeer.id) - entries.append(.localPeer(accountPeer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType)) - index += 1 - } + let lowercasedQuery = finalQuery.lowercased() + if lowercasedQuery.count > 1 && (presentationData.strings.DialogList_SavedMessages.lowercased().hasPrefix(lowercasedQuery) || "saved messages".hasPrefix(lowercasedQuery)) { + if !existingPeerIds.contains(accountPeer.id), filteredPeer(accountPeer, accountPeer) { + existingPeerIds.insert(accountPeer.id) + entries.append(.localPeer(accountPeer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType)) + index += 1 + } + } + + var numberOfLocalPeers = 0 + for renderedPeer in foundLocalPeers.peers { + if case .expand = localExpandType, numberOfLocalPeers >= 3 { + break } - var numberOfLocalPeers = 0 - for renderedPeer in foundLocalPeers.peers { - if case .expand = localExpandType, numberOfLocalPeers >= 5 { - break - } - - if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != context.account.peerId, filteredPeer(peer, accountPeer) { - if !existingPeerIds.contains(peer.id) { - existingPeerIds.insert(peer.id) - var associatedPeer: Peer? - if let associatedPeerId = peer.associatedPeerId { - associatedPeer = renderedPeer.peers[associatedPeerId] - } - entries.append(.localPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType)) - index += 1 - numberOfLocalPeers += 1 + if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != context.account.peerId, filteredPeer(peer, accountPeer) { + if !existingPeerIds.contains(peer.id) { + existingPeerIds.insert(peer.id) + var associatedPeer: Peer? + if let associatedPeerId = peer.associatedPeerId { + associatedPeer = renderedPeer.peers[associatedPeerId] } - } - } - - for peer in foundRemotePeers.0 { - if case .expand = localExpandType, numberOfLocalPeers >= 5 { - break - } - - if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, accountPeer) { - existingPeerIds.insert(peer.peer.id) - entries.append(.localPeer(peer.peer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType)) + entries.append(.localPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType)) index += 1 numberOfLocalPeers += 1 } } + } + + for peer in foundRemotePeers.0 { + if case .expand = localExpandType, numberOfLocalPeers >= 3 { + break + } + + if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, accountPeer) { + existingPeerIds.insert(peer.peer.id) + entries.append(.localPeer(peer.peer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType)) + index += 1 + numberOfLocalPeers += 1 + } + } - var numberOfGlobalPeers = 0 - index = 0 + var numberOfGlobalPeers = 0 + index = 0 + if let _ = options?.messageTags { + } else { for peer in foundRemotePeers.1 { if case .expand = globalExpandType, numberOfGlobalPeers >= 3 { break @@ -994,25 +1137,30 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo peer = RenderedPeer(peer: channelPeer) } } - entries.append(.message(message, peer, nil, presentationData)) + entries.append(.message(message, peer, nil, presentationData, 1, nil, true)) index += 1 } + var firstHeaderId: Int64? if !foundRemotePeers.2 { index = 0 for message in foundRemoteMessages.0.0 { + let headerId = listMessageDateHeaderId(timestamp: message.timestamp) + if firstHeaderId == nil { + firstHeaderId = headerId + } var peer = RenderedPeer(message: message) if let group = message.peers[message.id.peerId] as? TelegramGroup, let migrationReference = group.migrationReference { if let channelPeer = message.peers[migrationReference.peerId] { peer = RenderedPeer(peer: channelPeer) } } - entries.append(.message(message, peer, foundRemoteMessages.0.1[message.id.peerId], presentationData)) + entries.append(.message(message, peer, foundRemoteMessages.0.1[message.id.peerId], presentationData, foundRemoteMessages.0.2, searchState.selectedMessageIds?.contains(message.id), headerId == firstHeaderId)) index += 1 } } - if addContact != nil && isViablePhoneNumber(finalQuery) { + if let _ = addContact, isViablePhoneNumber(finalQuery) { entries.append(.addContact(finalQuery, presentationData.theme, presentationData.strings)) } @@ -1020,11 +1168,120 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } } + let foundMessages = searchContext.get() |> map { searchContext -> ([Message], Int32, Bool) in + if let result = searchContext?.result { + return (result.messages, result.totalCount, result.hasMore) + } else { + return ([], 0, false) + } + } + + let loadMore = { + updateSearchContext { previous in + guard let previous = previous else { + return (nil, false) + } + if previous.loadMoreIndex != nil { + return (previous, false) + } + guard let last = previous.result.messages.last else { + return (previous, false) + } + return (ChatListSearchMessagesContext(result: previous.result, loadMoreIndex: last.index), true) + } + } + + let openUrlImpl: (String) -> Void = { url in + openUserGeneratedUrl(context: context, url: url, concealed: false, present: { c in + present(c, nil) + }, openResolved: { [weak self] resolved in + context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, openPeer: { peerId, navigation in + // self?.openPeer(peerId: peerId, navigation: navigation) + }, sendFile: nil, + sendSticker: nil, + present: { c, a in + present(c, a) + }, dismissInput: { + self?.dismissInput() + }, contentContext: nil) + }) + } + + openMediaMessageImpl = { [weak self] message, mode in + let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, mode: mode, navigationController: navigationController, dismissInput: { + self?.dismissInput() + }, present: { c, a in + present(c, a) + }, transitionNode: { messageId, media in + return transitionNodeImpl?(messageId, media) + }, addToTransitionSurface: { view in + addToTransitionSurfaceImpl?(view) + }, openUrl: { url in + openUrlImpl(url) + }, openPeer: { peer, navigation in + //self?.openPeer(peerId: peer.id, navigation: navigation) + }, callPeer: { _, _ in + }, enqueueMessage: { _ in + }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, gallerySource: .custom(messages: foundMessages, messageId: message.id, loadMore: { + loadMore() + }))) + } + + messageContextActionImpl = { [weak self] message, sourceNode, sourceRect, gesture in + if let strongSelf = self { + strongSelf.messageContextActions(message, node: sourceNode, rect: sourceRect, gesture: gesture) + } + } + + toggleMessageSelectionImpl = { [weak self] messageId, selected in + if let strongSelf = self { + strongSelf.updateSearchState { state in + var selectedMessageIds = state.selectedMessageIds ?? Set() + if selected { + selectedMessageIds.insert(messageId) + } else { + selectedMessageIds.remove(messageId) + } + return state.withUpdatedSelectedMessageIds(selectedMessageIds) + } + } + } + + transitionNodeImpl = { [weak self] messageId, media in + if let strongSelf = self { + return strongSelf.mediaNode.transitionNodeForGallery(messageId: messageId, media: media) + } else { + return nil + } + } + + addToTransitionSurfaceImpl = { [weak self] view in + if let strongSelf = self { + strongSelf.mediaNode.addToTransitionSurface(view: view) + } + } + let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil) + let openPeer: (Peer, Bool) -> Void = { peer, value in + originalOpenPeer(peer, value) + + if peer.id.namespace != Namespaces.Peer.SecretChat { + addAppLogEvent(postbox: context.account.postbox, type: "search_global_open_peer", peerId: peer.id) + } + } + + let openMessage: (Peer, MessageId) -> Void = { peer, messageId in + originalOpenMessage(peer, messageId) + + if peer.id.namespace != Namespaces.Peer.SecretChat { + addAppLogEvent(postbox: context.account.postbox, type: "search_global_open_message", peerId: peer.id, data: .dictionary(["msg_id": .number(Double(messageId.id))])) + } + } + let interaction = ChatListNodeInteraction(activateSearch: { }, peerSelected: { [weak self] peer, _ in - self?.view.endEditing(true) + self?.dismissInput() openPeer(peer, false) let _ = addRecentlySearchedPeer(postbox: context.account.postbox, peerId: peer.id).start() self?.listNode.clearHighlightAnimated(true) @@ -1032,14 +1289,14 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo }, togglePeerSelected: { _ in }, additionalCategorySelected: { _ in }, messageSelected: { [weak self] peer, message, _ in - self?.view.endEditing(true) + self?.dismissInput() if let peer = message.peers[message.id.peerId] { openMessage(peer, message.id) } self?.listNode.clearHighlightAnimated(true) }, groupSelected: { _ in }, addContact: { [weak self] phoneNumber in - self?.view.endEditing(true) + self?.dismissInput() addContact?(phoneNumber) self?.listNode.clearHighlightAnimated(true) }, setPeerIdWithRevealedOptions: { [weak self] peerId, fromPeerId in @@ -1073,7 +1330,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo gesture?.cancel() } }, present: { c in - present(c) + present(c, nil) }) self.interaction = interaction @@ -1178,16 +1435,105 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } })) + let listInteraction = ListMessageItemInteraction(openMessage: { [weak self] message, mode -> Bool in + self?.dismissInput() + return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, mode: mode, navigationController: navigationController, dismissInput: { [weak self] in + self?.dismissInput() + }, present: { c, a in + present(c, a) + }, transitionNode: { [weak self] messageId, media in + var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? + if let strongSelf = self { + strongSelf.listNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? ListMessageNode { + if let result = itemNode.transitionNode(id: messageId, media: media) { + transitionNode = result + } + } + } + } + return transitionNode + }, addToTransitionSurface: { view in + self?.view.addSubview(view) + }, openUrl: { url in + openUrlImpl(url) + }, openPeer: { peer, navigation in + // self?.openPeer(peerId: peer.id, navigation: navigation) + }, callPeer: { _, _ in + }, enqueueMessage: { _ in + }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: .custom(messages: foundMessages, at: message.id, loadMore: { + loadMore() + }), gallerySource: .custom(messages: foundMessages, messageId: message.id, loadMore: { + loadMore() + }))) + }, openMessageContextMenu: { [weak self] message, bool, node, rect, gesture in + self?.messageContextAction(message, node: node, rect: rect, gesture: gesture) + }, toggleMessagesSelection: { messageId, selected in + if let messageId = messageId.first { + toggleMessageSelectionImpl?(messageId, selected) + } + }, openUrl: { url, _, _, message in + openUrlImpl(url) + }, openInstantPage: { message, data in + if let (webpage, anchor) = instantPageAndAnchor(message: message) { + let pageController = InstantPageController(context: context, webPage: webpage, sourcePeerType: .channel, anchor: anchor) + navigationController?.pushViewController(pageController) + } + }, longTap: { action, message in + }, getHiddenMedia: { + return [:] + }) + + let previousSearchState = Atomic(value: nil) self.searchDisposable.set((foundItems |> deliverOnMainQueue).start(next: { [weak self] entriesAndFlags in if let strongSelf = self { - strongSelf._isSearching.set(entriesAndFlags?.1 ?? false) + let previousState = previousSearchState.swap(strongSelf.searchStateValue) + + let isSearching = entriesAndFlags?.1 ?? false + strongSelf._isSearching.set(isSearching) + + if strongSelf.searchOptionsValue?.messageTags == .photoOrVideo { + var totalCount: Int32 = 0 + if let entries = entriesAndFlags?.0 { + for entry in entries { + if case let .message(_, _, _, _, count, _, _) = entry { + totalCount = count + break + } + } + } + var entries: [ChatListSearchEntry]? = entriesAndFlags?.0 ?? [] + if isSearching && (entries?.isEmpty ?? true) { + entries = nil + } + strongSelf.mediaNode.updateHistory(entries: entries, totalCount: totalCount, updateType: .Initial) + } + + var entriesAndFlags = entriesAndFlags + + var peers: [Peer] = [] + if let entries = entriesAndFlags?.0 { + var filteredEntries: [ChatListSearchEntry] = [] + for entry in entries { + if case let .localPeer(peer, _, _, _, _, _, _, _, _) = entry { + peers.append(peer) + } else { + filteredEntries.append(entry) + } + } + + if strongSelf.searchOptionsValue?.messageTags != nil || strongSelf.searchOptionsValue?.maxDate != nil || strongSelf.searchOptionsValue?.peer != nil { + entriesAndFlags?.0 = filteredEntries + } + } let previousEntries = previousSearchItems.swap(entriesAndFlags?.0) + let newEntries = entriesAndFlags?.0 ?? [] + let animated = (previousState?.selectedMessageIds == nil) != (strongSelf.searchStateValue.selectedMessageIds == nil) let firstTime = previousEntries == nil - let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entriesAndFlags?.0 ?? [], displayingResults: entriesAndFlags?.0 != nil, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: filter, interaction: interaction, peerContextAction: peerContextAction, - toggleExpandLocalResults: { + let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags?.0 != nil, isEmpty: !isSearching && (entriesAndFlags?.0.isEmpty ?? false), isLoading: isSearching, animated: animated, searchQuery: strongSelf.searchQueryValue ?? "", context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: filter, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: { guard let strongSelf = self else { return } @@ -1205,8 +1551,33 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo state.expandGlobalSearch = !state.expandGlobalSearch return state } + }, searchPeer: { peer in + guard let strongSelf = self else { + return + } + strongSelf.updateSearchOptions(strongSelf.currentSearchOptions.withUpdatedPeer((peer.id, peer.compactDisplayTitle)), clearQuery: true) + strongSelf.dismissInput?() + }, searchResults: newEntries.compactMap { entry -> Message? in + if case let .message(message, _, _, _, _, _, _) = entry { + return message + } else { + return nil + } + }, searchOptions: strongSelf.searchOptionsValue, messageContextAction: { message, node, rect, gesture in + guard let strongSelf = self else { + return + } + strongSelf.messageContextAction(message, node: node, rect: rect, gesture: gesture) }) strongSelf.enqueueTransition(transition, firstTime: firstTime) + + let previousPossiblePeers = strongSelf.possiblePeers + strongSelf.possiblePeers = Array(peers.prefix(10)) + + strongSelf.updatedSearchOptions?(strongSelf.searchOptionsValue, strongSelf.hasSuggestions) + if let (layout, navigationBarHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + } } })) @@ -1230,13 +1601,104 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.listNode.beganInteractiveDragging = { [weak self] in self?.dismissInput?() } + + self.mediaNode.beganInteractiveDragging = { [weak self] in + self?.dismissInput?() + } + + self.listNode.visibleBottomContentOffsetChanged = { offset in + guard case let .known(value) = offset, value < 160.0 else { + return + } + loadMore() + } + + self.mediaNode.loadMore = { + loadMore() + } + + self.filterContainerNode.filterPressed = { [weak self] filter in + guard let strongSelf = self else { + return + } + var messageTags = strongSelf.currentSearchOptions.messageTags + var maxDate = strongSelf.currentSearchOptions.maxDate + var peer = strongSelf.currentSearchOptions.peer + var clearQuery: Bool = false + switch filter { + case .media: + messageTags = .photoOrVideo + case .links: + messageTags = .webPage + case .files: + messageTags = .file + case .music: + messageTags = .music + case .voice: + messageTags = .voiceOrInstantVideo + case let .date(date, title): + maxDate = (date, title) + clearQuery = true + case let .peer(id, name): + peer = (id, name) + clearQuery = true + } + strongSelf.updateSearchOptions(strongSelf.currentSearchOptions.withUpdatedMessageTags(messageTags).withUpdatedMaxDate(maxDate).withUpdatedPeer(peer), clearQuery: clearQuery) + } + + self.mediaStatusDisposable = (combineLatest(context.sharedContext.mediaManager.globalMediaPlayerState, self.searchOptions.get()) + |> mapToSignal { playlistStateAndType, searchOptions -> Signal<(Account, SharedMediaPlayerItemPlaybackState, MediaManagerPlayerType)?, NoError> in + if let (account, state, type) = playlistStateAndType { + switch state { + case let .state(state): + if let playlistId = state.playlistId as? PeerMessagesMediaPlaylistId, case .custom = playlistId { + if case .music = type, searchOptions?.messageTags == .music { + return .single((account, state, type)) + } else if case .voice = type, searchOptions?.messageTags == .voiceOrInstantVideo { + return .single((account, state, type)) + } else { + return .single(nil) |> delay(0.1, queue: .mainQueue()) + } + } else { + return .single(nil) |> delay(0.1, queue: .mainQueue()) + } + case .loading: + return .single(nil) |> delay(0.1, queue: .mainQueue()) + } + } else { + return .single(nil) + } + } + |> deliverOnMainQueue).start(next: { [weak self] playlistStateAndType in + guard let strongSelf = self else { + return + } + if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.1.item) || + !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.1, playlistStateAndType?.1.previousItem) || + !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.2, playlistStateAndType?.1.nextItem) || + strongSelf.playlistStateAndType?.3 != playlistStateAndType?.1.order || strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 { + + if let playlistStateAndType = playlistStateAndType { + strongSelf.playlistStateAndType = (playlistStateAndType.1.item, playlistStateAndType.1.previousItem, playlistStateAndType.1.nextItem, playlistStateAndType.1.order, playlistStateAndType.2, playlistStateAndType.0) + } else { + strongSelf.playlistStateAndType = nil + } + + if let (layout, navigationBarHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.4, curve: .spring)) + } + } + }) } deinit { + self.activeActionDisposable.dispose() self.updatedRecentPeersDisposable.dispose() self.recentDisposable.dispose() self.searchDisposable.dispose() self.presentationDataDisposable?.dispose() + self.mediaStatusDisposable?.dispose() + self.playlistPreloadDisposable?.dispose() } override public func didLoad() { @@ -1250,10 +1712,80 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } } + private var currentSearchOptions: ChatListSearchOptions { + return self.searchOptionsValue ?? ChatListSearchOptions(peer: nil, minDate: nil, maxDate: nil, messageTags: nil) + } + + public override func searchTokensUpdated(tokens: [SearchBarToken]) { + var updatedOptions = self.searchOptionsValue + var tokensIdSet = Set() + for token in tokens { + tokensIdSet.insert(token.id) + } + if !tokensIdSet.contains(ChatListTokenId.filter.rawValue) && updatedOptions?.messageTags != nil { + updatedOptions = updatedOptions?.withUpdatedMessageTags(nil) + } + if !tokensIdSet.contains(ChatListTokenId.date.rawValue) && updatedOptions?.maxDate != nil { + updatedOptions = updatedOptions?.withUpdatedMaxDate(nil) + } + if !tokensIdSet.contains(ChatListTokenId.peer.rawValue) && updatedOptions?.peer != nil { + updatedOptions = updatedOptions?.withUpdatedPeer(nil) + } + self.updateSearchOptions(updatedOptions) + } + + private func updateSearchOptions(_ options: ChatListSearchOptions?, clearQuery: Bool = false) { + self.searchOptionsValue = options + self.searchOptions.set(.single(options)) + + var tokens: [SearchBarToken] = [] + if let messageTags = options?.messageTags { + var title: String? + var icon: UIImage? + if messageTags == .photoOrVideo { + title = self.presentationData.strings.ChatList_Search_FilterMedia + icon = UIImage(bundleImageName: "Chat List/Search/Media") + } else if messageTags == .webPage { + title = self.presentationData.strings.ChatList_Search_FilterLinks + icon = UIImage(bundleImageName: "Chat List/Search/Links") + } else if messageTags == .file { + title = self.presentationData.strings.ChatList_Search_FilterFiles + icon = UIImage(bundleImageName: "Chat List/Search/Files") + } else if messageTags == .music { + title = self.presentationData.strings.ChatList_Search_FilterMusic + icon = UIImage(bundleImageName: "Chat List/Search/Music") + } else if messageTags == .voiceOrInstantVideo { + title = self.presentationData.strings.ChatList_Search_FilterVoice + icon = UIImage(bundleImageName: "Chat List/Search/Voice") + } + + if let title = title { + tokens.append(SearchBarToken(id: ChatListTokenId.filter.rawValue, icon: icon, title: title)) + } + } + + if let (_, peerName) = options?.peer { + tokens.append(SearchBarToken(id: ChatListTokenId.peer.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/User"), title: peerName)) + } + + if let (_, dateTitle) = options?.maxDate { + tokens.append(SearchBarToken(id: ChatListTokenId.date.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Calendar"), title: dateTitle)) + + self.possibleDates = [] + } + + if clearQuery { + self.setQuery?(nil, tokens, "") + } else { + self.setQuery?(nil, tokens, self.searchQueryValue ?? "") + } + + self.updatedSearchOptions?(options, self.hasSuggestions) + } private func updateTheme(theme: PresentationTheme) { - self.backgroundColor = self.filter.contains(.excludeRecent) ? nil : theme.chatList.backgroundColor - self.dimNode.backgroundColor = self.filter.contains(.excludeRecent) ? UIColor.black.withAlphaComponent(0.5) : theme.chatList.backgroundColor + self.backgroundColor = self.peersFilter.contains(.excludeRecent) ? nil : theme.chatList.backgroundColor + self.dimNode.backgroundColor = self.peersFilter.contains(.excludeRecent) ? UIColor.black.withAlphaComponent(0.5) : theme.chatList.backgroundColor self.recentListNode.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor self.listNode.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor @@ -1283,12 +1815,38 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.searchStateValue = state self.searchStatePromise.set(state) } + self.mediaNode.selectedMessageIds = self.searchStateValue.selectedMessageIds + self.mediaNode.updateSelectedMessages(animated: true) + self.selectionPanelNode?.selectedMessages = self.searchStateValue.selectedMessageIds ?? [] } + var possibleDates: [(Date, String?)] = [] + var possiblePeers: [Peer] = [] override public func searchTextUpdated(text: String) { let searchQuery: String? = !text.isEmpty ? text : nil self.interaction?.searchTextHighightState = searchQuery self.searchQuery.set(.single(searchQuery)) + self.searchQueryValue = searchQuery + + let previousPossibleDate = self.possibleDates + self.possibleDates = suggestDates(for: text, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat) + + if previousPossibleDate.isEmpty != self.possibleDates.isEmpty { + self.updatedSearchOptions?(self.searchOptionsValue, self.hasSuggestions) + if let (layout, navigationBarHeight) = self.validLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + } + } + } + + private var hasSuggestions: Bool { + if !self.possibleDates.isEmpty && self.searchOptionsValue?.maxDate == nil { + return true + } else if !self.possiblePeers.isEmpty && self.searchOptionsValue?.peer == nil { + return true + } else { + return false + } } private func enqueueRecentTransition(_ transition: ChatListSearchContainerRecentTransition, firstTime: Bool) { @@ -1328,21 +1886,87 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } private func dequeueTransition() { - if let (transition, _) = self.enqueuedTransitions.first { + if let (transition, firstTime) = self.enqueuedTransitions.first { self.enqueuedTransitions.remove(at: 0) var options = ListViewDeleteAndInsertOptions() options.insert(.PreferSynchronousDrawing) options.insert(.PreferSynchronousResourceLoading) - let displayingResults = transition.displayingResults + if transition.animated { + options.insert(.AnimateInsertion) + } + self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in if let strongSelf = self { - strongSelf.listNode.isHidden = !displayingResults - strongSelf.recentListNode.isHidden = displayingResults || strongSelf.filter.contains(.excludeRecent) + let searchOptions = strongSelf.searchOptionsValue + strongSelf.listNode.isHidden = searchOptions?.messageTags == .photoOrVideo && (strongSelf.searchQueryValue ?? "").isEmpty + strongSelf.mediaNode.isHidden = !strongSelf.listNode.isHidden + + let displayingResults = transition.displayingResults + if !displayingResults { + strongSelf.listNode.isHidden = true + strongSelf.mediaNode.isHidden = true + } + + let emptyResults = displayingResults && transition.isEmpty + if emptyResults { + let emptyResultsTitle: String + let emptyResultsText: String + if !transition.query.isEmpty { + emptyResultsTitle = strongSelf.presentationData.strings.ChatList_Search_NoResults + emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsQueryDescription(transition.query).0 + } else { + if let searchOptions = searchOptions, searchOptions.messageTags != nil && searchOptions.minDate == nil && searchOptions.maxDate == nil && searchOptions.peer == nil { + emptyResultsTitle = strongSelf.presentationData.strings.ChatList_Search_NoResultsFilter + if searchOptions.messageTags == .photoOrVideo { + emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsFitlerMedia + } else if searchOptions.messageTags == .webPage { + emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsFitlerLinks + } else if searchOptions.messageTags == .file { + emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsFitlerFiles + } else if searchOptions.messageTags == .music { + emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsFitlerMusic + } else if searchOptions.messageTags == .voiceOrInstantVideo { + emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsFitlerVoice + } else { + emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsDescription + } + } else { + emptyResultsTitle = strongSelf.presentationData.strings.ChatList_Search_NoResults + emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsDescription + } + } + + strongSelf.emptyResultsTitleNode.attributedText = NSAttributedString(string: emptyResultsTitle, font: Font.semibold(17.0), textColor: strongSelf.presentationData.theme.list.freeTextColor) + strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: emptyResultsText, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.freeTextColor) + } + + if let (layout, navigationBarHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + } + + strongSelf.emptyResultsAnimationNode.isHidden = !emptyResults + strongSelf.emptyResultsTitleNode.isHidden = !emptyResults + strongSelf.emptyResultsTextNode.isHidden = !emptyResults + strongSelf.emptyResultsAnimationNode.visibility = emptyResults + + if let searchOptions = searchOptions, searchOptions.messageTags != nil && searchOptions.messageTags != .photoOrVideo, transition.query.isEmpty { + if searchOptions.messageTags == .webPage { + strongSelf.loadingNode.image = UIImage(bundleImageName: "Chat List/Search/M_Links") + } else if searchOptions.messageTags == .file { + strongSelf.loadingNode.image = UIImage(bundleImageName: "Chat List/Search/M_Files") + } else if searchOptions.messageTags == .music || searchOptions.messageTags == .voiceOrInstantVideo { + strongSelf.loadingNode.image = UIImage(bundleImageName: "Chat List/Search/M_Music") + } + + strongSelf.loadingNode.isHidden = !transition.isLoading + } else { + strongSelf.loadingNode.isHidden = true + } + strongSelf.recentListNode.isHidden = displayingResults || strongSelf.peersFilter.contains(.excludeRecent) strongSelf.dimNode.isHidden = displayingResults - strongSelf.backgroundColor = !displayingResults && strongSelf.filter.contains(.excludeRecent) ? nil : strongSelf.presentationData.theme.chatList.backgroundColor - + strongSelf.backgroundColor = !displayingResults && strongSelf.peersFilter.contains(.excludeRecent) ? nil : strongSelf.presentationData.theme.chatList.backgroundColor } }) } @@ -1352,18 +1976,335 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) let hadValidLayout = self.validLayout != nil - self.validLayout = layout + self.validLayout = (layout, navigationBarHeight) + + var topInset = navigationBarHeight + + var topPanelHeight: CGFloat = 0.0 + if let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType { + let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight + topPanelHeight = panelHeight + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight)) + if let (mediaAccessoryPanel, mediaType) = self.mediaAccessoryPanel, mediaType == type { + transition.updateFrame(layer: mediaAccessoryPanel.layer, frame: panelFrame) + mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition) + switch order { + case .regular: + mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, previousItem, nextItem) + case .reversed: + mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nextItem, previousItem) + case .random: + mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nil, nil) + } + let delayedStatus = self.context.sharedContext.mediaManager.globalMediaPlayerState + |> mapToSignal { value -> Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError> in + guard let value = value else { + return .single(nil) + } + switch value.1 { + case .state: + return .single(value) + case .loading: + return .single(value) |> delay(0.1, queue: .mainQueue()) + } + } + + mediaAccessoryPanel.containerNode.headerNode.playbackStatus = delayedStatus + |> map { state -> MediaPlayerStatus in + if let stateOrLoading = state?.1, case let .state(state) = stateOrLoading { + return state.status + } else { + return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) + } + } + } else { + if let (mediaAccessoryPanel, _) = self.mediaAccessoryPanel { + self.mediaAccessoryPanel = nil + self.dismissingPanel = mediaAccessoryPanel + mediaAccessoryPanel.animateOut(transition: transition, completion: { [weak self, weak mediaAccessoryPanel] in + mediaAccessoryPanel?.removeFromSupernode() + if let strongSelf = self, strongSelf.dismissingPanel === mediaAccessoryPanel { + strongSelf.dismissingPanel = nil + } + }) + } + + let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context) + mediaAccessoryPanel.containerNode.headerNode.displayScrubber = item.playbackData?.type != .instantVideo + mediaAccessoryPanel.close = { [weak self] in + if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { + strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause)) + } + } + mediaAccessoryPanel.toggleRate = { + [weak self] in + guard let strongSelf = self else { + return + } + let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> AudioPlaybackRate in + let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings) as? MusicPlaybackSettings ?? MusicPlaybackSettings.defaultSettings + + let nextRate: AudioPlaybackRate + switch settings.voicePlaybackRate { + case .x1: + nextRate = .x2 + case .x2: + nextRate = .x1 + } + transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in + return settings.withUpdatedVoicePlaybackRate(nextRate) + }) + return nextRate + } + |> deliverOnMainQueue).start(next: { baseRate in + guard let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType else { + return + } + strongSelf.context.sharedContext.mediaManager.playlistControl(.setBaseRate(baseRate), type: type) + }) + } + mediaAccessoryPanel.togglePlayPause = { [weak self] in + if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { + strongSelf.context.sharedContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: type) + } + } + mediaAccessoryPanel.playPrevious = { [weak self] in + if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { + strongSelf.context.sharedContext.mediaManager.playlistControl(.next, type: type) + } + } + mediaAccessoryPanel.playNext = { [weak self] in + if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { + strongSelf.context.sharedContext.mediaManager.playlistControl(.previous, type: type) + } + } + mediaAccessoryPanel.tapAction = { [weak self] in + guard let strongSelf = self, let (state, _, _, order, type, account) = strongSelf.playlistStateAndType else { + return + } + if let id = state.id as? PeerMessagesMediaPlaylistItemId { + if type == .music { + let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), context: strongSelf.context, chatLocation: .peer(id.messageId.peerId), chatLocationContextHolder: Atomic(value: nil), tagMask: MessageTags.music) + + var cancelImpl: (() -> Void)? + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + let progressSignal = Signal { subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + self?.interaction?.present(controller) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = MetaDisposable() + var progressStarted = false + strongSelf.playlistPreloadDisposable?.dispose() + + + strongSelf.playlistPreloadDisposable = (signal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + |> deliverOnMainQueue).start(next: { index in + guard let strongSelf = self else { + return + } + if let _ = index.0 { + let controllerContext: AccountContext + if account.id == strongSelf.context.account.id { + controllerContext = strongSelf.context + } else { + controllerContext = strongSelf.context.sharedContext.makeTempAccountContext(account: account) + } + let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, peerId: id.messageId.peerId, type: type, initialMessageId: id.messageId, initialOrder: order, isGlobalSearch: true, parentNavigationController: strongSelf.navigationController) + strongSelf.dismissInput() + strongSelf.interaction?.present(controller) + } else if index.1 { + if !progressStarted { + progressStarted = true + progressDisposable.set(progressSignal.start()) + } + } + }, completed: { + }) + cancelImpl = { + self?.playlistPreloadDisposable?.dispose() + } + } else { + strongSelf.context.sharedContext.navigateToChat(accountId: strongSelf.context.account.id, peerId: id.messageId.peerId, messageId: id.messageId) + } + } + } + mediaAccessoryPanel.frame = panelFrame + if let dismissingPanel = self.dismissingPanel { + self.mediaAccessoryPanelContainer.insertSubnode(mediaAccessoryPanel, aboveSubnode: dismissingPanel) + } else { + self.mediaAccessoryPanelContainer.addSubnode(mediaAccessoryPanel) + } + self.mediaAccessoryPanel = (mediaAccessoryPanel, type) + mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: .immediate) + switch order { + case .regular: + mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, previousItem, nextItem) + case .reversed: + mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nextItem, previousItem) + case .random: + mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nil, nil) + } + mediaAccessoryPanel.containerNode.headerNode.playbackStatus = self.context.sharedContext.mediaManager.globalMediaPlayerState + |> map { state -> MediaPlayerStatus in + if let stateOrLoading = state?.1, case let .state(state) = stateOrLoading { + return state.status + } else { + return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) + } + } + mediaAccessoryPanel.animateIn(transition: transition) + } + } else if let (mediaAccessoryPanel, _) = self.mediaAccessoryPanel { + self.mediaAccessoryPanel = nil + self.dismissingPanel = mediaAccessoryPanel + mediaAccessoryPanel.animateOut(transition: transition, completion: { [weak self, weak mediaAccessoryPanel] in + mediaAccessoryPanel?.removeFromSupernode() + if let strongSelf = self, strongSelf.dismissingPanel === mediaAccessoryPanel { + strongSelf.dismissingPanel = nil + } + }) + } + + transition.updateFrame(node: self.mediaAccessoryPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: MediaNavigationAccessoryHeaderNode.minimizedHeight))) + topInset += topPanelHeight - let topInset = navigationBarHeight transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset))) + transition.updateFrame(node: self.filterContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight + 6.0), size: CGSize(width: layout.size.width, height: 37.0))) + + self.loadingNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: 422.0)) + + let filters: [ChatListSearchFilter] + var customFilters: [ChatListSearchFilter] = [] + if !self.possibleDates.isEmpty && self.searchOptionsValue?.maxDate == nil { + let formatter = DateFormatter() + formatter.timeStyle = .none + formatter.dateStyle = .medium + + for (date, string) in self.possibleDates { + let title = string ?? formatter.string(from: date) + customFilters.append(.date(Int32(date.timeIntervalSince1970), title)) + } + } + if !self.possiblePeers.isEmpty && self.searchOptionsValue?.peer == nil { + for peer in self.possiblePeers { + customFilters.append(.peer(peer.id, peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder))) + } + } + + if !customFilters.isEmpty { + filters = customFilters + } else { + filters = [.media, .links, .files, .music, .voice] + } + + self.filterContainerNode.update(size: CGSize(width: layout.size.width, height: 37.0), sideInset: layout.safeInsets.left, filters: filters.map { .filter($0) }, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) + + + if let selectedMessageIds = self.searchStateValue.selectedMessageIds { + var wasAdded = false + let selectionPanelNode: ChatListSearchMessageSelectionPanelNode + if let current = self.selectionPanelNode { + selectionPanelNode = current + } else { + wasAdded = true + selectionPanelNode = ChatListSearchMessageSelectionPanelNode(context: self.context, deleteMessages: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.deleteMessages(messageIds: nil) + }, shareMessages: { [weak self] in + guard let strongSelf = self, let messageIds = strongSelf.searchStateValue.selectedMessageIds, !messageIds.isEmpty else { + return + } + let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Message] in + var messages: [Message] = [] + for id in messageIds { + if let message = transaction.getMessage(id) { + messages.append(message) + } + } + return messages + } + |> deliverOnMainQueue).start(next: { messages in + if let strongSelf = self, !messages.isEmpty { + let shareController = ShareController(context: strongSelf.context, subject: .messages(messages.sorted(by: { lhs, rhs in + return lhs.index < rhs.index + })), externalShare: true, immediateExternalShare: true) + strongSelf.dismissInput() + strongSelf.present?(shareController, nil) + } + }) + }, forwardMessages: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.forwardMessages(messageIds: nil) + }) + self.selectionPanelNode = selectionPanelNode + self.addSubnode(selectionPanelNode) + } + selectionPanelNode.selectedMessages = selectedMessageIds + let panelHeight = selectionPanelNode.update(layout: layout, presentationData: self.presentationData, transition: wasAdded ? .immediate : transition) + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight)) + if wasAdded { + selectionPanelNode.frame = panelFrame + transition.animatePositionAdditive(node: selectionPanelNode, offset: CGPoint(x: 0.0, y: panelHeight)) + } else { + transition.updateFrame(node: selectionPanelNode, frame: panelFrame) + } + } else if let selectionPanelNode = self.selectionPanelNode { + self.selectionPanelNode = nil + transition.updateFrame(node: selectionPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: selectionPanelNode.bounds.size), completion: { [weak selectionPanelNode] _ in + selectionPanelNode?.removeFromSupernode() + }) + } + let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) self.recentListNode.frame = CGRect(origin: CGPoint(), size: layout.size) - self.recentListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: navigationBarHeight, left: layout.safeInsets.left, bottom: layout.insets(options: [.input]).bottom, right: layout.safeInsets.right), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.recentListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: layout.insets(options: [.input]).bottom, right: layout.safeInsets.right), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size) - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: navigationBarHeight, left: layout.safeInsets.left, bottom: layout.insets(options: [.input]).bottom, right: layout.safeInsets.right), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: layout.insets(options: [.input]).bottom, right: layout.safeInsets.right), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + + self.mediaNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset)) + self.mediaNode.update(size: layout.size, sideInset: layout.safeInsets.left, bottomInset: layout.insets(options: [.input]).bottom, visibleHeight: layout.size.height - navigationBarHeight, isScrollingLockedAtTop: false, expandProgress: 1.0, presentationData: self.presentationData, synchronous: true, transition: transition) + + let padding: CGFloat = 16.0 + let emptyTitleSize = self.emptyResultsTitleNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude)) + let emptyTextSize = self.emptyResultsTextNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude)) + + let insets = layout.insets(options: [.input]) + var emptyAnimationHeight = self.animationSize.height + var emptyAnimationSpacing: CGFloat = 8.0 + if case .landscape = layout.orientation, case .compact = layout.metrics.widthClass { + emptyAnimationHeight = 0.0 + emptyAnimationSpacing = 0.0 + } + let emptyTextSpacing: CGFloat = 8.0 + let emptyTotalHeight = emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSize.height + emptyTextSpacing + let emptyAnimationY = navigationBarHeight + floorToScreenPixels((layout.size.height - navigationBarHeight - max(insets.bottom, layout.intrinsicInsets.bottom) - emptyTotalHeight) / 2.0) + + let textTransition = ContainedViewLayoutTransition.immediate + textTransition.updateFrame(node: self.emptyResultsAnimationNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - self.animationSize.width) / 2.0, y: emptyAnimationY), size: self.animationSize)) + textTransition.updateFrame(node: self.emptyResultsTitleNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - emptyTitleSize.width) / 2.0, y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing), size: emptyTitleSize)) + textTransition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize)) + self.emptyResultsAnimationNode.updateLayout(size: self.animationSize) if !hadValidLayout { while !self.enqueuedRecentTransitions.isEmpty { @@ -1438,7 +2379,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo actionSheet?.dismissAnimated() }) ])]) - self.view.window?.endEditing(true) + self.dismissInput() self.interaction?.present(actionSheet) } @@ -1449,4 +2390,267 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.recentListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } } + + func messageContextActions(_ message: Message, node: ASDisplayNode?, rect: CGRect?, gesture anyRecognizer: UIGestureRecognizer?) { + let gesture: ContextGesture? = anyRecognizer as? ContextGesture + let _ = (chatMediaListPreviewControllerData(context: self.context, chatLocation: .peer(message.id.peerId), chatLocationContextHolder: Atomic(value: nil), message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: self.navigationController) + |> deliverOnMainQueue).start(next: { [weak self] previewData in + guard let strongSelf = self else { + gesture?.cancel() + return + } + if let previewData = previewData { + let context = strongSelf.context + let strings = strongSelf.presentationData.strings +// let items = chatAvailableMessageActionsImpl(postbox: strongSelf.context.account.postbox, accountPeerId: strongSelf.context.account.peerId, messageIds: [message.id]) +// |> map { actions -> [ContextMenuItem] in + var items: [ContextMenuItem] = [] + + items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in + c.dismiss(completion: { + self?.openMessage(message.peers[message.id.peerId]!, message.id) + }) + }))) + + items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { c, f in + c.dismiss(completion: { + if let strongSelf = self { + strongSelf.forwardMessages(messageIds: [message.id]) + } + }) + }))) + + items.append(.separator) + items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuMore, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + if let strongSelf = self { + strongSelf.dismissInput() + + strongSelf.updateSearchState { state in + return state.withUpdatedSelectedMessageIds([message.id]) + } + + if let (layout, navigationBarHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + } + } + + f(.default) + }))) + + switch previewData { + case let .gallery(gallery): + gallery.setHintWillBePresentedInPreviewingContext(true) + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture) + strongSelf.presentInGlobalOverlay?(contextController, nil) + case .instantPage: + break + } + } + }) + } + + func messageContextAction(_ message: Message, node: ASDisplayNode?, rect: CGRect?, gesture anyRecognizer: UIGestureRecognizer?) { + guard let node = node as? ContextExtractedContentContainingNode else { + return + } + let _ = storedMessageFromSearch(account: self.context.account, message: message).start() + + var linkForCopying: String? + var currentSupernode: ASDisplayNode? = node + while true { + if currentSupernode == nil { + break + } else if let currentSupernode = currentSupernode as? ListMessageSnippetItemNode { + linkForCopying = currentSupernode.currentPrimaryUrl + break + } else { + currentSupernode = currentSupernode?.supernode + } + } + + let gesture: ContextGesture? = anyRecognizer as? ContextGesture + var items: [ContextMenuItem] = [] + + if let linkForCopying = linkForCopying { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuCopyLink, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c.dismiss(completion: {}) + UIPasteboard.general.string = linkForCopying + }))) + } + + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c.dismiss(completion: { [weak self] in + if let strongSelf = self { + strongSelf.forwardMessages(messageIds: Set([message.id])) + } + }) + }))) + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c.dismiss(completion: { [weak self] in + self?.openMessage(message.peers[message.id.peerId]!, message.id) + }) + }))) + + items.append(.separator) + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuMore, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c.dismiss(completion: { + if let strongSelf = self { + strongSelf.dismissInput() + + strongSelf.updateSearchState { state in + return state.withUpdatedSelectedMessageIds([message.id]) + } + + if let (layout, navigationBarHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + } + } + }) + }))) + + let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture) + self.presentInGlobalOverlay?(controller, nil) + } + + public override func searchTextClearTokens() { + self.updateSearchOptions(nil) + self.setQuery?(nil, [], self.searchQueryValue ?? "") + } + func deleteMessages(messageIds: Set?) { + let messageIds = messageIds ?? self.searchStateValue.selectedMessageIds + } + + func forwardMessages(messageIds: Set?) { + let messageIds = messageIds ?? self.searchStateValue.selectedMessageIds + if let messageIds = messageIds, !messageIds.isEmpty { + let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled])) + peerSelectionController.peerSelected = { [weak self, weak peerSelectionController] peerId in + if let strongSelf = self, let _ = peerSelectionController { + if peerId == strongSelf.context.account.peerId { + let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in + return .forward(source: id, grouping: .auto, attributes: []) + }) + |> deliverOnMainQueue).start(next: { [weak self] messageIds in + if let strongSelf = self { + let signals: [Signal] = messageIds.compactMap({ id -> Signal? in + guard let id = id else { + return nil + } + return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id) + |> mapToSignal { status, _ -> Signal in + if status != nil { + return .never() + } else { + return .single(true) + } + } + |> take(1) + }) + strongSelf.activeActionDisposable.set((combineLatest(signals) + |> deliverOnMainQueue).start(completed: { + guard let strongSelf = self else { + return + } + strongSelf.present?(OverlayStatusController(theme: strongSelf.presentationData.theme, type: .success), nil) + })) + } + }) + if let peerSelectionController = peerSelectionController { + peerSelectionController.dismiss() + } + + strongSelf.updateSearchState { state in + return state.withUpdatedSelectedMessageIds(nil) + } + if let (layout, navigationBarHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + } + } else { + let _ = (strongSelf.context.account.postbox.transaction({ transaction -> Void in + transaction.updatePeerChatInterfaceState(peerId, update: { currentState in + if let currentState = currentState as? ChatInterfaceState { + return currentState.withUpdatedForwardMessageIds(Array(messageIds)) + } else { + return ChatInterfaceState().withUpdatedForwardMessageIds(Array(messageIds)) + } + }) + }) |> deliverOnMainQueue).start(completed: { + if let strongSelf = self { +// strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone) + + let controller = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peerId), subject: nil, botStart: nil, mode: .standard(previewing: false)) + strongSelf.navigationController?.pushViewController(controller, animated: false, completion: { + if let peerSelectionController = peerSelectionController { + peerSelectionController.dismiss() + } + }) + + strongSelf.updateSearchState { state in + return state.withUpdatedSelectedMessageIds(nil) + } + if let (layout, navigationBarHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + } + } + }) + } + } + } + self.navigationController?.pushViewController(peerSelectionController) + } + } + + private func dismissInput() { + self.view.window?.endEditing(true) + } +} + +private final class MessageContextExtractedContentSource: ContextExtractedContentSource { + let keepInPlace: Bool = false + let ignoreContentTouches: Bool = true + + private let sourceNode: ContextExtractedContentContainingNode + + init(sourceNode: ContextExtractedContentContainingNode) { + self.sourceNode = sourceNode + } + + func takeView() -> ContextControllerTakeViewInfo? { + return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + } + + func putBack() -> ContextControllerPutBackViewInfo? { + return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds) + } +} + +private final class ContextControllerContentSourceImpl: ContextControllerContentSource { + let controller: ViewController + weak var sourceNode: ASDisplayNode? + + let navigationController: NavigationController? = nil + + let passthroughTouches: Bool = false + + init(controller: ViewController, sourceNode: ASDisplayNode?) { + self.controller = controller + self.sourceNode = sourceNode + } + + func transitionInfo() -> ContextControllerTakeControllerInfo? { + let sourceNode = self.sourceNode + return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in + if let sourceNode = sourceNode { + return (sourceNode, sourceNode.bounds) + } else { + return nil + } + }) + } + + func animatedIn() { + self.controller.didAppearInContextPreview() + } } diff --git a/submodules/ChatListUI/Sources/ChatListSearchFiltersContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchFiltersContainerNode.swift new file mode 100644 index 0000000000..0023b47b7b --- /dev/null +++ b/submodules/ChatListUI/Sources/ChatListSearchFiltersContainerNode.swift @@ -0,0 +1,306 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SyncCore +import Postbox +import TelegramCore +import TelegramPresentationData + +enum ChatListSearchFilter: Equatable { + case media + case links + case files + case music + case voice + case peer(PeerId, String) + case date(Int32, String) + + var id: Int32 { + switch self { + case .media: + return 0 + case .links: + return 1 + case .files: + return 2 + case .music: + return 3 + case .voice: + return 4 + case let .peer(peerId, _): + return peerId.id + case let .date(date, _): + return date + } + } +} + +private final class ItemNode: ASDisplayNode { + private let pressed: () -> Void + + private let iconNode: ASImageNode + private let titleNode: ImmediateTextNode + private let buttonNode: HighlightTrackingButtonNode + + private var selectionFraction: CGFloat = 0.0 + private(set) var unreadCount: Int = 0 + + private var isReordering: Bool = false + + private var theme: PresentationTheme? + + init(pressed: @escaping () -> Void) { + self.pressed = pressed + + let titleInset: CGFloat = 4.0 + + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + self.iconNode.displayWithoutProcessing = true + + self.titleNode = ImmediateTextNode() + self.titleNode.displaysAsynchronously = false + self.titleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0) + + self.buttonNode = HighlightTrackingButtonNode() + + super.init() + + self.addSubnode(self.titleNode) + self.addSubnode(self.iconNode) + self.addSubnode(self.buttonNode) + + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + self.buttonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.iconNode.layer.removeAnimation(forKey: "opacity") + strongSelf.iconNode.alpha = 0.4 + + strongSelf.titleNode.layer.removeAnimation(forKey: "opacity") + strongSelf.titleNode.alpha = 0.4 + } else { + strongSelf.iconNode.alpha = 1.0 + strongSelf.iconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + + strongSelf.titleNode.alpha = 1.0 + strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + } + + @objc private func buttonPressed() { + self.pressed() + } + + func update(type: ChatListSearchFilter, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { + let title: String + let icon: UIImage? + + let color = presentationData.theme.list.itemSecondaryTextColor + switch type { + case .media: + title = presentationData.strings.ChatList_Search_FilterMedia + icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Media"), color: color) + case .links: + title = presentationData.strings.ChatList_Search_FilterLinks + icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Links"), color: color) + case .files: + title = presentationData.strings.ChatList_Search_FilterFiles + icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Files"), color: color) + case .music: + title = presentationData.strings.ChatList_Search_FilterMusic + icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Music"), color: color) + case .voice: + title = presentationData.strings.ChatList_Search_FilterVoice + icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Voice"), color: color) + case let .peer(_, displayTitle): + title = displayTitle + icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/User"), color: color) + case let .date(_, displayTitle): + title = displayTitle + icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Calendar"), color: color) + } + + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: color) + + if self.theme !== presentationData.theme { + self.theme = presentationData.theme + self.iconNode.image = icon + } + } + + func updateLayout(height: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + let iconInset: CGFloat = 22.0 + if let image = self.iconNode.image { + self.iconNode.frame = CGRect(x: 0.0, y: floorToScreenPixels((height - image.size.height) / 2.0), width: image.size.width, height: image.size.height) + } + + let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude)) + let titleFrame = CGRect(origin: CGPoint(x: -self.titleNode.insets.left + iconInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize) + self.titleNode.frame = titleFrame + + return titleSize.width - self.titleNode.insets.left - self.titleNode.insets.right + iconInset + } + + func updateArea(size: CGSize, sideInset: CGFloat, transition: ContainedViewLayoutTransition) { + self.buttonNode.frame = CGRect(origin: CGPoint(x: -sideInset, y: 0.0), size: CGSize(width: size.width + sideInset * 2.0, height: size.height)) + + self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -sideInset, bottom: 0.0, right: -sideInset) + } +} + +enum ChatListSearchFilterEntryId: Hashable { + case filter(Int32) +} + +enum ChatListSearchFilterEntry: Equatable { + case filter(ChatListSearchFilter) + + var id: ChatListSearchFilterEntryId { + switch self { + case let .filter(filter): + return .filter(filter.id) + } + } +} + +final class ChatListSearchFiltersContainerNode: ASDisplayNode { + private let scrollNode: ASScrollNode + private var itemNodes: [ChatListSearchFilterEntryId: ItemNode] = [:] + + var filterPressed: ((ChatListSearchFilter) -> Void)? + + private var currentParams: (size: CGSize, sideInset: CGFloat, filters: [ChatListSearchFilterEntry], presentationData: PresentationData)? + + override init() { + self.scrollNode = ASScrollNode() + + super.init() + + self.scrollNode.view.showsHorizontalScrollIndicator = false + self.scrollNode.view.scrollsToTop = false + self.scrollNode.view.delaysContentTouches = false + self.scrollNode.view.canCancelContentTouches = true + if #available(iOS 11.0, *) { + self.scrollNode.view.contentInsetAdjustmentBehavior = .never + } + + self.addSubnode(self.scrollNode) + } + + func cancelAnimations() { + self.scrollNode.layer.removeAllAnimations() + } + + func update(size: CGSize, sideInset: CGFloat, filters: [ChatListSearchFilterEntry], presentationData: PresentationData, transition proposedTransition: ContainedViewLayoutTransition) { + let isFirstTime = self.currentParams == nil + let transition: ContainedViewLayoutTransition = isFirstTime ? .immediate : proposedTransition + + if self.currentParams?.presentationData.theme !== presentationData.theme { + self.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor + } + + self.currentParams = (size: size, sideInset: sideInset, filters: filters, presentationData: presentationData) + + transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size)) + + for i in 0 ..< filters.count { + let filter = filters[i] + if case let .filter(type) = filter { + let itemNode: ItemNode + var itemNodeTransition = transition + if let current = self.itemNodes[filter.id] { + itemNode = current + } else { + itemNodeTransition = .immediate + itemNode = ItemNode(pressed: { [weak self] in + self?.filterPressed?(type) + }) + self.itemNodes[filter.id] = itemNode + } + itemNode.update(type: type, presentationData: presentationData, transition: itemNodeTransition) + } + } + var removeKeys: [ChatListSearchFilterEntryId] = [] + for (id, _) in self.itemNodes { + if !filters.contains(where: { $0.id == id }) { + removeKeys.append(id) + } + } + for id in removeKeys { + if let itemNode = self.itemNodes.removeValue(forKey: id) { + transition.updateAlpha(node: itemNode, alpha: 0.0, completion: { [weak itemNode] _ in + itemNode?.removeFromSupernode() + }) + transition.updateTransformScale(node: itemNode, scale: 0.1) + } + } + + var tabSizes: [(ChatListSearchFilterEntryId, CGSize, ItemNode, Bool)] = [] + var totalRawTabSize: CGFloat = 0.0 + + for filter in filters { + guard let itemNode = self.itemNodes[filter.id] else { + continue + } + let wasAdded = itemNode.supernode == nil + var itemNodeTransition = transition + if wasAdded { + itemNodeTransition = .immediate + self.scrollNode.addSubnode(itemNode) + } + let paneNodeWidth = itemNode.updateLayout(height: size.height, transition: itemNodeTransition) + let paneNodeSize = CGSize(width: paneNodeWidth, height: size.height) + tabSizes.append((filter.id, paneNodeSize, itemNode, wasAdded)) + totalRawTabSize += paneNodeSize.width + } + + let minSpacing: CGFloat = 24.0 + var spacing = minSpacing + + let resolvedSideInset: CGFloat = 16.0 + sideInset + var leftOffset: CGFloat = resolvedSideInset + + var longTitlesWidth: CGFloat = resolvedSideInset + var titlesWidth: CGFloat = 0.0 + for i in 0 ..< tabSizes.count { + let (_, paneNodeSize, _, _) = tabSizes[i] + longTitlesWidth += paneNodeSize.width + titlesWidth += paneNodeSize.width + if i != tabSizes.count - 1 { + longTitlesWidth += minSpacing + } + } + longTitlesWidth += resolvedSideInset + + let verticalOffset: CGFloat = -3.0 + for i in 0 ..< tabSizes.count { + let (_, paneNodeSize, paneNode, wasAdded) = tabSizes[i] + let itemNodeTransition = transition + + let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0) + verticalOffset), size: paneNodeSize) + + if wasAdded { + paneNode.frame = paneFrame + paneNode.alpha = 0.0 + paneNode.subnodeTransform = CATransform3DMakeScale(0.1, 0.1, 1.0) + itemNodeTransition.updateSublayerTransformScale(node: paneNode, scale: 1.0) + itemNodeTransition.updateAlpha(node: paneNode, alpha: 1.0) + } else { + itemNodeTransition.updateFrameAdditive(node: paneNode, frame: paneFrame) + } + + paneNode.updateArea(size: paneFrame.size, sideInset: spacing / 2.0, transition: itemNodeTransition) + paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -spacing / 2.0, bottom: 0.0, right: -spacing / 2.0) + + leftOffset += paneNodeSize.width + spacing + } + leftOffset -= spacing + leftOffset += resolvedSideInset + + self.scrollNode.view.contentSize = CGSize(width: leftOffset, height: size.height) + } +} diff --git a/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift b/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift new file mode 100644 index 0000000000..2de228d537 --- /dev/null +++ b/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift @@ -0,0 +1,1135 @@ +import AsyncDisplayKit +import Display +import TelegramCore +import SyncCore +import SwiftSignalKit +import Postbox +import TelegramPresentationData +import AccountContext +import ContextUI +import PhotoResources +import RadialStatusNode +import TelegramStringFormatting +import UniversalMediaPlayer +import ListMessageItem +import ChatMessageInteractiveMediaBadge +import ShimmerEffect +import GridMessageSelectionNode + +private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6) +private let mediaBadgeTextColor = UIColor.white + +private final class VisualMediaItemInteraction { + let openMessage: (Message) -> Void + let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void + let toggleSelection: (MessageId, Bool) -> Void + + var hiddenMedia: [MessageId: [Media]] = [:] + var selectedMessageIds: Set? + + init( + openMessage: @escaping (Message) -> Void, + openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, + toggleSelection: @escaping (MessageId, Bool) -> Void + ) { + self.openMessage = openMessage + self.openMessageContextActions = openMessageContextActions + self.toggleSelection = toggleSelection + } +} + +private final class VisualMediaItemNode: ASDisplayNode { + private let context: AccountContext + private let interaction: VisualMediaItemInteraction + + private var displayLink: ConstantDisplayLinkAnimator? + private var displayLinkTimestamp: Double = 0.0 + + private let containerNode: ContextControllerSourceNode + private let imageNode: TransformImageNode + private var statusNode: RadialStatusNode + private let mediaBadgeNode: ChatMessageInteractiveMediaBadge + private var placeholderNode: ShimmerEffectNode? + private var selectionNode: GridMessageSelectionNode? + + private let fetchStatusDisposable = MetaDisposable() + private let fetchDisposable = MetaDisposable() + private var resourceStatus: MediaResourceStatus? + + private var item: (VisualMediaItem, Media?, CGSize, CGSize?)? + private var theme: PresentationTheme? + + private var hasVisibility: Bool = false + + init(context: AccountContext, interaction: VisualMediaItemInteraction) { + self.context = context + self.interaction = interaction + + self.containerNode = ContextControllerSourceNode() + self.imageNode = TransformImageNode() + self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6)) + let progressDiameter: CGFloat = 40.0 + self.statusNode.frame = CGRect(x: 0.0, y: 0.0, width: progressDiameter, height: progressDiameter) + self.statusNode.isUserInteractionEnabled = false + + self.mediaBadgeNode = ChatMessageInteractiveMediaBadge() + self.mediaBadgeNode.frame = CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 50.0, height: 50.0)) + + super.init() + + self.addSubnode(self.containerNode) + self.containerNode.addSubnode(self.imageNode) + self.containerNode.addSubnode(self.mediaBadgeNode) + + self.containerNode.activated = { [weak self] gesture, _ in + guard let strongSelf = self, let item = strongSelf.item else { + return + } + if let message = item.0.message { + strongSelf.interaction.openMessageContextActions(message, strongSelf.containerNode, strongSelf.containerNode.bounds, gesture) + } + } + } + + deinit { + self.fetchStatusDisposable.dispose() + self.fetchDisposable.dispose() + } + + override func didLoad() { + super.didLoad() + + let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) + recognizer.tapActionAtPoint = { _ in + return .waitForSingleTap + } + self.imageNode.view.addGestureRecognizer(recognizer) + + self.mediaBadgeNode.pressed = { [weak self] in + self?.progressPressed() + } + } + + @objc func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + guard let message = self.item?.0.message else { + return + } + if case .ended = recognizer.state { + if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation { + if case .tap = gesture { + if let (item, _, _, _) = self.item { + var media: Media? + for value in message.media { + if let image = value as? TelegramMediaImage { + media = image + break + } else if let file = value as? TelegramMediaFile { + media = file + break + } + } + + if let media = media { + if let file = media as? TelegramMediaFile { + if isMediaStreamable(message: message, media: file) { + self.interaction.openMessage(message) + } else { + self.progressPressed() + } + } else { + self.interaction.openMessage(message) + } + } + } + } + } + } + } + + private func progressPressed() { + guard let message = self.item?.0.message else { + return + } + + var media: Media? + for value in message.media { + if let image = value as? TelegramMediaImage { + media = image + break + } else if let file = value as? TelegramMediaFile { + media = file + break + } + } + + if let resourceStatus = self.resourceStatus, let file = media as? TelegramMediaFile { + switch resourceStatus { + case .Fetching: + messageMediaFileCancelInteractiveFetch(context: self.context, messageId: message.id, file: file) + case .Local: + self.interaction.openMessage(message) + case .Remote: + self.fetchDisposable.set(messageMediaFileInteractiveFetched(context: self.context, message: message, file: file, userInitiated: true).start()) + } + } + } + + func cancelPreviewGesture() { + self.containerNode.cancelGesture() + } + + func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) { + self.placeholderNode?.updateAbsoluteRect(absoluteRect, within: containerSize) + } + + func update(size: CGSize, item: VisualMediaItem, theme: PresentationTheme, synchronousLoad: Bool) { + if item === self.item?.0 && size == self.item?.2 { + return + } + self.theme = theme + var media: Media? + if let message = item.message { + for value in message.media { + if let image = value as? TelegramMediaImage { + media = image + break + } else if let file = value as? TelegramMediaFile { + media = file + break + } + } + } + + if let media = media, (self.item?.1 == nil || !media.isEqual(to: self.item!.1!)), let message = item.message { + var mediaDimensions: CGSize? + if let image = media as? TelegramMediaImage, let largestSize = largestImageRepresentation(image.representations)?.dimensions { + mediaDimensions = largestSize.cgSize + + self.imageNode.setSignal(mediaGridMessagePhoto(account: context.account, photoReference: .message(message: MessageReference(message), media: image), fullRepresentationSize: CGSize(width: 300.0, height: 300.0), synchronousLoad: synchronousLoad), attemptSynchronously: synchronousLoad, dispatchOnDisplayLink: true) + + self.fetchStatusDisposable.set(nil) + self.statusNode.transitionToState(.none, completion: { [weak self] in + self?.statusNode.isHidden = true + }) + self.mediaBadgeNode.isHidden = true + self.resourceStatus = nil + } else if let file = media as? TelegramMediaFile, file.isVideo { + mediaDimensions = file.dimensions?.cgSize + self.imageNode.setSignal(mediaGridMessageVideo(postbox: context.account.postbox, videoReference: .message(message: MessageReference(message), media: file), synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: true), attemptSynchronously: synchronousLoad) + + self.mediaBadgeNode.isHidden = file.isAnimated + + self.resourceStatus = nil + + self.item = (item, media, size, mediaDimensions) + + self.fetchStatusDisposable.set((messageMediaFileStatus(context: context, messageId: message.id, file: file) + |> deliverOnMainQueue).start(next: { [weak self] status in + if let strongSelf = self, let (item, _, _, _) = strongSelf.item { + strongSelf.resourceStatus = status + + let isStreamable = isMediaStreamable(message: message, media: file) + + var statusState: RadialStatusNodeState = .none + if isStreamable || file.isAnimated { + statusState = .none + } else { + switch status { + case let .Fetching(_, progress): + let adjustedProgress = max(progress, 0.027) + statusState = .progress(color: .white, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: true) + case .Local: + statusState = .none + case .Remote: + statusState = .download(.white) + } + } + + switch statusState { + case .none: + break + default: + strongSelf.statusNode.isHidden = false + } + + strongSelf.statusNode.transitionToState(statusState, animated: true, completion: { + if let strongSelf = self { + if case .none = statusState { + strongSelf.statusNode.isHidden = true + } + } + }) + + if let duration = file.duration { + let durationString = stringForDuration(duration) + + var badgeContent: ChatMessageInteractiveMediaBadgeContent? + var mediaDownloadState: ChatMessageInteractiveMediaDownloadState? + + if isStreamable { + switch status { + case let .Fetching(_, progress): + let progressString = String(format: "%d%%", Int(progress * 100.0)) + badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: progressString)) + mediaDownloadState = .compactFetching(progress: 0.0) + case .Local: + badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) + case .Remote: + badgeContent = .text(inset: 12.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) + mediaDownloadState = .compactRemote + } + } else { + badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString)) + } + + strongSelf.mediaBadgeNode.update(theme: nil, content: badgeContent, mediaDownloadState: mediaDownloadState, alignment: .right, animated: false, badgeAnimated: false) + } + } + })) + if self.statusNode.supernode == nil { + self.imageNode.addSubnode(self.statusNode) + } + } else { + self.mediaBadgeNode.isHidden = true + } + self.item = (item, media, size, mediaDimensions) + + let progressDiameter: CGFloat = 40.0 + self.statusNode.frame = CGRect(origin: CGPoint(x: floor((size.width - progressDiameter) / 2.0), y: floor((size.height - progressDiameter) / 2.0)), size: CGSize(width: progressDiameter, height: progressDiameter)) + + self.mediaBadgeNode.frame = CGRect(origin: CGPoint(x: size.width - 3.0, y: size.height - 18.0 - 3.0), size: CGSize(width: 50.0, height: 50.0)) + + self.selectionNode?.frame = CGRect(origin: CGPoint(), size: size) + + self.updateHiddenMedia() + + if let placeholderNode = self.placeholderNode { + self.placeholderNode = nil + placeholderNode.removeFromSupernode() + } + } else if item.isEmpty, self.placeholderNode == nil { + let placeholderNode = ShimmerEffectNode() + placeholderNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: [.rect(rect: CGRect(origin: CGPoint(), size: size))], size: size) + self.addSubnode(placeholderNode) + self.placeholderNode = placeholderNode + } + + let imageFrame = CGRect(origin: CGPoint(), size: size) + self.placeholderNode?.frame = imageFrame + + if let (item, media, _, mediaDimensions) = self.item { + self.item = (item, media, size, mediaDimensions) + + self.containerNode.frame = imageFrame + self.imageNode.frame = imageFrame + + if let mediaDimensions = mediaDimensions { + let imageSize = mediaDimensions.aspectFilled(imageFrame.size) + self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageFrame.size, intrinsicInsets: UIEdgeInsets(), emptyColor: theme.list.mediaPlaceholderColor))() + } + + self.updateSelectionState(animated: false) + } + } + + func updateIsVisible(_ isVisible: Bool) { + self.hasVisibility = isVisible +// if let _ = self.videoLayerFrameManager { +// let displayLink: ConstantDisplayLinkAnimator +// if let current = self.displayLink { +// displayLink = current +// } else { +// displayLink = ConstantDisplayLinkAnimator { [weak self] in +// guard let strongSelf = self else { +// return +// } +// strongSelf.displayLinkTimestamp += 1.0 / 30.0 +// } +// displayLink.frameInterval = 2 +// self.displayLink = displayLink +// } +// } + self.displayLink?.isPaused = !self.hasVisibility || self.isHidden + } + + func updateSelectionState(animated: Bool) { + if let (item, _, _, _) = self.item, let theme = self.theme, let message = item.message { + self.containerNode.isGestureEnabled = self.interaction.selectedMessageIds == nil + + if let selectedIds = self.interaction.selectedMessageIds { + let selected = selectedIds.contains(message.id) + + if let selectionNode = self.selectionNode { + selectionNode.updateSelected(selected, animated: animated) + selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size) + } else { + let selectionNode = GridMessageSelectionNode(theme: theme, toggle: { [weak self] value in + if let strongSelf = self, let messageId = strongSelf.item?.0.message?.id { + var toggledValue = true + if let selectedMessageIds = strongSelf.interaction.selectedMessageIds, selectedMessageIds.contains(messageId) { + toggledValue = false + } + strongSelf.interaction.toggleSelection(messageId, toggledValue) + } + }) + + selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size) + self.containerNode.addSubnode(selectionNode) + self.selectionNode = selectionNode + selectionNode.updateSelected(selected, animated: false) + if animated { + selectionNode.animateIn() + } + } + } else { + if let selectionNode = self.selectionNode { + self.selectionNode = nil + if animated { + selectionNode.animateOut { [weak selectionNode] in + selectionNode?.removeFromSupernode() + } + } else { + selectionNode.removeFromSupernode() + } + } + } + } + } + + func transitionNode() -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + let imageNode = self.imageNode + return (self.imageNode, self.imageNode.bounds, { [weak self, weak imageNode] in + var statusNodeHidden = false + var accessoryHidden = false + if let strongSelf = self { + statusNodeHidden = strongSelf.statusNode.isHidden + accessoryHidden = strongSelf.mediaBadgeNode.isHidden + strongSelf.statusNode.isHidden = true + strongSelf.mediaBadgeNode.isHidden = true + } + let view = imageNode?.view.snapshotView(afterScreenUpdates: false) + if let strongSelf = self { + strongSelf.statusNode.isHidden = statusNodeHidden + strongSelf.mediaBadgeNode.isHidden = accessoryHidden + } + return (view, nil) + }) + } + + func updateHiddenMedia() { + if let (item, _, _, _) = self.item, let message = item.message { + if let _ = self.interaction.hiddenMedia[message.id] { + self.isHidden = true + } else { + self.isHidden = false + } + } else { + self.isHidden = false + } + self.displayLink?.isPaused = !self.hasVisibility || self.isHidden + } +} + +private final class VisualMediaItem { + let index: UInt32? + let message: Message? + let dimensions: CGSize + let aspectRatio: CGFloat + let isEmpty: Bool + + init(index: UInt32) { + self.index = index + self.message = nil + self.dimensions = CGSize(width: 100.0, height: 100.0) + self.aspectRatio = 1.0 + self.isEmpty = true + } + + init(message: Message) { + self.index = nil + self.message = message + + var aspectRatio: CGFloat = 1.0 + var dimensions = CGSize(width: 100.0, height: 100.0) + for media in message.media { + if let file = media as? TelegramMediaFile { + if let dimensionsValue = file.dimensions, dimensions.height > 1 { + dimensions = dimensionsValue.cgSize + aspectRatio = CGFloat(dimensionsValue.width) / CGFloat(dimensionsValue.height) + } + } + } + self.aspectRatio = aspectRatio + self.dimensions = dimensions + self.isEmpty = false + } + + var stableId: UInt32 { + if let message = self.message { + return message.stableId + } else if let index = self.index { + return index + } else { + return 0 + } + } +} + +private final class FloatingHeaderNode: ASDisplayNode { + private let backgroundNode: ASImageNode + private let labelNode: ImmediateTextNode + + private var currentParams: (constrainedWidth: CGFloat, year: Int32, month: Int32, theme: PresentationTheme)? + private var currentSize: CGSize? + + override init() { + self.backgroundNode = ASImageNode() + self.backgroundNode.displaysAsynchronously = false + self.backgroundNode.displayWithoutProcessing = true + + self.labelNode = ImmediateTextNode() + self.labelNode.displaysAsynchronously = false + + super.init() + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.labelNode) + } + + func update(constrainedWidth: CGFloat, year: Int32, month: Int32, theme: PresentationTheme, strings: PresentationStrings) -> CGSize { + if let currentParams = self.currentParams, let currentSize = self.currentSize { + if currentParams.constrainedWidth == constrainedWidth && + currentParams.year == year && + currentParams.month == month && + currentParams.theme === theme { + return currentSize + } + } + + if self.currentParams?.theme !== theme { + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 27.0, color: mediaBadgeBackgroundColor) + } + + self.currentParams = (constrainedWidth, year, month, theme) + + self.labelNode.attributedText = NSAttributedString(string: stringForMonth(strings: strings, month: month, ofYear: year), font: Font.regular(14.0), textColor: .white) + let labelSize = self.labelNode.updateLayout(CGSize(width: constrainedWidth, height: .greatestFiniteMagnitude)) + + let sideInset: CGFloat = 10.0 + self.labelNode.frame = CGRect(origin: CGPoint(x: sideInset, y: floor((27.0 - labelSize.height) / 2.0)), size: labelSize) + self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: labelSize.width + sideInset * 2.0, height: 27.0)) + + let size = CGSize(width: labelSize.width + sideInset * 2.0, height: 27.0) + return size + } +} + +private enum ItemsLayout { + final class Grid { + let containerWidth: CGFloat + let itemCount: Int + let itemSpacing: CGFloat + let itemsInRow: Int + let itemSize: CGFloat + let rowCount: Int + let contentHeight: CGFloat + + init(containerWidth: CGFloat, itemCount: Int, bottomInset: CGFloat) { + self.containerWidth = containerWidth + self.itemCount = itemCount + self.itemSpacing = 1.0 + self.itemsInRow = max(3, min(6, Int(containerWidth / 140.0))) + self.itemSize = floor(containerWidth / CGFloat(itemsInRow)) + + self.rowCount = itemCount / self.itemsInRow + (itemCount % self.itemsInRow == 0 ? 0 : 1) + + self.contentHeight = CGFloat(self.rowCount + 1) * self.itemSpacing + CGFloat(rowCount) * itemSize + bottomInset + } + + func visibleRange(rect: CGRect) -> (Int, Int) { + var minVisibleRow = Int(floor((rect.minY - self.itemSpacing) / (self.itemSize + self.itemSpacing))) + minVisibleRow = max(0, minVisibleRow) + var maxVisibleRow = Int(ceil((rect.maxY - self.itemSpacing) / (self.itemSize + itemSpacing))) + maxVisibleRow = min(self.rowCount - 1, maxVisibleRow) + + let minVisibleIndex = minVisibleRow * itemsInRow + let maxVisibleIndex = min(self.itemCount - 1, (maxVisibleRow + 1) * itemsInRow - 1) + + return (minVisibleIndex, maxVisibleIndex) + } + + func frame(forItemAt index: Int, sideInset: CGFloat) -> CGRect { + let rowIndex = index / Int(self.itemsInRow) + let columnIndex = index % Int(self.itemsInRow) + let itemOrigin = CGPoint(x: sideInset + CGFloat(columnIndex) * (self.itemSize + self.itemSpacing), y: self.itemSpacing + CGFloat(rowIndex) * (self.itemSize + self.itemSpacing)) + return CGRect(origin: itemOrigin, size: CGSize(width: columnIndex == self.itemsInRow ? (self.containerWidth - itemOrigin.x) : self.itemSize, height: self.itemSize)) + } + } + + final class Balanced { + let frames: [CGRect] + let contentHeight: CGFloat + + init(containerWidth: CGFloat, items: [VisualMediaItem], bottomInset: CGFloat) { + self.frames = calculateItemFrames(items: items, containerWidth: containerWidth) + if let last = self.frames.last { + self.contentHeight = last.maxY + bottomInset + } else { + self.contentHeight = bottomInset + } + } + + func visibleRange(rect: CGRect) -> (Int, Int) { + for i in 0 ..< self.frames.count { + if self.frames[i].maxY >= rect.minY { + for j in i ..< self.frames.count { + if self.frames[j].minY >= rect.maxY { + return (i, j - 1) + } + } + return (i, self.frames.count - 1) + } + } + return (0, -1) + } + + func frame(forItemAt index: Int, sideInset: CGFloat) -> CGRect { + if index >= 0 && index < self.frames.count { + return self.frames[index] + } else { + assertionFailure() + return CGRect(origin: CGPoint(), size: CGSize(width: 100.0, height: 100.0)) + } + } + } + + case grid(Grid) + case balanced(Balanced) + + var contentHeight: CGFloat { + switch self { + case let .grid(grid): + return grid.contentHeight + case let .balanced(balanced): + return balanced.contentHeight + } + } + + func visibleRange(rect: CGRect) -> (Int, Int) { + switch self { + case let .grid(grid): + return grid.visibleRange(rect: rect) + case let .balanced(balanced): + return balanced.visibleRange(rect: rect) + } + } + + func frame(forItemAt index: Int, sideInset: CGFloat) -> CGRect { + switch self { + case let .grid(grid): + return grid.frame(forItemAt: index, sideInset: sideInset) + case let .balanced(balanced): + return balanced.frame(forItemAt: index, sideInset: sideInset) + } + } +} + +final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate { + enum ContentType { + case photoOrVideo + case gifs + } + + private let context: AccountContext + private let contentType: ContentType + + private let scrollNode: ASScrollNode + private let floatingHeaderNode: FloatingHeaderNode + private var flashHeaderDelayTimer: Foundation.Timer? + private var isDeceleratingAfterTracking = false + + private var _itemInteraction: VisualMediaItemInteraction? + private var itemInteraction: VisualMediaItemInteraction { + return self._itemInteraction! + } + + private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData)? + + private let ready = Promise() + private var didSetReady: Bool = false + var isReady: Signal { + return self.ready.get() + } + + let shouldReceiveExpandProgressUpdates: Bool = false + + private let listDisposable = MetaDisposable() + private var hiddenMediaDisposable: Disposable? + private var mediaItems: [VisualMediaItem] = [] + private var itemsLayout: ItemsLayout? + private var visibleMediaItems: [UInt32: VisualMediaItemNode] = [:] + private var initialized = false + + private var decelerationAnimator: ConstantDisplayLinkAnimator? + + private var animationTimer: SwiftSignalKit.Timer? + + public var beganInteractiveDragging: (() -> Void)? + public var loadMore: (() -> Void)? + + init(context: AccountContext, contentType: ContentType, openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Void, messageContextAction: @escaping (Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void, toggleMessageSelection: @escaping (MessageId, Bool) -> Void) { + self.context = context + self.contentType = contentType + + self.scrollNode = ASScrollNode() + self.floatingHeaderNode = FloatingHeaderNode() + self.floatingHeaderNode.alpha = 0.0 + + super.init() + + self._itemInteraction = VisualMediaItemInteraction( + openMessage: { message in + let _ = openMessage(message, .default) + }, + openMessageContextActions: { message, sourceNode, sourceRect, gesture in + messageContextAction(message, sourceNode, sourceRect, gesture) + }, + toggleSelection: { id, value in + toggleMessageSelection(id, value) + } + ) + + self.scrollNode.view.delaysContentTouches = false + self.scrollNode.view.canCancelContentTouches = true + self.scrollNode.view.showsVerticalScrollIndicator = true + if #available(iOS 11.0, *) { + self.scrollNode.view.contentInsetAdjustmentBehavior = .never + } + self.scrollNode.view.scrollsToTop = false + self.scrollNode.view.delegate = self + + self.addSubnode(self.scrollNode) + self.addSubnode(self.floatingHeaderNode) + + self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().start(next: { [weak self] ids in + guard let strongSelf = self else { + return + } + var hiddenMedia: [MessageId: [Media]] = [:] + for id in ids { + if case let .chat(accountId, messageId, media) = id, accountId == strongSelf.context.account.id { + hiddenMedia[messageId] = [media] + } + } + strongSelf.itemInteraction.hiddenMedia = hiddenMedia + for (_, itemNode) in strongSelf.visibleMediaItems { + itemNode.updateHiddenMedia() + } + }) + } + + deinit { + self.listDisposable.dispose() + self.hiddenMediaDisposable?.dispose() + self.animationTimer?.invalidate() + } + + + func updateHistory(entries: [ChatListSearchEntry]?, totalCount: Int32, updateType: ViewUpdateType) { + switch updateType { + case .FillHole: + break + default: + self.mediaItems.removeAll() + let loading: Bool + if let entries = entries { + loading = false + for entry in entries { + if case let .message(message, _, _, _, _, _, _) = entry { + self.mediaItems.append(VisualMediaItem(message: message)) + } + } + } else { + loading = true + for i in 0 ..< 21 { + self.mediaItems.append(VisualMediaItem(index: UInt32(i))) + } + } + self.itemsLayout = nil + + let wasInitialized = self.initialized + self.initialized = true + + if let (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams { + self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: true, transition: .immediate) + if !self.didSetReady { + self.didSetReady = true + self.ready.set(.single(true)) + } + } + } + } + + func scrollToTop() -> Bool { + if self.scrollNode.view.contentOffset.y > 0.0 { + self.scrollNode.view.setContentOffset(CGPoint(), animated: true) + return true + } else { + return false + } + } + + func findLoadedMessage(id: MessageId) -> Message? { + for item in self.mediaItems { + if item.message?.id == id { + return item.message + } + } + return nil + } + + func updateHiddenMedia() { + for (_, itemNode) in self.visibleMediaItems { + itemNode.updateHiddenMedia() + } + } + + func cancelPreviewGestures() { + for (_, itemNode) in self.visibleMediaItems { + itemNode.cancelPreviewGesture() + } + } + + func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + for item in self.mediaItems { + if let message = item.message, message.id == messageId { + if let itemNode = self.visibleMediaItems[message.stableId] { + return itemNode.transitionNode() + } + break + } + } + return nil + } + + func addToTransitionSurface(view: UIView) { + self.scrollNode.view.addSubview(view) + } + + var selectedMessageIds: Set? { + didSet { + self.itemInteraction.selectedMessageIds = self.selectedMessageIds + } + } + + func updateSelectedMessages(animated: Bool) { + for (_, itemNode) in self.visibleMediaItems { + itemNode.updateSelectionState(animated: animated) + } + } + + func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + self.currentParams = (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) + + transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) + + let availableWidth = size.width - sideInset * 2.0 + + let itemsLayout: ItemsLayout + if let current = self.itemsLayout { + itemsLayout = current + } else { + switch self.contentType { + case .photoOrVideo, .gifs: + itemsLayout = .grid(ItemsLayout.Grid(containerWidth: availableWidth, itemCount: self.mediaItems.count, bottomInset: bottomInset)) + /*case .gifs: + itemsLayout = .balanced(ItemsLayout.Balanced(containerWidth: availableWidth, items: self.mediaItems, bottomInset: bottomInset))*/ + } + self.itemsLayout = itemsLayout + } + + self.scrollNode.view.contentSize = CGSize(width: size.width, height: itemsLayout.contentHeight) + self.updateVisibleItems(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, theme: presentationData.theme, strings: presentationData.strings, synchronousLoad: synchronous) + + if isScrollingLockedAtTop { + if self.scrollNode.view.contentOffset.y > .ulpOfOne { + transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size)) + } + } + self.scrollNode.view.isScrollEnabled = !isScrollingLockedAtTop + } + + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + self.decelerationAnimator?.isPaused = true + self.decelerationAnimator = nil + + for (_, itemNode) in self.visibleMediaItems { + itemNode.cancelPreviewGesture() + } + + self.updateHeaderFlashing(animated: true) + + self.beganInteractiveDragging?() + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if let (size, sideInset, bottomInset, visibleHeight, _, _, presentationData) = self.currentParams { + self.updateVisibleItems(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, theme: presentationData.theme, strings: presentationData.strings, synchronousLoad: false) + + if scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.bounds.height * 2.0 { + self.loadMore?() + } + } + } + + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + if decelerate { + self.isDeceleratingAfterTracking = true + self.updateHeaderFlashing(animated: true) + } else { + self.isDeceleratingAfterTracking = false + self.resetHeaderFlashTimer(start: true) + self.updateHeaderFlashing(animated: true) + } + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + self.isDeceleratingAfterTracking = false + self.resetHeaderFlashTimer(start: true) + self.updateHeaderFlashing(animated: true) + } + + private func updateVisibleItems(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, theme: PresentationTheme, strings: PresentationStrings, synchronousLoad: Bool) { + guard let itemsLayout = self.itemsLayout else { + return + } + + let headerItemMinY = self.scrollNode.view.bounds.minY + 20.0 + let activeRect = self.scrollNode.view.bounds + let visibleRect = activeRect.insetBy(dx: 0.0, dy: -400.0) + + let (minVisibleIndex, maxVisibleIndex) = itemsLayout.visibleRange(rect: visibleRect) + + var headerItem: Message? + + var validIds = Set() + if minVisibleIndex <= maxVisibleIndex { + for i in minVisibleIndex ... maxVisibleIndex { + let stableId = self.mediaItems[i].stableId + validIds.insert(stableId) + + let itemFrame = itemsLayout.frame(forItemAt: i, sideInset: sideInset) + + let itemNode: VisualMediaItemNode + if let current = self.visibleMediaItems[stableId] { + itemNode = current + } else { + itemNode = VisualMediaItemNode(context: self.context, interaction: self.itemInteraction) + self.visibleMediaItems[stableId] = itemNode + self.scrollNode.addSubnode(itemNode) + } + itemNode.frame = itemFrame + itemNode.updateAbsoluteRect(itemFrame, within: self.scrollNode.view.bounds.size) + if headerItem == nil && itemFrame.maxY > headerItemMinY { + headerItem = self.mediaItems[i].message + } + var itemSynchronousLoad = false + if itemFrame.maxY <= visibleHeight { + itemSynchronousLoad = synchronousLoad + } + itemNode.update(size: itemFrame.size, item: self.mediaItems[i], theme: theme, synchronousLoad: itemSynchronousLoad) + itemNode.updateIsVisible(itemFrame.intersects(activeRect)) + } + } + var removeKeys: [UInt32] = [] + for (id, _) in self.visibleMediaItems { + if !validIds.contains(id) { + removeKeys.append(id) + } + } + for id in removeKeys { + if let itemNode = self.visibleMediaItems.removeValue(forKey: id) { + itemNode.removeFromSupernode() + } + } + + if let headerItem = headerItem { + let (year, month) = listMessageDateHeaderInfo(timestamp: headerItem.timestamp) + let headerSize = self.floatingHeaderNode.update(constrainedWidth: size.width, year: year, month: month, theme: theme, strings: strings) + self.floatingHeaderNode.frame = CGRect(origin: CGPoint(x: floor((size.width - headerSize.width) / 2.0), y: 7.0), size: headerSize) + self.floatingHeaderNode.isHidden = false + } else { + self.floatingHeaderNode.isHidden = true + } + } + + private func resetHeaderFlashTimer(start: Bool, duration: Double = 0.3) { + if let flashHeaderDelayTimer = self.flashHeaderDelayTimer { + flashHeaderDelayTimer.invalidate() + self.flashHeaderDelayTimer = nil + } + + if start { + final class TimerProxy: NSObject { + private let action: () -> () + + init(_ action: @escaping () -> ()) { + self.action = action + super.init() + } + + @objc func timerEvent() { + self.action() + } + } + + let timer = Timer(timeInterval: duration, target: TimerProxy { [weak self] in + if let strongSelf = self { + if let flashHeaderDelayTimer = strongSelf.flashHeaderDelayTimer { + flashHeaderDelayTimer.invalidate() + strongSelf.flashHeaderDelayTimer = nil + strongSelf.updateHeaderFlashing(animated: true) + } + } + }, selector: #selector(TimerProxy.timerEvent), userInfo: nil, repeats: false) + self.flashHeaderDelayTimer = timer + RunLoop.main.add(timer, forMode: RunLoop.Mode.common) + self.updateHeaderFlashing(animated: true) + } + } + + private func headerIsFlashing() -> Bool { + return self.scrollNode.view.isDragging || self.isDeceleratingAfterTracking || self.flashHeaderDelayTimer != nil + } + + private func updateHeaderFlashing(animated: Bool) { + let flashing = self.headerIsFlashing() + let alpha: CGFloat = flashing ? 1.0 : 0.0 + let previousAlpha = self.floatingHeaderNode.alpha + + if !previousAlpha.isEqual(to: alpha) { + self.floatingHeaderNode.alpha = alpha + if animated { + let duration: Double = flashing ? 0.3 : 0.4 + self.floatingHeaderNode.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration) + } + } + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + guard let result = super.hitTest(point, with: event) else { + return nil + } + if self.decelerationAnimator != nil { + self.decelerationAnimator?.isPaused = true + self.decelerationAnimator = nil + + return self.scrollNode.view + } + return result + } +} + +private func calculateItemFrames(items: [VisualMediaItem], containerWidth: CGFloat) -> [CGRect] { + var frames: [CGRect] = [] + + var rowsCount = 0 + var firstRowMax = 0; + + let viewPortAvailableSize = containerWidth + + let preferredRowSize: CGFloat = 100.0 + let itemsCount = items.count + let spanCount: CGFloat = 100.0 + var spanLeft = spanCount + var currentItemsInRow = 0 + var currentItemsSpanAmount: CGFloat = 0.0 + + var itemSpans: [Int: CGFloat] = [:] + var itemsToRow: [Int: Int] = [:] + + for a in 0 ..< itemsCount { + var size: CGSize = items[a].dimensions + if size.width <= 0.0 { + size.width = 100.0 + } + if size.height <= 0.0 { + size.height = 100.0 + } + let aspect: CGFloat = size.width / size.height + if aspect > 4.0 || aspect < 0.2 { + size.width = max(size.width, size.height) + size.height = size.width + } + + var requiredSpan = min(spanCount, floor(spanCount * (size.width / size.height * preferredRowSize / viewPortAvailableSize))) + let moveToNewRow = spanLeft < requiredSpan || requiredSpan > 33.0 && spanLeft < requiredSpan - 15.0 + if moveToNewRow { + if spanLeft > 0 { + let spanPerItem = floor(spanLeft / CGFloat(currentItemsInRow)) + + let start = a - currentItemsInRow + var b = start + while b < start + currentItemsInRow { + if (b == start + currentItemsInRow - 1) { + itemSpans[b] = itemSpans[b]! + spanLeft + } else { + itemSpans[b] = itemSpans[b]! + spanPerItem + } + spanLeft -= spanPerItem; + + b += 1 + } + + itemsToRow[a - 1] = rowsCount + } + rowsCount += 1 + currentItemsSpanAmount = 0 + currentItemsInRow = 0 + spanLeft = spanCount + } else { + if spanLeft < requiredSpan { + requiredSpan = spanLeft + } + } + if rowsCount == 0 { + firstRowMax = max(firstRowMax, a) + } + if a == itemsCount - 1 { + itemsToRow[a] = rowsCount + } + currentItemsSpanAmount += requiredSpan + currentItemsInRow += 1 + spanLeft -= requiredSpan + spanLeft = max(0, spanLeft) + + itemSpans[a] = requiredSpan + } + if itemsCount != 0 { + rowsCount += 1 + } + + var verticalOffset: CGFloat = 1.0 + + var currentRowHorizontalOffset: CGFloat = 0.0 + for index in 0 ..< items.count { + guard let width = itemSpans[index] else { + continue + } + let itemWidth = floor(width * containerWidth / 100.0) - 1 + + var itemSize = CGSize(width: itemWidth, height: preferredRowSize) + if itemsToRow[index] != nil && currentRowHorizontalOffset + itemSize.width >= containerWidth - 10.0 { + itemSize.width = max(itemSize.width, containerWidth - currentRowHorizontalOffset) + } + frames.append(CGRect(origin: CGPoint(x: currentRowHorizontalOffset, y: verticalOffset), size: itemSize)) + currentRowHorizontalOffset += itemSize.width + 1.0 + + if itemsToRow[index] != nil { + verticalOffset += preferredRowSize + 1.0 + currentRowHorizontalOffset = 0.0 + } + } + + return frames +} diff --git a/submodules/ChatListUI/Sources/ChatListSearchMessageSelectionPanelNode.swift b/submodules/ChatListUI/Sources/ChatListSearchMessageSelectionPanelNode.swift new file mode 100644 index 0000000000..09ae186c2a --- /dev/null +++ b/submodules/ChatListUI/Sources/ChatListSearchMessageSelectionPanelNode.swift @@ -0,0 +1,182 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit +import TelegramPresentationData +import AccountContext +import AppBundle + +final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode { + private let context: AccountContext + private var theme: PresentationTheme + + private let deleteMessages: () -> Void + private let shareMessages: () -> Void + private let forwardMessages: () -> Void + + private let separatorNode: ASDisplayNode + private let backgroundNode: ASDisplayNode + private let deleteButton: HighlightableButtonNode + private let forwardButton: HighlightableButtonNode + private let shareButton: HighlightableButtonNode + + private var actions: ChatAvailableMessageActions? + + private let canDeleteMessagesDisposable = MetaDisposable() + + private var validLayout: ContainerViewLayout? + + var selectedMessages = Set() { + didSet { + if oldValue != self.selectedMessages { + self.forwardButton.isEnabled = self.selectedMessages.count != 0 + + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + if self.selectedMessages.isEmpty { + self.actions = nil + if let layout = self.validLayout { + let _ = self.update(layout: layout, presentationData: presentationData, transition: .immediate) + } + self.canDeleteMessagesDisposable.set(nil) + } else { + self.canDeleteMessagesDisposable.set((self.context.sharedContext.chatAvailableMessageActions(postbox: context.account.postbox, accountPeerId: context.account.peerId, messageIds: self.selectedMessages) + |> deliverOnMainQueue).start(next: { [weak self] actions in + if let strongSelf = self { + strongSelf.actions = actions + if let layout = strongSelf.validLayout { + let _ = strongSelf.update(layout: layout, presentationData: presentationData, transition: .immediate) + } + } + })) + } + } + } + } + + init(context: AccountContext, deleteMessages: @escaping () -> Void, shareMessages: @escaping () -> Void, forwardMessages: @escaping () -> Void) { + self.context = context + self.deleteMessages = deleteMessages + self.shareMessages = shareMessages + self.forwardMessages = forwardMessages + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.theme = presentationData.theme + + self.separatorNode = ASDisplayNode() + self.separatorNode.backgroundColor = presentationData.theme.chat.inputPanel.panelSeparatorColor + + self.backgroundNode = ASDisplayNode() + self.backgroundNode.backgroundColor = presentationData.theme.chat.inputPanel.panelBackgroundColor + + self.deleteButton = HighlightableButtonNode(pointerStyle: .default) + self.deleteButton.isEnabled = false + self.deleteButton.isAccessibilityElement = true + self.deleteButton.accessibilityLabel = presentationData.strings.VoiceOver_MessageContextDelete + + self.forwardButton = HighlightableButtonNode(pointerStyle: .default) + self.forwardButton.isAccessibilityElement = true + self.forwardButton.accessibilityLabel = presentationData.strings.VoiceOver_MessageContextForward + + self.shareButton = HighlightableButtonNode(pointerStyle: .default) + self.shareButton.isEnabled = false + self.shareButton.isAccessibilityElement = true + self.shareButton.accessibilityLabel = presentationData.strings.VoiceOver_MessageContextShare + + self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) + self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) + self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) + self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) + self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) + self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) + + super.init() + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.deleteButton) + self.addSubnode(self.forwardButton) + self.addSubnode(self.shareButton) + self.addSubnode(self.separatorNode) + + self.forwardButton.isEnabled = false + + self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), forControlEvents: .touchUpInside) + self.forwardButton.addTarget(self, action: #selector(self.forwardButtonPressed), forControlEvents: .touchUpInside) + self.shareButton.addTarget(self, action: #selector(self.shareButtonPressed), forControlEvents: .touchUpInside) + } + + deinit { + self.canDeleteMessagesDisposable.dispose() + } + + func update(layout: ContainerViewLayout, presentationData: PresentationData, transition: ContainedViewLayoutTransition) -> CGFloat { + if presentationData.theme !== self.theme { + self.theme = presentationData.theme + + self.backgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor + self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor + + self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) + self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) + self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) + self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) + self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) + self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) + } + + let width = layout.size.width + let insets = layout.insets(options: []) + let leftInset = insets.left + let rightInset = insets.left + + let panelHeight: CGFloat + if case .regular = layout.metrics.widthClass, case .regular = layout.metrics.heightClass { + panelHeight = 49.0 + } else { + panelHeight = 45.0 + } + + if let actions = self.actions { + self.deleteButton.isEnabled = false + self.forwardButton.isEnabled = actions.options.contains(.forward) + self.shareButton.isEnabled = false + + self.deleteButton.isEnabled = !actions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty + self.shareButton.isEnabled = !actions.options.intersection([.forward]).isEmpty + + self.deleteButton.isHidden = !self.deleteButton.isEnabled + } else { + self.deleteButton.isEnabled = false + self.deleteButton.isHidden = true + self.forwardButton.isEnabled = false + self.shareButton.isEnabled = false + } + + self.deleteButton.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 57.0, height: panelHeight)) + self.forwardButton.frame = CGRect(origin: CGPoint(x: width - rightInset - 57.0, y: 0.0), size: CGSize(width: 57.0, height: panelHeight)) + self.shareButton.frame = CGRect(origin: CGPoint(x: floor((width - rightInset - 57.0) / 2.0), y: 0.0), size: CGSize(width: 57.0, height: panelHeight)) + + let panelHeightWithInset = panelHeight + layout.intrinsicInsets.bottom - 49.0 + + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: panelHeightWithInset))) + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) + + return panelHeightWithInset + } + + @objc func deleteButtonPressed() { + self.deleteMessages() + } + + @objc func forwardButtonPressed() { + self.forwardMessages() + } + + @objc func shareButtonPressed() { + self.shareMessages() + } +} diff --git a/submodules/ChatListUI/Sources/DateSuggestion.swift b/submodules/ChatListUI/Sources/DateSuggestion.swift new file mode 100644 index 0000000000..79b709a0ef --- /dev/null +++ b/submodules/ChatListUI/Sources/DateSuggestion.swift @@ -0,0 +1,144 @@ +import Foundation +import TelegramPresentationData + +private let telegramReleaseDate = Date(timeIntervalSince1970: 1376438400.0) + +func suggestDates(for string: String, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) -> [(Date, String?)] { + let string = string.folding(options: .diacriticInsensitive, locale: .current).trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + if string.count < 3 { + return [] + } + + let months: [Int: (String, String)] = [ + 1: (strings.Month_GenJanuary, strings.Month_ShortJanuary), + 2: (strings.Month_GenFebruary, strings.Month_ShortFebruary), + 3: (strings.Month_GenMarch, strings.Month_ShortMarch), + 4: (strings.Month_GenApril, strings.Month_ShortApril), + 5: (strings.Month_GenMay, strings.Month_ShortMay), + 6: (strings.Month_GenJune, strings.Month_ShortJune), + 7: (strings.Month_GenJuly, strings.Month_ShortJuly), + 8: (strings.Month_GenAugust, strings.Month_ShortAugust), + 9: (strings.Month_GenSeptember, strings.Month_ShortSeptember), + 10: (strings.Month_GenOctober, strings.Month_ShortOctober), + 11: (strings.Month_GenNovember, strings.Month_ShortNovember), + 12: (strings.Month_GenDecember, strings.Month_ShortDecember) + ] + + let weekDays: [Int: (String, String)] = [ + 1: (strings.Weekday_Monday, strings.Weekday_ShortMonday), + 2: (strings.Weekday_Tuesday, strings.Weekday_ShortTuesday), + 3: (strings.Weekday_Wednesday, strings.Weekday_ShortWednesday), + 4: (strings.Weekday_Thursday, strings.Weekday_ShortThursday), + 5: (strings.Weekday_Friday, strings.Weekday_ShortFriday), + 6: (strings.Weekday_Saturday, strings.Weekday_ShortSaturday), + 7: (strings.Weekday_Sunday, strings.Weekday_ShortSunday.lowercased()), + ] + + let today = strings.Weekday_Today + let yesterday = strings.Weekday_Yesterday + let dateSeparator = dateTimeFormat.dateSeparator + + var result: [(Date, String?)] = [] + + let calendar = Calendar.current + func getUpperDate(for date: Date) -> Date { + let components = calendar.dateComponents(in: .current, from: date) + let upperComponents = DateComponents(year: components.year, month: components.month, day: components.day, hour: 23, minute: 59, second: 59) + return calendar.date(from: upperComponents)! + } + + let now = Date() + let nowComponents = calendar.dateComponents(in: .current, from: now) + guard let year = nowComponents.year else { + return [] + } + + let midnight = calendar.startOfDay(for: now) + if today.lowercased().hasPrefix(string) { + let todayDate = getUpperDate(for: midnight) + result.append((todayDate, today)) + } + if yesterday.lowercased().hasPrefix(string) { + let yesterdayMidnight = calendar.date(byAdding: .day, value: -1, to: midnight)! + let yesterdayDate = getUpperDate(for: yesterdayMidnight) + result.append((yesterdayDate, yesterday)) + } + + func getUpperMonthDate(month: Int, year: Int) -> Date { + let monthComponents = DateComponents(year: year, month: month) + let date = calendar.date(from: monthComponents)! + let range = calendar.range(of: .day, in: .month, for: date)! + let numDays = range.count + let upperComponents = DateComponents(year: year, month: month, day: numDays, hour: 23, minute: 59, second: 59) + return calendar.date(from: upperComponents)! + } + + let decimalRange = string.rangeOfCharacter(from: .decimalDigits) + if decimalRange != nil { + if string.count == 4, let value = Int(string), value <= year { + let date = getUpperMonthDate(month: 12, year: value) + if date > telegramReleaseDate { + result.append((date, "\(value)")) + } + } else if !dateSeparator.isEmpty && string.contains(dateSeparator) { + let stringComponents = string.components(separatedBy: dateSeparator) + if stringComponents.count > 1 { + let locale = Locale(identifier: strings.baseLanguageCode) + do { + let dd = try NSDataDetector(types: NSTextCheckingResult.CheckingType.date.rawValue) + if let match = dd.firstMatch(in: string, options: [], range: NSMakeRange(0, string.utf16.count)), let date = match.date, date > telegramReleaseDate { + var resultDate = date + if resultDate > now { + if let date = calendar.date(byAdding: .year, value: -1, to: resultDate) { + resultDate = date + } + } + + for i in 0..<5 { + if let date = calendar.date(byAdding: .year, value: -i, to: resultDate) { + result.append((date, nil)) + } + } + } + } catch { + + } + } + } + } else { + for (day, value) in weekDays { + let dayName = value.0.lowercased() + let shortDayName = value.1.lowercased() + if string == shortDayName || (string.count >= shortDayName.count && dayName.hasPrefix(string)) { + var nextDateComponent = calendar.dateComponents([.hour, .minute, .second], from: now) + nextDateComponent.weekday = day + calendar.firstWeekday + if let date = calendar.nextDate(after: now, matching: nextDateComponent, matchingPolicy: .nextTime, direction: .backward) { + let upperDate = getUpperDate(for: date) + for i in 0..<5 { + if let date = calendar.date(byAdding: .hour, value: -24 * 7 * i, to: upperDate) { + if calendar.isDate(date, equalTo: now, toGranularity: .weekOfYear) { + result.append((date, value.0)) + } else { + result.append((date, nil)) + } + } + } + } + } + } + for (month, value) in months { + let monthName = value.0.lowercased() + let shortMonthName = value.1.lowercased() + if string == shortMonthName || (string.count >= shortMonthName.count && monthName.hasPrefix(string)) { + for i in (year - 7 ... year).reversed() { + let date = getUpperMonthDate(month: month, year: i) + if date <= now && date > telegramReleaseDate { + result.append((date, nil)) + } + } + } + } + } + + return result +} diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index f8f9c90175..399a096eb7 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -637,7 +637,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let peer = peer { var overrideImage: AvatarNodeImageOverride? - if peer.id == item.context.account.peerId && !displayAsMessage { + if peer.id.isReplies { + overrideImage = .repliesIcon + } else if peer.id == item.context.account.peerId && !displayAsMessage { overrideImage = .savedMessagesIcon } else if peer.isDeleted { overrideImage = .deletedIcon @@ -1099,7 +1101,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { titleAttributedString = NSAttributedString(string: item.presentationData.strings.ChatList_ArchivedChatsTitle, font: titleFont, textColor: theme.titleColor) } else if itemPeer.chatMainPeer?.id == item.context.account.peerId { titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_SavedMessages, font: titleFont, textColor: theme.titleColor) - } else if let displayTitle = itemPeer.chatMainPeer?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) { + } else if let id = itemPeer.chatMainPeer?.id, id.isReplies { + titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Replies, font: titleFont, textColor: theme.titleColor) + } else if let displayTitle = itemPeer.chatMainPeer?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) { titleAttributedString = NSAttributedString(string: displayTitle, font: titleFont, textColor: item.index.messageIndex.id.peerId.namespace == Namespaces.Peer.SecretChat ? theme.secretTitleColor : theme.titleColor) } case .group: diff --git a/submodules/ChatMessageInteractiveMediaBadge/BUCK b/submodules/ChatMessageInteractiveMediaBadge/BUCK new file mode 100644 index 0000000000..37339b5f96 --- /dev/null +++ b/submodules/ChatMessageInteractiveMediaBadge/BUCK @@ -0,0 +1,20 @@ +load("//Config:buck_rule_macros.bzl", "static_library") + +static_library( + name = "ChatMessageInteractiveMediaBadge", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", + "//submodules/Display:Display#shared", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/TextFormat:TextFormat", + "//submodules/RadialStatusNode:RadialStatusNode", + "//submodules/AppBundle:AppBundle", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + ], +) diff --git a/submodules/ChatMessageInteractiveMediaBadge/BUILD b/submodules/ChatMessageInteractiveMediaBadge/BUILD new file mode 100644 index 0000000000..bb8f11a6d3 --- /dev/null +++ b/submodules/ChatMessageInteractiveMediaBadge/BUILD @@ -0,0 +1,20 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageInteractiveMediaBadge", + module_name = "ChatMessageInteractiveMediaBadge", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/TextFormat:TextFormat", + "//submodules/RadialStatusNode:RadialStatusNode", + "//submodules/AppBundle:AppBundle", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaBadge.swift b/submodules/ChatMessageInteractiveMediaBadge/Sources/ChatMessageInteractiveMediaBadge.swift similarity index 95% rename from submodules/TelegramUI/Sources/ChatMessageInteractiveMediaBadge.swift rename to submodules/ChatMessageInteractiveMediaBadge/Sources/ChatMessageInteractiveMediaBadge.swift index 09ff15c32c..1c8fd02e91 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaBadge.swift +++ b/submodules/ChatMessageInteractiveMediaBadge/Sources/ChatMessageInteractiveMediaBadge.swift @@ -7,18 +7,21 @@ import TextFormat import RadialStatusNode import AppBundle -enum ChatMessageInteractiveMediaDownloadState: Equatable { +private let font = Font.regular(11.0) +private let boldFont = Font.semibold(11.0) + +public enum ChatMessageInteractiveMediaDownloadState: Equatable { case remote case fetching(progress: Float?) case compactRemote case compactFetching(progress: Float) } -enum ChatMessageInteractiveMediaBadgeContent: Equatable { +public enum ChatMessageInteractiveMediaBadgeContent: Equatable { case text(inset: CGFloat, backgroundColor: UIColor, foregroundColor: UIColor, text: NSAttributedString) case mediaDownload(backgroundColor: UIColor, foregroundColor: UIColor, duration: String, size: String?, muted: Bool, active: Bool) - static func ==(lhs: ChatMessageInteractiveMediaBadgeContent, rhs: ChatMessageInteractiveMediaBadgeContent) -> Bool { + public static func ==(lhs: ChatMessageInteractiveMediaBadgeContent, rhs: ChatMessageInteractiveMediaBadgeContent) -> Bool { switch lhs { case let .text(lhsInset, lhsBackgroundColor, lhsForegroundColor, lhsText): if case let .text(rhsInset, rhsBackgroundColor, rhsForegroundColor, rhsText) = rhs, lhsInset.isEqual(to: rhsInset), lhsBackgroundColor.isEqual(rhsBackgroundColor), lhsForegroundColor.isEqual(rhsForegroundColor), lhsText.isEqual(to: rhsText) { @@ -36,12 +39,9 @@ enum ChatMessageInteractiveMediaBadgeContent: Equatable { } } -private let font = Font.regular(11.0) -private let boldFont = Font.semibold(11.0) - -final class ChatMessageInteractiveMediaBadge: ASDisplayNode { +public final class ChatMessageInteractiveMediaBadge: ASDisplayNode { private var content: ChatMessageInteractiveMediaBadgeContent? - var pressed: (() -> Void)? + public var pressed: (() -> Void)? private var mediaDownloadState: ChatMessageInteractiveMediaDownloadState? @@ -56,7 +56,7 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode { private var iconNode: ASImageNode? private var mediaDownloadStatusNode: RadialStatusNode? - override init() { + override public init() { self.backgroundNode = ASImageNode() self.backgroundNode.clipsToBounds = true self.durationNode = ASTextNode() @@ -68,7 +68,7 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode { self.backgroundNode.addSubnode(self.durationNode) } - override func didLoad() { + override public func didLoad() { super.didLoad() self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) @@ -87,7 +87,7 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode { return self.measureNode.measure(CGSize(width: 240.0, height: 160.0)).width } - func update(theme: PresentationTheme?, content: ChatMessageInteractiveMediaBadgeContent?, mediaDownloadState: ChatMessageInteractiveMediaDownloadState?, alignment: NSTextAlignment = .left, animated: Bool, badgeAnimated: Bool = true) { + public func update(theme: PresentationTheme?, content: ChatMessageInteractiveMediaBadgeContent?, mediaDownloadState: ChatMessageInteractiveMediaDownloadState?, alignment: NSTextAlignment = .left, animated: Bool, badgeAnimated: Bool = true) { var transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate let previousContentSize = self.previousContentSize @@ -297,7 +297,7 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode { } } - override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + override public func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return self.backgroundNode.frame.contains(point) } } diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index 8623c54638..8f9dc6203b 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -140,6 +140,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { let deletePeer: ((PeerId) -> Void)? let itemHighlighting: ContactItemHighlighting? let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? + let arrowAction: (() -> Void)? public let selectable: Bool @@ -147,7 +148,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { public let header: ListViewItemHeader? - public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], additionalActions: [ContactsPeerItemAction] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) { + public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], additionalActions: [ContactsPeerItemAction] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, arrowAction: (() -> Void)? = nil) { self.presentationData = presentationData self.style = style self.sectionId = sectionId @@ -172,6 +173,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { self.itemHighlighting = itemHighlighting self.selectable = enabled || disabledAction != nil self.contextAction = contextAction + self.arrowAction = arrowAction if let index = index { var letter: String = "#" @@ -318,6 +320,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { private var badgeTextNode: TextNode? private var selectionNode: CheckNode? private var actionButtonNodes: [HighlightableButtonNode]? + private var arrowButtonNode: HighlightableButtonNode? private var isHighlighted: Bool = false @@ -503,6 +506,11 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { break } + var arrowButtonImage: UIImage? + if let _ = item.arrowAction { + arrowButtonImage = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Arrow"), color: item.presentationData.theme.list.disclosureArrowColor) + } + var actionButtons: [ActionButton]? struct ActionButton { let image: UIImage? @@ -548,6 +556,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { if let user = peer as? TelegramUser { if peer.id == item.context.account.peerId, case .generalSearch = item.peerMode { titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_SavedMessages, font: titleBoldFont, textColor: textColor) + } else if peer.id.isReplies { + titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Replies, font: titleBoldFont, textColor: textColor) } else if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty { let string = NSMutableAttributedString() switch item.displayOrder { @@ -665,6 +675,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { additionalTitleInset += badgeSize + if let arrowButtonImage = arrowButtonImage { + additionalTitleInset += arrowButtonImage.size.width + 4.0 + } + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset - additionalTitleInset), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: multilineStatus ? 3 : 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset - badgeSize), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -735,6 +749,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { var overrideImage: AvatarNodeImageOverride? if peer.id == item.context.account.peerId, case .generalSearch = item.peerMode { overrideImage = .savedMessagesIcon + } else if peer.id.isReplies, case .generalSearch = item.peerMode { + overrideImage = .repliesIcon } else if peer.isDeleted { overrideImage = .deletedIcon } @@ -856,6 +872,23 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { actionButtonNodes.forEach { $0.removeFromSupernode() } } + if let arrowButtonImage = arrowButtonImage { + if strongSelf.arrowButtonNode == nil { + let arrowButtonNode = HighlightableButtonNode() + arrowButtonNode.addTarget(self, action: #selector(strongSelf.arrowButtonPressed), forControlEvents: .touchUpInside) + strongSelf.arrowButtonNode = arrowButtonNode + strongSelf.containerNode.addSubnode(arrowButtonNode) + } + if let arrowButtonNode = strongSelf.arrowButtonNode { + arrowButtonNode.setImage(arrowButtonImage, for: .normal) + + transition.updateFrame(node: arrowButtonNode, frame: CGRect(origin: CGPoint(x: params.width - params.rightInset - 12.0 - arrowButtonImage.size.width, y: floor((nodeLayout.contentSize.height - arrowButtonImage.size.height) / 2.0)), size: arrowButtonImage.size)) + } + } else if let arrowButtonNode = strongSelf.arrowButtonNode { + strongSelf.arrowButtonNode = nil + arrowButtonNode.removeFromSupernode() + } + let badgeBackgroundWidth: CGFloat if let currentBadgeBackgroundImage = currentBadgeBackgroundImage, let (badgeTextLayout, badgeTextApply) = badgeTextLayoutAndApply { let badgeBackgroundNode: ASImageNode @@ -876,7 +909,12 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { badgeBackgroundNode.image = currentBadgeBackgroundImage badgeBackgroundWidth = max(badgeTextLayout.size.width + 10.0, currentBadgeBackgroundImage.size.width) - let badgeBackgroundFrame = CGRect(x: revealOffset + params.width - params.rightInset - badgeBackgroundWidth - 6.0, y: floor((nodeLayout.contentSize.height - currentBadgeBackgroundImage.size.height) / 2.0), width: badgeBackgroundWidth, height: currentBadgeBackgroundImage.size.height) + var badgeBackgroundFrame = CGRect(x: revealOffset + params.width - params.rightInset - badgeBackgroundWidth - 6.0, y: floor((nodeLayout.contentSize.height - currentBadgeBackgroundImage.size.height) / 2.0), width: badgeBackgroundWidth, height: currentBadgeBackgroundImage.size.height) + + if let arrowButtonImage = arrowButtonImage { + badgeBackgroundFrame.origin.x -= arrowButtonImage.size.width + 6.0 + } + let badgeTextFrame = CGRect(origin: CGPoint(x: badgeBackgroundFrame.midX - badgeTextLayout.size.width / 2.0, y: badgeBackgroundFrame.minY + 2.0), size: badgeTextLayout.size) let badgeTextNode = badgeTextApply() @@ -1064,4 +1102,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { return nil } } + + @objc func arrowButtonPressed() { + if let (item, _, _, _, _, _) = self.layoutParams { + item.arrowAction?() + } + } } diff --git a/submodules/DeleteChatPeerActionSheetItem/Sources/DeleteChatPeerActionSheetItem.swift b/submodules/DeleteChatPeerActionSheetItem/Sources/DeleteChatPeerActionSheetItem.swift index 1e1d100d6f..afa68e40d7 100644 --- a/submodules/DeleteChatPeerActionSheetItem/Sources/DeleteChatPeerActionSheetItem.swift +++ b/submodules/DeleteChatPeerActionSheetItem/Sources/DeleteChatPeerActionSheetItem.swift @@ -77,6 +77,8 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode { if chatPeer.id == context.account.peerId { self.avatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .savedMessagesIcon) + } else if chatPeer.id.isReplies { + self.avatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .repliesIcon) } else { var overrideImage: AvatarNodeImageOverride? if chatPeer.isDeleted { diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 0398a1f050..58eb59e1c9 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -3,6 +3,17 @@ import AsyncDisplayKit private var backArrowImageCache: [Int32: UIImage] = [:] +class SparseNode: ASDisplayNode { + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + if result != self.view { + return result + } else { + return nil + } + } +} + public final class NavigationBarTheme { public static func generateBackArrowImage(color: UIColor) -> UIImage? { return generateImage(CGSize(width: 13.0, height: 22.0), rotatedContext: { size, context in @@ -126,7 +137,8 @@ open class NavigationBar: ASDisplayNode { } private let stripeNode: ASDisplayNode - private let clippingNode: ASDisplayNode + private let clippingNode: SparseNode + private let buttonsContainerNode: ASDisplayNode public private(set) var contentNode: NavigationBarContentNode? public private(set) var secondaryContentNode: ASDisplayNode? @@ -260,7 +272,7 @@ open class NavigationBar: ASDisplayNode { self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: self.presentationData.theme.primaryTextColor) self.titleNode.accessibilityLabel = title if self.titleNode.supernode == nil { - self.clippingNode.addSubnode(self.titleNode) + self.buttonsContainerNode.addSubnode(self.titleNode) } } else { self.titleNode.removeFromSupernode() @@ -279,7 +291,7 @@ open class NavigationBar: ASDisplayNode { } if let titleView = self.titleView { - self.clippingNode.view.addSubview(titleView) + self.buttonsContainerNode.view.addSubview(titleView) } self.invalidateCalculatedLayout() @@ -499,7 +511,7 @@ open class NavigationBar: ASDisplayNode { } if self.leftButtonNode.supernode == nil { - self.clippingNode.addSubnode(self.leftButtonNode) + self.buttonsContainerNode.addSubnode(self.leftButtonNode) } if animated { @@ -540,9 +552,9 @@ open class NavigationBar: ASDisplayNode { if let backTitle = backTitle { self.backButtonNode.updateManualText(backTitle) if self.backButtonNode.supernode == nil { - self.clippingNode.addSubnode(self.backButtonNode) - self.clippingNode.addSubnode(self.backButtonArrow) - self.clippingNode.addSubnode(self.badgeNode) + self.buttonsContainerNode.addSubnode(self.backButtonNode) + self.buttonsContainerNode.addSubnode(self.backButtonArrow) + self.buttonsContainerNode.addSubnode(self.badgeNode) } if animated { @@ -588,7 +600,7 @@ open class NavigationBar: ASDisplayNode { } self.rightButtonNode.updateItems(items) if self.rightButtonNode.supernode == nil { - self.clippingNode.addSubnode(self.rightButtonNode) + self.buttonsContainerNode.addSubnode(self.rightButtonNode) } if animated { self.rightButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) @@ -648,25 +660,25 @@ open class NavigationBar: ASDisplayNode { if let transitionTitleNode = value.navigationBar?.makeTransitionTitleNode(foregroundColor: self.presentationData.theme.primaryTextColor) { self.transitionTitleNode = transitionTitleNode if self.leftButtonNode.supernode != nil { - self.clippingNode.insertSubnode(transitionTitleNode, belowSubnode: self.leftButtonNode) + self.buttonsContainerNode.insertSubnode(transitionTitleNode, belowSubnode: self.leftButtonNode) } else if self.backButtonNode.supernode != nil { - self.clippingNode.insertSubnode(transitionTitleNode, belowSubnode: self.backButtonNode) + self.buttonsContainerNode.insertSubnode(transitionTitleNode, belowSubnode: self.backButtonNode) } else { - self.clippingNode.addSubnode(transitionTitleNode) + self.buttonsContainerNode.addSubnode(transitionTitleNode) } } case .bottom: if let transitionBackButtonNode = value.navigationBar?.makeTransitionBackButtonNode(accentColor: self.presentationData.theme.buttonColor) { self.transitionBackButtonNode = transitionBackButtonNode - self.clippingNode.addSubnode(transitionBackButtonNode) + self.buttonsContainerNode.addSubnode(transitionBackButtonNode) } if let transitionBackArrowNode = value.navigationBar?.makeTransitionBackArrowNode(accentColor: self.presentationData.theme.buttonColor) { self.transitionBackArrowNode = transitionBackArrowNode - self.clippingNode.addSubnode(transitionBackArrowNode) + self.buttonsContainerNode.addSubnode(transitionBackArrowNode) } if let transitionBadgeNode = value.navigationBar?.makeTransitionBadgeNode() { self.transitionBadgeNode = transitionBadgeNode - self.clippingNode.addSubnode(transitionBadgeNode) + self.buttonsContainerNode.addSubnode(transitionBadgeNode) } } } @@ -701,9 +713,12 @@ open class NavigationBar: ASDisplayNode { self.rightButtonNode = NavigationButtonNode() self.rightButtonNode.hitTestSlop = UIEdgeInsets(top: -4.0, left: -4.0, bottom: -4.0, right: -10.0) - self.clippingNode = ASDisplayNode() + self.clippingNode = SparseNode() self.clippingNode.clipsToBounds = true + self.buttonsContainerNode = ASDisplayNode() + self.buttonsContainerNode.clipsToBounds = true + self.backButtonNode.color = self.presentationData.theme.buttonColor self.backButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor self.leftButtonNode.color = self.presentationData.theme.buttonColor @@ -720,6 +735,7 @@ open class NavigationBar: ASDisplayNode { super.init() + self.addSubnode(self.buttonsContainerNode) self.addSubnode(self.clippingNode) self.backgroundColor = self.presentationData.theme.backgroundColor @@ -821,7 +837,7 @@ open class NavigationBar: ASDisplayNode { self.validLayout = (size, defaultHeight, additionalHeight, leftInset, rightInset, appearsHidden) if let secondaryContentNode = self.secondaryContentNode { - transition.updateAlpha(node: secondaryContentNode, alpha: appearsHidden ? 0.0 : 1.0) +// transition.updateAlpha(node: secondaryContentNode, alpha: appearsHidden ? 0.0 : 1.0) } let apparentAdditionalHeight: CGFloat = self.secondaryContentNode != nil ? NavigationBar.defaultSecondaryContentHeight : 0.0 @@ -830,6 +846,7 @@ open class NavigationBar: ASDisplayNode { let backButtonInset: CGFloat = leftInset + 27.0 transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: size)) + transition.updateFrame(node: self.buttonsContainerNode, frame: CGRect(origin: CGPoint(), size: size)) var expansionHeight: CGFloat = 0.0 if let contentNode = self.contentNode { var contentNodeFrame: CGRect @@ -839,7 +856,9 @@ open class NavigationBar: ASDisplayNode { contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)) case .expansion: expansionHeight = contentNode.height - contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - expansionHeight - apparentAdditionalHeight), size: CGSize(width: size.width, height: expansionHeight)) + + let additionalExpansionHeight: CGFloat = self.secondaryContentNode != nil && appearsHidden ? NavigationBar.defaultSecondaryContentHeight : 0.0 + contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - expansionHeight - apparentAdditionalHeight - additionalExpansionHeight), size: CGSize(width: size.width, height: expansionHeight)) if appearsHidden { if self.secondaryContentNode != nil { contentNodeFrame.origin.y += NavigationBar.defaultSecondaryContentHeight @@ -1174,10 +1193,10 @@ open class NavigationBar: ASDisplayNode { contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - if case .replacement = contentNode.mode, !self.clippingNode.alpha.isZero { - self.clippingNode.alpha = 0.0 + if case .replacement = contentNode.mode, !self.buttonsContainerNode.alpha.isZero { + self.buttonsContainerNode.alpha = 0.0 if animated { - self.clippingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + self.buttonsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) } } @@ -1187,23 +1206,36 @@ open class NavigationBar: ASDisplayNode { } else { self.requestLayout() } - } else if self.clippingNode.alpha.isZero { - self.clippingNode.alpha = 1.0 + } else if self.buttonsContainerNode.alpha.isZero { + self.buttonsContainerNode.alpha = 1.0 if animated { - self.clippingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.buttonsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } } } } - public func setSecondaryContentNode(_ secondatryContentNode: ASDisplayNode?) { - if self.secondaryContentNode !== secondatryContentNode { + public func setSecondaryContentNode(_ secondaryContentNode: ASDisplayNode?, animated: Bool = false) { + if self.secondaryContentNode !== secondaryContentNode { if let previous = self.secondaryContentNode { - previous.removeFromSupernode() + if animated && previous.supernode === self.clippingNode { + previous.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak previous] finished in + if finished { + previous?.removeFromSupernode() + previous?.layer.removeAllAnimations() + } + }) + } else { + previous.removeFromSupernode() + } } - self.secondaryContentNode = secondatryContentNode - if let secondaryContentNode = secondatryContentNode { + self.secondaryContentNode = secondaryContentNode + if let secondaryContentNode = secondaryContentNode { self.clippingNode.addSubnode(secondaryContentNode) + + if animated { + secondaryContentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + } } } } @@ -1223,11 +1255,11 @@ open class NavigationBar: ASDisplayNode { if let contentNode = self.contentNode, case .replacement = contentNode.mode { } else { let targetAlpha: CGFloat = hidden ? 0.0 : 1.0 - let previousAlpha = self.clippingNode.alpha + let previousAlpha = self.buttonsContainerNode.alpha if previousAlpha != targetAlpha { - self.clippingNode.alpha = targetAlpha + self.buttonsContainerNode.alpha = targetAlpha if animated { - self.clippingNode.layer.animateAlpha(from: previousAlpha, to: targetAlpha, duration: 0.2) + self.buttonsContainerNode.layer.animateAlpha(from: previousAlpha, to: targetAlpha, duration: 0.2) } } } @@ -1247,7 +1279,7 @@ open class NavigationBar: ASDisplayNode { return nil } - if result == self.view || result == self.clippingNode.view { + if result == self.view || result == self.buttonsContainerNode.view { return nil } diff --git a/submodules/Display/Source/TabBarController.swift b/submodules/Display/Source/TabBarController.swift index 313b2382e1..6adad68208 100644 --- a/submodules/Display/Source/TabBarController.swift +++ b/submodules/Display/Source/TabBarController.swift @@ -340,9 +340,9 @@ open class TabBarController: ViewController { } } - public func updateLayout() { + public func updateLayout(transition: ContainedViewLayoutTransition = .immediate) { if let layout = self.validLayout { - self.containerLayoutUpdated(layout, transition: .immediate) + self.containerLayoutUpdated(layout, transition: transition) } } diff --git a/submodules/Display/Source/ViewController.swift b/submodules/Display/Source/ViewController.swift index c3d7b1d01d..37c1a7a2a1 100644 --- a/submodules/Display/Source/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -400,6 +400,9 @@ public enum TabBarItemContextActionType { if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode, !self.displayNavigationBar { navigationBarFrame.origin.y += contentNode.height + statusBarHeight } + if let _ = navigationBar.contentNode, let _ = navigationBar.secondaryContentNode, !self.displayNavigationBar { + navigationBarFrame.origin.y += NavigationBar.defaultSecondaryContentHeight + } navigationBar.updateLayout(size: navigationBarFrame.size, defaultHeight: defaultNavigationBarHeight, additionalHeight: 0.0, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, appearsHidden: !self.displayNavigationBar, transition: transition) if !transition.isAnimated { navigationBar.layer.cancelAnimationsRecursive(key: "bounds") diff --git a/submodules/FileMediaResourceStatus/BUCK b/submodules/FileMediaResourceStatus/BUCK new file mode 100644 index 0000000000..1fa2f38081 --- /dev/null +++ b/submodules/FileMediaResourceStatus/BUCK @@ -0,0 +1,23 @@ +load("//Config:buck_rule_macros.bzl", "static_library") + +static_library( + name = "FileMediaResourceStatus", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", + "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", + "//submodules/Display:Display#shared", + "//submodules/Postbox:Postbox#shared", + "//submodules/TelegramCore:TelegramCore#shared", + "//submodules/SyncCore:SyncCore#shared", + "//submodules/AccountContext:AccountContext", + "//submodules/MediaPlayer:UniversalMediaPlayer", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + "$SDKROOT/System/Library/Frameworks/WebKit.framework", + ], +) diff --git a/submodules/FileMediaResourceStatus/BUILD b/submodules/FileMediaResourceStatus/BUILD new file mode 100644 index 0000000000..b58e90917c --- /dev/null +++ b/submodules/FileMediaResourceStatus/BUILD @@ -0,0 +1,22 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "FileMediaResourceStatus", + module_name = "FileMediaResourceStatus", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/AccountContext:AccountContext", + "//submodules/MediaPlayer:UniversalMediaPlayer", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/FileMediaResourceStatus.swift b/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift similarity index 80% rename from submodules/TelegramUI/Sources/FileMediaResourceStatus.swift rename to submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift index 66f8e5198d..30b7e823eb 100644 --- a/submodules/TelegramUI/Sources/FileMediaResourceStatus.swift +++ b/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift @@ -7,12 +7,12 @@ import SwiftSignalKit import UniversalMediaPlayer import AccountContext -private func internalMessageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool) -> Signal { +private func internalMessageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> Signal { guard let playerType = peerMessageMediaPlayerType(message) else { return .single(nil) } - if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions) { + if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) { return context.sharedContext.mediaManager.filteredPlaylistState(accountId: context.account.id, playlistId: playlistId, itemId: itemId, type: playerType) |> mapToSignal { state -> Signal in return .single(state?.status) @@ -22,31 +22,31 @@ private func internalMessageFileMediaPlaybackStatus(context: AccountContext, fil } } -func messageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool) -> Signal { +public func messageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> Signal { var duration = 0.0 if let value = file.duration { duration = Double(value) } let defaultStatus = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) - return internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions) |> map { status in + return internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) |> map { status in return status ?? defaultStatus } } -func messageFileMediaPlaybackAudioLevelEvents(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool) -> Signal { +public func messageFileMediaPlaybackAudioLevelEvents(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> Signal { guard let playerType = peerMessageMediaPlayerType(message) else { return .never() } - if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions) { + if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) { return context.sharedContext.mediaManager.filteredPlayerAudioLevelEvents(accountId: context.account.id, playlistId: playlistId, itemId: itemId, type: playerType) } else { return .never() } } -func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false) -> Signal { - let playbackStatus = internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions) |> map { status -> MediaPlayerPlaybackStatus? in +public func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false) -> Signal { + let playbackStatus = internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) |> map { status -> MediaPlayerPlaybackStatus? in return status?.status } diff --git a/submodules/GalleryData/BUCK b/submodules/GalleryData/BUCK new file mode 100644 index 0000000000..34c899e95f --- /dev/null +++ b/submodules/GalleryData/BUCK @@ -0,0 +1,31 @@ +load("//Config:buck_rule_macros.bzl", "static_library") + +static_library( + name = "GalleryData", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", + "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", + "//submodules/Display:Display#shared", + "//submodules/Postbox:Postbox#shared", + "//submodules/TelegramCore:TelegramCore#shared", + "//submodules/SyncCore:SyncCore#shared", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/AccountContext:AccountContext", + "//submodules/AppBundle:AppBundle", + "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/InstantPageUI:InstantPageUI", + "//submodules/GalleryUI:GalleryUI", + "//submodules/PeerAvatarGalleryUI:PeerAvatarGalleryUI", + "//submodules/MediaResources:MediaResources", + "//submodules/WebsiteType:WebsiteType", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + "$SDKROOT/System/Library/Frameworks/QuickLook.framework", + "$SDKROOT/System/Library/Frameworks/SafariServices.framework", + ], +) diff --git a/submodules/GalleryData/BUILD b/submodules/GalleryData/BUILD new file mode 100644 index 0000000000..100f76b985 --- /dev/null +++ b/submodules/GalleryData/BUILD @@ -0,0 +1,29 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GalleryData", + module_name = "GalleryData", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/AccountContext:AccountContext", + "//submodules/AppBundle:AppBundle", + "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/InstantPageUI:InstantPageUI", + "//submodules/GalleryUI:GalleryUI", + "//submodules/PeerAvatarGalleryUI:PeerAvatarGalleryUI", + "//submodules/MediaResources:MediaResources", + "//submodules/WebsiteType:WebsiteType", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/GalleryData/Sources/GalleryData.swift b/submodules/GalleryData/Sources/GalleryData.swift new file mode 100644 index 0000000000..a04e70fa18 --- /dev/null +++ b/submodules/GalleryData/Sources/GalleryData.swift @@ -0,0 +1,296 @@ +import Foundation +import Display +import AsyncDisplayKit +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit +import PassKit +import Lottie +import TelegramUIPreferences +import TelegramPresentationData +import AccountContext +import InstantPageUI +import PeerAvatarGalleryUI +import GalleryUI +import MediaResources +import WebsiteType + +public enum ChatMessageGalleryControllerData { + case url(String) + case pass(TelegramMediaFile) + case instantPage(InstantPageGalleryController, Int, Media) + case map(TelegramMediaMap) + case stickerPack(StickerPackReference) + case audio(TelegramMediaFile) + case document(TelegramMediaFile, Bool) + case gallery(Signal) + case secretGallery(SecretMediaPreviewController) + case chatAvatars(AvatarGalleryController, Media) + case theme(TelegramMediaFile) + case other(Media) +} + +private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, media: [MediaId: Media], counter: inout Int) -> [InstantPageGalleryEntry] { + 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, 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, _, _): + if let m = media[id] { + 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 .collage(items, _): + var result: [InstantPageGalleryEntry] = [] + for item in items { + result.append(contentsOf: instantPageBlockMedia(pageId: pageId, block: item, media: media, counter: &counter)) + } + return result + case let .slideshow(items, _): + var result: [InstantPageGalleryEntry] = [] + for item in items { + result.append(contentsOf: instantPageBlockMedia(pageId: pageId, block: item, media: media, counter: &counter)) + } + return result + default: + break + } + return [] +} + +public func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage, galleryMedia: Media) -> [InstantPageGalleryEntry] { + var result: [InstantPageGalleryEntry] = [] + var counter: Int = 0 + + for block in page.blocks { + result.append(contentsOf: instantPageBlockMedia(pageId: webpageId, block: block, media: page.media, counter: &counter)) + } + + var found = false + for item in result { + if item.media.media.id == galleryMedia.id { + found = true + break + } + } + + if !found { + 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, credit: item.credit, location: InstantPageGalleryEntryLocation(position: Int32(i), totalCount: Int32(result.count))) + } + return result +} + +public func chatMessageGalleryControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic?, message: Message, navigationController: NavigationController?, standalone: Bool, reverseMessageGalleryOrder: Bool, mode: ChatControllerInteractionOpenMessageMode, source: GalleryControllerItemSource?, synchronousLoad: Bool, actionInteraction: GalleryControllerActionInteraction?) -> ChatMessageGalleryControllerData? { + var galleryMedia: Media? + var otherMedia: Media? + var instantPageMedia: (TelegramMediaWebpage, [InstantPageGalleryEntry])? + for media in message.media { + if let action = media as? TelegramMediaAction { + switch action.action { + case let .photoUpdated(image): + if let peer = messageMainPeer(message), let image = image { + let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer, message.timestamp, nil, message.id, image.immediateThumbnailData, "action")]) + let galleryController = AvatarGalleryController(context: context, peer: peer, sourceCorners: .roundRect(15.5), remoteEntries: promise, skipInitial: true, replaceRootController: { controller, ready in + + }) + return .chatAvatars(galleryController, image) + } + default: + break + } + } else if let file = media as? TelegramMediaFile { + galleryMedia = file + } else if let image = media as? TelegramMediaImage { + galleryMedia = image + } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { + if let file = content.file { + galleryMedia = file + } else if let image = content.image { + if case .link = mode { + } else if ["photo", "document", "video", "gif", "telegram_album"].contains(content.type) { + galleryMedia = image + } + } + + if let instantPage = content.instantPage, let galleryMedia = galleryMedia { + switch instantPageType(of: content) { + case .album: + let medias = instantPageGalleryMedia(webpageId: webpage.webpageId, page: instantPage, galleryMedia: galleryMedia) + if medias.count > 1 { + instantPageMedia = (webpage, medias) + } + default: + break + } + } + } else if let mapMedia = media as? TelegramMediaMap { + galleryMedia = mapMedia + } else if let contactMedia = media as? TelegramMediaContact { + otherMedia = contactMedia + } + } + + var stream = false + var autoplayingVideo = false + var landscape = false + var timecode: Double? = nil + + switch mode { + case .stream: + stream = true + case .automaticPlayback: + autoplayingVideo = true + case .landscape: + autoplayingVideo = true + landscape = true + case let .timecode(time): + timecode = time + default: + break + } + + if let (webPage, instantPageMedia) = instantPageMedia, let galleryMedia = galleryMedia { + var centralIndex: Int = 0 + for i in 0 ..< instantPageMedia.count { + if instantPageMedia[i].media.media.id == galleryMedia.id { + centralIndex = i + break + } + } + + let gallery = InstantPageGalleryController(context: context, webPage: webPage, message: message, entries: instantPageMedia, centralIndex: centralIndex, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, replaceRootController: { [weak navigationController] controller, ready in + if let navigationController = navigationController { + navigationController.replaceTopController(controller, animated: false, ready: ready) + } + }, baseNavigationController: navigationController) + return .instantPage(gallery, centralIndex, galleryMedia) + } else if let galleryMedia = galleryMedia { + if let mapMedia = galleryMedia as? TelegramMediaMap { + return .map(mapMedia) + } else if let file = galleryMedia as? TelegramMediaFile, (file.isSticker || file.isAnimatedSticker) { + for attribute in file.attributes { + if case let .Sticker(_, reference, _) = attribute { + if let reference = reference { + return .stickerPack(reference) + } + break + } + } + } else if let file = galleryMedia as? TelegramMediaFile, file.isAnimatedSticker { + return nil + } else if let file = galleryMedia as? TelegramMediaFile, file.isMusic || file.isVoice || file.isInstantVideo { + return .audio(file) + } else if let file = galleryMedia as? TelegramMediaFile, file.mimeType == "application/vnd.apple.pkpass" || (file.fileName != nil && file.fileName!.lowercased().hasSuffix(".pkpass")) { + return .pass(file) + } else { + if let file = galleryMedia as? TelegramMediaFile { + if let fileName = file.fileName { + let ext = (fileName as NSString).pathExtension.lowercased() + if ext == "tgios-theme" { + return .theme(file) + } else if ext == "wav" || ext == "opus" { + return .audio(file) + } else if ext == "json", let fileSize = file.size, fileSize < 1024 * 1024 { + if let path = context.account.postbox.mediaBox.completedResourcePath(file.resource), let composition = LOTComposition(filePath: path), composition.timeDuration > 0.0 { + let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? ChatLocation.peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic(value: nil)), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in + navigationController?.replaceTopController(controller, animated: false, ready: ready) + }, baseNavigationController: navigationController, actionInteraction: actionInteraction) + return .gallery(.single(gallery)) + } + } + + if ext == "mkv" { + return .document(file, true) + } + } + + if internalDocumentItemSupportsMimeType(file.mimeType, fileName: file.fileName ?? "file") { + let gallery = GalleryController(context: context, source: source ?? .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? .peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic(value: nil)), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in + navigationController?.replaceTopController(controller, animated: false, ready: ready) + }, baseNavigationController: navigationController, actionInteraction: actionInteraction) + return .gallery(.single(gallery)) + } + + if !file.isVideo { + return .document(file, false) + } + } + + if message.containsSecretMedia { + let gallery = SecretMediaPreviewController(context: context, messageId: message.id) + return .secretGallery(gallery) + } else { + let startTimecode: Signal + if let timecode = timecode { + startTimecode = .single(timecode) + } else { + startTimecode = mediaPlaybackStoredState(postbox: context.account.postbox, messageId: message.id) + |> map { state in + return state?.timestamp + } + } + + return .gallery(startTimecode + |> deliverOnMainQueue + |> map { timecode in + let gallery = GalleryController(context: context, source: source ?? (standalone ? .standaloneMessage(message) : .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? .peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic(value: nil))), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in + navigationController?.replaceTopController(controller, animated: false, ready: ready) + }, baseNavigationController: navigationController, actionInteraction: actionInteraction) + gallery.temporaryDoNotWaitForReady = autoplayingVideo + return gallery + }) + } + } + } + if let otherMedia = otherMedia { + return .other(otherMedia) + } else { + return nil + } +} + +public enum ChatMessagePreviewControllerData { + case instantPage(InstantPageGalleryController, Int, Media) + case gallery(GalleryController) +} + +public func chatMessagePreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> ChatMessagePreviewControllerData? { + if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) { + switch mediaData { + case let .gallery(gallery): + break + case let .instantPage(gallery, centralIndex, galleryMedia): + return .instantPage(gallery, centralIndex, galleryMedia) + default: + break + } + } + return nil +} + +public func chatMediaListPreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> Signal { + if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) { + switch mediaData { + case let .gallery(gallery): + return gallery + |> map { gallery in + return .gallery(gallery) + } + case let .instantPage(gallery, centralIndex, galleryMedia): + return .single(.instantPage(gallery, centralIndex, galleryMedia)) + default: + break + } + } + return .single(nil) +} diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index 16f78eee92..c785e51ba3 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -246,20 +246,20 @@ public final class GalleryControllerPresentationArguments { private enum GalleryMessageHistoryView { case view(MessageHistoryView) - case single(MessageHistoryEntry) + case entries([MessageHistoryEntry], Bool, Bool) var entries: [MessageHistoryEntry] { switch self { case let .view(view): return view.entries - case let .single(entry): - return [entry] + case let .entries(entries, _, _): + return entries } } var tagMask: MessageTags? { switch self { - case .single: + case .entries: return nil case let .view(view): return view.tagMask @@ -268,8 +268,8 @@ private enum GalleryMessageHistoryView { var hasEarlier: Bool { switch self { - case .single: - return false + case let .entries(_, hasEarlier, _): + return hasEarlier case let .view(view): return view.earlierId != nil } @@ -277,19 +277,14 @@ private enum GalleryMessageHistoryView { var hasLater: Bool { switch self { - case .single: - return false + case let .entries(_ , _, hasLater): + return hasLater case let .view(view): return view.laterId != nil } } } -public enum GalleryControllerItemSource { - case peerMessagesAtId(MessageId) - case standaloneMessage(Message) -} - public enum GalleryControllerInteractionTapAction { case url(url: String, concealed: Bool) case textMention(String) @@ -357,6 +352,7 @@ public class GalleryController: ViewController, StandalonePresentableController private var entries: [MessageHistoryEntry] = [] private var hasLeftEntries: Bool = false private var hasRightEntries: Bool = false + private var loadingMore: Bool = false private var tagMask: MessageTags? private var centralEntryStableId: UInt32? private var configuration: GalleryConfiguration? @@ -417,17 +413,23 @@ public class GalleryController: ViewController, StandalonePresentableController let message: Signal switch source { - case let .peerMessagesAtId(messageId): + case let .peerMessagesAtId(messageId, _, _): message = context.account.postbox.messageAtId(messageId) case let .standaloneMessage(m): message = .single(m) + case let .custom(messages, messageId, _): + message = messages + |> take(1) + |> map { messages, _, _ in + return messages.first(where: { $0.id == messageId }) + } } let messageView = message |> filter({ $0 != nil }) |> mapToSignal { message -> Signal in switch source { - case .peerMessagesAtId: + case let .peerMessagesAtId(_, chatLocation, chatLocationContextHolder): if let tags = tagsForMessage(message!) { let namespaces: MessageIdNamespaces if Namespaces.Message.allScheduled.contains(message!.id.namespace) { @@ -435,16 +437,27 @@ public class GalleryController: ViewController, StandalonePresentableController } else { namespaces = .not(Namespaces.Message.allScheduled) } - return context.account.postbox.aroundMessageHistoryViewForLocation(.peer(message!.id.peerId), anchor: .index(message!.index), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, namespaces: namespaces, orderStatistics: [.combinedLocation]) - |> mapToSignal { (view, _, _) -> Signal in - let mapped = GalleryMessageHistoryView.view(view) - return .single(mapped) + return context.account.postbox.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(message!.index), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, namespaces: namespaces, orderStatistics: [.combinedLocation]) + |> mapToSignal { (view, _, _) -> Signal in + let mapped = GalleryMessageHistoryView.view(view) + return .single(mapped) } } else { - return .single(GalleryMessageHistoryView.single(MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)))) - } + return .single(GalleryMessageHistoryView.entries([MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))], false, false)) + } case .standaloneMessage: - return .single(GalleryMessageHistoryView.single(MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)))) + return .single(GalleryMessageHistoryView.entries([MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))], false ,false)) + case let .custom(messages, _, _): + return messages + |> map { messages, totalCount, hasMore in + var entries: [MessageHistoryEntry] = [] + var index = messages.count + for message in messages.reversed() { + entries.append(MessageHistoryEntry(message: message, isRead: false, location: MessageHistoryEntryLocation(index: Int(totalCount) - index, count: Int(totalCount)), monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))) + index -= 1 + } + return GalleryMessageHistoryView.entries(entries, hasMore, false) + } } } |> take(1) @@ -470,7 +483,7 @@ public class GalleryController: ViewController, StandalonePresentableController loop: for i in 0 ..< entries.count { let message = entries[i].message switch source { - case let .peerMessagesAtId(messageId): + case let .peerMessagesAtId(messageId, _, _): if message.id == messageId { centralEntryStableId = message.stableId break loop @@ -480,6 +493,11 @@ public class GalleryController: ViewController, StandalonePresentableController centralEntryStableId = message.stableId break loop } + case let .custom(_, messageId, _): + if message.id == messageId { + centralEntryStableId = message.stableId + break loop + } } } @@ -813,7 +831,7 @@ public class GalleryController: ViewController, StandalonePresentableController self.isOpaqueWhenInOverlay = true switch source { - case let .peerMessagesAtId(id): + case let .peerMessagesAtId(id, _, _): if id.peerId.namespace == Namespaces.Peer.SecretChat { self.screenCaptureEventsDisposable = (screenCaptureEvents() |> deliverOnMainQueue).start(next: { [weak self] _ in @@ -984,70 +1002,125 @@ public class GalleryController: ViewController, StandalonePresentableController } switch strongSelf.source { - case let .peerMessagesAtId(initialMessageId): - var reloadAroundIndex: MessageIndex? - if index <= 2 && strongSelf.hasLeftEntries { - reloadAroundIndex = strongSelf.entries.first?.index - } else if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries { - reloadAroundIndex = strongSelf.entries.last?.index - } - if let reloadAroundIndex = reloadAroundIndex, let tagMask = strongSelf.tagMask { - let namespaces: MessageIdNamespaces - if Namespaces.Message.allScheduled.contains(message.id.namespace) { - namespaces = .just(Namespaces.Message.allScheduled) - } else { - namespaces = .not(Namespaces.Message.allScheduled) + case let .peerMessagesAtId(_, chatLocation, chatLocationContextHolder): + var reloadAroundIndex: MessageIndex? + if index <= 2 && strongSelf.hasLeftEntries { + reloadAroundIndex = strongSelf.entries.first?.index + } else if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries { + reloadAroundIndex = strongSelf.entries.last?.index } - let signal = strongSelf.context.account.postbox.aroundMessageHistoryViewForLocation(.peer(initialMessageId.peerId), anchor: .index(reloadAroundIndex), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [.combinedLocation]) - |> mapToSignal { (view, _, _) -> Signal in - let mapped = GalleryMessageHistoryView.view(view) - return .single(mapped) - } - |> take(1) - - strongSelf.updateVisibleDisposable.set((signal - |> deliverOnMainQueue).start(next: { view in - guard let strongSelf = self, let view = view else { - return - } - - let entries = view.entries - - if strongSelf.invertItemOrder { - strongSelf.entries = entries.reversed() - strongSelf.hasLeftEntries = view.hasLater - strongSelf.hasRightEntries = view.hasEarlier + if let reloadAroundIndex = reloadAroundIndex, let tagMask = strongSelf.tagMask { + let namespaces: MessageIdNamespaces + if Namespaces.Message.allScheduled.contains(message.id.namespace) { + namespaces = .just(Namespaces.Message.allScheduled) } else { - strongSelf.entries = entries - strongSelf.hasLeftEntries = view.hasEarlier - strongSelf.hasRightEntries = view.hasLater + namespaces = .not(Namespaces.Message.allScheduled) } - if strongSelf.isViewLoaded { - var items: [GalleryItem] = [] - var centralItemIndex: Int? - for entry in strongSelf.entries { - var isCentral = false - if entry.message.stableId == strongSelf.centralEntryStableId { - isCentral = true + let signal = strongSelf.context.account.postbox.aroundMessageHistoryViewForLocation(strongSelf.context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(reloadAroundIndex), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [.combinedLocation]) + |> mapToSignal { (view, _, _) -> Signal in + let mapped = GalleryMessageHistoryView.view(view) + return .single(mapped) + } + |> take(1) + + strongSelf.updateVisibleDisposable.set((signal + |> deliverOnMainQueue).start(next: { view in + guard let strongSelf = self, let view = view else { + return } - if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in - if let strongSelf = self { - strongSelf.presentInGlobalOverlay(c, with: a) + + let entries = view.entries + + if strongSelf.invertItemOrder { + strongSelf.entries = entries.reversed() + strongSelf.hasLeftEntries = view.hasLater + strongSelf.hasRightEntries = view.hasEarlier + } else { + strongSelf.entries = entries + strongSelf.hasLeftEntries = view.hasEarlier + strongSelf.hasRightEntries = view.hasLater + } + if strongSelf.isViewLoaded { + var items: [GalleryItem] = [] + var centralItemIndex: Int? + for entry in strongSelf.entries { + var isCentral = false + if entry.message.stableId == strongSelf.centralEntryStableId { + isCentral = true + } + if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in + if let strongSelf = self { + strongSelf.presentInGlobalOverlay(c, with: a) + } + }) { + if isCentral { + centralItemIndex = items.count + } + items.append(item) + } } - }) { - if isCentral { - centralItemIndex = items.count + + strongSelf.galleryNode.pager.replaceItems(items, centralItemIndex: centralItemIndex) + } + })) + } + case let .custom(messages, _, loadMore): + if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries && !strongSelf.loadingMore { + strongSelf.loadingMore = true + loadMore?() + + strongSelf.updateVisibleDisposable.set((messages + |> deliverOnMainQueue).start(next: { messages, totalCount, hasMore in + guard let strongSelf = self else { + return + } + + var entries: [MessageHistoryEntry] = [] + var index = messages.count + for message in messages.reversed() { + entries.append(MessageHistoryEntry(message: message, isRead: false, location: MessageHistoryEntryLocation(index: Int(totalCount) - index, count: Int(totalCount)), monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))) + index -= 1 + } + + if entries.count > strongSelf.entries.count { + if strongSelf.invertItemOrder { + strongSelf.entries = entries.reversed() + strongSelf.hasLeftEntries = false + strongSelf.hasRightEntries = hasMore + } else { + strongSelf.entries = entries + strongSelf.hasLeftEntries = hasMore + strongSelf.hasRightEntries = false + } + if strongSelf.isViewLoaded { + var items: [GalleryItem] = [] + var centralItemIndex: Int? + for entry in strongSelf.entries { + var isCentral = false + if entry.message.stableId == strongSelf.centralEntryStableId { + isCentral = true + } + if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in + if let strongSelf = self { + strongSelf.presentInGlobalOverlay(c, with: a) + } + }) { + if isCentral { + centralItemIndex = items.count + } + items.append(item) + } } - items.append(item) + + strongSelf.galleryNode.pager.replaceItems(items, centralItemIndex: centralItemIndex) } } - strongSelf.galleryNode.pager.replaceItems(items, centralItemIndex: centralItemIndex) - } - })) - } - default: - break + strongSelf.loadingMore = false + })) + } + default: + break } } if strongSelf.didSetReady { @@ -1152,6 +1225,7 @@ public class GalleryController: ViewController, StandalonePresentableController self.adjustedForInitialPreviewingLayout = true self.galleryNode.setControlsHidden(true, animated: false) if let centralItemNode = self.galleryNode.pager.centralItemNode(), let itemSize = centralItemNode.contentSize() { + centralItemNode.adjustForPreviewing() self.preferredContentSize = itemSize.aspectFitted(layout.size) self.containerLayoutUpdated(ContainerViewLayout(size: self.preferredContentSize, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate) } diff --git a/submodules/GalleryUI/Sources/GalleryItemNode.swift b/submodules/GalleryUI/Sources/GalleryItemNode.swift index ebd39a13fe..7f90361440 100644 --- a/submodules/GalleryUI/Sources/GalleryItemNode.swift +++ b/submodules/GalleryUI/Sources/GalleryItemNode.swift @@ -85,6 +85,9 @@ open class GalleryItemNode: ASDisplayNode { open func controlsVisibilityUpdated(isVisible: Bool) { } + open func adjustForPreviewing() { + } + open func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) { } diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index e406599fa7..f5e21124f7 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -1352,7 +1352,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { switch contentInfo { case let .message(message): - let gallery = GalleryController(context: context, source: .peerMessagesAtId(message.id), replaceRootController: { controller, ready in + let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: .peer(message.id.peerId), chatLocationContextHolder: Atomic(value: nil)), replaceRootController: { controller, ready in if let baseNavigationController = baseNavigationController { baseNavigationController.replaceTopController(controller, animated: false, ready: ready) } @@ -1434,7 +1434,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { switch contentInfo { case let .message(message): - let gallery = GalleryController(context: context, source: .peerMessagesAtId(message.id), replaceRootController: { controller, ready in + let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: .peer(message.id.peerId), chatLocationContextHolder: Atomic(value: nil)), replaceRootController: { controller, ready in if let baseNavigationController = baseNavigationController { baseNavigationController.replaceTopController(controller, animated: false, ready: ready) } @@ -1537,6 +1537,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } + override func adjustForPreviewing() { + super.adjustForPreviewing() + + self.scrubberView.isHidden = true + } + override func footerContent() -> Signal<(GalleryFooterContentNode?, GalleryOverlayContentNode?), NoError> { return .single((self.footerContentNode, nil)) } diff --git a/submodules/HashtagSearchUI/BUCK b/submodules/HashtagSearchUI/BUCK index dd0dc6ebee..430c1f9319 100644 --- a/submodules/HashtagSearchUI/BUCK +++ b/submodules/HashtagSearchUI/BUCK @@ -17,6 +17,7 @@ static_library( "//submodules/TelegramBaseController:TelegramBaseController", "//submodules/ChatListUI:ChatListUI", "//submodules/SegmentedControlNode:SegmentedControlNode", + "//submodules/ListMessageItem:ListMessageItem", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/HashtagSearchUI/BUILD b/submodules/HashtagSearchUI/BUILD index 0384121b97..40d2b21af0 100644 --- a/submodules/HashtagSearchUI/BUILD +++ b/submodules/HashtagSearchUI/BUILD @@ -18,6 +18,7 @@ swift_library( "//submodules/TelegramBaseController:TelegramBaseController", "//submodules/ChatListUI:ChatListUI", "//submodules/SegmentedControlNode:SegmentedControlNode", + "//submodules/ListMessageItem:ListMessageItem", ], visibility = [ "//visibility:public", diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index 538fa413c0..45c43d96d7 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -9,6 +9,7 @@ import TelegramPresentationData import TelegramBaseController import AccountContext import ChatListUI +import ListMessageItem public final class HashtagSearchController: TelegramBaseController { private let queue = Queue() @@ -41,11 +42,11 @@ public final class HashtagSearchController: TelegramBaseController { let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations) - let location: SearchMessagesLocation = .general(tags: nil) + let location: SearchMessagesLocation = .general(tags: nil, minDate: nil, maxDate: nil) let search = searchMessages(account: context.account, location: location, query: query, state: nil) let foundMessages: Signal<[ChatListSearchEntry], NoError> = search |> map { result, _ in - return result.messages.map({ .message($0, RenderedPeer(message: $0), result.readStates[$0.id.peerId], chatListPresentationData) }) + return result.messages.map({ .message($0, RenderedPeer(message: $0), result.readStates[$0.id.peerId], chatListPresentationData, result.totalCount, nil, false) }) } let interaction = ChatListNodeInteraction(activateSearch: { }, peerSelected: { _, _ in @@ -82,10 +83,27 @@ public final class HashtagSearchController: TelegramBaseController { if let strongSelf = self { let previousEntries = previousSearchItems.swap(entries) - let firstTime = previousEntries == nil - let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], interaction: interaction, peerContextAction: nil, toggleExpandLocalResults: { - }, toggleExpandGlobalResults: { + let listInteraction = ListMessageItemInteraction(openMessage: { message, mode -> Bool in + return true + }, openMessageContextMenu: { message, bool, node, rect, gesture in + + }, toggleMessagesSelection: { messageId, selected in + + }, openUrl: { url, _, _, message in + }, openInstantPage: { message, data in + + }, longTap: { action, message in + + }, getHiddenMedia: { + return [:] }) + + let firstTime = previousEntries == nil + let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, searchQuery: "", context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: { + }, toggleExpandGlobalResults: { + }, searchPeer: { _ in + + }, searchResults: [], searchOptions: nil, messageContextAction: nil) strongSelf.controllerNode.enqueueTransition(transition, firstTime: firstTime) } }) diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift index ef47b6d8ea..e722b64105 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift @@ -42,6 +42,8 @@ final class HashtagSearchControllerNode: ASDisplayNode { var items: [String] = [] if peer?.id == context.account.peerId { items.append(presentationData.strings.Conversation_SavedMessages) + } else if let id = peer?.id, id.isReplies { + items.append(presentationData.strings.DialogList_Replies) } else { items.append(peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) ?? "") } diff --git a/submodules/InstantPageUI/Sources/InstantPageController.swift b/submodules/InstantPageUI/Sources/InstantPageController.swift index b8d3fb4cfe..5bbb5f9c17 100644 --- a/submodules/InstantPageUI/Sources/InstantPageController.swift +++ b/submodules/InstantPageUI/Sources/InstantPageController.swift @@ -9,6 +9,56 @@ import TelegramPresentationData import TelegramUIPreferences import AccountContext +public func instantPageAndAnchor(message: Message) -> (TelegramMediaWebpage, String?)? { + for media in message.media { + if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { + if let _ = content.instantPage { + var textUrl: String? + if let pageUrl = URL(string: content.url) { + inner: for attribute in message.attributes { + if let attribute = attribute as? TextEntitiesMessageAttribute { + for entity in attribute.entities { + switch entity.type { + case let .TextUrl(url): + if let parsedUrl = URL(string: url) { + if pageUrl.scheme == parsedUrl.scheme && pageUrl.host == parsedUrl.host && pageUrl.path == parsedUrl.path { + textUrl = url + } + } + case .Url: + let nsText = message.text as NSString + var entityRange = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) + if entityRange.location + entityRange.length > nsText.length { + entityRange.location = max(0, nsText.length - entityRange.length) + entityRange.length = nsText.length - entityRange.location + } + let url = nsText.substring(with: entityRange) + if let parsedUrl = URL(string: url) { + if pageUrl.scheme == parsedUrl.scheme && pageUrl.host == parsedUrl.host && pageUrl.path == parsedUrl.path { + textUrl = url + } + } + default: + break + } + } + break inner + } + } + } + var anchor: String? + if let textUrl = textUrl, let anchorRange = textUrl.range(of: "#") { + anchor = String(textUrl[anchorRange.upperBound...]) + } + + return (webpage, anchor) + } + break + } + } + return nil +} + public final class InstantPageController: ViewController { private let context: AccountContext private var webPage: TelegramMediaWebpage diff --git a/submodules/InstantPageUI/Sources/InstantPageLayout.swift b/submodules/InstantPageUI/Sources/InstantPageLayout.swift index 65fe599451..4de40ca9b9 100644 --- a/submodules/InstantPageUI/Sources/InstantPageLayout.swift +++ b/submodules/InstantPageUI/Sources/InstantPageLayout.swift @@ -855,6 +855,9 @@ func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth: if let image = loadedContent.image, let id = image.id { media[id] = image } + if let video = loadedContent.file, let id = video.id { + media[id] = video + } var mediaIndexCounter: Int = 0 var embedIndexCounter: Int = 0 diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index 55e678f1cb..5df854a406 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -683,6 +683,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo if item.peer.id == item.context.account.peerId, case .threatSelfAsSaved = item.aliasHandling { titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_SavedMessages, font: currentBoldFont, textColor: titleColor) + } else if item.peer.id.isReplies { + titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Replies, font: currentBoldFont, textColor: titleColor) } else if let user = item.peer as? TelegramUser { if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty { let string = NSMutableAttributedString() @@ -1059,6 +1061,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo if item.peer.id == item.context.account.peerId, case .threatSelfAsSaved = item.aliasHandling { strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.peer, overrideImage: .savedMessagesIcon, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad) + } else if item.peer.id.isReplies { + strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.peer, overrideImage: .repliesIcon, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad) } else { var overrideImage: AvatarNodeImageOverride? if item.peer.isDeleted { diff --git a/submodules/LegacyDataImport/Sources/LegacyChatImport.swift b/submodules/LegacyDataImport/Sources/LegacyChatImport.swift index 99c05c5600..8a14aebe91 100644 --- a/submodules/LegacyDataImport/Sources/LegacyChatImport.swift +++ b/submodules/LegacyDataImport/Sources/LegacyChatImport.swift @@ -570,7 +570,7 @@ private func loadLegacyMessages(account: TemporaryAccount, basePath: String, acc } let (parsedTags, parsedGlobalTags) = tagsForStoreMessage(incoming: parsedFlags.contains(.Incoming), attributes: parsedAttributes, media: parsedMedia, textEntities: nil) - messages.append(StoreMessage(id: parsedId, globallyUniqueId: globallyUniqueId, groupingKey: parsedGroupingKey, timestamp: timestamp, flags: parsedFlags, tags: parsedTags, globalTags: parsedGlobalTags, localTags: [], forwardInfo: nil, authorId: parsedAuthorId, text: text, attributes: parsedAttributes, media: parsedMedia)) + messages.append(StoreMessage(id: parsedId, globallyUniqueId: globallyUniqueId, groupingKey: parsedGroupingKey, threadId: nil, timestamp: timestamp, flags: parsedFlags, tags: parsedTags, globalTags: parsedGlobalTags, localTags: [], forwardInfo: nil, authorId: parsedAuthorId, text: text, attributes: parsedAttributes, media: parsedMedia)) //Logger.shared.log("loadLegacyMessages", "message \(messageId) completed") diff --git a/submodules/ListMessageItem/BUCK b/submodules/ListMessageItem/BUCK new file mode 100644 index 0000000000..64f9dd50e7 --- /dev/null +++ b/submodules/ListMessageItem/BUCK @@ -0,0 +1,36 @@ +load("//Config:buck_rule_macros.bzl", "static_library") + +static_library( + name = "ListMessageItem", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", + "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", + "//submodules/Display:Display#shared", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/ItemListUI:ItemListUI", + "//submodules/AccountContext:AccountContext", + "//submodules/TextFormat:TextFormat", + "//submodules/AppBundle:AppBundle", + "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/ListSectionHeaderNode:ListSectionHeaderNode", + "//submodules/TelegramStringFormatting:TelegramStringFormatting", + "//submodules/UrlHandling:UrlHandling", + "//submodules/UrlWhitelist:UrlWhitelist", + "//submodules/WebsiteType:WebsiteType", + "//submodules/PhotoResources:PhotoResources", + "//submodules/RadialStatusNode:RadialStatusNode", + "//submodules/SemanticStatusNode:SemanticStatusNode", + "//submodules/MusicAlbumArtResources:MusicAlbumArtResources", + "//submodules/MediaPlayer:UniversalMediaPlayer", + "//submodules/ContextUI:ContextUI", + "//submodules/FileMediaResourceStatus:FileMediaResourceStatus", + ], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/Foundation.framework", + "$SDKROOT/System/Library/Frameworks/UIKit.framework", + ], +) diff --git a/submodules/ListMessageItem/BUILD b/submodules/ListMessageItem/BUILD new file mode 100644 index 0000000000..55edc54d95 --- /dev/null +++ b/submodules/ListMessageItem/BUILD @@ -0,0 +1,38 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ListMessageItem", + module_name = "ListMessageItem", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/ItemListUI:ItemListUI", + "//submodules/AccountContext:AccountContext", + "//submodules/TextFormat:TextFormat", + "//submodules/AppBundle:AppBundle", + "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/ListSectionHeaderNode:ListSectionHeaderNode", + "//submodules/TelegramStringFormatting:TelegramStringFormatting", + "//submodules/UrlHandling:UrlHandling", + "//submodules/UrlWhitelist:UrlWhitelist", + "//submodules/WebsiteType:WebsiteType", + "//submodules/PhotoResources:PhotoResources", + "//submodules/RadialStatusNode:RadialStatusNode", + "//submodules/SemanticStatusNode:SemanticStatusNode", + "//submodules/MusicAlbumArtResources:MusicAlbumArtResources", + "//submodules/MediaPlayer:UniversalMediaPlayer", + "//submodules/ContextUI:ContextUI", + "//submodules/FileMediaResourceStatus:FileMediaResourceStatus", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ListMessageDateHeader.swift b/submodules/ListMessageItem/Sources/ListMessageDateHeader.swift similarity index 89% rename from submodules/TelegramUI/Sources/ListMessageDateHeader.swift rename to submodules/ListMessageItem/Sources/ListMessageDateHeader.swift index 67f3523968..8205218905 100644 --- a/submodules/TelegramUI/Sources/ListMessageDateHeader.swift +++ b/submodules/ListMessageItem/Sources/ListMessageDateHeader.swift @@ -16,7 +16,7 @@ private let timezoneOffset: Int32 = { return Int32(timeinfoNow.tm_gmtoff) }() -func listMessageDateHeaderId(timestamp: Int32) -> Int64 { +public func listMessageDateHeaderId(timestamp: Int32) -> Int64 { let unclippedValue: Int64 = min(Int64(Int32.max), Int64(timestamp) + Int64(timezoneOffset)) var time: time_t = time_t(Int32(clamping: unclippedValue)) @@ -28,7 +28,7 @@ func listMessageDateHeaderId(timestamp: Int32) -> Int64 { return Int64(roundedTimestamp) } -func listMessageDateHeaderInfo(timestamp: Int32) -> (year: Int32, month: Int32) { +public func listMessageDateHeaderInfo(timestamp: Int32) -> (year: Int32, month: Int32) { var time: time_t = time_t(timestamp + timezoneOffset) var timeinfo: tm = tm() localtime_r(&time, &timeinfo) @@ -76,7 +76,7 @@ final class ListMessageDateHeader: ListViewItemHeader { } } -final class ListMessageDateHeaderNode: ListViewItemHeaderNode { +public final class ListMessageDateHeaderNode: ListViewItemHeaderNode { var theme: PresentationTheme var strings: PresentationStrings let headerNode: ListSectionHeaderNode @@ -99,7 +99,7 @@ final class ListMessageDateHeaderNode: ListViewItemHeaderNode { self.headerNode.title = stringForMonth(strings: strings, month: month, ofYear: year).uppercased() } - func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + public func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { self.theme = theme self.headerNode.updateTheme(theme: theme) @@ -109,7 +109,7 @@ final class ListMessageDateHeaderNode: ListViewItemHeaderNode { self.setNeedsLayout() } - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { + override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: size.height + UIScreenPixel)) self.headerNode.frame = headerFrame self.headerNode.updateLayout(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset) diff --git a/submodules/TelegramUI/Sources/ListMessageFileItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift similarity index 81% rename from submodules/TelegramUI/Sources/ListMessageFileItemNode.swift rename to submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift index fd4ea3088e..e625ab6971 100644 --- a/submodules/TelegramUI/Sources/ListMessageFileItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift @@ -18,6 +18,7 @@ import PhotoResources import MusicAlbumArtResources import UniversalMediaPlayer import ContextUI +import FileMediaResourceStatus private let extensionImageCache = Atomic<[UInt32: UIImage]>(value: [:]) @@ -88,6 +89,36 @@ private func extensionImage(fileExtension: String?) -> UIImage? { } private let extensionFont = Font.with(size: 15.0, design: .round, traits: [.bold]) +func fullAuthorString(for item: ListMessageItem) -> String { + var authorString = "" + if let author = item.message.author, [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(item.message.id.peerId.namespace) { + var authorName = "" + if author.id == item.context.account.peerId { + authorName = item.presentationData.strings.DialogList_You + } else { + authorName = author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) + } + if let peer = item.message.peers[item.message.id.peerId], author.id != peer.id { + authorString = "\(authorName) → \(peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder))" + } else { + authorString = authorName + } + } else if let peer = item.message.peers[item.message.id.peerId] { + if item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel { + authorString = peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) + } else { + if item.message.id.peerId == item.context.account.peerId { + authorString = item.presentationData.strings.DialogList_SavedMessages + } else if item.message.flags.contains(.Incoming) { + authorString = peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) + } else { + authorString = "\(item.presentationData.strings.DialogList_You) → \(peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder))" + } + } + } + return authorString +} + private struct FetchControls { let fetch: () -> Void let cancel: () -> Void @@ -122,7 +153,7 @@ private enum FileIconImage: Equatable { } } -final class ListMessageFileItemNode: ListMessageNode { +public final class ListMessageFileItemNode: ListMessageNode { private let contextSourceNode: ContextExtractedContentContainingNode private let containerNode: ContextControllerSourceNode private let extractedBackgroundImageNode: ASImageNode @@ -140,6 +171,7 @@ final class ListMessageFileItemNode: ListMessageNode { private let titleNode: TextNode private let descriptionNode: TextNode private let descriptionProgressNode: ImmediateTextNode + private let dateNode: TextNode private let extensionIconNode: ASImageNode private let extensionIconText: TextNode @@ -195,6 +227,9 @@ final class ListMessageFileItemNode: ListMessageNode { self.descriptionProgressNode.isUserInteractionEnabled = false self.descriptionProgressNode.maximumNumberOfLines = 1 + self.dateNode = TextNode() + self.dateNode.isUserInteractionEnabled = false + self.extensionIconNode = ASImageNode() self.extensionIconNode.isLayerBacked = true self.extensionIconNode.displaysAsynchronously = false @@ -228,6 +263,7 @@ final class ListMessageFileItemNode: ListMessageNode { self.offsetContainerNode.addSubnode(self.titleNode) self.offsetContainerNode.addSubnode(self.descriptionNode) self.offsetContainerNode.addSubnode(self.descriptionProgressNode) + self.offsetContainerNode.addSubnode(self.dateNode) self.offsetContainerNode.addSubnode(self.extensionIconNode) self.offsetContainerNode.addSubnode(self.extensionIconText) self.offsetContainerNode.addSubnode(self.iconStatusNode) @@ -237,7 +273,7 @@ final class ListMessageFileItemNode: ListMessageNode { return } - item.controllerInteraction.openMessageContextMenu(item.message, false, strongSelf.contextSourceNode, strongSelf.contextSourceNode.bounds, gesture) + item.interaction.openMessageContextMenu(item.message, false, strongSelf.contextSourceNode, strongSelf.contextSourceNode.bounds, gesture) } self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in @@ -246,7 +282,7 @@ final class ListMessageFileItemNode: ListMessageNode { } if isExtracted { - strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.theme.list.plainBackgroundColor) + strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.presentationData.theme.theme.list.plainBackgroundColor) } if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect { @@ -260,6 +296,7 @@ final class ListMessageFileItemNode: ListMessageNode { self?.extractedBackgroundImageNode.image = nil } }) + transition.updateAlpha(node: strongSelf.dateNode, alpha: isExtracted ? 0.0 : 1.0) } } @@ -294,14 +331,15 @@ final class ListMessageFileItemNode: ListMessageNode { self.addTransitionOffsetAnimation(0.0, duration: duration, beginAt: currentTimestamp) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) } - override func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) { + override public func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) { let titleNodeMakeLayout = TextNode.asyncLayout(self.titleNode) let descriptionNodeMakeLayout = TextNode.asyncLayout(self.descriptionNode) let extensionIconTextMakeLayout = TextNode.asyncLayout(self.extensionIconText) + let dateNodeMakeLayout = TextNode.asyncLayout(self.dateNode) let iconImageLayout = self.iconImageNode.asyncLayout() let currentMedia = self.currentMedia @@ -315,13 +353,14 @@ final class ListMessageFileItemNode: ListMessageNode { return { [weak self] item, params, _, _, dateHeaderAtBottom in var updatedTheme: PresentationTheme? - if currentItem?.theme !== item.theme { - updatedTheme = item.theme + if currentItem?.presentationData.theme.theme !== item.presentationData.theme.theme { + updatedTheme = item.presentationData.theme.theme } - let titleFont = Font.semibold(floor(item.fontSize.baseDisplaySize * 16.0 / 17.0)) - let audioTitleFont = Font.semibold(floor(item.fontSize.baseDisplaySize * 16.0 / 17.0)) - let descriptionFont = Font.regular(floor(item.fontSize.baseDisplaySize * 14.0 / 17.0)) + let titleFont = Font.semibold(floor(item.presentationData.fontSize.baseDisplaySize * 16.0 / 17.0)) + let audioTitleFont = Font.semibold(floor(item.presentationData.fontSize.baseDisplaySize * 16.0 / 17.0)) + let descriptionFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)) + let dateFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) var leftInset: CGFloat = 65.0 + params.leftInset let rightInset: CGFloat = 8.0 + params.rightInset @@ -329,7 +368,7 @@ final class ListMessageFileItemNode: ListMessageNode { var leftOffset: CGFloat = 0.0 var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)? if case let .selectable(selected) = item.selection { - let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected, false) + let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.theme.list.itemCheckColors.fillColor, item.presentationData.theme.theme.list.itemCheckColors.foregroundColor, selected, false) selectionNodeWidthAndApply = (selectionWidth, selectionApply) leftOffset += selectionWidth } @@ -363,61 +402,85 @@ final class ListMessageFileItemNode: ListMessageNode { isAudio = true isVoice = voice - titleText = NSAttributedString(string: title ?? (file.fileName ?? "Unknown Track"), font: audioTitleFont, textColor: item.theme.list.itemPrimaryTextColor) + titleText = NSAttributedString(string: title ?? (file.fileName ?? "Unknown Track"), font: audioTitleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor) - let descriptionString: String + var descriptionString: String if let performer = performer { - descriptionString = "\(stringForDuration(Int32(duration))) • \(performer)" + if item.isGlobalSearchResult { + descriptionString = performer + } else { + descriptionString = "\(stringForDuration(Int32(duration))) • \(performer)" + } } else if let size = file.size { - descriptionString = dataSizeString(size, decimalSeparator: item.dateTimeFormat.decimalSeparator) + descriptionString = dataSizeString(size, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator) } else { descriptionString = "" } - descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor) + if item.isGlobalSearchResult { + let authorString = fullAuthorString(for: item) + if descriptionString.isEmpty { + descriptionString = authorString + } else { + descriptionString = "\(descriptionString) • \(authorString)" + } + } + + descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor) if !voice { iconImage = .albumArt(file, SharedMediaPlaybackAlbumArt(thumbnailResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: true), fullSizeResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: false))) } else { - titleText = NSAttributedString(string: " ", font: audioTitleFont, textColor: item.theme.list.itemPrimaryTextColor) - descriptionText = NSAttributedString(string: item.message.author?.displayTitle(strings: item.strings, displayOrder: .firstLast) ?? " ", font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor) + titleText = NSAttributedString(string: " ", font: audioTitleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor) + descriptionText = NSAttributedString(string: item.message.author?.displayTitle(strings: item.presentationData.strings, displayOrder: .firstLast) ?? " ", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor) } } } if isInstantVideo || isVoice { - let authorName: String + var authorName: String if let author = message.forwardInfo?.author { if author.id == item.context.account.peerId { - authorName = item.strings.DialogList_You + authorName = item.presentationData.strings.DialogList_You } else { - authorName = author.displayTitle(strings: item.strings, displayOrder: .firstLast) + authorName = author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) } } else if let signature = message.forwardInfo?.authorSignature { authorName = signature } else if let author = message.author { if author.id == item.context.account.peerId { - authorName = item.strings.DialogList_You + authorName = item.presentationData.strings.DialogList_You } else { - authorName = author.displayTitle(strings: item.strings, displayOrder: .firstLast) + authorName = author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) } } else { authorName = " " } - titleText = NSAttributedString(string: authorName, font: audioTitleFont, textColor: item.theme.list.itemPrimaryTextColor) - let dateString = stringForFullDate(timestamp: item.message.timestamp, strings: item.strings, dateTimeFormat: item.dateTimeFormat) - let descriptionString: String - if let duration = file.duration { - descriptionString = "\(stringForDuration(Int32(duration))) • \(dateString)" - } else { - descriptionString = dateString + + if item.isGlobalSearchResult { + authorName = fullAuthorString(for: item) } - descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor) + titleText = NSAttributedString(string: authorName, font: audioTitleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor) + let dateString = stringForFullDate(timestamp: item.message.timestamp, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat) + var descriptionString: String = "" + if let duration = file.duration { + if item.isGlobalSearchResult { + descriptionString = stringForDuration(Int32(duration)) + } else { + descriptionString = "\(stringForDuration(Int32(duration))) • \(dateString)" + } + } else { + if !item.isGlobalSearchResult { + descriptionString = dateString + } + } + + descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor) iconImage = .roundVideo(file) } else if !isAudio { let fileName: String = file.fileName ?? "" - titleText = NSAttributedString(string: fileName, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor) + titleText = NSAttributedString(string: fileName, font: titleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor) var fileExtension: String? if let range = fileName.range(of: ".", options: [.backwards]) { @@ -432,16 +495,31 @@ final class ListMessageFileItemNode: ListMessageNode { iconImage = .imageRepresentation(file, representation) } - let dateString = stringForFullDate(timestamp: item.message.timestamp, strings: item.strings, dateTimeFormat: item.dateTimeFormat) + let dateString = stringForFullDate(timestamp: item.message.timestamp, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat) - let descriptionString: String + var descriptionString: String = "" if let size = file.size { - descriptionString = "\(dataSizeString(size, decimalSeparator: item.dateTimeFormat.decimalSeparator)) • \(dateString)" + if item.isGlobalSearchResult { + descriptionString = (dataSizeString(size, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)) + } else { + descriptionString = "\(dataSizeString(size, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)) • \(dateString)" + } } else { - descriptionString = "\(dateString)" + if !item.isGlobalSearchResult { + descriptionString = "\(dateString)" + } + } + + if item.isGlobalSearchResult { + let authorString = fullAuthorString(for: item) + if descriptionString.isEmpty { + descriptionString = authorString + } else { + descriptionString = "\(descriptionString) • \(authorString)" + } } - descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor) + descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor) } break @@ -478,7 +556,7 @@ final class ListMessageFileItemNode: ListMessageNode { } if statusUpdated { - updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false, isSharedMedia: true) + updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult) if isAudio || isInstantVideo { if let currentUpdatedStatusSignal = updatedStatusSignal { @@ -494,14 +572,20 @@ final class ListMessageFileItemNode: ListMessageNode { } } if isVoice { - updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false) + updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false, isGlobalSearch: item.isGlobalSearchResult) } } } - let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(TextNodeLayoutArguments(attributedString: titleText, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 40.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + let dateText = stringForRelativeTimestamp(strings: item.presentationData.strings, relativeTimestamp: item.message.timestamp, relativeTo: timestamp, dateTimeFormat: item.presentationData.dateTimeFormat) + let dateAttributedString = NSAttributedString(string: dateText, font: dateFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor) - let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 12.0 - 40.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (dateNodeLayout, dateNodeApply) = dateNodeMakeLayout(TextNodeLayoutArguments(attributedString: dateAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 12.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(TextNodeLayoutArguments(attributedString: titleText, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - leftOffset - rightInset - dateNodeLayout.size.width - 4.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (extensionTextLayout, extensionTextApply) = extensionIconTextMakeLayout(TextNodeLayoutArguments(attributedString: extensionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 38.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -511,17 +595,17 @@ final class ListMessageFileItemNode: ListMessageNode { case let .imageRepresentation(_, representation): let iconSize = CGSize(width: 40.0, height: 40.0) let imageCorners = ImageCorners(radius: 6.0) - let arguments = TransformImageArguments(corners: imageCorners, imageSize: representation.dimensions.cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor) + let arguments = TransformImageArguments(corners: imageCorners, imageSize: representation.dimensions.cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.presentationData.theme.theme.list.mediaPlaceholderColor) iconImageApply = iconImageLayout(arguments) case .albumArt: let iconSize = CGSize(width: 40.0, height: 40.0) let imageCorners = ImageCorners(radius: iconSize.width / 2.0) - let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor) + let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.presentationData.theme.theme.list.mediaPlaceholderColor) iconImageApply = iconImageLayout(arguments) case let .roundVideo(file): let iconSize = CGSize(width: 40.0, height: 40.0) let imageCorners = ImageCorners(radius: iconSize.width / 2.0) - let arguments = TransformImageArguments(corners: imageCorners, imageSize: (file.dimensions ?? PixelDimensions(width: 320, height: 320)).cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor) + let arguments = TransformImageArguments(corners: imageCorners, imageSize: (file.dimensions ?? PixelDimensions(width: 320, height: 320)).cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.presentationData.theme.theme.list.mediaPlaceholderColor) iconImageApply = iconImageLayout(arguments) } } @@ -532,7 +616,7 @@ final class ListMessageFileItemNode: ListMessageNode { case let .imageRepresentation(file, representation): updateIconImageSignal = chatWebpageSnippetFile(account: item.context.account, fileReference: .message(message: MessageReference(message), media: file), representation: representation) case let .albumArt(file, albumArt): - updateIconImageSignal = playerAlbumArt(postbox: item.context.account.postbox, fileReference: .message(message: MessageReference(message), media: file), albumArt: albumArt, thumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3), emptyColor: item.theme.list.itemAccentColor) + updateIconImageSignal = playerAlbumArt(postbox: item.context.account.postbox, fileReference: .message(message: MessageReference(message), media: file), albumArt: albumArt, thumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3), emptyColor: item.presentationData.theme.theme.list.itemAccentColor) case let .roundVideo(file): updateIconImageSignal = mediaGridMessageVideo(postbox: item.context.account.postbox, videoReference: FileMediaReference.message(message: MessageReference(message), media: file), autoFetchFullSizeThumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3)) } @@ -583,9 +667,9 @@ final class ListMessageFileItemNode: ListMessageNode { strongSelf.currentLeftOffset = leftOffset if let _ = updatedTheme { - strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor - strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor - strongSelf.linearProgressNode?.updateTheme(theme: item.theme) + strongSelf.separatorNode.backgroundColor = item.presentationData.theme.theme.list.itemPlainSeparatorColor + strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.theme.list.itemHighlightedBackgroundColor + strongSelf.linearProgressNode?.updateTheme(theme: item.presentationData.theme.theme) } if let (selectionWidth, selectionApply) = selectionNodeWidthAndApply { @@ -632,6 +716,10 @@ final class ListMessageFileItemNode: ListMessageNode { transition.updateFrame(node: strongSelf.descriptionNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset, y: strongSelf.titleNode.frame.maxY + 1.0), size: descriptionNodeLayout.size)) let _ = descriptionNodeApply() + let _ = dateNodeApply() + transition.updateFrame(node: strongSelf.dateNode, frame: CGRect(origin: CGPoint(x: params.width - rightInset - dateNodeLayout.size.width, y: 11.0), size: dateNodeLayout.size)) + strongSelf.dateNode.isHidden = !item.isGlobalSearchResult + let iconFrame: CGRect if isAudio { let iconSize = CGSize(width: 40.0, height: 40.0) @@ -732,8 +820,8 @@ final class ListMessageFileItemNode: ListMessageNode { var iconStatusForegroundColor: UIColor = .white if isVoice { - iconStatusBackgroundColor = item.theme.list.itemAccentColor - iconStatusForegroundColor = item.theme.list.itemCheckColors.foregroundColor + iconStatusBackgroundColor = item.presentationData.theme.theme.list.itemAccentColor + iconStatusForegroundColor = item.presentationData.theme.theme.list.itemCheckColors.foregroundColor } if !isAudio && !isInstantVideo { @@ -767,7 +855,7 @@ final class ListMessageFileItemNode: ListMessageNode { self.iconStatusNode.transitionToState(iconStatusState) } - override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { + override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { super.setHighlighted(highlighted, at: point, animated: animated) if highlighted, let item = self.item, case .none = item.selection { @@ -793,7 +881,7 @@ final class ListMessageFileItemNode: ListMessageNode { } } - override func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if let item = self.item, item.message.id == id, self.iconImageNode.supernode != nil { let iconImageNode = self.iconImageNode return (self.iconImageNode, self.iconImageNode.bounds, { [weak iconImageNode] in @@ -803,15 +891,15 @@ final class ListMessageFileItemNode: ListMessageNode { return nil } - override func updateHiddenMedia() { - if let controllerInteraction = self.controllerInteraction, let item = self.item, controllerInteraction.hiddenMedia[item.message.id] != nil { + override public func updateHiddenMedia() { + if let interaction = self.interaction, let item = self.item, interaction.getHiddenMedia()[item.message.id] != nil { self.iconImageNode.isHidden = true } else { self.iconImageNode.isHidden = false } } - override func updateSelectionState(animated: Bool) { + override public func updateSelectionState(animated: Bool) { } private func updateProgressFrame(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { @@ -831,7 +919,7 @@ final class ListMessageFileItemNode: ListMessageNode { switch fetchStatus { case let .Fetching(_, progress): if let file = self.currentMedia as? TelegramMediaFile, let size = file.size { - downloadingString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, decimalSeparator: item.dateTimeFormat.decimalSeparator)) / \(dataSizeString(size, forceDecimal: true, decimalSeparator: item.dateTimeFormat.decimalSeparator))" + downloadingString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)) / \(dataSizeString(size, forceDecimal: true, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator))" } descriptionOffset = 14.0 case .Remote: @@ -849,7 +937,7 @@ final class ListMessageFileItemNode: ListMessageNode { linearProgressNode = current } else { linearProgressNode = LinearProgressNode() - linearProgressNode.updateTheme(theme: item.theme) + linearProgressNode.updateTheme(theme: item.presentationData.theme.theme) self.linearProgressNode = linearProgressNode self.addSubnode(linearProgressNode) } @@ -859,7 +947,7 @@ final class ListMessageFileItemNode: ListMessageNode { if self.downloadStatusIconNode.supernode == nil { self.offsetContainerNode.addSubnode(self.downloadStatusIconNode) } - self.downloadStatusIconNode.image = PresentationResourcesChat.sharedMediaFileDownloadPauseIcon(item.theme) + self.downloadStatusIconNode.image = PresentationResourcesChat.sharedMediaFileDownloadPauseIcon(item.presentationData.theme.theme) case .Local: if let linearProgressNode = self.linearProgressNode { self.linearProgressNode = nil @@ -883,7 +971,7 @@ final class ListMessageFileItemNode: ListMessageNode { if self.downloadStatusIconNode.supernode == nil { self.offsetContainerNode.addSubnode(self.downloadStatusIconNode) } - self.downloadStatusIconNode.image = PresentationResourcesChat.sharedMediaFileDownloadStartIcon(item.theme) + self.downloadStatusIconNode.image = PresentationResourcesChat.sharedMediaFileDownloadStartIcon(item.presentationData.theme.theme) } } else { if let linearProgressNode = self.linearProgressNode { @@ -911,8 +999,8 @@ final class ListMessageFileItemNode: ListMessageNode { self.descriptionProgressNode.isHidden = true self.descriptionNode.isHidden = false } - let descriptionFont = Font.regular(floor(item.fontSize.baseDisplaySize * 13.0 / 17.0)) - self.descriptionProgressNode.attributedText = NSAttributedString(string: downloadingString ?? "", font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor) + let descriptionFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 13.0 / 17.0)) + self.descriptionProgressNode.attributedText = NSAttributedString(string: downloadingString ?? "", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor) let descriptionSize = self.descriptionProgressNode.updateLayout(CGSize(width: size.width - 14.0, height: size.height)) transition.updateFrame(node: self.descriptionProgressNode, frame: CGRect(origin: self.descriptionNode.frame.origin, size: descriptionSize)) @@ -936,8 +1024,8 @@ final class ListMessageFileItemNode: ListMessageNode { fetch() } case .Local: - if let item = self.item, let controllerInteraction = self.controllerInteraction { - let _ = controllerInteraction.openMessage(item.message, .default) + if let item = self.item, let interaction = self.interaction { + let _ = interaction.openMessage(item.message, .default) } } case .playbackStatus: @@ -948,11 +1036,11 @@ final class ListMessageFileItemNode: ListMessageNode { } } - override func header() -> ListViewItemHeader? { + override public func header() -> ListViewItemHeader? { return self.item?.header } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let item = self.item, case .selectable = item.selection { if self.bounds.contains(point) { return self.view diff --git a/submodules/TelegramUI/Sources/ListMessageHoleItem.swift b/submodules/ListMessageItem/Sources/ListMessageHoleItem.swift similarity index 100% rename from submodules/TelegramUI/Sources/ListMessageHoleItem.swift rename to submodules/ListMessageItem/Sources/ListMessageHoleItem.swift diff --git a/submodules/TelegramUI/Sources/ListMessageItem.swift b/submodules/ListMessageItem/Sources/ListMessageItem.swift similarity index 61% rename from submodules/TelegramUI/Sources/ListMessageItem.swift rename to submodules/ListMessageItem/Sources/ListMessageItem.swift index b3264a7cad..dfd0e4ef98 100644 --- a/submodules/TelegramUI/Sources/ListMessageItem.swift +++ b/submodules/ListMessageItem/Sources/ListMessageItem.swift @@ -10,51 +10,73 @@ import TelegramPresentationData import AccountContext import TelegramUIPreferences -final class ListMessageItem: ListViewItem { - let theme: PresentationTheme - let strings: PresentationStrings - let fontSize: PresentationFontSize - let dateTimeFormat: PresentationDateTimeFormat +public final class ListMessageItemInteraction { + let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool + let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void + let toggleMessagesSelection: ([MessageId], Bool) -> Void + let openUrl: (String, Bool, Bool?, Message?) -> Void + let openInstantPage: (Message, ChatMessageItemAssociatedData?) -> Void + let longTap: (ChatControllerInteractionLongTapAction, Message?) -> Void + let getHiddenMedia: () -> [MessageId: [Media]] + + public init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, getHiddenMedia: @escaping () -> [MessageId: [Media]]) { + self.openMessage = openMessage + self.openMessageContextMenu = openMessageContextMenu + self.toggleMessagesSelection = toggleMessagesSelection + self.openUrl = openUrl + self.openInstantPage = openInstantPage + self.longTap = longTap + self.getHiddenMedia = getHiddenMedia + } +} + +public final class ListMessageItem: ListViewItem { + let presentationData: ChatPresentationData let context: AccountContext let chatLocation: ChatLocation - let controllerInteraction: ChatControllerInteraction + let interaction: ListMessageItemInteraction let message: Message let selection: ChatHistoryMessageSelection + let hintIsLink: Bool + let isGlobalSearchResult: Bool - let header: ListMessageDateHeader? + let header: ListViewItemHeader? - let selectable: Bool = true + public let selectable: Bool = true - public init(theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, context: AccountContext, chatLocation: ChatLocation, controllerInteraction: ChatControllerInteraction, message: Message, selection: ChatHistoryMessageSelection, displayHeader: Bool) { - self.theme = theme - self.strings = strings - self.fontSize = fontSize - self.dateTimeFormat = dateTimeFormat + public init(presentationData: ChatPresentationData, context: AccountContext, chatLocation: ChatLocation, interaction: ListMessageItemInteraction, message: Message, selection: ChatHistoryMessageSelection, displayHeader: Bool, customHeader: ListViewItemHeader? = nil, hintIsLink: Bool = false, isGlobalSearchResult: Bool = false) { + self.presentationData = presentationData self.context = context self.chatLocation = chatLocation - self.controllerInteraction = controllerInteraction + self.interaction = interaction self.message = message - if displayHeader { - self.header = ListMessageDateHeader(timestamp: message.timestamp, theme: theme, strings: strings, fontSize: fontSize) + if let header = customHeader { + self.header = header + } else if displayHeader { + self.header = ListMessageDateHeader(timestamp: message.timestamp, theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize) } else { self.header = nil } self.selection = selection + self.hintIsLink = hintIsLink + self.isGlobalSearchResult = isGlobalSearchResult } public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { var viewClassName: AnyClass = ListMessageSnippetItemNode.self - for media in message.media { - if let _ = media as? TelegramMediaFile { - viewClassName = ListMessageFileItemNode.self - break + if !self.hintIsLink { + for media in self.message.media { + if let _ = media as? TelegramMediaFile { + viewClassName = ListMessageFileItemNode.self + break + } } } let configure = { () -> Void in let node = (viewClassName as! ListMessageNode.Type).init() - node.controllerInteraction = self.controllerInteraction + node.interaction = self.interaction node.setupItem(self) let nodeLayout = node.asyncLayout() @@ -106,11 +128,11 @@ final class ListMessageItem: ListViewItem { } } - func selected(listView: ListView) { + public func selected(listView: ListView) { listView.clearHighlightAnimated(true) if case let .selectable(selected) = self.selection { - self.controllerInteraction.toggleMessagesSelection([self.message.id], !selected) + self.interaction.toggleMessagesSelection([self.message.id], !selected) } else { listView.forEachItemNode { itemNode in if let itemNode = itemNode as? ListMessageFileItemNode { diff --git a/submodules/TelegramUI/Sources/ListMessageNode.swift b/submodules/ListMessageItem/Sources/ListMessageNode.swift similarity index 56% rename from submodules/TelegramUI/Sources/ListMessageNode.swift rename to submodules/ListMessageItem/Sources/ListMessageNode.swift index f110ca203e..7c3cea93b0 100644 --- a/submodules/TelegramUI/Sources/ListMessageNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageNode.swift @@ -3,10 +3,11 @@ import UIKit import Display import AsyncDisplayKit import Postbox +import AccountContext -class ListMessageNode: ListViewItemNode { +public class ListMessageNode: ListViewItemNode { var item: ListMessageItem? - var controllerInteraction: ChatControllerInteraction? + var interaction: ListMessageItemInteraction? required init() { super.init(layerBacked: false, dynamicBounce: false) @@ -19,7 +20,7 @@ class ListMessageNode: ListViewItemNode { override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { } - func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) { + public func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) { return { _, params, _, _, _ in return (ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 1.0), insets: UIEdgeInsets()), { _ in @@ -27,13 +28,13 @@ class ListMessageNode: ListViewItemNode { } } - func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + public func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } - func updateHiddenMedia() { + public func updateHiddenMedia() { } - func updateSelectionState(animated: Bool) { + public func updateSelectionState(animated: Bool) { } } diff --git a/submodules/TelegramUI/Sources/ListMessageSnippetItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift similarity index 73% rename from submodules/TelegramUI/Sources/ListMessageSnippetItemNode.swift rename to submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift index 773aadf8af..7d4706b13b 100644 --- a/submodules/TelegramUI/Sources/ListMessageSnippetItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift @@ -13,12 +13,15 @@ import TextFormat import PhotoResources import WebsiteType import UrlHandling +import UrlWhitelist +import AccountContext +import TelegramStringFormatting private let iconFont = Font.with(size: 30.0, design: .round, traits: [.bold]) private let iconTextBackgroundImage = generateStretchableFilledCircleImage(radius: 6.0, color: UIColor(rgb: 0xFF9500)) -final class ListMessageSnippetItemNode: ListMessageNode { +public final class ListMessageSnippetItemNode: ListMessageNode { private let contextSourceNode: ContextExtractedContentContainingNode private let containerNode: ContextControllerSourceNode private let extractedBackgroundImageNode: ASImageNode @@ -34,9 +37,11 @@ final class ListMessageSnippetItemNode: ListMessageNode { private let titleNode: TextNode private let descriptionNode: TextNode + private let dateNode: TextNode private let instantViewIconNode: ASImageNode private let linkNode: TextNode private var linkHighlightingNode: LinkHighlightingNode? + private let authorNode: TextNode private let iconTextBackgroundNode: ASImageNode private let iconTextNode: TextNode @@ -44,7 +49,7 @@ final class ListMessageSnippetItemNode: ListMessageNode { private var currentIconImageRepresentation: TelegramMediaImageRepresentation? private var currentMedia: Media? - var currentPrimaryUrl: String? + public var currentPrimaryUrl: String? private var currentIsInstantView: Bool? private var appliedItem: ListMessageItem? @@ -72,6 +77,9 @@ final class ListMessageSnippetItemNode: ListMessageNode { self.descriptionNode = TextNode() self.descriptionNode.isUserInteractionEnabled = false + self.dateNode = TextNode() + self.dateNode.isUserInteractionEnabled = false + self.instantViewIconNode = ASImageNode() self.instantViewIconNode.isLayerBacked = true self.instantViewIconNode.displaysAsynchronously = false @@ -90,6 +98,9 @@ final class ListMessageSnippetItemNode: ListMessageNode { self.iconImageNode = TransformImageNode() self.iconImageNode.displaysAsynchronously = false + self.authorNode = TextNode() + self.authorNode.isUserInteractionEnabled = false + super.init() self.addSubnode(self.separatorNode) @@ -102,16 +113,18 @@ final class ListMessageSnippetItemNode: ListMessageNode { self.contextSourceNode.contentNode.addSubnode(self.offsetContainerNode) self.offsetContainerNode.addSubnode(self.titleNode) self.offsetContainerNode.addSubnode(self.descriptionNode) + self.offsetContainerNode.addSubnode(self.dateNode) self.offsetContainerNode.addSubnode(self.linkNode) self.offsetContainerNode.addSubnode(self.instantViewIconNode) self.offsetContainerNode.addSubnode(self.iconImageNode) + self.offsetContainerNode.addSubnode(self.authorNode) self.containerNode.activated = { [weak self] gesture, _ in guard let strongSelf = self, let item = strongSelf.item else { return } - item.controllerInteraction.openMessageContextMenu(item.message, false, strongSelf.contextSourceNode, strongSelf.contextSourceNode.bounds, gesture) + item.interaction.openMessageContextMenu(item.message, false, strongSelf.contextSourceNode, strongSelf.contextSourceNode.bounds, gesture) } self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in @@ -120,7 +133,7 @@ final class ListMessageSnippetItemNode: ListMessageNode { } if isExtracted { - strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.theme.list.plainBackgroundColor) + strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.presentationData.theme.theme.list.plainBackgroundColor) } if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect { @@ -135,6 +148,7 @@ final class ListMessageSnippetItemNode: ListMessageNode { self?.extractedBackgroundImageNode.image = nil } }) + transition.updateAlpha(node: strongSelf.dateNode, alpha: isExtracted ? 0.0 : 1.0) } } @@ -142,7 +156,7 @@ final class ListMessageSnippetItemNode: ListMessageNode { fatalError("init(coder:) has not been implemented") } - override func didLoad() { + override public func didLoad() { super.didLoad() let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) @@ -182,17 +196,19 @@ final class ListMessageSnippetItemNode: ListMessageNode { self.addTransitionOffsetAnimation(0.0, duration: duration, beginAt: currentTimestamp) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) } - override func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) { + override public func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) { let titleNodeMakeLayout = TextNode.asyncLayout(self.titleNode) let descriptionNodeMakeLayout = TextNode.asyncLayout(self.descriptionNode) let linkNodeMakeLayout = TextNode.asyncLayout(self.linkNode) + let dateNodeMakeLayout = TextNode.asyncLayout(self.dateNode) let iconTextMakeLayout = TextNode.asyncLayout(self.iconTextNode) let iconImageLayout = self.iconImageNode.asyncLayout() - + let authorNodeMakeLayout = TextNode.asyncLayout(self.authorNode) + let currentIconImageRepresentation = self.currentIconImageRepresentation let currentItem = self.appliedItem @@ -202,19 +218,21 @@ final class ListMessageSnippetItemNode: ListMessageNode { return { [weak self] item, params, _, _, dateHeaderAtBottom in var updatedTheme: PresentationTheme? - if currentItem?.theme !== item.theme { - updatedTheme = item.theme + if currentItem?.presentationData.theme.theme !== item.presentationData.theme.theme { + updatedTheme = item.presentationData.theme.theme } - let titleFont = Font.semibold(floor(item.fontSize.baseDisplaySize * 16.0 / 17.0)) - let descriptionFont = Font.regular(floor(item.fontSize.baseDisplaySize * 14.0 / 17.0)) + let titleFont = Font.semibold(floor(item.presentationData.fontSize.baseDisplaySize * 16.0 / 17.0)) + let descriptionFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)) + let dateFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) + let authorFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)) let leftInset: CGFloat = 65.0 + params.leftInset var leftOffset: CGFloat = 0.0 var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)? if case let .selectable(selected) = item.selection { - let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected, false) + let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.theme.list.itemCheckColors.fillColor, item.presentationData.theme.theme.list.itemCheckColors.foregroundColor, selected, false) selectionNodeWidthAndApply = (selectionWidth, selectionApply) leftOffset += selectionWidth } @@ -255,7 +273,7 @@ final class ListMessageSnippetItemNode: ListMessageNode { iconText = NSAttributedString(string: host[.. nsString.length { + range.location = max(0, nsString.length - range.length) + range.length = nsString.length - range.location + } + let tempTitleString = (nsString.substring(with: range) as String).trimmingCharacters(in: .whitespacesAndNewlines) + + var (urlString, concealed) = parseUrl(url: url, wasConcealed: false) + let rawUrlString = urlString + var parsedUrl = URL(string: urlString) + if parsedUrl == nil || parsedUrl!.host == nil || parsedUrl!.host!.isEmpty { + urlString = "http://" + urlString + parsedUrl = URL(string: urlString) + } + let host: String? = concealed ? urlString : parsedUrl?.host + if let url = parsedUrl, let host = host { + primaryUrl = urlString + title = NSAttributedString(string: tempTitleString as String, font: titleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor) + if url.path.hasPrefix("/addstickers/") { + iconText = NSAttributedString(string: "S", font: iconFont, textColor: UIColor.white) + } else { + iconText = NSAttributedString(string: host[.. (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if let item = self.item, item.message.id == id, self.iconImageNode.supernode != nil { let iconImageNode = self.iconImageNode return (self.iconImageNode, self.iconImageNode.bounds, { [weak iconImageNode] in @@ -564,15 +667,15 @@ final class ListMessageSnippetItemNode: ListMessageNode { return nil } - override func updateHiddenMedia() { - if let controllerInteraction = self.controllerInteraction, let item = self.item, controllerInteraction.hiddenMedia[item.message.id] != nil { + override public func updateHiddenMedia() { + if let interaction = self.interaction, let item = self.item, interaction.getHiddenMedia()[item.message.id] != nil { self.iconImageNode.isHidden = true } else { self.iconImageNode.isHidden = false } } - override func updateSelectionState(animated: Bool) { + override public func updateSelectionState(animated: Bool) { } func activateMedia() { @@ -580,30 +683,28 @@ final class ListMessageSnippetItemNode: ListMessageNode { if let webpage = self.currentMedia as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { if content.instantPage != nil { if websiteType(of: content.websiteName) == .instagram { - if !item.controllerInteraction.openMessage(item.message, .default) { - item.controllerInteraction.openInstantPage(item.message, nil) + if !item.interaction.openMessage(item.message, .default) { + item.interaction.openInstantPage(item.message, nil) } } else { - item.controllerInteraction.openInstantPage(item.message, nil) + item.interaction.openInstantPage(item.message, nil) } } else { - if isTelegramMeLink(content.url) || !item.controllerInteraction.openMessage(item.message, .link) { - item.controllerInteraction.openUrl(currentPrimaryUrl, false, false, nil) + if isTelegramMeLink(content.url) || !item.interaction.openMessage(item.message, .link) { + item.interaction.openUrl(currentPrimaryUrl, false, false, nil) } } } else { - if !item.controllerInteraction.openMessage(item.message, .default) { - item.controllerInteraction.openUrl(currentPrimaryUrl, false, false, nil) - } + item.interaction.openUrl(currentPrimaryUrl, false, false, nil) } } } - override func header() -> ListViewItemHeader? { + override public func header() -> ListViewItemHeader? { return self.item?.header } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let item = self.item, case .selectable = item.selection { if self.bounds.contains(point) { return self.view @@ -640,13 +741,13 @@ final class ListMessageSnippetItemNode: ListMessageNode { case .tap, .longTap: if let item = self.item, let url = self.urlAtPoint(location) { if case .longTap = gesture { - item.controllerInteraction.longTap(ChatControllerInteractionLongTapAction.url(url), item.message) + item.interaction.longTap(ChatControllerInteractionLongTapAction.url(url), item.message) } else if url == self.currentPrimaryUrl { - if !item.controllerInteraction.openMessage(item.message, .default) { - item.controllerInteraction.openUrl(url, false, false, nil) + if !item.interaction.openMessage(item.message, .default) { + item.interaction.openUrl(url, false, false, nil) } } else { - item.controllerInteraction.openUrl(url, false, true, nil) + item.interaction.openUrl(url, false, true, nil) } } case .hold, .doubleTap: @@ -683,7 +784,7 @@ final class ListMessageSnippetItemNode: ListMessageNode { if let current = self.linkHighlightingNode { linkHighlightingNode = current } else { - linkHighlightingNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.theme.chat.message.incoming.linkHighlightColor : item.theme.chat.message.outgoing.linkHighlightColor) + linkHighlightingNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor) self.linkHighlightingNode = linkHighlightingNode self.offsetContainerNode.insertSubnode(linkHighlightingNode, belowSubnode: self.linkNode) } diff --git a/submodules/LiveLocationManager/Sources/LiveLocationManager.swift b/submodules/LiveLocationManager/Sources/LiveLocationManager.swift index acc55b9a41..4c31f5589d 100644 --- a/submodules/LiveLocationManager/Sources/LiveLocationManager.swift +++ b/submodules/LiveLocationManager/Sources/LiveLocationManager.swift @@ -250,7 +250,7 @@ public final class LiveLocationManagerImpl: LiveLocationManager { updatedMedia[i] = TelegramMediaMap(latitude: media.latitude, longitude: media.longitude, geoPlace: media.geoPlace, venue: media.venue, liveBroadcastingTimeout: max(0, timestamp - currentMessage.timestamp - 1)) } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: updatedMedia)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: updatedMedia)) }) } }).start() diff --git a/submodules/MediaResources/BUCK b/submodules/MediaResources/BUCK index 681d3aabaf..8635efccea 100644 --- a/submodules/MediaResources/BUCK +++ b/submodules/MediaResources/BUCK @@ -10,6 +10,7 @@ static_library( "//submodules/SyncCore:SyncCore#shared", "//submodules/Postbox:Postbox#shared", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/MediaResources/BUILD b/submodules/MediaResources/BUILD index 146fc750fd..7d8503018d 100644 --- a/submodules/MediaResources/BUILD +++ b/submodules/MediaResources/BUILD @@ -11,6 +11,7 @@ swift_library( "//submodules/SyncCore:SyncCore", "//submodules/Postbox:Postbox", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Sources/MediaPlaybackStoredState.swift b/submodules/MediaResources/Sources/MediaPlaybackStoredState.swift similarity index 100% rename from submodules/TelegramUI/Sources/MediaPlaybackStoredState.swift rename to submodules/MediaResources/Sources/MediaPlaybackStoredState.swift diff --git a/submodules/MtProtoKit/BUCK b/submodules/MtProtoKit/BUCK index 215e91f784..e7c782ef4e 100644 --- a/submodules/MtProtoKit/BUCK +++ b/submodules/MtProtoKit/BUCK @@ -3,10 +3,10 @@ load("//Config:buck_rule_macros.bzl", "static_library", "framework", "glob_map", framework( name = "MtProtoKit", srcs = glob([ - "Sources/*.m", + "Sources/**/*.m", ]), headers = glob([ - "Sources/*.h", + "Sources/**/*.h", ]), exported_headers = glob([ "PublicHeaders/**/*.h", diff --git a/submodules/MtProtoKit/BUILD b/submodules/MtProtoKit/BUILD index cdc4b42171..c316d024a3 100644 --- a/submodules/MtProtoKit/BUILD +++ b/submodules/MtProtoKit/BUILD @@ -4,8 +4,8 @@ objc_library( enable_modules = True, module_name = "MtProtoKit", srcs = glob([ - "Sources/*.m", - "Sources/*.h", + "Sources/**/*.m", + "Sources/**/*.h", ]), hdrs = glob([ "PublicHeaders/**/*.h", diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTBindKeyMessageService.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTBindKeyMessageService.h new file mode 100644 index 0000000000..9e5771a203 --- /dev/null +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTBindKeyMessageService.h @@ -0,0 +1,9 @@ +#import +#import +#import + +@interface MTBindKeyMessageService : NSObject + +- (instancetype)initWithPersistentKey:(MTDatacenterAuthKey *)persistentKey ephemeralKey:(MTDatacenterAuthKey *)ephemeralKey completion:(void (^)(bool))completion; + +@end diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h index c4cff82ef3..c4898b32db 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h @@ -20,7 +20,7 @@ @optional - (void)contextDatacenterAddressSetUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId addressSet:(MTDatacenterAddressSet *)addressSet; -- (void)contextDatacenterAuthInfoUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo; +- (void)contextDatacenterAuthInfoUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo selector:(MTDatacenterAuthInfoSelector)selector; - (void)contextDatacenterAuthTokenUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authToken:(id)authToken; - (void)contextDatacenterTransportSchemesUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId shouldReset:(bool)shouldReset; - (void)contextIsPasswordRequiredUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId; @@ -49,6 +49,7 @@ @property (nonatomic, strong, readonly) MTApiEnvironment *apiEnvironment; @property (nonatomic, readonly) bool isTestingEnvironment; @property (nonatomic, readonly) bool useTempAuthKeys; +@property (nonatomic) int32_t tempKeyExpiration; + (int32_t)fixedTimeDifference; + (void)setFixedTimeDifference:(int32_t)fixedTimeDifference; @@ -73,7 +74,7 @@ - (void)updateAddressSetForDatacenterWithId:(NSInteger)datacenterId addressSet:(MTDatacenterAddressSet *)addressSet forceUpdateSchemes:(bool)forceUpdateSchemes; - (void)addAddressForDatacenterWithId:(NSInteger)datacenterId address:(MTDatacenterAddress *)address; - (void)updateTransportSchemeForDatacenterWithId:(NSInteger)datacenterId transportScheme:(MTTransportScheme *)transportScheme media:(bool)media isProxy:(bool)isProxy; -- (void)updateAuthInfoForDatacenterWithId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo; +- (void)updateAuthInfoForDatacenterWithId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo selector:(MTDatacenterAuthInfoSelector)selector; - (bool)isPasswordInputRequiredForDatacenterWithId:(NSInteger)datacenterId; - (bool)updatePasswordInputRequiredForDatacenterWithId:(NSInteger)datacenterId required:(bool)required; @@ -95,7 +96,7 @@ - (void)transportSchemeForDatacenterWithIdRequired:(NSInteger)datacenterId media:(bool)media; - (void)invalidateTransportSchemeForDatacenterId:(NSInteger)datacenterId transportScheme:(MTTransportScheme *)transportScheme isProbablyHttp:(bool)isProbablyHttp media:(bool)media; - (void)revalidateTransportSchemeForDatacenterId:(NSInteger)datacenterId transportScheme:(MTTransportScheme *)transportScheme media:(bool)media; -- (MTDatacenterAuthInfo *)authInfoForDatacenterWithId:(NSInteger)datacenterId; +- (MTDatacenterAuthInfo *)authInfoForDatacenterWithId:(NSInteger)datacenterId selector:(MTDatacenterAuthInfoSelector)selector; - (NSArray *)publicKeysForDatacenterWithId:(NSInteger)datacenterId; - (void)updatePublicKeysForDatacenterWithId:(NSInteger)datacenterId publicKeys:(NSArray *)publicKeys; @@ -107,8 +108,7 @@ - (void)updateAuthTokenForDatacenterWithId:(NSInteger)datacenterId authToken:(id)authToken; - (void)addressSetForDatacenterWithIdRequired:(NSInteger)datacenterId; -- (void)authInfoForDatacenterWithIdRequired:(NSInteger)datacenterId isCdn:(bool)isCdn; -- (void)tempAuthKeyForDatacenterWithIdRequired:(NSInteger)datacenterId keyType:(MTDatacenterAuthTempKeyType)keyType; +- (void)authInfoForDatacenterWithIdRequired:(NSInteger)datacenterId isCdn:(bool)isCdn selector:(MTDatacenterAuthInfoSelector)selector; - (void)authTokenForDatacenterWithIdRequired:(NSInteger)datacenterId authToken:(id)authToken masterDatacenterId:(NSInteger)masterDatacenterId; - (void)reportProblemsWithDatacenterAddressForId:(NSInteger)datacenterId address:(MTDatacenterAddress *)address; diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTDatacenterAuthAction.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTDatacenterAuthAction.h index c9643cdaf3..df67730fc7 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTDatacenterAuthAction.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTDatacenterAuthAction.h @@ -4,23 +4,12 @@ @class MTContext; -@class MTDatacenterAuthAction; - -@protocol MTDatacenterAuthActionDelegate - -- (void)datacenterAuthActionCompleted:(MTDatacenterAuthAction *)action; - -@end @interface MTDatacenterAuthAction : NSObject -@property (nonatomic, readonly) bool tempAuth; -@property (nonatomic, weak) id delegate; -@property (nonatomic, copy) void (^completedWithResult)(bool); +- (instancetype)initWithAuthKeyInfoSelector:(MTDatacenterAuthInfoSelector)authKeyInfoSelector isCdn:(bool)isCdn completion:(void (^)(MTDatacenterAuthAction *, bool))completion; -- (instancetype)initWithTempAuth:(bool)tempAuth tempAuthKeyType:(MTDatacenterAuthTempKeyType)tempAuthKeyType bindKey:(MTDatacenterAuthKey *)bindKey; - -- (void)execute:(MTContext *)context datacenterId:(NSInteger)datacenterId isCdn:(bool)isCdn; +- (void)execute:(MTContext *)context datacenterId:(NSInteger)datacenterId; - (void)cancel; @end diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTDatacenterAuthInfo.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTDatacenterAuthInfo.h index ae73123a64..26c7c7ea3f 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTDatacenterAuthInfo.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTDatacenterAuthInfo.h @@ -1,10 +1,5 @@ #import -typedef NS_ENUM(NSUInteger, MTDatacenterAuthTempKeyType) { - MTDatacenterAuthTempKeyTypeMain, - MTDatacenterAuthTempKeyTypeMedia -}; - @interface MTDatacenterAuthKey: NSObject @property (nonatomic, strong, readonly) NSData *authKey; @@ -15,24 +10,24 @@ typedef NS_ENUM(NSUInteger, MTDatacenterAuthTempKeyType) { @end +typedef NS_ENUM(int64_t, MTDatacenterAuthInfoSelector) { + MTDatacenterAuthInfoSelectorPersistent = 0, + MTDatacenterAuthInfoSelectorEphemeralMain, + MTDatacenterAuthInfoSelectorEphemeralMedia +}; + @interface MTDatacenterAuthInfo : NSObject @property (nonatomic, strong, readonly) NSData *authKey; @property (nonatomic, readonly) int64_t authKeyId; @property (nonatomic, strong, readonly) NSArray *saltSet; @property (nonatomic, strong, readonly) NSDictionary *authKeyAttributes; -@property (nonatomic, strong, readonly) MTDatacenterAuthKey *mainTempAuthKey; -@property (nonatomic, strong, readonly) MTDatacenterAuthKey *mediaTempAuthKey; -@property (nonatomic, strong, readonly) MTDatacenterAuthKey *persistentAuthKey; - -- (instancetype)initWithAuthKey:(NSData *)authKey authKeyId:(int64_t)authKeyId saltSet:(NSArray *)saltSet authKeyAttributes:(NSDictionary *)authKeyAttributes mainTempAuthKey:(MTDatacenterAuthKey *)mainTempAuthKey mediaTempAuthKey:(MTDatacenterAuthKey *)mediaTempAuthKey; +- (instancetype)initWithAuthKey:(NSData *)authKey authKeyId:(int64_t)authKeyId saltSet:(NSArray *)saltSet authKeyAttributes:(NSDictionary *)authKeyAttributes; - (int64_t)authSaltForMessageId:(int64_t)messageId; - (MTDatacenterAuthInfo *)mergeSaltSet:(NSArray *)updatedSaltSet forTimestamp:(NSTimeInterval)timestamp; - (MTDatacenterAuthInfo *)withUpdatedAuthKeyAttributes:(NSDictionary *)authKeyAttributes; -- (MTDatacenterAuthKey *)tempAuthKeyWithType:(MTDatacenterAuthTempKeyType)type; -- (MTDatacenterAuthInfo *)withUpdatedTempAuthKeyWithType:(MTDatacenterAuthTempKeyType)type key:(MTDatacenterAuthKey *)key; @end diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTMessageService.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTMessageService.h index 2b89f85ecf..761ed3029e 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTMessageService.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTMessageService.h @@ -1,11 +1,13 @@ #import +#import @class MTProto; @class MTIncomingMessage; @class MTMessageTransaction; @class MTApiEnvironment; +@class MTSessionInfo; @protocol MTMessageService @@ -15,10 +17,10 @@ - (void)mtProtoDidAddService:(MTProto *)mtProto; - (void)mtProtoDidRemoveService:(MTProto *)mtProto; - (void)mtProtoPublicKeysUpdated:(MTProto *)mtProto datacenterId:(NSInteger)datacenterId publicKeys:(NSArray *)publicKeys; -- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto; +- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo; - (void)mtProtoDidChangeSession:(MTProto *)mtProto; - (void)mtProtoServerDidChangeSession:(MTProto *)mtProto firstValidMessageId:(int64_t)firstValidMessageId otherValidMessageIds:(NSArray *)otherValidMessageIds; -- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message; +- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector; - (void)mtProto:(MTProto *)mtProto receivedQuickAck:(int32_t)quickAckId; - (void)mtProto:(MTProto *)mtProto transactionsMayHaveFailed:(NSArray *)transactionIds; - (void)mtProtoAllTransactionsMayHaveFailed:(MTProto *)mtProto; diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProto.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProto.h index 231d1b4a26..3405a7060b 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProto.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProto.h @@ -71,4 +71,6 @@ - (void)_messageResendRequestFailed:(int64_t)messageId; ++ (NSData *)_manuallyEncryptedMessage:(NSData *)preparedData messageId:(int64_t)messageId authKey:(MTDatacenterAuthKey *)authKey; + @end diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoEngine.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoEngine.h new file mode 100644 index 0000000000..a5e507e8de --- /dev/null +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoEngine.h @@ -0,0 +1,13 @@ +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MTProtoEngine : NSObject + +- (instancetype)initWithPersistenceInterface:(id)persistenceInterface; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoInstance.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoInstance.h new file mode 100644 index 0000000000..91a58939e5 --- /dev/null +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoInstance.h @@ -0,0 +1,12 @@ +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MTProtoInstance : NSObject + +- (instancetype)initWithEngine:(MTProtoEngine *)engine; + +@end + +NS_ASSUME_NONNULL_END diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoPersistenceInterface.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoPersistenceInterface.h new file mode 100644 index 0000000000..ecabfee72f --- /dev/null +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProtoPersistenceInterface.h @@ -0,0 +1,14 @@ +#import + +@class MTSignal; + +NS_ASSUME_NONNULL_BEGIN + +@protocol MTProtoPersistenceInterface + +- (MTSignal *)get:(NSData *)key; +- (void)set:(NSData *)key value:(NSData *)value; + +@end + +NS_ASSUME_NONNULL_END diff --git a/submodules/MtProtoKit/Sources/MTBindKeyMessageService.m b/submodules/MtProtoKit/Sources/MTBindKeyMessageService.m new file mode 100644 index 0000000000..6a9b69723b --- /dev/null +++ b/submodules/MtProtoKit/Sources/MTBindKeyMessageService.m @@ -0,0 +1,156 @@ +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import "MTInternalMessageParser.h" +#import "MTRpcResultMessage.h" +#import "MTBuffer.h" + +@interface MTBindKeyMessageService () { + MTDatacenterAuthKey *_persistentKey; + MTDatacenterAuthKey *_ephemeralKey; + void (^_completion)(bool); + + int64_t _currentMessageId; + id _currentTransactionId; +} + +@end + +@implementation MTBindKeyMessageService + +- (instancetype)initWithPersistentKey:(MTDatacenterAuthKey *)persistentKey ephemeralKey:(MTDatacenterAuthKey *)ephemeralKey completion:(void (^)(bool))completion { + self = [super init]; + if (self != nil) { + _persistentKey = persistentKey; + _ephemeralKey = ephemeralKey; + _completion = [completion copy]; + } + return self; +} + +- (void)mtProtoDidAddService:(MTProto *)mtProto +{ + [mtProto requestTransportTransaction]; +} + +- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo +{ + if (_currentTransactionId != nil) { + return nil; + } + + int64_t bindingMessageId = [sessionInfo generateClientMessageId:NULL]; + int32_t bindingSeqNo = [sessionInfo takeSeqNo:true]; + + int32_t expiresAt = (int32_t)([mtProto.context globalTime] + mtProto.context.tempKeyExpiration); + + int64_t randomId = 0; + arc4random_buf(&randomId, 8); + + int64_t nonce = 0; + arc4random_buf(&nonce, 8); + + MTBuffer *decryptedMessage = [[MTBuffer alloc] init]; + //bind_auth_key_inner#75a3f765 nonce:long temp_auth_key_id:long perm_auth_key_id:long temp_session_id:long expires_at:int = BindAuthKeyInner; + [decryptedMessage appendInt32:(int32_t)0x75a3f765]; + [decryptedMessage appendInt64:nonce]; + [decryptedMessage appendInt64:_ephemeralKey.authKeyId]; + [decryptedMessage appendInt64:_persistentKey.authKeyId]; + [decryptedMessage appendInt64:sessionInfo.sessionId]; + [decryptedMessage appendInt32:expiresAt]; + + NSData *encryptedMessage = [MTProto _manuallyEncryptedMessage:[decryptedMessage data] messageId:bindingMessageId authKey:_persistentKey]; + + MTBuffer *bindRequestData = [[MTBuffer alloc] init]; + + //auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int encrypted_message:bytes = Bool; + + [bindRequestData appendInt32:(int32_t)0xcdd42a05]; + [bindRequestData appendInt64:_persistentKey.authKeyId]; + [bindRequestData appendInt64:nonce]; + [bindRequestData appendInt32:expiresAt]; + [bindRequestData appendTLBytes:encryptedMessage]; + + MTOutgoingMessage *outgoingMessage = [[MTOutgoingMessage alloc] initWithData:bindRequestData.data metadata:[NSString stringWithFormat:@"auth.bindTempAuthKey"] additionalDebugDescription:nil shortMetadata:@"auth.bindTempAuthKey" messageId:bindingMessageId messageSeqNo:bindingSeqNo]; + + return [[MTMessageTransaction alloc] initWithMessagePayload:@[outgoingMessage] prepared:nil failed:nil completion:^(NSDictionary *messageInternalIdToTransactionId, NSDictionary *messageInternalIdToPreparedMessage, __unused NSDictionary *messageInternalIdToQuickAckId) { + MTPreparedMessage *preparedMessage = messageInternalIdToPreparedMessage[outgoingMessage.internalId]; + if (preparedMessage != nil && messageInternalIdToTransactionId[outgoingMessage.internalId] != nil) { + _currentMessageId = preparedMessage.messageId; + _currentTransactionId = messageInternalIdToTransactionId[outgoingMessage.internalId]; + } + }]; + + return nil; +} + +- (void)mtProto:(MTProto *)__unused mtProto messageDeliveryFailed:(int64_t)messageId { + if (messageId == _currentMessageId) { + _currentMessageId = 0; + _currentTransactionId = nil; + + [mtProto requestTransportTransaction]; + } +} + +- (void)mtProto:(MTProto *)mtProto transactionsMayHaveFailed:(NSArray *)transactionIds { + if (_currentTransactionId != nil && [transactionIds containsObject:_currentTransactionId]) { + _currentTransactionId = nil; + + [mtProto requestTransportTransaction]; + } +} + +- (void)mtProtoAllTransactionsMayHaveFailed:(MTProto *)mtProto { + if (_currentTransactionId != nil) { + _currentTransactionId = nil; + + [mtProto requestTransportTransaction]; + } +} + +- (void)mtProtoDidChangeSession:(MTProto *)mtProto { + _currentMessageId = 0; + _currentTransactionId = nil; + + [mtProto requestTransportTransaction]; +} + +- (void)mtProtoServerDidChangeSession:(MTProto *)mtProto firstValidMessageId:(int64_t)firstValidMessageId messageIdsInFirstValidContainer:(NSArray *)messageIdsInFirstValidContainer { + if (_currentMessageId != 0 && _currentMessageId < firstValidMessageId && ![messageIdsInFirstValidContainer containsObject:@(_currentMessageId)]) { + _currentMessageId = 0; + _currentTransactionId = nil; + + [mtProto requestTransportTransaction]; + } +} + +- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector { + if ([message.body isKindOfClass:[MTRpcResultMessage class]]) { + MTRpcResultMessage *rpcResultMessage = message.body; + if (rpcResultMessage.requestMessageId == _currentMessageId) { + bool success = false; + if (rpcResultMessage.data.length >= 4) { + uint32_t signature = 0; + [rpcResultMessage.data getBytes:&signature range:NSMakeRange(0, 4)]; + + //boolTrue#997275b5 = Bool; + if (signature == 0x997275b5U) { + success = true; + } + } + _completion(success); + } + } +} + +@end diff --git a/submodules/MtProtoKit/Sources/MTContext.m b/submodules/MtProtoKit/Sources/MTContext.m index 506a954d98..059f974907 100644 --- a/submodules/MtProtoKit/Sources/MTContext.m +++ b/submodules/MtProtoKit/Sources/MTContext.m @@ -114,7 +114,34 @@ @end -@interface MTContext () +typedef int64_t MTDatacenterAuthInfoMapKey; + +typedef struct { + int32_t datacenterId; + MTDatacenterAuthInfoSelector selector; +} MTDatacenterAuthInfoMapKeyStruct; + +static MTDatacenterAuthInfoMapKey authInfoMapKey(int32_t datacenterId, MTDatacenterAuthInfoSelector selector) { + int64_t result = (((int64_t)(selector)) << 32) | ((int64_t)(datacenterId)); + return result; +} + +static NSNumber *authInfoMapIntegerKey(int32_t datacenterId, MTDatacenterAuthInfoSelector selector) { + return [NSNumber numberWithLongLong:authInfoMapKey(datacenterId, selector)]; +} + +static MTDatacenterAuthInfoMapKeyStruct parseAuthInfoMapKey(int64_t key) { + MTDatacenterAuthInfoMapKeyStruct result; + result.datacenterId = (int32_t)(key & 0x7fffffff); + result.selector = (int32_t)(((key >> 32) & 0x7fffffff)); + return result; +} + +static MTDatacenterAuthInfoMapKeyStruct parseAuthInfoMapKeyInteger(NSNumber *key) { + return parseAuthInfoMapKey([key longLongValue]); +} + +@interface MTContext () { int64_t _uniqueId; @@ -127,7 +154,7 @@ NSMutableDictionary *> *_transportSchemeStats; MTTimer *_schemeStatsSyncTimer; - NSMutableDictionary *_datacenterAuthInfoById; + NSMutableDictionary *_datacenterAuthInfoById; NSMutableDictionary *_datacenterPublicKeysById; @@ -138,8 +165,7 @@ MTSignal *_discoverBackupAddressListSignal; NSMutableDictionary *_discoverDatacenterAddressActions; - NSMutableDictionary *_datacenterAuthActions; - NSMutableDictionary *_datacenterTempAuthActions; + NSMutableDictionary *_datacenterAuthActions; NSMutableDictionary *_datacenterTransferAuthActions; NSMutableDictionary *_datacenterCheckKeyRemovedActionTimestamps; @@ -201,6 +227,11 @@ static int32_t fixedTimeDifferenceValue = 0; _apiEnvironment = apiEnvironment; _isTestingEnvironment = isTestingEnvironment; _useTempAuthKeys = useTempAuthKeys; +#if DEBUG + _tempKeyExpiration = 1 * 60 * 60; +#else + _tempKeyExpiration = 1 * 60 * 60; +#endif _datacenterSeedAddressSetById = [[NSMutableDictionary alloc] init]; @@ -218,7 +249,6 @@ static int32_t fixedTimeDifferenceValue = 0; _discoverDatacenterAddressActions = [[NSMutableDictionary alloc] init]; _datacenterAuthActions = [[NSMutableDictionary alloc] init]; - _datacenterTempAuthActions = [[NSMutableDictionary alloc] init]; _datacenterTransferAuthActions = [[NSMutableDictionary alloc] init]; _datacenterCheckKeyRemovedActionTimestamps = [[NSMutableDictionary alloc] init]; _datacenterCheckKeyRemovedActions = [[NSMutableDictionary alloc] init]; @@ -258,9 +288,6 @@ static int32_t fixedTimeDifferenceValue = 0; NSDictionary *datacenterAuthActions = _datacenterAuthActions; _datacenterAuthActions = nil; - NSDictionary *datacenterTempAuthActions = _datacenterTempAuthActions; - _datacenterTempAuthActions = nil; - NSDictionary *discoverDatacenterAddressActions = _discoverDatacenterAddressActions; _discoverDatacenterAddressActions = nil; @@ -284,17 +311,9 @@ static int32_t fixedTimeDifferenceValue = 0; [action cancel]; } - for (NSNumber *nDatacenterId in datacenterAuthActions) + for (NSNumber *key in datacenterAuthActions) { - MTDatacenterAuthAction *action = datacenterAuthActions[nDatacenterId]; - action.delegate = nil; - [action cancel]; - } - - for (NSNumber *nDatacenterId in datacenterTempAuthActions) - { - MTDatacenterAuthAction *action = datacenterTempAuthActions[nDatacenterId]; - action.delegate = nil; + MTDatacenterAuthAction *action = datacenterAuthActions[key]; [action cancel]; } @@ -360,6 +379,14 @@ static int32_t fixedTimeDifferenceValue = 0; NSDictionary *datacenterAuthInfoById = [keychain objectForKey:@"datacenterAuthInfoById" group:@"persistent"]; if (datacenterAuthInfoById != nil) { _datacenterAuthInfoById = [[NSMutableDictionary alloc] initWithDictionary:datacenterAuthInfoById]; +#if DEBUG + /*NSArray *keys = [_datacenterAuthInfoById allKeys]; + for (NSNumber *key in keys) { + if (parseAuthInfoMapKeyInteger(key).selector != MTDatacenterAuthInfoSelectorPersistent) { + [_datacenterAuthInfoById removeObjectForKey:key]; + } + }*/ +#endif } NSDictionary *datacenterPublicKeysById = [keychain objectForKey:@"datacenterPublicKeysById" group:@"ephemeral"]; @@ -582,29 +609,46 @@ static int32_t fixedTimeDifferenceValue = 0; }]; } -- (void)updateAuthInfoForDatacenterWithId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo +- (void)updateAuthInfoForDatacenterWithId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo selector:(MTDatacenterAuthInfoSelector)selector { [[MTContext contextQueue] dispatchOnQueue:^ { if (datacenterId != 0) { - if (MTLogEnabled()) { - MTLog(@"[MTContext#%x: auth info updated for %d to %@]", (int)self, datacenterId, authInfo); - } + NSNumber *infoKey = authInfoMapIntegerKey((int32_t)datacenterId, selector); + + bool wasNil = _datacenterAuthInfoById[infoKey] == nil; if (authInfo != nil) { - _datacenterAuthInfoById[@(datacenterId)] = authInfo; + _datacenterAuthInfoById[infoKey] = authInfo; } else { - [_datacenterAuthInfoById removeObjectForKey:@(datacenterId)]; + if (_datacenterAuthInfoById[infoKey] == nil) { + return; + } + [_datacenterAuthInfoById removeObjectForKey:infoKey]; } + + if (MTLogEnabled()) { + MTLog(@"[MTContext#%x: auth info updated for %d selector %d to %@]", (int)self, datacenterId, selector, authInfo); + } + [_keychain setObject:_datacenterAuthInfoById forKey:@"datacenterAuthInfoById" group:@"persistent"]; NSArray *currentListeners = [[NSArray alloc] initWithArray:_changeListeners]; for (id listener in currentListeners) { - if ([listener respondsToSelector:@selector(contextDatacenterAuthInfoUpdated:datacenterId:authInfo:)]) - [listener contextDatacenterAuthInfoUpdated:self datacenterId:datacenterId authInfo:authInfo]; + if ([listener respondsToSelector:@selector(contextDatacenterAuthInfoUpdated:datacenterId:authInfo:selector:)]) + [listener contextDatacenterAuthInfoUpdated:self datacenterId:datacenterId authInfo:authInfo selector:selector]; + } + + if (wasNil && authInfo != nil && selector == MTDatacenterAuthInfoSelectorPersistent) { + for (NSNumber *key in _datacenterAuthActions) { + MTDatacenterAuthInfoMapKeyStruct parsedKey = parseAuthInfoMapKeyInteger(key); + if (parsedKey.datacenterId == datacenterId && parsedKey.selector != MTDatacenterAuthInfoSelectorPersistent) { + [_datacenterAuthActions[key] execute:self datacenterId:datacenterId]; + } + } } } }]; @@ -864,10 +908,11 @@ static int32_t fixedTimeDifferenceValue = 0; return results; } -- (MTDatacenterAuthInfo *)authInfoForDatacenterWithId:(NSInteger)datacenterId { +- (MTDatacenterAuthInfo *)authInfoForDatacenterWithId:(NSInteger)datacenterId selector:(MTDatacenterAuthInfoSelector)selector { __block MTDatacenterAuthInfo *result = nil; [[MTContext contextQueue] dispatchOnQueue:^{ - result = _datacenterAuthInfoById[@(datacenterId)]; + NSNumber *infoKey = authInfoMapIntegerKey((int32_t)datacenterId, selector); + result = _datacenterAuthInfoById[infoKey]; } synchronous:true]; return result; @@ -1251,48 +1296,44 @@ static int32_t fixedTimeDifferenceValue = 0; }]; } -- (void)authInfoForDatacenterWithIdRequired:(NSInteger)datacenterId isCdn:(bool)isCdn +- (void)authInfoForDatacenterWithIdRequired:(NSInteger)datacenterId isCdn:(bool)isCdn selector:(MTDatacenterAuthInfoSelector)selector { [[MTContext contextQueue] dispatchOnQueue:^ { - if (_datacenterAuthActions[@(datacenterId)] == nil) + NSNumber *infoKey = authInfoMapIntegerKey((int32_t)datacenterId, selector); + + if (_datacenterAuthActions[infoKey] == nil) { - MTDatacenterAuthAction *authAction = [[MTDatacenterAuthAction alloc] initWithTempAuth:false tempAuthKeyType:MTDatacenterAuthTempKeyTypeMain bindKey:nil]; - authAction.delegate = self; - _datacenterAuthActions[@(datacenterId)] = authAction; - [authAction execute:self datacenterId:datacenterId isCdn:isCdn]; - } - }]; -} - -- (void)tempAuthKeyForDatacenterWithIdRequired:(NSInteger)datacenterId keyType:(MTDatacenterAuthTempKeyType)keyType { - [[MTContext contextQueue] dispatchOnQueue:^{ - if (_datacenterTempAuthActions[@(datacenterId)] == nil) { - MTDatacenterAuthAction *authAction = [[MTDatacenterAuthAction alloc] initWithTempAuth:true tempAuthKeyType:keyType bindKey:nil]; - authAction.delegate = self; - _datacenterTempAuthActions[@(datacenterId)] = authAction; - [authAction execute:self datacenterId:datacenterId isCdn:false]; - } - }]; -} - -- (void)datacenterAuthActionCompleted:(MTDatacenterAuthAction *)action -{ - [[MTContext contextQueue] dispatchOnQueue:^ - { - if (action.tempAuth) { - for (NSNumber *nDatacenterId in _datacenterTempAuthActions) { - if (_datacenterTempAuthActions[nDatacenterId] == action) { - [_datacenterTempAuthActions removeObjectForKey:nDatacenterId]; + __weak MTContext *weakSelf = self; + MTDatacenterAuthAction *authAction = [[MTDatacenterAuthAction alloc] initWithAuthKeyInfoSelector:selector isCdn:isCdn completion:^(MTDatacenterAuthAction *action, __unused bool success) { + [[MTContext contextQueue] dispatchOnQueue:^{ + __strong MTContext *strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + for (NSNumber *key in _datacenterAuthActions) { + if (_datacenterAuthActions[key] == action) { + [_datacenterAuthActions removeObjectForKey:key]; + break; + } + } + }]; + }]; + _datacenterAuthActions[infoKey] = authAction; + + switch (selector) { + case MTDatacenterAuthInfoSelectorEphemeralMain: + case MTDatacenterAuthInfoSelectorEphemeralMedia: { + if ([self authInfoForDatacenterWithId:datacenterId selector:MTDatacenterAuthInfoSelectorPersistent] == nil) { + [self authInfoForDatacenterWithIdRequired:datacenterId isCdn:false selector:MTDatacenterAuthInfoSelectorPersistent]; + } else { + [authAction execute:self datacenterId:datacenterId]; + } break; } - } - } else { - for (NSNumber *nDatacenterId in _datacenterAuthActions) { - if (_datacenterAuthActions[nDatacenterId] == action) { - [_datacenterAuthActions removeObjectForKey:nDatacenterId]; - + default: { + [authAction execute:self datacenterId:datacenterId]; break; } } @@ -1357,21 +1398,11 @@ static int32_t fixedTimeDifferenceValue = 0; - (void)updatePeriodicTasks { - [[MTContext contextQueue] dispatchOnQueue:^ - { - int64_t saltsRequiredAtLeastUntilMessageId = (int64_t)(([self globalTime] + 24 * 60.0 * 60.0) * 4294967296); - - [_datacenterAuthInfoById enumerateKeysAndObjectsUsingBlock:^(NSNumber *nDatacenterId, MTDatacenterAuthInfo *authInfo, __unused BOOL *stop) - { - if ([authInfo authSaltForMessageId:saltsRequiredAtLeastUntilMessageId == 0]) { - } - }]; - }]; } - (void)checkIfLoggedOut:(NSInteger)datacenterId { [[MTContext contextQueue] dispatchOnQueue:^{ - MTDatacenterAuthInfo *authInfo = [self authInfoForDatacenterWithId:datacenterId]; + MTDatacenterAuthInfo *authInfo = [self authInfoForDatacenterWithId:datacenterId selector:MTDatacenterAuthInfoSelectorPersistent]; if (authInfo == nil || authInfo.authKey == nil) { return; } @@ -1382,7 +1413,7 @@ static int32_t fixedTimeDifferenceValue = 0; _datacenterCheckKeyRemovedActionTimestamps[@(datacenterId)] = currentTimestamp; [_datacenterCheckKeyRemovedActions[@(datacenterId)] dispose]; __weak MTContext *weakSelf = self; - _datacenterCheckKeyRemovedActions[@(datacenterId)] = [[MTDiscoverConnectionSignals checkIfAuthKeyRemovedWithContext:self datacenterId:datacenterId authKey:authInfo.authKey] startWithNext:^(NSNumber *isRemoved) { + _datacenterCheckKeyRemovedActions[@(datacenterId)] = [[MTDiscoverConnectionSignals checkIfAuthKeyRemovedWithContext:self datacenterId:datacenterId authKey:[[MTDatacenterAuthKey alloc] initWithAuthKey:authInfo.authKey authKeyId:authInfo.authKeyId notBound:false]] startWithNext:^(NSNumber* isRemoved) { [[MTContext contextQueue] dispatchOnQueue:^{ __strong MTContext *strongSelf = weakSelf; if (strongSelf == nil) { diff --git a/submodules/MtProtoKit/Sources/MTDatacenterAuthAction.m b/submodules/MtProtoKit/Sources/MTDatacenterAuthAction.m index 3896afcd51..736b69f546 100644 --- a/submodules/MtProtoKit/Sources/MTDatacenterAuthAction.m +++ b/submodules/MtProtoKit/Sources/MTDatacenterAuthAction.m @@ -12,13 +12,15 @@ #import #import #import +#import #import "MTBuffer.h" @interface MTDatacenterAuthAction () { + void (^_completion)(MTDatacenterAuthAction *, bool); + bool _isCdn; - MTDatacenterAuthTempKeyType _tempAuthKeyType; - MTDatacenterAuthKey *_bindKey; + MTDatacenterAuthInfoSelector _authKeyInfoSelector; NSInteger _datacenterId; __weak MTContext *_context; @@ -26,71 +28,61 @@ bool _awaitingAddresSetUpdate; MTProto *_authMtProto; MTProto *_bindMtProto; - - MTMetaDisposable *_verifyDisposable; } @end @implementation MTDatacenterAuthAction -- (instancetype)initWithTempAuth:(bool)tempAuth tempAuthKeyType:(MTDatacenterAuthTempKeyType)tempAuthKeyType bindKey:(MTDatacenterAuthKey *)bindKey { +- (instancetype)initWithAuthKeyInfoSelector:(MTDatacenterAuthInfoSelector)authKeyInfoSelector isCdn:(bool)isCdn completion:(void (^)(MTDatacenterAuthAction *, bool))completion { self = [super init]; if (self != nil) { - _tempAuth = tempAuth; - _tempAuthKeyType = tempAuthKeyType; - _bindKey = bindKey; - _verifyDisposable = [[MTMetaDisposable alloc] init]; + _authKeyInfoSelector = authKeyInfoSelector; + _isCdn = isCdn; + _completion = [completion copy]; } return self; } -- (void)dealloc -{ +- (void)dealloc { [self cleanup]; } -- (void)execute:(MTContext *)context datacenterId:(NSInteger)datacenterId isCdn:(bool)isCdn -{ +- (void)execute:(MTContext *)context datacenterId:(NSInteger)datacenterId { _datacenterId = datacenterId; _context = context; - _isCdn = isCdn; if (_datacenterId != 0 && context != nil) { bool alreadyCompleted = false; - MTDatacenterAuthInfo *currentAuthInfo = [context authInfoForDatacenterWithId:_datacenterId]; - if (currentAuthInfo != nil && _bindKey == nil) { - if (_tempAuth) { - if ([currentAuthInfo tempAuthKeyWithType:_tempAuthKeyType] != nil) { - alreadyCompleted = true; - } - } else { - alreadyCompleted = true; - } + + MTDatacenterAuthInfo *currentAuthInfo = [context authInfoForDatacenterWithId:_datacenterId selector:_authKeyInfoSelector]; + if (currentAuthInfo != nil) { + alreadyCompleted = true; } if (alreadyCompleted) { [self complete]; } else { _authMtProto = [[MTProto alloc] initWithContext:context datacenterId:_datacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0]; - _authMtProto.cdn = isCdn; + _authMtProto.cdn = _isCdn; _authMtProto.useUnauthorizedMode = true; - if (_tempAuth) { - switch (_tempAuthKeyType) { - case MTDatacenterAuthTempKeyTypeMain: - _authMtProto.media = false; - break; - case MTDatacenterAuthTempKeyTypeMedia: - _authMtProto.media = true; - _authMtProto.enforceMedia = true; - break; - default: - break; - } + bool tempAuth = false; + switch (_authKeyInfoSelector) { + case MTDatacenterAuthInfoSelectorEphemeralMain: + tempAuth = true; + _authMtProto.media = false; + break; + case MTDatacenterAuthInfoSelectorEphemeralMedia: + tempAuth = true; + _authMtProto.media = true; + _authMtProto.enforceMedia = true; + break; + default: + break; } - MTDatacenterAuthMessageService *authService = [[MTDatacenterAuthMessageService alloc] initWithContext:context tempAuth:_tempAuth]; + MTDatacenterAuthMessageService *authService = [[MTDatacenterAuthMessageService alloc] initWithContext:context tempAuth:tempAuth]; authService.delegate = self; [_authMtProto addMessageService:authService]; } @@ -104,45 +96,71 @@ } - (void)completeWithAuthKey:(MTDatacenterAuthKey *)authKey timestamp:(int64_t)timestamp { - if (_tempAuth) { - MTContext *mainContext = _context; - if (mainContext != nil) { - if (_bindKey != nil) { - _bindMtProto = [[MTProto alloc] initWithContext:mainContext datacenterId:_datacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0]; - _bindMtProto.cdn = false; - _bindMtProto.useUnauthorizedMode = false; - _bindMtProto.useTempAuthKeys = true; - __weak MTDatacenterAuthAction *weakSelf = self; - _bindMtProto.tempAuthKeyBindingResultUpdated = ^(bool success) { - __strong MTDatacenterAuthAction *strongSelf = weakSelf; - if (strongSelf == nil) { - return; + if (MTLogEnabled()) { + MTLog(@"[MTDatacenterAuthAction#%p@%p: completeWithAuthKey %lld selector %d]", self, _context, authKey.authKeyId, _authKeyInfoSelector); + } + + switch (_authKeyInfoSelector) { + case MTDatacenterAuthInfoSelectorPersistent: { + MTDatacenterAuthInfo *authInfo = [[MTDatacenterAuthInfo alloc] initWithAuthKey:authKey.authKey authKeyId:authKey.authKeyId saltSet:@[[[MTDatacenterSaltInfo alloc] initWithSalt:0 firstValidMessageId:timestamp lastValidMessageId:timestamp + (29.0 * 60.0) * 4294967296]] authKeyAttributes:nil]; + + MTContext *context = _context; + [context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:authInfo selector:_authKeyInfoSelector]; + [self complete]; + } + break; + + case MTDatacenterAuthInfoSelectorEphemeralMain: + case MTDatacenterAuthInfoSelectorEphemeralMedia: { + MTContext *mainContext = _context; + if (mainContext != nil) { + MTDatacenterAuthInfo *persistentAuthInfo = [mainContext authInfoForDatacenterWithId:_datacenterId selector:MTDatacenterAuthInfoSelectorPersistent]; + if (persistentAuthInfo != nil) { + _bindMtProto = [[MTProto alloc] initWithContext:mainContext datacenterId:_datacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0]; + _bindMtProto.cdn = false; + _bindMtProto.useUnauthorizedMode = false; + _bindMtProto.useTempAuthKeys = true; + _bindMtProto.useExplicitAuthKey = authKey; + + switch (_authKeyInfoSelector) { + case MTDatacenterAuthInfoSelectorEphemeralMain: + _bindMtProto.media = false; + break; + case MTDatacenterAuthInfoSelectorEphemeralMedia: + _bindMtProto.media = true; + _bindMtProto.enforceMedia = true; + break; + default: + break; } - [strongSelf->_bindMtProto stop]; - if (strongSelf->_completedWithResult) { - strongSelf->_completedWithResult(success); - } - }; - _bindMtProto.useExplicitAuthKey = authKey; - [_bindMtProto resume]; - } else { - MTContext *context = _context; - [context performBatchUpdates:^{ - MTDatacenterAuthInfo *authInfo = [context authInfoForDatacenterWithId:_datacenterId]; - if (authInfo != nil) { - authInfo = [authInfo withUpdatedTempAuthKeyWithType:_tempAuthKeyType key:authKey]; - [context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:authInfo]; - } - }]; - [self complete]; + + __weak MTDatacenterAuthAction *weakSelf = self; + [_bindMtProto addMessageService:[[MTBindKeyMessageService alloc] initWithPersistentKey:[[MTDatacenterAuthKey alloc] initWithAuthKey:persistentAuthInfo.authKey authKeyId:persistentAuthInfo.authKeyId notBound:false] ephemeralKey:authKey completion:^(bool success) { + __strong MTDatacenterAuthAction *strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + [strongSelf->_bindMtProto stop]; + + if (success) { + MTDatacenterAuthInfo *authInfo = [[MTDatacenterAuthInfo alloc] initWithAuthKey:authKey.authKey authKeyId:authKey.authKeyId saltSet:@[[[MTDatacenterSaltInfo alloc] initWithSalt:0 firstValidMessageId:timestamp lastValidMessageId:timestamp + (29.0 * 60.0) * 4294967296]] authKeyAttributes:nil]; + + [strongSelf->_context updateAuthInfoForDatacenterWithId:strongSelf->_datacenterId authInfo:authInfo selector:strongSelf->_authKeyInfoSelector]; + + [strongSelf complete]; + } else { + [strongSelf fail]; + } + }]]; + [_bindMtProto resume]; + } } } - } else { - MTDatacenterAuthInfo *authInfo = [[MTDatacenterAuthInfo alloc] initWithAuthKey:authKey.authKey authKeyId:authKey.authKeyId saltSet:@[[[MTDatacenterSaltInfo alloc] initWithSalt:0 firstValidMessageId:timestamp lastValidMessageId:timestamp + (29.0 * 60.0) * 4294967296]] authKeyAttributes:nil mainTempAuthKey:nil mediaTempAuthKey:nil]; - - MTContext *context = _context; - [context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:authInfo]; - [self complete]; + break; + + default: + assert(false); + break; } } @@ -153,7 +171,10 @@ [authMtProto stop]; - [_verifyDisposable dispose]; + MTProto *bindMtProto = _bindMtProto; + _bindMtProto = nil; + + [bindMtProto stop]; } - (void)cancel @@ -162,18 +183,17 @@ [self fail]; } -- (void)complete -{ - id delegate = _delegate; - if ([delegate respondsToSelector:@selector(datacenterAuthActionCompleted:)]) - [delegate datacenterAuthActionCompleted:self]; +- (void)complete { + if (_completion) { + _completion(self, true); + } } - (void)fail { - id delegate = _delegate; - if ([delegate respondsToSelector:@selector(datacenterAuthActionCompleted:)]) - [delegate datacenterAuthActionCompleted:self]; + if (_completion) { + _completion(self, false); + } } @end diff --git a/submodules/MtProtoKit/Sources/MTDatacenterAuthInfo.m b/submodules/MtProtoKit/Sources/MTDatacenterAuthInfo.m index f9959626a9..d6b783dfcd 100644 --- a/submodules/MtProtoKit/Sources/MTDatacenterAuthInfo.m +++ b/submodules/MtProtoKit/Sources/MTDatacenterAuthInfo.m @@ -27,7 +27,7 @@ @implementation MTDatacenterAuthInfo -- (instancetype)initWithAuthKey:(NSData *)authKey authKeyId:(int64_t)authKeyId saltSet:(NSArray *)saltSet authKeyAttributes:(NSDictionary *)authKeyAttributes mainTempAuthKey:(MTDatacenterAuthKey *)mainTempAuthKey mediaTempAuthKey:(MTDatacenterAuthKey *)mediaTempAuthKey +- (instancetype)initWithAuthKey:(NSData *)authKey authKeyId:(int64_t)authKeyId saltSet:(NSArray *)saltSet authKeyAttributes:(NSDictionary *)authKeyAttributes { self = [super init]; if (self != nil) @@ -36,8 +36,6 @@ _authKeyId = authKeyId; _saltSet = saltSet; _authKeyAttributes = authKeyAttributes; - _mainTempAuthKey = mainTempAuthKey; - _mediaTempAuthKey = mediaTempAuthKey; } return self; } @@ -51,8 +49,6 @@ _authKeyId = [aDecoder decodeInt64ForKey:@"authKeyId"]; _saltSet = [aDecoder decodeObjectForKey:@"saltSet"]; _authKeyAttributes = [aDecoder decodeObjectForKey:@"authKeyAttributes"]; - _mainTempAuthKey = [aDecoder decodeObjectForKey:@"tempAuthKey"]; - _mediaTempAuthKey = [aDecoder decodeObjectForKey:@"mediaTempAuthKey"]; } return self; } @@ -63,8 +59,6 @@ [aCoder encodeInt64:_authKeyId forKey:@"authKeyId"]; [aCoder encodeObject:_saltSet forKey:@"saltSet"]; [aCoder encodeObject:_authKeyAttributes forKey:@"authKeyAttributes"]; - [aCoder encodeObject:_mainTempAuthKey forKey:@"tempAuthKey"]; - [aCoder encodeObject:_mediaTempAuthKey forKey:@"mediaTempAuthKey"]; } - (int64_t)authSaltForMessageId:(int64_t)messageId @@ -113,35 +107,11 @@ } } - return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:mergedSaltSet authKeyAttributes:_authKeyAttributes mainTempAuthKey:_mainTempAuthKey mediaTempAuthKey:_mediaTempAuthKey]; + return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:mergedSaltSet authKeyAttributes:_authKeyAttributes]; } - (MTDatacenterAuthInfo *)withUpdatedAuthKeyAttributes:(NSDictionary *)authKeyAttributes { - return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:_saltSet authKeyAttributes:authKeyAttributes mainTempAuthKey:_mainTempAuthKey mediaTempAuthKey:_mediaTempAuthKey]; -} - -- (MTDatacenterAuthKey *)tempAuthKeyWithType:(MTDatacenterAuthTempKeyType)type { - switch (type) { - case MTDatacenterAuthTempKeyTypeMain: - return _mainTempAuthKey; - case MTDatacenterAuthTempKeyTypeMedia: - return _mediaTempAuthKey; - default: - NSAssert(false, @"unknown MTDatacenterAuthTempKeyType"); - return nil; - } -} - -- (MTDatacenterAuthInfo *)withUpdatedTempAuthKeyWithType:(MTDatacenterAuthTempKeyType)type key:(MTDatacenterAuthKey *)key { - switch (type) { - case MTDatacenterAuthTempKeyTypeMain: - return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:_saltSet authKeyAttributes:_authKeyAttributes mainTempAuthKey:key mediaTempAuthKey:_mediaTempAuthKey]; - case MTDatacenterAuthTempKeyTypeMedia: - return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:_saltSet authKeyAttributes:_authKeyAttributes mainTempAuthKey:_mainTempAuthKey mediaTempAuthKey:key]; - default: - NSAssert(false, @"unknown MTDatacenterAuthTempKeyType"); - return self; - } + return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:_saltSet authKeyAttributes:authKeyAttributes]; } - (MTDatacenterAuthKey *)persistentAuthKey { diff --git a/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m b/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m index a72758f2a0..e9d94c678b 100644 --- a/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m +++ b/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m @@ -222,7 +222,7 @@ typedef enum { } } -- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto +- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo { if (_currentStageTransactionId == nil) { @@ -308,7 +308,7 @@ typedef enum { return nil; } -- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message +- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector { if (_stage == MTDatacenterAuthStagePQ && [message.body isKindOfClass:[MTResPqMessage class]]) { @@ -389,7 +389,7 @@ typedef enum { [innerDataBuffer appendBytes:_nonce.bytes length:_nonce.length]; [innerDataBuffer appendBytes:_serverNonce.bytes length:_serverNonce.length]; [innerDataBuffer appendBytes:_newNonce.bytes length:_newNonce.length]; - [innerDataBuffer appendInt32:60 * 60 * 32]; + [innerDataBuffer appendInt32:mtProto.context.tempKeyExpiration]; NSData *innerDataBytes = innerDataBuffer.data; diff --git a/submodules/MtProtoKit/Sources/MTDiscoverConnectionSignals.m b/submodules/MtProtoKit/Sources/MTDiscoverConnectionSignals.m index 6ad6cc0976..429ff755af 100644 --- a/submodules/MtProtoKit/Sources/MTDiscoverConnectionSignals.m +++ b/submodules/MtProtoKit/Sources/MTDiscoverConnectionSignals.m @@ -249,12 +249,11 @@ MTMetaDisposable *disposable = [[MTMetaDisposable alloc] init]; [[MTContext contextQueue] dispatchOnQueue:^{ - MTDatacenterAuthAction *action = [[MTDatacenterAuthAction alloc] initWithTempAuth:true tempAuthKeyType:MTDatacenterAuthTempKeyTypeMain bindKey:authKey]; - action.completedWithResult = ^(bool success) { + MTDatacenterAuthAction *action = [[MTDatacenterAuthAction alloc] initWithAuthKeyInfoSelector:MTDatacenterAuthInfoSelectorEphemeralMain isCdn:false completion:^(__unused MTDatacenterAuthAction *action, bool success) { [subscriber putNext:@(!success)]; [subscriber putCompletion]; - }; - [action execute:context datacenterId:datacenterId isCdn:false]; + }]; + [action execute:context datacenterId:datacenterId]; [disposable setDisposable:[[MTBlockDisposable alloc] initWithBlock:^{ [action cancel]; diff --git a/submodules/MtProtoKit/Sources/MTDiscoverDatacenterAddressAction.m b/submodules/MtProtoKit/Sources/MTDiscoverDatacenterAddressAction.m index 940d8eb942..bfd5b5292d 100644 --- a/submodules/MtProtoKit/Sources/MTDiscoverDatacenterAddressAction.m +++ b/submodules/MtProtoKit/Sources/MTDiscoverDatacenterAddressAction.m @@ -88,7 +88,7 @@ [self fail]; else { - if ([context authInfoForDatacenterWithId:_targetDatacenterId] != nil) + if ([context authInfoForDatacenterWithId:_targetDatacenterId selector:MTDatacenterAuthInfoSelectorPersistent] != nil) { _mtProto = [[MTProto alloc] initWithContext:context datacenterId:_targetDatacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0]; _mtProto.useTempAuthKeys = useTempAuthKeys; @@ -118,11 +118,11 @@ [_requestService addRequest:request]; } else - [context authInfoForDatacenterWithIdRequired:_targetDatacenterId isCdn:false]; + [context authInfoForDatacenterWithIdRequired:_targetDatacenterId isCdn:false selector:MTDatacenterAuthInfoSelectorPersistent]; } } -- (void)contextDatacenterAuthInfoUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)__unused authInfo +- (void)contextDatacenterAuthInfoUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)__unused authInfo selector:(MTDatacenterAuthInfoSelector)selector { if (_context != context || !_awaitingAddresSetUpdate) return; diff --git a/submodules/MtProtoKit/Sources/MTProto.m b/submodules/MtProtoKit/Sources/MTProto.m index 7c96b4930f..b872c91085 100644 --- a/submodules/MtProtoKit/Sources/MTProto.m +++ b/submodules/MtProtoKit/Sources/MTProto.m @@ -14,7 +14,6 @@ #import #import #import -#import "MTBindingTempAuthKeyContext.h" #import #import @@ -58,13 +57,11 @@ typedef enum { MTProtoStateAwaitingDatacenterScheme = 1, MTProtoStateAwaitingDatacenterAuthorization = 2, - MTProtoStateAwaitingDatacenterTempAuthKey = 4, MTProtoStateAwaitingDatacenterAuthToken = 8, MTProtoStateAwaitingTimeFixAndSalts = 16, MTProtoStateAwaitingLostMessages = 32, MTProtoStateStopped = 64, - MTProtoStatePaused = 128, - MTProtoStateBindingTempAuthKey = 256 + MTProtoStatePaused = 128 } MTProtoState; static const NSUInteger MTMaxContainerSize = 3 * 1024; @@ -85,17 +82,36 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; @end +@interface MTProtoValidAuthInfo : NSObject + +@property (nonatomic, strong, readonly) MTDatacenterAuthInfo *authInfo; +@property (nonatomic, readonly) MTDatacenterAuthInfoSelector selector; + +@end + +@implementation MTProtoValidAuthInfo + +- (instancetype)initWithAuthInfo:(MTDatacenterAuthInfo *)authInfo selector:(MTDatacenterAuthInfoSelector)selector { + self = [super init]; + if (self != nil) { + _authInfo = authInfo; + _selector = selector; + } + return self; +} + +@end + @interface MTProto () { NSMutableArray *_messageServices; - MTDatacenterAuthInfo *_authInfo; + MTProtoValidAuthInfo *_validAuthInfo; + NSNumber *_awaitingAuthInfoForSelector; + MTSessionInfo *_sessionInfo; MTTimeFixContext *_timeFixContext; - int64_t _bindingTempAuthKeyId; - MTBindingTempAuthKeyContext *_bindingTempAuthKeyContext; - MTTransport *_transport; int _mtState; @@ -149,7 +165,8 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; _messageServices = [[NSMutableArray alloc] init]; _sessionInfo = [[MTSessionInfo alloc] initWithRandomSessionIdAndContext:_context]; - _authInfo = [_context authInfoForDatacenterWithId:_datacenterId]; + + _shouldStayConnected = true; } @@ -170,15 +187,6 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; }]; } -- (void)setUseExplicitAuthKey:(MTDatacenterAuthKey *)useExplicitAuthKey { - _useExplicitAuthKey = useExplicitAuthKey; - if (_useExplicitAuthKey != nil) { - _authInfo = [_authInfo withUpdatedTempAuthKeyWithType:MTDatacenterAuthTempKeyTypeMain key:useExplicitAuthKey]; - [self setMtState:_mtState | MTProtoStateBindingTempAuthKey]; - [self requestTransportTransaction]; - } -} - - (void)setUsageCalculationInfo:(MTNetworkUsageCalculationInfo *)usageCalculationInfo { [[MTProto managerQueue] dispatchOnQueue:^{ _usageCalculationInfo = usageCalculationInfo; @@ -278,7 +286,6 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; }]; _timeFixContext = nil; - _bindingTempAuthKeyContext = nil; if (_transport != nil) [self removeMessageService:_transport]; @@ -286,20 +293,6 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; _transport = transport; [previousTransport stop]; - if (_transport != nil && _useTempAuthKeys) { - MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; - /*if (_transport.scheme.address.preferForMedia) { - tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; - }*/ - - MTDatacenterAuthKey *effectiveAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; - if (effectiveAuthKey == nil) { - if (MTLogEnabled()) { - MTLog(@"[MTProto#%p setTransport temp auth key missing]", self); - } - } - } - if (_transport != nil) [self addMessageService:_transport]; @@ -323,38 +316,13 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; NSArray *transportSchemes = [_context transportSchemesForDatacenterWithId:_datacenterId media:_media enforceMedia:_enforceMedia isProxy:_apiEnvironment.socksProxySettings != nil]; - /*MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; - if (_transportScheme.address.preferForMedia) { - tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; - }*/ - if (transportSchemes.count == 0) { if ((_mtState & MTProtoStateAwaitingDatacenterScheme) == 0) { [self setMtState:_mtState | MTProtoStateAwaitingDatacenterScheme]; [_context transportSchemeForDatacenterWithIdRequired:_datacenterId media:_media]; } - } else if (!_useUnauthorizedMode && [_context authInfoForDatacenterWithId:_datacenterId] == nil) { - if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p authInfoForDatacenterWithId:%d is nil]", self, _context, _datacenterId); - } - MTShortLog(@"[MTProto#%p@%p authInfoForDatacenterWithId:%d is nil]", self, _context, _datacenterId); - if ((_mtState & MTProtoStateAwaitingDatacenterAuthorization) == 0) { - [self setMtState:_mtState | MTProtoStateAwaitingDatacenterAuthorization]; - - if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p requesting authInfo for %d]", self, _context, _datacenterId); - } - MTShortLog(@"[MTProto#%p@%p requesting authInfo for %d]", self, _context, _datacenterId); - [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:_cdn]; - } - }/* else if (!_useUnauthorizedMode && _useTempAuthKeys && [[_context authInfoForDatacenterWithId:_datacenterId] tempAuthKeyWithType:tempAuthKeyType] == nil) { - if ((_mtState & MTProtoStateAwaitingDatacenterTempAuthKey) == 0) { - [self setMtState:_mtState | MTProtoStateAwaitingDatacenterTempAuthKey]; - - [_context tempAuthKeyForDatacenterWithIdRequired:_datacenterId keyType:tempAuthKeyType]; - } - }*/ + } else if (_requiredAuthToken != nil && !_useUnauthorizedMode && ![_requiredAuthToken isEqual:[_context authTokenForDatacenterWithId:_datacenterId]]) { if ((_mtState & MTProtoStateAwaitingDatacenterAuthToken) == 0) { [self setMtState:_mtState | MTProtoStateAwaitingDatacenterAuthToken]; @@ -366,8 +334,6 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; MTTransport *transport = [[MTTcpTransport alloc] initWithDelegate:self context:_context datacenterId:_datacenterId schemes:transportSchemes proxySettings:_context.apiEnvironment.socksProxySettings usageCalculationInfo:_usageCalculationInfo]; [self setTransport:transport]; - - //[self checkTempAuthKeyBinding:transport.scheme.address]; } }]; } @@ -381,13 +347,8 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; } MTShortLog(@"[MTProto#%p@%p resetting session]", self, _context); - if (_authInfo.authKeyId != 0 && !self.cdn) { - [_context scheduleSessionCleanupForAuthKeyId:_authInfo.authKeyId sessionInfo:_sessionInfo]; - } - _sessionInfo = [[MTSessionInfo alloc] initWithRandomSessionIdAndContext:_context]; _timeFixContext = nil; - _bindingTempAuthKeyContext = nil; for (NSInteger i = (NSInteger)_messageServices.count - 1; i >= 0; i--) { @@ -402,11 +363,6 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; } - (void)finalizeSession { - [[MTProto managerQueue] dispatchOnQueue:^{ - if (_authInfo.authKeyId != 0 && !self.cdn) { - [_context scheduleSessionCleanupForAuthKeyId:_authInfo.authKeyId sessionInfo:_sessionInfo]; - } - }]; } - (void)requestTimeResync @@ -695,12 +651,12 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; - (bool)canAskForTransactions { - return (_mtState & (MTProtoStateAwaitingDatacenterScheme | MTProtoStateAwaitingDatacenterAuthorization | MTProtoStateAwaitingDatacenterTempAuthKey | MTProtoStateAwaitingDatacenterAuthToken | MTProtoStateAwaitingTimeFixAndSalts | MTProtoStateBindingTempAuthKey | MTProtoStateStopped)) == 0; + return (_mtState & (MTProtoStateAwaitingDatacenterScheme | MTProtoStateAwaitingDatacenterAuthorization | MTProtoStateAwaitingDatacenterAuthToken | MTProtoStateAwaitingTimeFixAndSalts | MTProtoStateStopped)) == 0; } - (bool)canAskForServiceTransactions { - return (_mtState & (MTProtoStateAwaitingDatacenterScheme | MTProtoStateAwaitingDatacenterAuthorization | MTProtoStateAwaitingDatacenterTempAuthKey | MTProtoStateAwaitingDatacenterAuthToken | MTProtoStateStopped)) == 0; + return (_mtState & (MTProtoStateAwaitingDatacenterScheme | MTProtoStateAwaitingDatacenterAuthorization | MTProtoStateAwaitingDatacenterAuthToken | MTProtoStateStopped)) == 0; } - (bool)timeFixOrSaltsMissing @@ -708,11 +664,6 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; return _mtState & MTProtoStateAwaitingTimeFixAndSalts; } -- (bool)bindingTempAuthKey -{ - return _mtState & MTProtoStateBindingTempAuthKey; -} - - (bool)isStopped { return (_mtState & MTProtoStateStopped) != 0; @@ -876,6 +827,65 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; return [[NSString alloc] initWithFormat:@"%@ (%" PRId64", %" PRId64"/%" PRId64")", message.body, message.messageId, message.authKeyId, message.sessionId]; } +- (MTDatacenterAuthKey *)getAuthKeyForCurrentScheme:(MTTransportScheme *)scheme createIfNeeded:(bool)createIfNeeded authInfoSelector:(MTDatacenterAuthInfoSelector *)authInfoSelector { + if (_useExplicitAuthKey) { + MTDatacenterAuthInfoSelector selector = MTDatacenterAuthInfoSelectorEphemeralMain; + if (authInfoSelector != nil) { + *authInfoSelector = selector; + } + + if (_validAuthInfo != nil && _validAuthInfo.selector == selector) { + return [[MTDatacenterAuthKey alloc] initWithAuthKey:_validAuthInfo.authInfo.authKey authKeyId:_validAuthInfo.authInfo.authKeyId notBound:false]; + } + + MTDatacenterAuthInfo *authInfo = [[MTDatacenterAuthInfo alloc] initWithAuthKey:_useExplicitAuthKey.authKey authKeyId:_useExplicitAuthKey.authKeyId saltSet:@[[[MTDatacenterSaltInfo alloc] initWithSalt:0 firstValidMessageId:0 lastValidMessageId:0]] authKeyAttributes:nil]; + + _validAuthInfo = [[MTProtoValidAuthInfo alloc] initWithAuthInfo:authInfo selector:selector]; + return [[MTDatacenterAuthKey alloc] initWithAuthKey:_validAuthInfo.authInfo.authKey authKeyId:_validAuthInfo.authInfo.authKeyId notBound:false]; + } else { + MTDatacenterAuthInfoSelector selector = MTDatacenterAuthInfoSelectorPersistent; + + if (_cdn) { + selector = MTDatacenterAuthInfoSelectorPersistent; + } else { + if (_useTempAuthKeys) { + if (scheme.address.preferForMedia) { + selector = MTDatacenterAuthInfoSelectorEphemeralMedia; + } else { + selector = MTDatacenterAuthInfoSelectorEphemeralMain; + } + } else { + selector = MTDatacenterAuthInfoSelectorPersistent; + } + } + + if (authInfoSelector != nil) { + *authInfoSelector = selector; + } + + if (_validAuthInfo != nil && _validAuthInfo.selector == selector) { + return [[MTDatacenterAuthKey alloc] initWithAuthKey:_validAuthInfo.authInfo.authKey authKeyId:_validAuthInfo.authInfo.authKeyId notBound:false]; + } else { + MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:_datacenterId selector:selector]; + if (authInfo != nil) { + _validAuthInfo = [[MTProtoValidAuthInfo alloc] initWithAuthInfo:authInfo selector:selector]; + return [[MTDatacenterAuthKey alloc] initWithAuthKey:_validAuthInfo.authInfo.authKey authKeyId:_validAuthInfo.authInfo.authKeyId notBound:false]; + } else { + if (createIfNeeded) { + [_context performBatchUpdates:^{ + [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:nil selector:selector]; + [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:_cdn selector:selector]; + }]; + _mtState |= MTProtoStateAwaitingDatacenterAuthorization; + _awaitingAuthInfoForSelector = @(selector); + } + + return nil; + } + } + } +} + - (void)transportReadyForTransaction:(MTTransport *)transport scheme:(MTTransportScheme *)scheme transportSpecificTransaction:(MTMessageTransaction *)transportSpecificTransaction forceConfirmations:(bool)forceConfirmations transactionReady:(void (^)(NSArray *))transactionReady { [[MTProto managerQueue] dispatchOnQueue:^ @@ -887,6 +897,19 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; return; } + MTDatacenterAuthKey *authKey = nil; + MTDatacenterAuthInfoSelector authInfoSelector = MTDatacenterAuthInfoSelectorPersistent; + if (!_useUnauthorizedMode) { + authKey = [self getAuthKeyForCurrentScheme:scheme createIfNeeded:true authInfoSelector:&authInfoSelector]; + + if (authKey == nil) { + if (transactionReady) { + transactionReady(nil); + } + return; + } + } + bool extendedPadding = false; if (transport.proxySettings != nil && transport.proxySettings.secret != nil) { MTProxySecret *parsedSecret = [MTProxySecret parseData:transport.proxySettings.secret]; @@ -908,7 +931,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; if (transportSpecificTransaction != nil) { - if (!(transportSpecificTransaction.requiresEncryption && _useUnauthorizedMode) && (!transportSpecificTransaction.requiresEncryption || _authInfo != nil)) + if (!(transportSpecificTransaction.requiresEncryption && _useUnauthorizedMode) && (!transportSpecificTransaction.requiresEncryption || authKey != nil)) { [messageTransactions addObject:transportSpecificTransaction]; } @@ -919,9 +942,9 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; NSMutableArray *messageServiceTransactions = [[NSMutableArray alloc] init]; for (id messageService in _messageServices) { - if ([messageService respondsToSelector:@selector(mtProtoMessageTransaction:)]) + if ([messageService respondsToSelector:@selector(mtProtoMessageTransaction:authInfoSelector:sessionInfo:)]) { - MTMessageTransaction *messageTransaction = [messageService mtProtoMessageTransaction:self]; + MTMessageTransaction *messageTransaction = [messageService mtProtoMessageTransaction:self authInfoSelector:authInfoSelector sessionInfo:transactionSessionInfo]; if (messageTransaction != nil) { for (MTOutgoingMessage *message in messageTransaction.messagePayload) @@ -977,7 +1000,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; int64_t messageSalt = 0; if (!_useUnauthorizedMode) { - messageSalt = [_authInfo authSaltForMessageId:[transactionSessionInfo actualClientMessagId]]; + messageSalt = [_validAuthInfo.authInfo authSaltForMessageId:[transactionSessionInfo actualClientMessagId]]; if (messageSalt == 0) saltSetEmpty = true; } @@ -1156,7 +1179,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; if (currentContainerMessages.count == 1) { int32_t quickAckId = 0; - NSData *messageData = [self _dataForEncryptedMessage:currentContainerMessages[0] sessionInfo:transactionSessionInfo quickAckId:&quickAckId address:scheme.address extendedPadding:extendedPadding]; + NSData *messageData = [self _dataForEncryptedMessage:currentContainerMessages[0] authKey:authKey sessionInfo:transactionSessionInfo quickAckId:&quickAckId address:scheme.address extendedPadding:extendedPadding]; if (messageData != nil) { [transactionPayloadList addObject:messageData]; @@ -1167,7 +1190,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; else if (currentContainerMessages.count != 0) { int32_t quickAckId = 0; - NSData *containerData = [self _dataForEncryptedContainerWithMessages:currentContainerMessages sessionInfo:transactionSessionInfo quickAckId:&quickAckId address:scheme.address extendedPadding:extendedPadding]; + NSData *containerData = [self _dataForEncryptedContainerWithMessages:currentContainerMessages authKey:authKey sessionInfo:transactionSessionInfo quickAckId:&quickAckId address:scheme.address extendedPadding:extendedPadding]; if (containerData != nil) { [transactionPayloadList addObject:containerData]; @@ -1309,7 +1332,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; } MTShortLog(@"[MTProto#%p@%p sending time fix ping (%" PRId64 "/%" PRId32 ", %" PRId64 ")]", self, _context, timeFixMessageId, timeFixSeqNo, _sessionInfo.sessionId); - [decryptedOs writeInt64:[_authInfo authSaltForMessageId:timeFixMessageId]]; // salt + [decryptedOs writeInt64:[_validAuthInfo.authInfo authSaltForMessageId:timeFixMessageId]]; // salt [decryptedOs writeInt64:_sessionInfo.sessionId]; [decryptedOs writeInt64:timeFixMessageId]; [decryptedOs writeInt32:timeFixSeqNo]; @@ -1319,18 +1342,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; NSData *decryptedData = [self paddedData:[decryptedOs currentBytes] extendedPadding:extendedPadding]; - MTDatacenterAuthKey *effectiveAuthKey; - if (_useTempAuthKeys) { - MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; - if (scheme.address.preferForMedia) { - tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; - } - - effectiveAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; - NSAssert(effectiveAuthKey != nil, @"effectiveAuthKey == nil"); - } else { - effectiveAuthKey = [[MTDatacenterAuthKey alloc] initWithAuthKey:_authInfo.authKey authKeyId:_authInfo.authKeyId notBound:false]; - } + MTDatacenterAuthKey *effectiveAuthKey = authKey; int xValue = 0; NSMutableData *msgKeyLargeData = [[NSMutableData alloc] init]; @@ -1384,115 +1396,6 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; transactionReady(nil); } } - else if (![self timeFixOrSaltsMissing] && [self bindingTempAuthKey] && [self canAskForServiceTransactions] && (_bindingTempAuthKeyContext == nil || _bindingTempAuthKeyContext.transactionId == nil)) - { - MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; - if (scheme.address.preferForMedia) { - tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; - } - - MTDatacenterAuthKey *effectiveTempAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; - NSAssert(effectiveTempAuthKey != nil, @"effectiveAuthKey == nil"); - - int64_t bindingMessageId = [_sessionInfo generateClientMessageId:NULL]; - int32_t bindingSeqNo = [_sessionInfo takeSeqNo:true]; - - int32_t expiresAt = (int32_t)([_context globalTime] + 60 * 60 * 32); - - int64_t randomId = 0; - arc4random_buf(&randomId, 8); - - int64_t nonce = 0; - arc4random_buf(&nonce, 8); - - MTBuffer *decryptedMessage = [[MTBuffer alloc] init]; - //bind_auth_key_inner#75a3f765 nonce:long temp_auth_key_id:long perm_auth_key_id:long temp_session_id:long expires_at:int = BindAuthKeyInner; - [decryptedMessage appendInt32:(int32_t)0x75a3f765]; - [decryptedMessage appendInt64:nonce]; - [decryptedMessage appendInt64:effectiveTempAuthKey.authKeyId]; - [decryptedMessage appendInt64:_authInfo.authKeyId]; - [decryptedMessage appendInt64:_sessionInfo.sessionId]; - [decryptedMessage appendInt32:expiresAt]; - - NSData *encryptedMessage = [self _manuallyEncryptedMessage:[decryptedMessage data] messageId:bindingMessageId authKey:_authInfo.persistentAuthKey]; - - MTBuffer *bindRequestData = [[MTBuffer alloc] init]; - - //auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int encrypted_message:bytes = Bool; - - [bindRequestData appendInt32:(int32_t)0xcdd42a05]; - [bindRequestData appendInt64:_authInfo.persistentAuthKey.authKeyId]; - [bindRequestData appendInt64:nonce]; - [bindRequestData appendInt32:expiresAt]; - [bindRequestData appendTLBytes:encryptedMessage]; - - NSData *messageData = bindRequestData.data; - - MTOutputStream *decryptedOs = [[MTOutputStream alloc] init]; - - if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p sending temp key binding message (%" PRId64 "/%" PRId32 ") for %lld (%d)]", self, _context, bindingMessageId, bindingSeqNo, effectiveTempAuthKey.authKeyId, (int)tempAuthKeyType); - } - MTShortLog(@"[MTProto#%p@%p sending temp key binding message (%" PRId64 "/%" PRId32 ") for %lld (%d)]", self, _context, bindingMessageId, bindingSeqNo, effectiveTempAuthKey.authKeyId, (int)tempAuthKeyType); - - [decryptedOs writeInt64:[_authInfo authSaltForMessageId:bindingMessageId]]; - [decryptedOs writeInt64:_sessionInfo.sessionId]; - [decryptedOs writeInt64:bindingMessageId]; - [decryptedOs writeInt32:bindingSeqNo]; - - [decryptedOs writeInt32:(int32_t)messageData.length]; - [decryptedOs writeData:messageData]; - - NSData *decryptedData = [self paddedData:[decryptedOs currentBytes] extendedPadding:extendedPadding]; - - int xValue = 0; - NSMutableData *msgKeyLargeData = [[NSMutableData alloc] init]; - [msgKeyLargeData appendBytes:effectiveTempAuthKey.authKey.bytes + 88 + xValue length:32]; - [msgKeyLargeData appendData:decryptedData]; - - NSData *msgKeyLarge = MTSha256(msgKeyLargeData); - NSData *messageKey = [msgKeyLarge subdataWithRange:NSMakeRange(8, 16)]; - MTMessageEncryptionKey *encryptionKey = [MTMessageEncryptionKey messageEncryptionKeyV2ForAuthKey:effectiveTempAuthKey.authKey messageKey:messageKey toClient:false]; - - NSData *transactionData = nil; - - if (encryptionKey != nil) - { - NSMutableData *encryptedData = [[NSMutableData alloc] initWithCapacity:14 + decryptedData.length]; - [encryptedData appendData:decryptedData]; - MTAesEncryptInplace(encryptedData, encryptionKey.key, encryptionKey.iv); - - int64_t authKeyId = effectiveTempAuthKey.authKeyId; - [encryptedData replaceBytesInRange:NSMakeRange(0, 0) withBytes:&authKeyId length:8]; - [encryptedData replaceBytesInRange:NSMakeRange(8, 0) withBytes:messageKey.bytes length:messageKey.length]; - - transactionData = encryptedData; - } - - if (transactionReady != nil) - { - if (transactionData != nil) - { - __weak MTProto *weakSelf = self; - transactionReady(@[[[MTTransportTransaction alloc] initWithPayload:transactionData completion:^(bool success, id transactionId) { - [[MTProto managerQueue] dispatchOnQueue:^{ - if (success) { - if (transactionId != nil) { - _bindingTempAuthKeyContext = [[MTBindingTempAuthKeyContext alloc] initWithMessageId:bindingMessageId messageSeqNo:bindingSeqNo transactionId:transactionId]; - } - } - else - { - __strong MTProto *strongSelf = weakSelf; - [strongSelf requestTransportTransaction]; - } - }]; - } needsQuickAck:false expectsDataInResponse:true]]); - } - else - transactionReady(nil); - } - } else if (transactionReady != nil) transactionReady(nil); @@ -1503,19 +1406,8 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; }]; } -- (NSData *)_dataForEncryptedContainerWithMessages:(NSArray *)preparedMessages sessionInfo:(MTSessionInfo *)sessionInfo quickAckId:(int32_t *)quickAckId address:(MTDatacenterAddress *)address extendedPadding:(bool)extendedPadding { - MTDatacenterAuthKey *effectiveAuthKey; - if (_useTempAuthKeys) { - MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; - if (address.preferForMedia) { - tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; - } - effectiveAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; - - NSAssert(effectiveAuthKey != nil, @"effectiveAuthKey == nil"); - } else { - effectiveAuthKey = [[MTDatacenterAuthKey alloc] initWithAuthKey:_authInfo.authKey authKeyId:_authInfo.authKeyId notBound:false]; - } +- (NSData *)_dataForEncryptedContainerWithMessages:(NSArray *)preparedMessages authKey:(MTDatacenterAuthKey *)authKey sessionInfo:(MTSessionInfo *)sessionInfo quickAckId:(int32_t *)quickAckId address:(MTDatacenterAddress *)address extendedPadding:(bool)extendedPadding { + MTDatacenterAuthKey *effectiveAuthKey = authKey; NSMutableArray *containerMessageIds = [[NSMutableArray alloc] init]; @@ -1622,7 +1514,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; return messageData; } -- (NSData *)paddedDataV1:(NSData *)data { ++ (NSData *)paddedDataV1:(NSData *)data { NSMutableData *padded = [[NSMutableData alloc] initWithData:data]; uint8_t randomBytes[128]; arc4random_buf(randomBytes, 128); @@ -1669,7 +1561,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; return padded; } -- (NSData *)_manuallyEncryptedMessage:(NSData *)preparedData messageId:(int64_t)messageId authKey:(MTDatacenterAuthKey *)authKey { ++ (NSData *)_manuallyEncryptedMessage:(NSData *)preparedData messageId:(int64_t)messageId authKey:(MTDatacenterAuthKey *)authKey { MTOutputStream *decryptedOs = [[MTOutputStream alloc] init]; int64_t random1 = 0; @@ -1685,7 +1577,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; [decryptedOs writeInt32:(int32_t)preparedData.length]; [decryptedOs writeData:preparedData]; - NSData *decryptedData = [self paddedDataV1:[decryptedOs currentBytes]]; + NSData *decryptedData = [MTProto paddedDataV1:[decryptedOs currentBytes]]; NSData *messageKeyFull = MTSubdataSha1(decryptedData, 0, 32 + preparedData.length); NSData *messageKey = [[NSData alloc] initWithBytes:(((int8_t *)messageKeyFull.bytes) + messageKeyFull.length - 16) length:16]; @@ -1708,19 +1600,10 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; return nil; } -- (NSData *)_dataForEncryptedMessage:(MTPreparedMessage *)preparedMessage sessionInfo:(MTSessionInfo *)sessionInfo quickAckId:(int32_t *)quickAckId address:(MTDatacenterAddress *)address extendedPadding:(bool)extendedPadding +- (NSData *)_dataForEncryptedMessage:(MTPreparedMessage *)preparedMessage authKey:(MTDatacenterAuthKey *)authKey sessionInfo:(MTSessionInfo *)sessionInfo quickAckId:(int32_t *)quickAckId address:(MTDatacenterAddress *)address extendedPadding:(bool)extendedPadding { - MTDatacenterAuthKey *effectiveAuthKey; - if (_useTempAuthKeys) { - MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; - if (address.preferForMedia) { - tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; - } - effectiveAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; - NSAssert(effectiveAuthKey != nil, @"effectiveAuthKey == nil"); - } else { - effectiveAuthKey = [[MTDatacenterAuthKey alloc] initWithAuthKey:_authInfo.authKey authKeyId:_authInfo.authKeyId notBound:false]; - } + MTDatacenterAuthKey *effectiveAuthKey = authKey; + NSAssert(effectiveAuthKey != nil, @"effectiveAuthKey == nil"); MTOutputStream *decryptedOs = [[MTOutputStream alloc] init]; @@ -1774,12 +1657,6 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; requestTransaction = true; } - if (_bindingTempAuthKeyContext != nil && _bindingTempAuthKeyContext.transactionId != nil && [transactionIds containsObject:_bindingTempAuthKeyContext.transactionId]) - { - _bindingTempAuthKeyContext = nil; - requestTransaction = true; - } - for (NSInteger i = (NSInteger)_messageServices.count - 1; i >= 0; i--) { id messageService = _messageServices[(NSUInteger)i]; @@ -1806,12 +1683,6 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; requestTransaction = true; } - if (_bindingTempAuthKeyContext != nil && _bindingTempAuthKeyContext.transactionId != nil) - { - _bindingTempAuthKeyContext = nil; - requestTransaction = true; - } - for (NSInteger i = (NSInteger)_messageServices.count - 1; i >= 0; i--) { id messageService = _messageServices[(NSUInteger)i]; @@ -1848,24 +1719,18 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; if (transport != _transport || completion == nil) return; - MTDatacenterAuthKey *effectiveAuthKey; - if (_useTempAuthKeys) { - MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; - if (scheme.address.preferForMedia) { - tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; - } - - effectiveAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; - NSAssert(effectiveAuthKey != nil, @"effectiveAuthKey == nil"); - } else { - effectiveAuthKey = [[MTDatacenterAuthKey alloc] initWithAuthKey:_authInfo.authKey authKeyId:_authInfo.authKeyId notBound:false]; + MTDatacenterAuthKey *authKey = [self getAuthKeyForCurrentScheme:scheme createIfNeeded:false authInfoSelector:nil]; + if (authKey == nil) { + return; } + MTDatacenterAuthKey *effectiveAuthKey = authKey; + MTInputStream *is = [[MTInputStream alloc] initWithData:data]; int64_t keyId = [is readInt64]; - if (keyId != 0 && _authInfo != nil) + if (keyId == authKey.authKeyId) { NSData *messageKey = [is readData:16]; @@ -2070,7 +1935,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { } if (protocolErrorCode == -404) { - [self handleMissingKey:scheme.address]; + [self handleMissingKey:scheme]; } if (currentTransport == _transport) @@ -2081,10 +1946,17 @@ static NSString *dumpHexString(NSData *data, int maxLength) { NSData *decryptedData = nil; - if (_useUnauthorizedMode) + int64_t embeddedAuthKeyId = 0; + MTDatacenterAuthInfoSelector authInfoSelector = MTDatacenterAuthInfoSelectorPersistent; + if (_useUnauthorizedMode) { decryptedData = data; - else - decryptedData = [self _decryptIncomingTransportData:data address:scheme.address]; + } else { + MTDatacenterAuthKey *authKey = [self getAuthKeyForCurrentScheme:scheme createIfNeeded:false authInfoSelector:&authInfoSelector]; + if (authKey != nil) { + embeddedAuthKeyId = authKey.authKeyId; + decryptedData = [self _decryptIncomingTransportData:data address:scheme.address authKey:authKey]; + } + } if (decryptedData != nil) { @@ -2093,9 +1965,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { int64_t dataMessageId = 0; bool parseError = false; - NSArray *parsedMessages = [self _parseIncomingMessages:decryptedData dataMessageId:&dataMessageId parseError:&parseError]; - - + NSArray *parsedMessages = [self _parseIncomingMessages:decryptedData dataMessageId:&dataMessageId embeddedAuthKeyId:embeddedAuthKeyId parseError:&parseError]; for (MTIncomingMessage *message in parsedMessages) { if ([message.body isKindOfClass:[MTRpcResultMessage class]]) { @@ -2108,7 +1978,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { MTLog(@"[MTProto#%p@%p received AUTH_KEY_PERM_EMPTY]", self, _context); } MTShortLog(@"[MTProto#%p@%p received AUTH_KEY_PERM_EMPTY]", self, _context); - [self handleMissingKey:scheme.address]; + [self handleMissingKey:scheme]; [self requestSecureTransportReset]; return; @@ -2132,7 +2002,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { for (MTIncomingMessage *incomingMessage in parsedMessages) { - [self _processIncomingMessage:incomingMessage totalSize:(int)data.length withTransactionId:transactionId address:scheme.address]; + [self _processIncomingMessage:incomingMessage totalSize:(int)data.length withTransactionId:transactionId address:scheme.address authInfoSelector:authInfoSelector]; } if (requestTransactionAfterProcessing) @@ -2155,71 +2025,74 @@ static NSString *dumpHexString(NSData *data, int maxLength) { }]; } -- (void)handleMissingKey:(MTDatacenterAddress *)address { +- (void)handleMissingKey:(MTTransportScheme *)scheme { NSAssert([[MTProto managerQueue] isCurrentQueue], @"invalid queue"); + MTDatacenterAuthInfoSelector authInfoSelector; + [self getAuthKeyForCurrentScheme:scheme createIfNeeded:false authInfoSelector:&authInfoSelector]; + + if (MTLogEnabled()) { + MTLog(@"[MTProto#%p@%p missing key %lld selector]", self, _context, _validAuthInfo.authInfo.authKeyId, authInfoSelector); + } + if (_useExplicitAuthKey != nil) { } else if (_cdn) { - _authInfo = nil; + _validAuthInfo = nil; + [_context performBatchUpdates:^{ - [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:nil]; - [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:true]; + [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:nil selector:authInfoSelector]; + [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:true selector:authInfoSelector]; }]; _mtState |= MTProtoStateAwaitingDatacenterAuthorization; - } else if (_useTempAuthKeys) { - MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; - if (address.preferForMedia) { - tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; - } - - MTDatacenterAuthKey *currentTempAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; - - _authInfo = [_authInfo withUpdatedTempAuthKeyWithType:tempAuthKeyType key:nil]; - _mtState |= MTProtoStateAwaitingDatacenterTempAuthKey; - - [_context performBatchUpdates:^{ - MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:_datacenterId]; - if ([authInfo tempAuthKeyWithType:tempAuthKeyType].authKeyId == currentTempAuthKey.authKeyId) { - authInfo = [authInfo withUpdatedTempAuthKeyWithType:tempAuthKeyType key:nil]; - [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:authInfo]; - [_context tempAuthKeyForDatacenterWithIdRequired:_datacenterId keyType:tempAuthKeyType]; - } - }]; + _awaitingAuthInfoForSelector = @(authInfoSelector); } else { + MTDatacenterAuthInfoSelector authInfoSelector; + [self getAuthKeyForCurrentScheme:scheme createIfNeeded:false authInfoSelector:&authInfoSelector]; + if (_requiredAuthToken != nil && _authTokenMasterDatacenterId != _datacenterId) { - _authInfo = nil; + _validAuthInfo = nil; + [_context removeTokenForDatacenterWithId:_datacenterId]; [_context performBatchUpdates:^{ - [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:nil]; - [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:false]; + [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:nil selector:authInfoSelector]; + [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:false selector:authInfoSelector]; }]; _mtState |= MTProtoStateAwaitingDatacenterAuthorization; + _awaitingAuthInfoForSelector = @(authInfoSelector); } else if (_canResetAuthData) { - _authInfo = nil; + _validAuthInfo = nil; + [_context performBatchUpdates:^{ - [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:nil]; - [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:false]; + [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:nil selector:authInfoSelector]; + [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:false selector:authInfoSelector]; }]; _mtState |= MTProtoStateAwaitingDatacenterAuthorization; + _awaitingAuthInfoForSelector = @(authInfoSelector); } else { - [_context checkIfLoggedOut:_datacenterId]; + switch (authInfoSelector) { + case MTDatacenterAuthInfoSelectorEphemeralMain: + case MTDatacenterAuthInfoSelectorEphemeralMedia: { + _validAuthInfo = nil; + + [_context performBatchUpdates:^{ + [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:nil selector:authInfoSelector]; + [_context authInfoForDatacenterWithIdRequired:_datacenterId isCdn:false selector:authInfoSelector]; + }]; + _mtState |= MTProtoStateAwaitingDatacenterAuthorization; + _awaitingAuthInfoForSelector = @(authInfoSelector); + break; + } + default: + [_context checkIfLoggedOut:_datacenterId]; + break; + } } } } -- (NSData *)_decryptIncomingTransportData:(NSData *)transportData address:(MTDatacenterAddress *)address +- (NSData *)_decryptIncomingTransportData:(NSData *)transportData address:(MTDatacenterAddress *)address authKey:(MTDatacenterAuthKey *)authKey { - MTDatacenterAuthKey *effectiveAuthKey; - if (_useTempAuthKeys) { - MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; - if (address.preferForMedia) { - tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; - } - - effectiveAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; - } else { - effectiveAuthKey = [[MTDatacenterAuthKey alloc] initWithAuthKey:_authInfo.authKey authKeyId:_authInfo.authKeyId notBound:false]; - } + MTDatacenterAuthKey *effectiveAuthKey = authKey; if (effectiveAuthKey == nil) return nil; @@ -2281,13 +2154,12 @@ static NSString *dumpHexString(NSData *data, int maxLength) { return [_context.serialization parseMessage:unwrappedData]; } -- (NSArray *)_parseIncomingMessages:(NSData *)data dataMessageId:(out int64_t *)dataMessageId parseError:(out bool *)parseError +- (NSArray *)_parseIncomingMessages:(NSData *)data dataMessageId:(out int64_t *)dataMessageId embeddedAuthKeyId:(int64_t)embeddedAuthKeyId parseError:(out bool *)parseError { MTInputStream *is = [[MTInputStream alloc] initWithData:data]; bool readError = false; - int64_t embeddedAuthKeyId = 0; int64_t embeddedSessionId = 0; int64_t embeddedMessageId = 0; int32_t embeddedSeqNo = 0; @@ -2303,7 +2175,6 @@ static NSString *dumpHexString(NSData *data, int maxLength) { *parseError = true; return nil; } - embeddedAuthKeyId = authKeyId; embeddedMessageId = [is readInt64:&readError]; if (readError) @@ -2326,7 +2197,6 @@ static NSString *dumpHexString(NSData *data, int maxLength) { } else { - embeddedAuthKeyId = _authInfo.authKeyId; embeddedSalt = [is readInt64:&readError]; if (readError) { @@ -2436,7 +2306,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { return messages; } -- (void)_processIncomingMessage:(MTIncomingMessage *)incomingMessage totalSize:(int)totalSize withTransactionId:(id)transactionId address:(MTDatacenterAddress *)address +- (void)_processIncomingMessage:(MTIncomingMessage *)incomingMessage totalSize:(int)totalSize withTransactionId:(id)transactionId address:(MTDatacenterAddress *)address authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector { if ([_sessionInfo messageProcessed:incomingMessage.messageId]) { @@ -2482,7 +2352,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { int64_t validSalt = ((MTBadServerSaltNotificationMessage *)badMsgNotification).nextServerSalt; NSTimeInterval timeDifference = incomingMessage.messageId / 4294967296.0 - [[NSDate date] timeIntervalSince1970]; [self completeTimeSync]; - [self timeSyncInfoChanged:timeDifference saltList:@[[[MTDatacenterSaltInfo alloc] initWithSalt:validSalt firstValidMessageId:incomingMessage.messageId lastValidMessageId:incomingMessage.messageId + (4294967296 * 30 * 60)]]]; + [self timeSyncInfoChanged:timeDifference saltList:@[[[MTDatacenterSaltInfo alloc] initWithSalt:validSalt firstValidMessageId:incomingMessage.messageId lastValidMessageId:incomingMessage.messageId + (4294967296 * 30 * 60)]] authInfoSelector:authInfoSelector]; } else [self initiateTimeSync]; @@ -2500,7 +2370,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { NSTimeInterval timeDifference = incomingMessage.messageId / 4294967296.0 - [[NSDate date] timeIntervalSince1970]; [self completeTimeSync]; - [self timeSyncInfoChanged:timeDifference saltList:nil]; + [self timeSyncInfoChanged:timeDifference saltList:nil authInfoSelector:authInfoSelector]; } else [self initiateTimeSync]; @@ -2542,11 +2412,6 @@ static NSString *dumpHexString(NSData *data, int maxLength) { } } - if (_bindingTempAuthKeyContext != nil && badMessageId == _bindingTempAuthKeyContext.messageId) - { - _bindingTempAuthKeyContext = nil; - } - if ([self canAskForTransactions] || [self canAskForServiceTransactions]) [self requestTransportTransaction]; } @@ -2624,8 +2489,8 @@ static NSString *dumpHexString(NSData *data, int maxLength) { { id messageService = _messageServices[(NSUInteger)i]; - if ([messageService respondsToSelector:@selector(mtProto:receivedMessage:)]) - [messageService mtProto:self receivedMessage:incomingMessage]; + if ([messageService respondsToSelector:@selector(mtProto:receivedMessage:authInfoSelector:)]) + [messageService mtProto:self receivedMessage:incomingMessage authInfoSelector:authInfoSelector]; } if (_timeFixContext != nil && [incomingMessage.body isKindOfClass:[MTPongMessage class]] && ((MTPongMessage *)incomingMessage.body).messageId == _timeFixContext.messageId) @@ -2636,71 +2501,6 @@ static NSString *dumpHexString(NSData *data, int maxLength) { if ([self canAskForTransactions] || [self canAskForServiceTransactions]) [self requestTransportTransaction]; } - - if (_bindingTempAuthKeyContext != nil && [incomingMessage.body isKindOfClass:[MTRpcResultMessage class]] && ((MTRpcResultMessage *)incomingMessage.body).requestMessageId == _bindingTempAuthKeyContext.messageId) { - MTRpcResultMessage *rpcResultMessage = (MTRpcResultMessage *)incomingMessage.body; - - _bindingTempAuthKeyContext = nil; - - id maybeInternalMessage = [MTInternalMessageParser parseMessage:rpcResultMessage.data]; - - id rpcResult = nil; - MTRpcError *rpcError = nil; - - if ([maybeInternalMessage isKindOfClass:[MTRpcError class]]) - rpcError = maybeInternalMessage; - else - { - if (rpcResultMessage.data.length >= 4) { - int32_t signature = 0; - [rpcResultMessage.data getBytes:&signature range:NSMakeRange(0, 4)]; - if (signature == (int32_t)0xbc799737) { - rpcResult = @true; - } else if (signature == (int32_t)0x997275b5) { - rpcResult = @false; - } - } - if (rpcResult == nil) { - rpcError = [[MTRpcError alloc] initWithErrorCode:500 errorDescription:@"TL_PARSING_ERROR"]; - } - } - - if ([rpcResult respondsToSelector:@selector(boolValue)]) { - MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; - if (address.preferForMedia) { - tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; - } - MTDatacenterAuthKey *effectiveTempAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; - if (effectiveTempAuthKey != nil) { - _authInfo = [_authInfo withUpdatedTempAuthKeyWithType:tempAuthKeyType key:[[MTDatacenterAuthKey alloc] initWithAuthKey:effectiveTempAuthKey.authKey authKeyId:effectiveTempAuthKey.authKeyId notBound:false]]; - NSMutableDictionary *authKeyAttributes = [[NSMutableDictionary alloc] initWithDictionary:_authInfo.authKeyAttributes]; - [authKeyAttributes removeObjectForKey:@"apiInitializationHash"]; - _authInfo = [_authInfo withUpdatedAuthKeyAttributes:authKeyAttributes]; - if (_useExplicitAuthKey == nil) { - [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:_authInfo]; - } - if (_tempAuthKeyBindingResultUpdated) { - _tempAuthKeyBindingResultUpdated(true); - } - } - _bindingTempAuthKeyId = 0; - if ((_mtState & MTProtoStateBindingTempAuthKey) != 0) { - [self setMtState:_mtState & (~MTProtoStateBindingTempAuthKey)]; - [self requestTransportTransaction]; - } - } else { - if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p bindTempAuthKey error %@]", self, _context, rpcError); - } - MTShortLog(@"[MTProto#%p@%p bindTempAuthKey error %@]", self, _context, rpcError); - - [self requestTransportTransaction]; - - if (_tempAuthKeyBindingResultUpdated) { - _tempAuthKeyBindingResultUpdated(false); - } - } - } } } @@ -2722,18 +2522,27 @@ static NSString *dumpHexString(NSData *data, int maxLength) { }]; } -- (void)contextDatacenterAuthInfoUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo +- (void)contextDatacenterAuthInfoUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo selector:(MTDatacenterAuthInfoSelector)selector { [[MTProto managerQueue] dispatchOnQueue:^ { if (!_useUnauthorizedMode && context == _context && datacenterId == _datacenterId) { - _authInfo = authInfo; - if (_useExplicitAuthKey != nil) { - _authInfo = [_authInfo withUpdatedTempAuthKeyWithType:MTDatacenterAuthTempKeyTypeMain key:_useExplicitAuthKey]; + if (_validAuthInfo != nil) { + if (_validAuthInfo.selector != selector) { + return; + } + } else if (_awaitingAuthInfoForSelector != nil) { + if ([_awaitingAuthInfoForSelector intValue] != selector) { + return; + } else if (authInfo != nil) { + _awaitingAuthInfoForSelector = nil; + } + } else { + return; } - bool wasSuspended = _mtState & (MTProtoStateAwaitingDatacenterAuthorization | MTProtoStateAwaitingDatacenterTempAuthKey); + bool wasSuspended = _mtState & (MTProtoStateAwaitingDatacenterAuthorization); if (authInfo != nil) { if (_mtState & MTProtoStateAwaitingDatacenterAuthorization) { @@ -2741,38 +2550,8 @@ static NSString *dumpHexString(NSData *data, int maxLength) { } } - /*if (_transportScheme != nil && _useTempAuthKeys) { - MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; - if (_transportScheme.address.preferForMedia) { - tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; - } - if ([[_context authInfoForDatacenterWithId:_datacenterId] tempAuthKeyWithType:tempAuthKeyType] == nil) { - if ((_mtState & MTProtoStateAwaitingDatacenterTempAuthKey) == 0) { - [self setMtState:_mtState | MTProtoStateAwaitingDatacenterTempAuthKey]; - - [_context tempAuthKeyForDatacenterWithIdRequired:_datacenterId keyType:tempAuthKeyType]; - } - } - } - - if (_transportScheme != nil && (_mtState & MTProtoStateAwaitingDatacenterTempAuthKey)) - { - MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; - if (_transportScheme.address.preferForMedia) { - tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; - } - - if ([_authInfo tempAuthKeyWithType:tempAuthKeyType] != nil) { - [self setMtState:_mtState & (~MTProtoStateAwaitingDatacenterTempAuthKey)]; - } - } - - if (_transportScheme != nil) { - [self checkTempAuthKeyBinding:_transportScheme.address]; - }*/ - if (authInfo != nil) { - if ((_mtState & (MTProtoStateAwaitingDatacenterAuthorization | MTProtoStateAwaitingDatacenterTempAuthKey)) == 0) { + if ((_mtState & (MTProtoStateAwaitingDatacenterAuthorization)) == 0) { if (wasSuspended) { [self resetTransport]; [self requestTransportTransaction]; @@ -2785,38 +2564,6 @@ static NSString *dumpHexString(NSData *data, int maxLength) { }]; } -- (void)checkTempAuthKeyBinding:(MTDatacenterAddress *)address { - NSAssert([[MTProto managerQueue] isCurrentQueue], @"invalid queue"); - - MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; - if (address.preferForMedia) { - tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; - } - MTDatacenterAuthKey *effectiveTempAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; - - if (_useTempAuthKeys && effectiveTempAuthKey != nil && effectiveTempAuthKey.notBound) { - bool isAlreadyBinding = false; - if (_bindingTempAuthKeyId != 0) { - if (_bindingTempAuthKeyId == effectiveTempAuthKey.authKeyId) { - isAlreadyBinding = true; - } else { - _bindingTempAuthKeyId = 0; - _bindingTempAuthKeyContext = nil; - } - } - if (!isAlreadyBinding && (_mtState & MTProtoStateBindingTempAuthKey) == 0) { - [self bindToPersistentKey:address]; - } - } else { - _bindingTempAuthKeyId = 0; - _bindingTempAuthKeyContext = nil; - if ((_mtState & MTProtoStateBindingTempAuthKey) != 0) { - [self setMtState:_mtState & (~MTProtoStateBindingTempAuthKey)]; - [self requestTransportTransaction]; - } - } -} - - (void)contextDatacenterAuthTokenUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authToken:(id)authToken { [[MTProto managerQueue] dispatchOnQueue:^ @@ -2842,28 +2589,38 @@ static NSString *dumpHexString(NSData *data, int maxLength) { }]; } -- (void)timeSyncServiceCompleted:(MTTimeSyncMessageService *)timeSyncService timeDifference:(NSTimeInterval)timeDifference saltList:(NSArray *)saltList +- (void)timeSyncServiceCompleted:(MTTimeSyncMessageService *)timeSyncService timeDifference:(NSTimeInterval)timeDifference saltList:(NSArray *)saltList authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector { if ([_messageServices containsObject:timeSyncService]) { [self completeTimeSync]; [_messageServices removeObject:timeSyncService]; - [self timeSyncInfoChanged:timeDifference saltList:saltList]; + [self timeSyncInfoChanged:timeDifference saltList:saltList authInfoSelector:authInfoSelector]; } } -- (void)timeSyncInfoChanged:(NSTimeInterval)timeDifference saltList:(NSArray *)saltList +- (void)timeSyncInfoChanged:(NSTimeInterval)timeDifference saltList:(NSArray *)saltList authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector { [_context setGlobalTimeDifference:timeDifference]; if (saltList != nil) { - MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:_datacenterId]; - if (authInfo != nil) - { - MTDatacenterAuthInfo *updatedAuthInfo = [authInfo mergeSaltSet:saltList forTimestamp:[_context globalTime]]; - [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:updatedAuthInfo]; + if (_useExplicitAuthKey) { + if (_validAuthInfo != nil && _validAuthInfo.selector == authInfoSelector) { + MTDatacenterAuthInfo *updatedAuthInfo = [_validAuthInfo.authInfo mergeSaltSet:saltList forTimestamp:[_context globalTime]]; + _validAuthInfo = [[MTProtoValidAuthInfo alloc] initWithAuthInfo:updatedAuthInfo selector:authInfoSelector]; + } + } else { + MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:_datacenterId selector:authInfoSelector]; + if (authInfo != nil) + { + MTDatacenterAuthInfo *updatedAuthInfo = [authInfo mergeSaltSet:saltList forTimestamp:[_context globalTime]]; + [_context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:updatedAuthInfo selector:authInfoSelector]; + if (_validAuthInfo != nil && _validAuthInfo.selector == authInfoSelector) { + _validAuthInfo = [[MTProtoValidAuthInfo alloc] initWithAuthInfo:updatedAuthInfo selector:authInfoSelector]; + } + } } } @@ -2925,18 +2682,4 @@ static NSString *dumpHexString(NSData *data, int maxLength) { }]; } -- (void)bindToPersistentKey:(MTDatacenterAddress *)address { - [[MTProto managerQueue] dispatchOnQueue:^{ - MTDatacenterAuthTempKeyType tempAuthKeyType = MTDatacenterAuthTempKeyTypeMain; - if (address.preferForMedia) { - tempAuthKeyType = MTDatacenterAuthTempKeyTypeMedia; - } - MTDatacenterAuthKey *effectiveTempAuthKey = [_authInfo tempAuthKeyWithType:tempAuthKeyType]; - - _bindingTempAuthKeyId = effectiveTempAuthKey.authKeyId; - _bindingTempAuthKeyContext = nil; - _mtState |= MTProtoStateBindingTempAuthKey; - }]; -} - @end diff --git a/submodules/MtProtoKit/Sources/MTProtoEngine.m b/submodules/MtProtoKit/Sources/MTProtoEngine.m new file mode 100644 index 0000000000..f48cc993cd --- /dev/null +++ b/submodules/MtProtoKit/Sources/MTProtoEngine.m @@ -0,0 +1,52 @@ +#import + +#import "Utils/MTQueueLocalObject.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MTProtoEngineImpl : NSObject { + MTQueue *_queue; + id _persistenceInterface; +} + +@end + +@implementation MTProtoEngineImpl + +- (instancetype)initWithQueue:(MTQueue *)queue persistenceInterface:(id)persistenceInterface { + self = [super init]; + if (self != nil) { + _queue = queue; + _persistenceInterface = persistenceInterface; + } + return self; +} + +@end + +@interface MTProtoEngine () { + MTQueue *_queue; + MTQueueLocalObject *_impl; +} + +@end + +@implementation MTProtoEngine + +- (instancetype)initWithPersistenceInterface:(id)persistenceInterface { + self = [super init]; + if (self != nil) { + _queue = [[MTQueue alloc] init]; + __auto_type queue = _queue; + _impl = [[MTQueueLocalObject alloc] initWithQueue:queue generator:^MTProtoEngineImpl *{ + return [[MTProtoEngineImpl alloc] initWithQueue:queue persistenceInterface:persistenceInterface]; + }]; + } + return self; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/submodules/MtProtoKit/Sources/MTProtoInstance.m b/submodules/MtProtoKit/Sources/MTProtoInstance.m new file mode 100644 index 0000000000..ba5da46abf --- /dev/null +++ b/submodules/MtProtoKit/Sources/MTProtoInstance.m @@ -0,0 +1,48 @@ +#import + +#import +#import "Utils/MTQueueLocalObject.h" + +@interface MTProtoInstanceImpl : NSObject { + MTQueue *_queue; + MTProtoEngine *_engine; +} + +@end + +@implementation MTProtoInstanceImpl + +- (instancetype)initWithQueue:(MTQueue *)queue engine:(MTProtoEngine *)engine { + self = [super init]; + if (self != nil) { + _queue = queue; + _engine = engine; + } + return self; +} + +@end + +@interface MTProtoInstance () { + MTQueue *_queue; + MTQueueLocalObject *_impl; +} + +@end + +@implementation MTProtoInstance + +- (instancetype)initWithEngine:(MTProtoEngine *)engine { + self = [super init]; + if (self != nil) { + _queue = [[MTQueue alloc] init]; + __auto_type queue = _queue; + _impl = [[MTQueueLocalObject alloc] initWithQueue:queue generator:^MTProtoInstanceImpl *{ + return [[MTProtoInstanceImpl alloc] initWithQueue:queue engine:engine]; + }]; + } + return self; +} + +@end diff --git a/submodules/MtProtoKit/Sources/MTRequestMessageService.m b/submodules/MtProtoKit/Sources/MTRequestMessageService.m index 23f12dc625..fb85f6ff47 100644 --- a/submodules/MtProtoKit/Sources/MTRequestMessageService.m +++ b/submodules/MtProtoKit/Sources/MTRequestMessageService.m @@ -391,12 +391,12 @@ return currentData; } -- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto +- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo { NSMutableArray *messages = nil; NSMutableDictionary *requestInternalIdToMessageInternalId = nil; - bool requestsWillInitializeApi = _apiEnvironment != nil && ![_apiEnvironment.apiInitializationHash isEqualToString:[_context authInfoForDatacenterWithId:mtProto.datacenterId].authKeyAttributes[@"apiInitializationHash"]]; + bool requestsWillInitializeApi = _apiEnvironment != nil && ![_apiEnvironment.apiInitializationHash isEqualToString:[_context authInfoForDatacenterWithId:mtProto.datacenterId selector:authInfoSelector].authKeyAttributes[@"apiInitializationHash"]]; CFAbsoluteTime currentTime = MTAbsoluteSystemTime(); @@ -561,7 +561,7 @@ return nil; } -- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)message +- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector { if ([message.body isKindOfClass:[MTRpcResultMessage class]]) { @@ -610,13 +610,13 @@ { rpcError = [[MTRpcError alloc] initWithErrorCode:500 errorDescription:@"TL_PARSING_ERROR"]; [_context performBatchUpdates:^{ - MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId]; + MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId selector:authInfoSelector]; NSMutableDictionary *authKeyAttributes = [[NSMutableDictionary alloc] initWithDictionary:authInfo.authKeyAttributes]; authKeyAttributes[@"apiInitializationHash"] = @""; authInfo = [authInfo withUpdatedAuthKeyAttributes:authKeyAttributes]; - [_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo]; + [_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo selector:authInfoSelector]; }]; } } @@ -636,7 +636,7 @@ if (rpcResult != nil && request.requestContext.willInitializeApi) { - MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId]; + MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId selector:authInfoSelector]; if (![_apiEnvironment.apiInitializationHash isEqualToString:authInfo.authKeyAttributes[@"apiInitializationHash"]]) { @@ -644,7 +644,7 @@ authKeyAttributes[@"apiInitializationHash"] = _apiEnvironment.apiInitializationHash; authInfo = [authInfo withUpdatedAuthKeyAttributes:authKeyAttributes]; - [_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo]; + [_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo selector:authInfoSelector]; } } @@ -726,13 +726,13 @@ { [_context performBatchUpdates:^ { - MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId]; + MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId selector:authInfoSelector]; NSMutableDictionary *authKeyAttributes = [[NSMutableDictionary alloc] initWithDictionary:authInfo.authKeyAttributes]; [authKeyAttributes removeObjectForKey:@"apiInitializationHash"]; authInfo = [authInfo withUpdatedAuthKeyAttributes:authKeyAttributes]; - [_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo]; + [_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo selector:authInfoSelector]; }]; } else if (rpcError.errorCode == 406) { if (_didReceiveSoftAuthResetError) { diff --git a/submodules/MtProtoKit/Sources/MTResendMessageService.m b/submodules/MtProtoKit/Sources/MTResendMessageService.m index 4e515ba39f..585ec29599 100644 --- a/submodules/MtProtoKit/Sources/MTResendMessageService.m +++ b/submodules/MtProtoKit/Sources/MTResendMessageService.m @@ -41,7 +41,7 @@ [mtProto requestTransportTransaction]; } -- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto +- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo { if (_currentRequestMessageId == 0 || _currentRequestTransactionId == nil) { @@ -121,7 +121,7 @@ } } -- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message +- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector { if (message.messageId == _messageId) { diff --git a/submodules/MtProtoKit/Sources/MTTcpTransport.m b/submodules/MtProtoKit/Sources/MTTcpTransport.m index 6b4cc265f1..3b0883b3aa 100644 --- a/submodules/MtProtoKit/Sources/MTTcpTransport.m +++ b/submodules/MtProtoKit/Sources/MTTcpTransport.m @@ -747,7 +747,7 @@ static const NSTimeInterval MTTcpTransportSleepWatchdogTimeout = 60.0; }]; } -- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)incomingMessage +- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)incomingMessage authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector { if ([incomingMessage.body isKindOfClass:[MTPongMessage class]]) { diff --git a/submodules/MtProtoKit/Sources/MTTimeSyncMessageService.m b/submodules/MtProtoKit/Sources/MTTimeSyncMessageService.m index 6d8919c176..8672ed541d 100644 --- a/submodules/MtProtoKit/Sources/MTTimeSyncMessageService.m +++ b/submodules/MtProtoKit/Sources/MTTimeSyncMessageService.m @@ -45,7 +45,7 @@ [mtProto requestTransportTransaction]; } -- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto +- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo { if (_currentTransactionId == nil) { @@ -127,7 +127,7 @@ } } -- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message +- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector { if ([message.body isKindOfClass:[MTFutureSaltsMessage class]] && ((MTFutureSaltsMessage *)message.body).requestMessageId == _currentMessageId) { diff --git a/submodules/MtProtoKit/Sources/Utils/MTQueueLocalObject.h b/submodules/MtProtoKit/Sources/Utils/MTQueueLocalObject.h new file mode 100644 index 0000000000..63165bd89f --- /dev/null +++ b/submodules/MtProtoKit/Sources/Utils/MTQueueLocalObject.h @@ -0,0 +1,14 @@ +#import + +@class MTQueue; + +NS_ASSUME_NONNULL_BEGIN + +@interface MTQueueLocalObject<__covariant T> : NSObject + +- (instancetype)initWithQueue:(MTQueue *)queue generator:(T(^)())generator; +- (void)with:(void (^)(T))f; + +@end + +NS_ASSUME_NONNULL_END diff --git a/submodules/MtProtoKit/Sources/Utils/MTQueueLocalObject.m b/submodules/MtProtoKit/Sources/Utils/MTQueueLocalObject.m new file mode 100644 index 0000000000..a84da57845 --- /dev/null +++ b/submodules/MtProtoKit/Sources/Utils/MTQueueLocalObject.m @@ -0,0 +1,56 @@ +#import "MTQueueLocalObject.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MTQueueLocalObjectHolder : NSObject + +@property (nonatomic, assign) CFTypeRef impl; + +@end + +@implementation MTQueueLocalObjectHolder + +@end + +@interface MTQueueLocalObject () { + MTQueue *_queue; + MTQueueLocalObjectHolder *_holder; +} + +@end + +@implementation MTQueueLocalObject + +- (instancetype)initWithQueue:(MTQueue *)queue generator:(id(^)())generator { + self = [super init]; + if (self != nil) { + _queue = queue; + _holder = [[MTQueueLocalObjectHolder alloc] init]; + __auto_type holder = _holder; + [queue dispatchOnQueue:^{ + id value = generator(); + holder.impl = CFBridgingRetain(value); + } synchronous:false]; + } + return self; +} + +- (void)dealloc { + __auto_type holder = _holder; + [_queue dispatchOnQueue:^{ + CFBridgingRelease(holder.impl); + } synchronous:false]; +} + +- (void)with:(void (^)(id))f { + __auto_type holder = _holder; + [_queue dispatchOnQueue:^{ + id value = (__bridge id)holder.impl; + f(value); + } synchronous:false]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift index d2544ac4d5..5874c4672c 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift @@ -487,6 +487,8 @@ private func stringForRight(strings: PresentationStrings, right: TelegramChatAdm return strings.Channel_EditAdmin_PermissionPinMessages } else if right.contains(.canAddAdmins) { return strings.Channel_EditAdmin_PermissionAddAdmins + } else if right.contains(.canBeAnonymous) { + return strings.Channel_AdminLog_CanBeAnonymous } else { return "" } @@ -509,6 +511,8 @@ private func rightDependencies(_ right: TelegramChatAdminRightsFlags) -> [Telegr return [] } else if right.contains(.canAddAdmins) { return [] + } else if right.contains(.canBeAnonymous) { + return [] } else { return [] } @@ -607,11 +611,43 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s .canBanUsers, .canInviteUsers, .canPinMessages, + .canBeAnonymous, .canAddAdmins ] } if isCreator { + if isGroup { + entries.append(.rightsTitle(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionsHeader)) + + let accountUserRightsFlags: TelegramChatAdminRightsFlags + if channel.flags.contains(.isCreator) { + accountUserRightsFlags = maskRightsFlags + } else if let adminRights = channel.adminRights { + accountUserRightsFlags = maskRightsFlags.intersection(adminRights.flags) + } else { + accountUserRightsFlags = [] + } + + let currentRightsFlags: TelegramChatAdminRightsFlags + if let updatedFlags = state.updatedFlags { + currentRightsFlags = updatedFlags + } else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminRights, _, _) = initialParticipant, let adminRights = maybeAdminRights { + currentRightsFlags = adminRights.rights.flags + } else if let initialParticipant = initialParticipant, case let .creator(_, maybeAdminRights, _) = initialParticipant, let adminRights = maybeAdminRights { + currentRightsFlags = adminRights.rights.flags + } else { + currentRightsFlags = accountUserRightsFlags.subtracting(.canAddAdmins).subtracting(.canBeAnonymous) + } + + var index = 0 + for right in rightsOrder { + if accountUserRightsFlags.contains(right) { + entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, defaultBannedRights: channel.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), right == .canBeAnonymous)) + index += 1 + } + } + } } else { entries.append(.rightsTitle(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionsHeader)) @@ -631,7 +667,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s } else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminRights, _, _) = initialParticipant, let adminRights = maybeAdminRights { currentRightsFlags = adminRights.rights.flags } else { - currentRightsFlags = accountUserRightsFlags.subtracting(.canAddAdmins) + currentRightsFlags = accountUserRightsFlags.subtracting(.canAddAdmins).subtracting(.canBeAnonymous) } var index = 0 @@ -731,6 +767,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s .canBanUsers, .canInviteUsers, .canPinMessages, + .canBeAnonymous, .canAddAdmins ] diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift index 3a6adeff07..54e6664322 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift @@ -696,7 +696,7 @@ public func channelAdminsController(context: AccountContext, peerId initialPeerI if let peer = peerView.peers[participant.peerId] { switch participant { case .creator: - result.append(RenderedChannelParticipant(participant: .creator(id: peer.id, rank: nil), peer: peer)) + result.append(RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer)) case .admin: var peers: [PeerId: Peer] = [:] peers[creator.id] = creator diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift index eea39f5c4c..93e5dbdf56 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift @@ -166,7 +166,7 @@ private func channelDiscussionGroupSetupControllerEntries(presentationData: Pres var entries: [ChannelDiscussionGroupSetupControllerEntry] = [] - if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId { + if case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId { if let group = view.peers[linkedDiscussionPeerId] { if case .group = peer.info { entries.append(.header(presentationData.theme, presentationData.strings, group.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), true, presentationData.strings.Channel_DiscussionGroup_HeaderLabel)) @@ -299,7 +299,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI return } - if groupId == cachedData.linkedDiscussionPeerId { + if case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, maybeLinkedDiscussionPeerId == groupId { navigateToGroupImpl?(groupId) return } @@ -483,7 +483,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI let applyPeerId: PeerId if case .broadcast = peer.info { applyPeerId = peerId - } else if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId { + } else if case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId { applyPeerId = linkedDiscussionPeerId } else { return diff --git a/submodules/PeerInfoUI/Sources/ChannelInfoController.swift b/submodules/PeerInfoUI/Sources/ChannelInfoController.swift index 80d960096e..a8d2e5aa20 100644 --- a/submodules/PeerInfoUI/Sources/ChannelInfoController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelInfoController.swift @@ -497,7 +497,7 @@ private func channelInfoEntries(account: Account, presentationData: Presentation let discussionGroupTitle: String if let cachedData = view.cachedData as? CachedChannelData { - if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] { + if case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] { if let addressName = peer.addressName, !addressName.isEmpty { discussionGroupTitle = "@\(addressName)" } else { @@ -532,7 +532,7 @@ private func channelInfoEntries(account: Account, presentationData: Presentation if let _ = state.editingState, let adminRights = peer.adminRights, !adminRights.isEmpty { let discussionGroupTitle: String? if let cachedData = view.cachedData as? CachedChannelData { - if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] { + if case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] { if let addressName = peer.addressName, !addressName.isEmpty { discussionGroupTitle = "@\(addressName)" } else { @@ -951,7 +951,7 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi if canEditChannel { hasSomethingToEdit = true } else if let adminRights = peer.adminRights, !adminRights.isEmpty { - if let cachedData = view.cachedData as? CachedChannelData, let _ = cachedData.linkedDiscussionPeerId { + if let cachedData = view.cachedData as? CachedChannelData, case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let _ = maybeLinkedDiscussionPeerId { hasSomethingToEdit = true } } diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift index e2f39cd063..2e451d1400 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift @@ -867,7 +867,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon let renderedParticipant: RenderedChannelParticipant switch participant { case .creator: - renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, rank: nil), peer: peer) + renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer) case .admin: var peers: [PeerId: Peer] = [:] if let creator = creatorPeer { diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift index 874ede1852..9cf4386f48 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift @@ -216,7 +216,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { let renderedParticipant: RenderedChannelParticipant switch participant { case .creator: - renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, rank: nil), peer: peer) + renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer) case .admin: var peers: [PeerId: Peer] = [:] peers[creator.id] = creator diff --git a/submodules/PeerInfoUI/Sources/GroupInfoController.swift b/submodules/PeerInfoUI/Sources/GroupInfoController.swift index a9b1029dfd..14daf3c79a 100644 --- a/submodules/PeerInfoUI/Sources/GroupInfoController.swift +++ b/submodules/PeerInfoUI/Sources/GroupInfoController.swift @@ -896,7 +896,7 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa } else { if cachedChannelData.flags.contains(.canChangeUsername) { entries.append(GroupInfoEntry.groupTypeSetup(presentationData.theme, presentationData.strings.GroupInfo_GroupType, isPublic ? presentationData.strings.Channel_Setup_TypePublic : presentationData.strings.Channel_Setup_TypePrivate)) - if let linkedDiscussionPeerId = cachedChannelData.linkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] { + if case let .known(maybeLinkedDiscussionPeerId) = cachedChannelData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] { let peerTitle: String if let addressName = peer.addressName, !addressName.isEmpty { peerTitle = "@\(addressName)" @@ -1069,7 +1069,7 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa let participant: ChannelParticipant switch sortedParticipants[i] { case .creator: - participant = .creator(id: sortedParticipants[i].peerId, rank: nil) + participant = .creator(id: sortedParticipants[i].peerId, adminInfo: nil, rank: nil) memberStatus = .owner(rank: nil) case .admin: participant = .member(id: sortedParticipants[i].peerId, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(flags: .groupSpecific), promotedBy: account.peerId, canBeEditedByAccountPeer: true), banInfo: nil, rank: nil) @@ -1201,7 +1201,7 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa let participant = participants[i] let memberStatus: GroupInfoMemberStatus switch participant.participant { - case let .creator(_, rank): + case let .creator(_, _, rank): memberStatus = .owner(rank: rank) case let .member(_, _, adminInfo, _, rank): if adminInfo != nil { diff --git a/submodules/Postbox/Sources/AdditionalMessageHistoryViewData.swift b/submodules/Postbox/Sources/AdditionalMessageHistoryViewData.swift index abf31af054..50f874f213 100644 --- a/submodules/Postbox/Sources/AdditionalMessageHistoryViewData.swift +++ b/submodules/Postbox/Sources/AdditionalMessageHistoryViewData.swift @@ -10,6 +10,7 @@ public enum AdditionalMessageHistoryViewData { case preferencesEntry(ValueBoxKey) case peer(PeerId) case peerIsContact(PeerId) + case message(MessageId) } public enum AdditionalMessageHistoryViewDataEntry { @@ -22,4 +23,5 @@ public enum AdditionalMessageHistoryViewDataEntry { case preferencesEntry(ValueBoxKey, PreferencesEntry?) case peerIsContact(PeerId, Bool) case peer(PeerId, Peer?) + case message(MessageId, Message?) } diff --git a/submodules/Postbox/Sources/ChatListViewState.swift b/submodules/Postbox/Sources/ChatListViewState.swift index c535fa6317..a3fc67730f 100644 --- a/submodules/Postbox/Sources/ChatListViewState.swift +++ b/submodules/Postbox/Sources/ChatListViewState.swift @@ -96,7 +96,7 @@ private func updateMessagePeers(_ message: Message, updatedPeers: [PeerId: Peer] peers[peerId] = currentPeer } } - return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) + return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) } return nil } diff --git a/submodules/Postbox/Sources/ChatLocation.swift b/submodules/Postbox/Sources/ChatLocation.swift index aa10414475..bdf23981a7 100644 --- a/submodules/Postbox/Sources/ChatLocation.swift +++ b/submodules/Postbox/Sources/ChatLocation.swift @@ -1,6 +1,12 @@ import Foundation +import SwiftSignalKit -public enum ChatLocation: Equatable { +public enum ChatLocationInput { case peer(PeerId) - //case group(PeerGroupId) + case external(PeerId, Signal) +} + +enum ResolvedChatLocationInput { + case peer(PeerId) + case external(MessageHistoryViewExternalInput) } diff --git a/submodules/Postbox/Sources/GlobalMessageTagsView.swift b/submodules/Postbox/Sources/GlobalMessageTagsView.swift index c52fa1f315..51bd14f19a 100644 --- a/submodules/Postbox/Sources/GlobalMessageTagsView.swift +++ b/submodules/Postbox/Sources/GlobalMessageTagsView.swift @@ -163,11 +163,11 @@ final class MutableGlobalMessageTagsView: MutablePostboxView { hasChanges = true } case let .intermediateMessage(message): - if self.add(.intermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: updatedTimestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: message.embeddedMediaData, referencedMedia: message.referencedMedia))) { + if self.add(.intermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: updatedTimestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: message.embeddedMediaData, referencedMedia: message.referencedMedia))) { hasChanges = true } case let .message(message): - if self.add(.message(Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: updatedTimestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds))) { + if self.add(.message(Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: updatedTimestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds))) { hasChanges = true } } diff --git a/submodules/Postbox/Sources/IntermediateMessage.swift b/submodules/Postbox/Sources/IntermediateMessage.swift index 6835a546ec..65d29e403c 100644 --- a/submodules/Postbox/Sources/IntermediateMessage.swift +++ b/submodules/Postbox/Sources/IntermediateMessage.swift @@ -34,6 +34,7 @@ class IntermediateMessage { let globallyUniqueId: Int64? let groupingKey: Int64? let groupInfo: MessageGroupInfo? + let threadId: Int64? let timestamp: Int32 let flags: MessageFlags let tags: MessageTags @@ -50,13 +51,14 @@ class IntermediateMessage { return MessageIndex(id: self.id, timestamp: self.timestamp) } - init(stableId: UInt32, stableVersion: UInt32, id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, groupInfo: MessageGroupInfo?, timestamp: Int32, flags: MessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: IntermediateMessageForwardInfo?, authorId: PeerId?, text: String, attributesData: ReadBuffer, embeddedMediaData: ReadBuffer, referencedMedia: [MediaId]) { + init(stableId: UInt32, stableVersion: UInt32, id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, groupInfo: MessageGroupInfo?, threadId: Int64?, timestamp: Int32, flags: MessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: IntermediateMessageForwardInfo?, authorId: PeerId?, text: String, attributesData: ReadBuffer, embeddedMediaData: ReadBuffer, referencedMedia: [MediaId]) { self.stableId = stableId self.stableVersion = stableVersion self.id = id self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey self.groupInfo = groupInfo + self.threadId = threadId self.timestamp = timestamp self.flags = flags self.tags = tags @@ -71,18 +73,18 @@ class IntermediateMessage { } func withUpdatedTimestamp(_ timestamp: Int32) -> IntermediateMessage { - return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) + return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) } func withUpdatedGroupingKey(_ groupingKey: Int64?) -> IntermediateMessage { - return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) + return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) } func withUpdatedGroupInfo(_ groupInfo: MessageGroupInfo?) -> IntermediateMessage { - return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) + return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: self.embeddedMediaData, referencedMedia: self.referencedMedia) } func withUpdatedEmbeddedMedia(_ embeddedMedia: ReadBuffer) -> IntermediateMessage { - return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: embeddedMedia, referencedMedia: self.referencedMedia) + return IntermediateMessage(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributesData: self.attributesData, embeddedMediaData: embeddedMedia, referencedMedia: self.referencedMedia) } } diff --git a/submodules/Postbox/Sources/Message.swift b/submodules/Postbox/Sources/Message.swift index ebcce69cd8..6d476aa661 100644 --- a/submodules/Postbox/Sources/Message.swift +++ b/submodules/Postbox/Sources/Message.swift @@ -107,10 +107,6 @@ public struct MessageIndex: Comparable, Hashable { return MessageIndex(id: MessageId(peerId: self.id.peerId, namespace: self.id.namespace, id: self.id.id == Int32.max ? self.id.id : (self.id.id + 1)), timestamp: self.timestamp) } - public var hashValue: Int { - return self.id.hashValue - } - public static func absoluteUpperBound() -> MessageIndex { return MessageIndex(id: MessageId(peerId: PeerId(namespace: Int32(Int8.max), id: Int32.max), namespace: Int32(Int8.max), id: Int32.max), timestamp: Int32.max) } @@ -492,6 +488,7 @@ public final class Message { public let globallyUniqueId: Int64? public let groupingKey: Int64? public let groupInfo: MessageGroupInfo? + public let threadId: Int64? public let timestamp: Int32 public let flags: MessageFlags public let tags: MessageTags @@ -510,13 +507,14 @@ public final class Message { return MessageIndex(id: self.id, timestamp: self.timestamp) } - public init(stableId: UInt32, stableVersion: UInt32, id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, groupInfo: MessageGroupInfo?, timestamp: Int32, flags: MessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: MessageForwardInfo?, author: Peer?, text: String, attributes: [MessageAttribute], media: [Media], peers: SimpleDictionary, associatedMessages: SimpleDictionary, associatedMessageIds: [MessageId]) { + public init(stableId: UInt32, stableVersion: UInt32, id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, groupInfo: MessageGroupInfo?, threadId: Int64?, timestamp: Int32, flags: MessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: MessageForwardInfo?, author: Peer?, text: String, attributes: [MessageAttribute], media: [Media], peers: SimpleDictionary, associatedMessages: SimpleDictionary, associatedMessageIds: [MessageId]) { self.stableId = stableId self.stableVersion = stableVersion self.id = id self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey self.groupInfo = groupInfo + self.threadId = threadId self.timestamp = timestamp self.flags = flags self.tags = tags @@ -533,23 +531,23 @@ public final class Message { } public func withUpdatedMedia(_ media: [Media]) -> Message { - return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) + return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) } public func withUpdatedPeers(_ peers: SimpleDictionary) -> Message { - return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) + return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) } public func withUpdatedFlags(_ flags: MessageFlags) -> Message { - return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) + return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) } func withUpdatedGroupInfo(_ groupInfo: MessageGroupInfo?) -> Message { - return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) + return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: self.associatedMessages, associatedMessageIds: self.associatedMessageIds) } func withUpdatedAssociatedMessages(_ associatedMessages: SimpleDictionary) -> Message { - return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: associatedMessages, associatedMessageIds: self.associatedMessageIds) + return Message(stableId: self.stableId, stableVersion: self.stableVersion, id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, groupInfo: self.groupInfo, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, author: self.author, text: self.text, attributes: self.attributes, media: self.media, peers: self.peers, associatedMessages: associatedMessages, associatedMessageIds: self.associatedMessageIds) } } @@ -637,11 +635,22 @@ public enum StoreMessageId { } } +public func makeMessageThreadId(_ messageId: MessageId) -> Int64 { + return (Int64(messageId.namespace) << 32) | Int64(bitPattern: UInt64(UInt32(bitPattern: messageId.id))) +} + +public func makeThreadIdMessageId(peerId: PeerId, threadId: Int64) -> MessageId { + let namespace = Int32((threadId >> 32) & 0x7fffffff) + let id = Int32(bitPattern: UInt32(threadId & 0xffffffff)) + return MessageId(peerId: peerId, namespace: namespace, id: id) +} + public final class StoreMessage { public let id: StoreMessageId public let timestamp: Int32 public let globallyUniqueId: Int64? public let groupingKey: Int64? + public let threadId: Int64? public let flags: StoreMessageFlags public let tags: MessageTags public let globalTags: GlobalMessageTags @@ -652,10 +661,11 @@ public final class StoreMessage { public let attributes: [MessageAttribute] public let media: [Media] - public init(id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { + public init(id: MessageId, globallyUniqueId: Int64?, groupingKey: Int64?, threadId: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { self.id = .Id(id) self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey + self.threadId = threadId self.timestamp = timestamp self.flags = flags self.tags = tags @@ -668,11 +678,12 @@ public final class StoreMessage { self.media = media } - public init(peerId: PeerId, namespace: MessageId.Namespace, globallyUniqueId: Int64?, groupingKey: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { + public init(peerId: PeerId, namespace: MessageId.Namespace, globallyUniqueId: Int64?, groupingKey: Int64?, threadId: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { self.id = .Partial(peerId, namespace) self.timestamp = timestamp self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey + self.threadId = threadId self.flags = flags self.tags = tags self.globalTags = globalTags @@ -684,11 +695,12 @@ public final class StoreMessage { self.media = media } - public init(id: StoreMessageId, globallyUniqueId: Int64?, groupingKey: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { + public init(id: StoreMessageId, globallyUniqueId: Int64?, groupingKey: Int64?, threadId: Int64?, timestamp: Int32, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { self.id = id self.timestamp = timestamp self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey + self.threadId = threadId self.flags = flags self.tags = tags self.globalTags = globalTags @@ -712,19 +724,19 @@ public final class StoreMessage { if flags == self.flags { return self } else { - return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media) + return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, threadId: self.threadId, timestamp: self.timestamp, flags: flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media) } } public func withUpdatedAttributes(_ attributes: [MessageAttribute]) -> StoreMessage { - return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media) + return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media) } public func withUpdatedLocalTags(_ localTags: LocalMessageTags) -> StoreMessage { if localTags == self.localTags { return self } else { - return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: self.attributes, media: self.media) + return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, threadId: self.threadId, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: self.attributes, media: self.media) } } } @@ -734,6 +746,7 @@ final class InternalStoreMessage { let timestamp: Int32 let globallyUniqueId: Int64? let groupingKey: Int64? + let threadId: Int64? let flags: StoreMessageFlags let tags: MessageTags let globalTags: GlobalMessageTags @@ -748,11 +761,12 @@ final class InternalStoreMessage { return MessageIndex(id: self.id, timestamp: self.timestamp) } - init(id: MessageId, timestamp: Int32, globallyUniqueId: Int64?, groupingKey: Int64?, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { + init(id: MessageId, timestamp: Int32, globallyUniqueId: Int64?, groupingKey: Int64?, threadId: Int64?, flags: StoreMessageFlags, tags: MessageTags, globalTags: GlobalMessageTags, localTags: LocalMessageTags, forwardInfo: StoreMessageForwardInfo?, authorId: PeerId?, text: String, attributes: [MessageAttribute], media: [Media]) { self.id = id self.timestamp = timestamp self.globallyUniqueId = globallyUniqueId self.groupingKey = groupingKey + self.threadId = threadId self.flags = flags self.tags = tags self.globalTags = globalTags diff --git a/submodules/Postbox/Sources/MessageHistoryHolesView.swift b/submodules/Postbox/Sources/MessageHistoryHolesView.swift index 14c65efea0..503580e653 100644 --- a/submodules/Postbox/Sources/MessageHistoryHolesView.swift +++ b/submodules/Postbox/Sources/MessageHistoryHolesView.swift @@ -37,3 +37,40 @@ public final class MessageHistoryHolesView { self.entries = mutableView.entries } } + +public struct MessageHistoryExternalHolesViewEntry: Equatable, Hashable { + public let hole: MessageHistoryViewHole + public let direction: MessageHistoryViewRelativeHoleDirection + public let count: Int + + public init(hole: MessageHistoryViewHole, direction: MessageHistoryViewRelativeHoleDirection, count: Int) { + self.hole = hole + self.direction = direction + self.count = count + } +} + +final class MutableMessageHistoryExternalHolesView { + fileprivate var entries = Set() + + init() { + } + + func update(_ holes: Set) -> Bool { + if self.entries != holes { + self.entries = holes + return true + } else { + return false + } + } +} + +public final class MessageHistoryExternalHolesView { + public let entries: Set + + init(_ mutableView: MutableMessageHistoryExternalHolesView) { + self.entries = mutableView.entries + } +} + diff --git a/submodules/Postbox/Sources/MessageHistoryTable.swift b/submodules/Postbox/Sources/MessageHistoryTable.swift index 4bf428c00a..bd0c1988b1 100644 --- a/submodules/Postbox/Sources/MessageHistoryTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTable.swift @@ -50,6 +50,7 @@ private struct MessageDataFlags: OptionSet { static let hasGroupingKey = MessageDataFlags(rawValue: 1 << 2) static let hasGroupInfo = MessageDataFlags(rawValue: 1 << 3) static let hasLocalTags = MessageDataFlags(rawValue: 1 << 4) + static let hasThreadId = MessageDataFlags(rawValue: 1 << 5) } private func extractKey(_ key: ValueBoxKey) -> MessageIndex { @@ -71,6 +72,7 @@ final class MessageHistoryTable: Table { let unsentTable: MessageHistoryUnsentTable let failedTable: MessageHistoryFailedTable let tagsTable: MessageHistoryTagsTable + let threadsTable: MessageHistoryThreadsTable let globalTagsTable: GlobalMessageHistoryTagsTable let localTagsTable: LocalMessageHistoryTagsTable let readStateTable: MessageHistoryReadStateTable @@ -79,7 +81,7 @@ final class MessageHistoryTable: Table { let summaryTable: MessageHistoryTagsSummaryTable let pendingActionsTable: PendingMessageActionsTable - init(valueBox: ValueBox, table: ValueBoxTable, seedConfiguration: SeedConfiguration, messageHistoryIndexTable: MessageHistoryIndexTable, messageHistoryHoleIndexTable: MessageHistoryHoleIndexTable, messageMediaTable: MessageMediaTable, historyMetadataTable: MessageHistoryMetadataTable, globallyUniqueMessageIdsTable: MessageGloballyUniqueIdTable, unsentTable: MessageHistoryUnsentTable, failedTable: MessageHistoryFailedTable, tagsTable: MessageHistoryTagsTable, globalTagsTable: GlobalMessageHistoryTagsTable, localTagsTable: LocalMessageHistoryTagsTable, readStateTable: MessageHistoryReadStateTable, synchronizeReadStateTable: MessageHistorySynchronizeReadStateTable, textIndexTable: MessageHistoryTextIndexTable, summaryTable: MessageHistoryTagsSummaryTable, pendingActionsTable: PendingMessageActionsTable) { + init(valueBox: ValueBox, table: ValueBoxTable, seedConfiguration: SeedConfiguration, messageHistoryIndexTable: MessageHistoryIndexTable, messageHistoryHoleIndexTable: MessageHistoryHoleIndexTable, messageMediaTable: MessageMediaTable, historyMetadataTable: MessageHistoryMetadataTable, globallyUniqueMessageIdsTable: MessageGloballyUniqueIdTable, unsentTable: MessageHistoryUnsentTable, failedTable: MessageHistoryFailedTable, tagsTable: MessageHistoryTagsTable, threadsTable: MessageHistoryThreadsTable, globalTagsTable: GlobalMessageHistoryTagsTable, localTagsTable: LocalMessageHistoryTagsTable, readStateTable: MessageHistoryReadStateTable, synchronizeReadStateTable: MessageHistorySynchronizeReadStateTable, textIndexTable: MessageHistoryTextIndexTable, summaryTable: MessageHistoryTagsSummaryTable, pendingActionsTable: PendingMessageActionsTable) { self.seedConfiguration = seedConfiguration self.messageHistoryIndexTable = messageHistoryIndexTable self.messageHistoryHoleIndexTable = messageHistoryHoleIndexTable @@ -89,6 +91,7 @@ final class MessageHistoryTable: Table { self.unsentTable = unsentTable self.failedTable = failedTable self.tagsTable = tagsTable + self.threadsTable = threadsTable self.globalTagsTable = globalTagsTable self.localTagsTable = localTagsTable self.readStateTable = readStateTable @@ -268,6 +271,9 @@ final class MessageHistoryTable: Table { } } } + if let threadId = message.threadId { + self.threadsTable.add(threadId: threadId, index: message.index) + } let globalTags = message.globalTags.rawValue if globalTags != 0 { for i in 0 ..< 32 { @@ -374,10 +380,10 @@ final class MessageHistoryTable: Table { for message in messages { switch message.id { case let .Id(id): - internalStoreMessages.append(InternalStoreMessage(id: id, timestamp: message.timestamp, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributes: message.attributes, media: message.media)) + internalStoreMessages.append(InternalStoreMessage(id: id, timestamp: message.timestamp, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributes: message.attributes, media: message.media)) case let .Partial(peerId, namespace): let id = self.historyMetadataTable.getNextMessageIdAndIncrement(peerId, namespace: namespace) - internalStoreMessages.append(InternalStoreMessage(id: id, timestamp: message.timestamp, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributes: message.attributes, media: message.media)) + internalStoreMessages.append(InternalStoreMessage(id: id, timestamp: message.timestamp, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributes: message.attributes, media: message.media)) } } return internalStoreMessages @@ -979,6 +985,9 @@ final class MessageHistoryTable: Table { if !message.localTags.isEmpty { dataFlags.insert(.hasLocalTags) } + if message.threadId != nil { + dataFlags.insert(.hasThreadId) + } sharedBuffer.write(&dataFlags, offset: 0, length: 1) if let globallyUniqueId = message.globallyUniqueId { var globallyUniqueIdValue = globallyUniqueId @@ -1001,6 +1010,9 @@ final class MessageHistoryTable: Table { var localTagsValue: UInt32 = message.localTags.rawValue sharedBuffer.write(&localTagsValue, offset: 0, length: 4) } + if var threadId = message.threadId { + sharedBuffer.write(&threadId, length: 8) + } if self.seedConfiguration.peerNamespacesRequiringMessageTextIndex.contains(message.id.peerId.namespace) { var indexableText = message.text @@ -1160,7 +1172,7 @@ final class MessageHistoryTable: Table { self.valueBox.set(self.table, key: self.key(message.index, key: sharedKey), value: sharedBuffer) - let result = (IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: groupInfo, timestamp: message.timestamp, flags: flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: intermediateForwardInfo, authorId: message.authorId, text: message.text, attributesData: attributesBuffer.makeReadBufferAndReset(), embeddedMediaData: embeddedMediaBuffer.makeReadBufferAndReset(), referencedMedia: referencedMedia), updatedGroupInfos) + let result = (IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: intermediateForwardInfo, authorId: message.authorId, text: message.text, attributesData: attributesBuffer.makeReadBufferAndReset(), embeddedMediaData: embeddedMediaBuffer.makeReadBufferAndReset(), referencedMedia: referencedMedia), updatedGroupInfos) return result } @@ -1237,6 +1249,9 @@ final class MessageHistoryTable: Table { for tag in message.tags { self.tagsTable.remove(tags: tag, index: index, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) } + if let threadId = message.threadId { + self.threadsTable.remove(threadId: threadId, index: index) + } for tag in message.globalTags { self.globalTagsTable.remove(tag, index: index) } @@ -1331,7 +1346,7 @@ final class MessageHistoryTable: Table { } withExtendedLifetime(updatedEmbeddedMediaBuffer, { - self.storeIntermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: updatedEmbeddedMediaBuffer.readBufferNoCopy(), referencedMedia: message.referencedMedia), sharedKey: self.key(index)) + self.storeIntermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: updatedEmbeddedMediaBuffer.readBufferNoCopy(), referencedMedia: message.referencedMedia), sharedKey: self.key(index)) }) let operation: MessageHistoryOperation = .UpdateEmbeddedMedia(index, updatedEmbeddedMediaBuffer.makeReadBufferAndReset()) @@ -1419,6 +1434,14 @@ final class MessageHistoryTable: Table { self.tagsTable.add(tags: message.tags, index: message.index, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) } } + if previousMessage.threadId != message.threadId || index != message.index { + if let threadId = previousMessage.threadId { + self.threadsTable.remove(threadId: threadId, index: index) + } + if let threadId = message.threadId { + self.threadsTable.add(threadId: threadId, index: message.index) + } + } if !previousMessage.globalTags.isEmpty || !message.globalTags.isEmpty { if !previousMessage.globalTags.isEmpty { @@ -1542,6 +1565,9 @@ final class MessageHistoryTable: Table { if !updatedLocalTags.isEmpty { dataFlags.insert(.hasLocalTags) } + if message.threadId != nil { + dataFlags.insert(.hasThreadId) + } sharedBuffer.write(&dataFlags, offset: 0, length: 1) if let globallyUniqueId = message.globallyUniqueId { var globallyUniqueIdValue = globallyUniqueId @@ -1564,6 +1590,9 @@ final class MessageHistoryTable: Table { var localTagsValue: UInt32 = updatedLocalTags.rawValue sharedBuffer.write(&localTagsValue, offset: 0, length: 4) } + if var threadId = message.threadId { + sharedBuffer.write(&threadId, length: 8) + } var flags = MessageFlags(message.flags) sharedBuffer.write(&flags.rawValue, offset: 0, length: 4) @@ -1708,7 +1737,7 @@ final class MessageHistoryTable: Table { self.valueBox.set(self.table, key: self.key(message.index, key: sharedKey), value: sharedBuffer) - let result = (IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: groupInfo, timestamp: message.timestamp, flags: flags, tags: tags, globalTags: message.globalTags, localTags: updatedLocalTags, forwardInfo: intermediateForwardInfo, authorId: message.authorId, text: message.text, attributesData: attributesBuffer.makeReadBufferAndReset(), embeddedMediaData: embeddedMediaBuffer.makeReadBufferAndReset(), referencedMedia: referencedMedia), previousMessage.tags) + let result = (IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: flags, tags: tags, globalTags: message.globalTags, localTags: updatedLocalTags, forwardInfo: intermediateForwardInfo, authorId: message.authorId, text: message.text, attributesData: attributesBuffer.makeReadBufferAndReset(), embeddedMediaData: embeddedMediaBuffer.makeReadBufferAndReset(), referencedMedia: referencedMedia), previousMessage.tags) for media in mediaToUpdate { if let id = media.id { @@ -1786,7 +1815,7 @@ final class MessageHistoryTable: Table { let updatedIndex = MessageIndex(id: index.id, timestamp: timestamp) - let _ = self.justUpdate(index, message: InternalStoreMessage(id: previousMessage.id, timestamp: timestamp, globallyUniqueId: previousMessage.globallyUniqueId, groupingKey: previousMessage.groupingKey, flags: StoreMessageFlags(previousMessage.flags), tags: previousMessage.tags, globalTags: previousMessage.globalTags, localTags: previousMessage.localTags, forwardInfo: storeForwardInfo, authorId: previousMessage.authorId, text: previousMessage.text, attributes: parsedAttributes, media: parsedMedia), keepLocalTags: false, sharedKey: self.key(updatedIndex), sharedBuffer: WriteBuffer(), sharedEncoder: PostboxEncoder(), unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, updatedMedia: &updatedMedia) + let _ = self.justUpdate(index, message: InternalStoreMessage(id: previousMessage.id, timestamp: timestamp, globallyUniqueId: previousMessage.globallyUniqueId, groupingKey: previousMessage.groupingKey, threadId: previousMessage.threadId, flags: StoreMessageFlags(previousMessage.flags), tags: previousMessage.tags, globalTags: previousMessage.globalTags, localTags: previousMessage.localTags, forwardInfo: storeForwardInfo, authorId: previousMessage.authorId, text: previousMessage.text, attributes: parsedAttributes, media: parsedMedia), keepLocalTags: false, sharedKey: self.key(updatedIndex), sharedBuffer: WriteBuffer(), sharedEncoder: PostboxEncoder(), unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, updatedMedia: &updatedMedia) return (previousMessage.tags, previousMessage.globalTags) } else { return nil @@ -1826,7 +1855,7 @@ final class MessageHistoryTable: Table { var updatedReferencedMedia = message.referencedMedia updatedReferencedMedia.append(extractedMedia.id!) withExtendedLifetime(updatedEmbeddedMediaBuffer, { - self.storeIntermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: updatedEmbeddedMediaBuffer.readBufferNoCopy(), referencedMedia: updatedReferencedMedia), sharedKey: self.key(index)) + self.storeIntermediateMessage(IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: updatedEmbeddedMediaBuffer.readBufferNoCopy(), referencedMedia: updatedReferencedMedia), sharedKey: self.key(index)) }) return extractedMedia @@ -1863,6 +1892,9 @@ final class MessageHistoryTable: Table { if !message.localTags.isEmpty { dataFlags.insert(.hasLocalTags) } + if message.threadId != nil { + dataFlags.insert(.hasThreadId) + } sharedBuffer.write(&dataFlags, offset: 0, length: 1) if let globallyUniqueId = message.globallyUniqueId { var globallyUniqueIdValue = globallyUniqueId @@ -1885,6 +1917,9 @@ final class MessageHistoryTable: Table { var localTagsValue: UInt32 = message.localTags.rawValue sharedBuffer.write(&localTagsValue, offset: 0, length: 4) } + if var threadId = message.threadId { + sharedBuffer.write(&threadId, length: 8) + } var flagsValue: UInt32 = message.flags.rawValue sharedBuffer.write(&flagsValue, offset: 0, length: 4) @@ -2087,6 +2122,13 @@ final class MessageHistoryTable: Table { localTags = LocalMessageTags(rawValue: localTagsValue) } + var threadId: Int64? + if dataFlags.contains(.hasThreadId) { + var threadIdValue: Int64 = 0 + value.read(&threadIdValue, offset: 0, length: 8) + threadId = threadIdValue + } + var flagsValue: UInt32 = 0 value.read(&flagsValue, offset: 0, length: 4) let flags = MessageFlags(rawValue: flagsValue) @@ -2193,7 +2235,7 @@ final class MessageHistoryTable: Table { referencedMediaIds.append(MediaId(namespace: idNamespace, id: idId)) } - return IntermediateMessageHistoryEntry(message: IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: index.id, globallyUniqueId: globallyUniqueId, groupingKey: groupingKey, groupInfo: groupInfo, timestamp: index.timestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: forwardInfo, authorId: authorId, text: text, attributesData: attributesData, embeddedMediaData: embeddedMediaData, referencedMedia: referencedMediaIds)) + return IntermediateMessageHistoryEntry(message: IntermediateMessage(stableId: stableId, stableVersion: stableVersion, id: index.id, globallyUniqueId: globallyUniqueId, groupingKey: groupingKey, groupInfo: groupInfo, threadId: threadId, timestamp: index.timestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: forwardInfo, authorId: authorId, text: text, attributesData: attributesData, embeddedMediaData: embeddedMediaData, referencedMedia: referencedMediaIds)) } else { preconditionFailure() } @@ -2342,7 +2384,7 @@ final class MessageHistoryTable: Table { } } - return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: parsedAttributes, media: parsedMedia, peers: peers, associatedMessages: associatedMessages, associatedMessageIds: associatedMessageIds) + return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: parsedAttributes, media: parsedMedia, peers: peers, associatedMessages: associatedMessages, associatedMessageIds: associatedMessageIds) } func renderMessagePeers(_ message: Message, peerTable: PeerTable) -> Message { @@ -2677,11 +2719,57 @@ final class MessageHistoryTable: Table { return (result, mediaRefs, count == 0 ? nil : lastIndex) } - func fetch(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags?, from fromIndex: MessageIndex, includeFrom: Bool, to toIndex: MessageIndex, limit: Int) -> [IntermediateMessage] { + func fetch(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags?, threadId: Int64?, from fromIndex: MessageIndex, includeFrom: Bool, to toIndex: MessageIndex, limit: Int) -> [IntermediateMessage] { precondition(fromIndex.id.peerId == toIndex.id.peerId) precondition(fromIndex.id.namespace == toIndex.id.namespace) var result: [IntermediateMessage] = [] - if let tag = tag { + if let threadId = threadId { + var indices: [MessageIndex] = [] + var startIndex = fromIndex + var localIncludeFrom = includeFrom + while true { + let sliceIndices: [MessageIndex] + if fromIndex < toIndex { + sliceIndices = self.threadsTable.laterIndices(threadId: threadId, peerId: peerId, namespace: namespace, index: startIndex, includeFrom: localIncludeFrom, count: limit) + } else { + sliceIndices = self.threadsTable.earlierIndices(threadId: threadId, peerId: peerId, namespace: namespace, index: startIndex, includeFrom: localIncludeFrom, count: limit) + } + if sliceIndices.isEmpty { + break + } + startIndex = sliceIndices[sliceIndices.count - 1] + localIncludeFrom = false + + for index in sliceIndices { + if let tag = tag { + if self.tagsTable.entryExists(tag: tag, index: index) { + indices.append(index) + } + } else { + indices.append(index) + } + } + if indices.count >= limit { + break + } + } + for index in indices { + if fromIndex < toIndex { + if index < fromIndex || index > toIndex { + continue + } + } else { + if index < toIndex || index > fromIndex { + continue + } + } + if let message = self.getMessage(index) { + result.append(message) + } else { + assertionFailure() + } + } + } else if let tag = tag { let indices: [MessageIndex] if fromIndex < toIndex { indices = self.tagsTable.laterIndices(tag: tag, peerId: peerId, namespace: namespace, index: fromIndex, includeFrom: includeFrom, count: limit) diff --git a/submodules/Postbox/Sources/MessageHistoryTagsTable.swift b/submodules/Postbox/Sources/MessageHistoryTagsTable.swift index 2af930855d..a8102d32de 100644 --- a/submodules/Postbox/Sources/MessageHistoryTagsTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTagsTable.swift @@ -61,6 +61,10 @@ class MessageHistoryTagsTable: Table { } } + func entryExists(tag: MessageTags, index: MessageIndex) -> Bool { + return self.valueBox.exists(self.table, key: self.key(tag: tag, index: index, key: self.sharedKey)) + } + func entryLocation(at index: MessageIndex, tag: MessageTags) -> MessageHistoryEntryLocation? { if let _ = self.valueBox.get(self.table, key: self.key(tag: tag, index: index)) { var greaterCount = 0 diff --git a/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift b/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift new file mode 100644 index 0000000000..776893cdcd --- /dev/null +++ b/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift @@ -0,0 +1,98 @@ +import Foundation + +private func extractKey(_ key: ValueBoxKey) -> MessageIndex { + return MessageIndex(id: MessageId(peerId: PeerId(key.getInt64(0)), namespace: key.getInt32(8 + 8), id: key.getInt32(8 + 8 + 4 + 4)), timestamp: key.getInt32(8 + 8 + 4)) +} + +class MessageHistoryThreadsTable: Table { + static func tableSpec(_ id: Int32) -> ValueBoxTable { + return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: true) + } + + private let sharedKey = ValueBoxKey(length: 8 + 8 + 4 + 4 + 4) + + override init(valueBox: ValueBox, table: ValueBoxTable) { + super.init(valueBox: valueBox, table: table) + } + + private func key(threadId: Int64, index: MessageIndex, key: ValueBoxKey = ValueBoxKey(length: 8 + 8 + 4 + 4 + 4)) -> ValueBoxKey { + key.setInt64(0, value: index.id.peerId.toInt64()) + key.setInt64(8, value: threadId) + key.setInt32(8 + 8, value: index.id.namespace) + key.setInt32(8 + 8 + 4, value: index.timestamp) + key.setInt32(8 + 8 + 4 + 4, value: index.id.id) + return key + } + + private func lowerBound(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace) -> ValueBoxKey { + let key = ValueBoxKey(length: 8 + 8 + 4) + key.setInt64(0, value: peerId.toInt64()) + key.setInt64(8, value: threadId) + key.setInt32(8 + 8, value: namespace) + return key + } + + private func upperBound(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace) -> ValueBoxKey { + return self.lowerBound(threadId: threadId, peerId: peerId, namespace: namespace).successor + } + + func add(threadId: Int64, index: MessageIndex) { + self.valueBox.set(self.table, key: self.key(threadId: threadId, index: index, key: self.sharedKey), value: MemoryBuffer()) + } + + func remove(threadId: Int64, index: MessageIndex) { + self.valueBox.remove(self.table, key: self.key(threadId: threadId, index: index, key: self.sharedKey), secure: false) + } + + func earlierIndices(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace, index: MessageIndex?, includeFrom: Bool, count: Int) -> [MessageIndex] { + var indices: [MessageIndex] = [] + let key: ValueBoxKey + if let index = index { + if includeFrom { + key = self.key(threadId: threadId, index: index).successor + } else { + key = self.key(threadId: threadId, index: index) + } + } else { + key = self.upperBound(threadId: threadId, peerId: peerId, namespace: namespace) + } + self.valueBox.range(self.table, start: key, end: self.lowerBound(threadId: threadId, peerId: peerId, namespace: namespace), keys: { key in + indices.append(extractKey(key)) + return true + }, limit: count) + return indices + } + + func laterIndices(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace, index: MessageIndex?, includeFrom: Bool, count: Int) -> [MessageIndex] { + var indices: [MessageIndex] = [] + let key: ValueBoxKey + if let index = index { + if includeFrom { + key = self.key(threadId: threadId, index: index).predecessor + } else { + key = self.key(threadId: threadId, index: index) + } + } else { + key = self.lowerBound(threadId: threadId, peerId: peerId, namespace: namespace) + } + self.valueBox.range(self.table, start: key, end: self.upperBound(threadId: threadId, peerId: peerId, namespace: namespace), keys: { key in + indices.append(extractKey(key)) + return true + }, limit: count) + return indices + } + + func getMessageCountInRange(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace, lowerBound: MessageIndex, upperBound: MessageIndex) -> Int { + precondition(lowerBound.id.namespace == namespace) + precondition(upperBound.id.namespace == namespace) + var lowerBoundKey = self.key(threadId: threadId, index: lowerBound) + if lowerBound.timestamp > 1 { + lowerBoundKey = lowerBoundKey.predecessor + } + var upperBoundKey = self.key(threadId: threadId, index: upperBound) + if upperBound.timestamp < Int32.max - 1 { + upperBoundKey = upperBoundKey.successor + } + return Int(self.valueBox.count(self.table, start: lowerBoundKey, end: upperBoundKey)) + } +} diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index 87c2e0db6a..a5aaef4ee8 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -3,9 +3,16 @@ import Foundation public struct MessageHistoryViewPeerHole: Equatable, Hashable, CustomStringConvertible { public let peerId: PeerId public let namespace: MessageId.Namespace + public let threadId: Int64? + + public init(peerId: PeerId, namespace: MessageId.Namespace, threadId: Int64?) { + self.peerId = peerId + self.namespace = namespace + self.threadId = threadId + } public var description: String { - return "peerId: \(self.peerId), namespace: \(self.namespace)" + return "peerId: \(self.peerId), namespace: \(self.namespace), threadId: \(String(describing: self.threadId))" } } @@ -126,18 +133,18 @@ enum MutableMessageHistoryEntry { func updatedTimestamp(_ timestamp: Int32) -> MutableMessageHistoryEntry { switch self { case let .IntermediateMessageEntry(message, location, monthLocation): - let updatedMessage = IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: message.embeddedMediaData, referencedMedia: message.referencedMedia) + let updatedMessage = IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: message.embeddedMediaData, referencedMedia: message.referencedMedia) return .IntermediateMessageEntry(updatedMessage, location, monthLocation) case let .MessageEntry(value, reloadAssociatedMessages, reloadPeers): let message = value.message - let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) + let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers) } } func getAssociatedMessageIds() -> [MessageId] { switch self { - case let .IntermediateMessageEntry(message, location, monthLocation): + case .IntermediateMessageEntry: return [] case let .MessageEntry(value, _, _): return value.message.associatedMessageIds @@ -149,6 +156,11 @@ public struct MessageHistoryEntryLocation: Equatable { public let index: Int public let count: Int + public init(index: Int, count: Int) { + self.index = index + self.count = count + } + var predecessor: MessageHistoryEntryLocation? { if index == 0 { return nil @@ -236,9 +248,45 @@ public struct MessageHistoryViewOrderStatistics: OptionSet { public static let locationWithinMonth = MessageHistoryViewOrderStatistics(rawValue: 1 << 1) } -public enum MessageHistoryViewPeerIds: Equatable { +public final class MessageHistoryViewExternalInput: Equatable { + public let peerId: PeerId + public let threadId: Int64 + public let maxReadMessageId: MessageId? + public let holes: [MessageId.Namespace: IndexSet] + + public init( + peerId: PeerId, + threadId: Int64, + maxReadMessageId: MessageId?, + holes: [MessageId.Namespace: IndexSet] + ) { + self.peerId = peerId + self.threadId = threadId + self.maxReadMessageId = maxReadMessageId + self.holes = holes + } + + public static func ==(lhs: MessageHistoryViewExternalInput, rhs: MessageHistoryViewExternalInput) -> Bool { + if lhs === rhs { + return true + } + if lhs.peerId != rhs.peerId { + return false + } + if lhs.threadId != rhs.threadId { + return false + } + if lhs.holes != rhs.holes { + return false + } + return true + } +} + +public enum MessageHistoryViewInput: Equatable { case single(PeerId) case associated(PeerId, MessageId?) + case external(MessageHistoryViewExternalInput) } public enum MessageHistoryViewReadState { @@ -254,7 +302,7 @@ public enum HistoryViewInputAnchor: Equatable { } final class MutableMessageHistoryView { - private(set) var peerIds: MessageHistoryViewPeerIds + private(set) var peerIds: MessageHistoryViewInput let tag: MessageTags? let namespaces: MessageIdNamespaces private let orderStatistics: MessageHistoryViewOrderStatistics @@ -274,7 +322,7 @@ final class MutableMessageHistoryView { fileprivate var isAddedToChatList: Bool - init(postbox: Postbox, orderStatistics: MessageHistoryViewOrderStatistics, clipHoles: Bool, peerIds: MessageHistoryViewPeerIds, anchor inputAnchor: HistoryViewInputAnchor, combinedReadStates: MessageHistoryViewReadState?, transientReadStates: MessageHistoryViewReadState?, tag: MessageTags?, namespaces: MessageIdNamespaces, count: Int, topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?], additionalDatas: [AdditionalMessageHistoryViewDataEntry], getMessageCountInRange: (MessageIndex, MessageIndex) -> Int32) { + init(postbox: Postbox, orderStatistics: MessageHistoryViewOrderStatistics, clipHoles: Bool, peerIds: MessageHistoryViewInput, anchor inputAnchor: HistoryViewInputAnchor, combinedReadStates: MessageHistoryViewReadState?, transientReadStates: MessageHistoryViewReadState?, tag: MessageTags?, namespaces: MessageIdNamespaces, count: Int, topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?], additionalDatas: [AdditionalMessageHistoryViewDataEntry], getMessageCountInRange: (MessageIndex, MessageIndex) -> Int32) { self.anchor = inputAnchor self.orderStatistics = orderStatistics @@ -288,14 +336,14 @@ final class MutableMessageHistoryView { self.topTaggedMessages = topTaggedMessages self.additionalDatas = additionalDatas - let mainPeerId: PeerId switch peerIds { case let .associated(peerId, _): - mainPeerId = peerId + self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: peerId) != nil case let .single(peerId): - mainPeerId = peerId + self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: peerId) != nil + case let .external(input): + self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: input.peerId) != nil } - self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: mainPeerId) != nil self.state = HistoryViewState(postbox: postbox, inputAnchor: inputAnchor, tag: tag, namespaces: namespaces, statistics: self.orderStatistics, halfLimit: count + 1, locations: peerIds) if case let .loading(loadingState) = self.state { @@ -355,12 +403,14 @@ final class MutableMessageHistoryView { self.peerIds = .associated(peerId, updatedData.associatedHistoryMessageId) } } + case .external: + break } } func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool { var operations: [[MessageHistoryOperation]] = [] - var peerIdsSet = Set() + var holePeerIdsSet = Set() if !transaction.chatListOperations.isEmpty { let mainPeerId: PeerId @@ -369,52 +419,67 @@ final class MutableMessageHistoryView { mainPeerId = peerId case let .single(peerId): mainPeerId = peerId + case let .external(input): + mainPeerId = input.peerId } self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: mainPeerId) != nil } switch self.peerIds { case let .single(peerId): - peerIdsSet.insert(peerId) + holePeerIdsSet.insert(peerId) if let value = transaction.currentOperationsByPeerId[peerId] { operations.append(value) } case .associated: switch self.peerIds { - case .single: + case .single, .external: assertionFailure() case let .associated(mainPeerId, associatedPeerId): - peerIdsSet.insert(mainPeerId) + holePeerIdsSet.insert(mainPeerId) if let associatedPeerId = associatedPeerId { - peerIdsSet.insert(associatedPeerId.peerId) + holePeerIdsSet.insert(associatedPeerId.peerId) } } for (peerId, value) in transaction.currentOperationsByPeerId { - if peerIdsSet.contains(peerId) { + if holePeerIdsSet.contains(peerId) { operations.append(value) } } + case let .external(input): + if let value = transaction.currentOperationsByPeerId[input.peerId] { + operations.append(value) + } } var hasChanges = false let unwrappedTag: MessageTags = self.tag ?? [] + let threadId: Int64? + switch self.peerIds { + case .single, .associated: + threadId = nil + case let .external(input): + threadId = input.threadId + } switch self.state { case let .loading(loadingState): for (key, holeOperations) in transaction.currentPeerHoleOperations { var matchesSpace = false - switch key.space { - case .everywhere: - matchesSpace = unwrappedTag.isEmpty - case let .tag(tag): - if let currentTag = self.tag, currentTag == tag { - matchesSpace = true + if threadId == nil { + switch key.space { + case .everywhere: + matchesSpace = unwrappedTag.isEmpty + case let .tag(tag): + if let currentTag = self.tag, currentTag == tag { + matchesSpace = true + } } } if matchesSpace { - if peerIdsSet.contains(key.peerId) { + if holePeerIdsSet.contains(key.peerId) { for operation in holeOperations { switch operation { case let .insert(range): @@ -447,13 +512,22 @@ final class MutableMessageHistoryView { for operation in operationSet { switch operation { case let .InsertMessage(message): + var matches = false if unwrappedTag.isEmpty || message.tags.contains(unwrappedTag) { - if self.namespaces.contains(message.id.namespace) { - if loadedState.add(entry: .IntermediateMessageEntry(message, nil, nil)) { - hasChanges = true + if threadId == nil || message.threadId == threadId { + if self.namespaces.contains(message.id.namespace) { + matches = true + if loadedState.add(entry: .IntermediateMessageEntry(message, nil, nil)) { + hasChanges = true + } } } } + if !matches { + if loadedState.addAssociated(entry: .IntermediateMessageEntry(message, nil, nil)) { + hasChanges = true + } + } case let .Remove(indicesAndTags): for (index, _) in indicesAndTags { if self.namespaces.contains(index.id.namespace) { @@ -491,16 +565,18 @@ final class MutableMessageHistoryView { } for (key, holeOperations) in transaction.currentPeerHoleOperations { var matchesSpace = false - switch key.space { - case .everywhere: - matchesSpace = unwrappedTag.isEmpty - case let .tag(tag): - if let currentTag = self.tag, currentTag == tag { - matchesSpace = true + if threadId == nil { + switch key.space { + case .everywhere: + matchesSpace = unwrappedTag.isEmpty + case let .tag(tag): + if let currentTag = self.tag, currentTag == tag { + matchesSpace = true + } } } if matchesSpace { - if peerIdsSet.contains(key.peerId) { + if holePeerIdsSet.contains(key.peerId) { for operation in holeOperations { switch operation { case let .insert(range): @@ -579,6 +655,50 @@ final class MutableMessageHistoryView { } case .cachedPeerDataMessages: break + case let .message(id, _): + if let operations = transaction.currentOperationsByPeerId[id.peerId] { + var updateMessage = false + findOperation: for operation in operations { + switch operation { + case let .InsertMessage(message): + if message.id == id { + updateMessage = true + break findOperation + } + case let .Remove(indices): + for (index, _) in indices { + if index.id == id { + updateMessage = true + break findOperation + } + } + case let .UpdateEmbeddedMedia(index, _): + if index.id == id { + updateMessage = true + break findOperation + } + case let .UpdateGroupInfos(dict): + if dict[id] != nil { + updateMessage = true + break findOperation + } + case let .UpdateTimestamp(index, _): + if index.id == id { + updateMessage = true + break findOperation + } + case .UpdateReadState: + break + } + } + if updateMessage { + let message = postbox.messageHistoryIndexTable.getIndex(id).flatMap(postbox.messageHistoryTable.getMessage).flatMap { message in + postbox.messageHistoryTable.renderMessage(message, peerTable: postbox.peerTable) + } + self.additionalDatas[i] = .message(id, message) + hasChanges = true + } + } case let .peerChatState(peerId, _): if transaction.currentUpdatedPeerChatStates.contains(peerId) { self.additionalDatas[i] = .peerChatState(peerId, postbox.peerChatStateTable.get(peerId) as? PeerChatState) @@ -667,19 +787,21 @@ final class MutableMessageHistoryView { } if !transaction.currentPeerHoleOperations.isEmpty { - var peerIdsSet: [PeerId] = [] - switch peerIds { + var holePeerIdsSet: [PeerId] = [] + switch self.peerIds { case let .single(peerId): - peerIdsSet.append(peerId) + holePeerIdsSet.append(peerId) case let .associated(peerId, associatedId): - peerIdsSet.append(peerId) + holePeerIdsSet.append(peerId) if let associatedId = associatedId { - peerIdsSet.append(associatedId.peerId) + holePeerIdsSet.append(associatedId.peerId) } + case .external: + break } let space: MessageHistoryHoleSpace = self.tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere for key in transaction.currentPeerHoleOperations.keys { - if peerIdsSet.contains(key.peerId) && key.space == space { + if holePeerIdsSet.contains(key.peerId) && key.space == space { hasChanges = true } } @@ -707,8 +829,8 @@ final class MutableMessageHistoryView { switch loadingSample { case .ready: return nil - case let .loadHole(peerId, namespace, _, id): - return (.peer(MessageHistoryViewPeerHole(peerId: peerId, namespace: namespace)), .aroundId(MessageId(peerId: peerId, namespace: namespace, id: id)), self.fillCount * 2) + case let .loadHole(peerId, namespace, _, threadId, id): + return (.peer(MessageHistoryViewPeerHole(peerId: peerId, namespace: namespace, threadId: threadId)), .aroundId(MessageId(peerId: peerId, namespace: namespace, id: id)), self.fillCount * 2) } case let .loaded(loadedSample): if let hole = loadedSample.hole { @@ -718,7 +840,7 @@ final class MutableMessageHistoryView { } else { direction = .aroundId(MessageId(peerId: hole.peerId, namespace: hole.namespace, id: hole.startId)) } - return (.peer(MessageHistoryViewPeerHole(peerId: hole.peerId, namespace: hole.namespace)), direction, self.fillCount * 2) + return (.peer(MessageHistoryViewPeerHole(peerId: hole.peerId, namespace: hole.namespace, threadId: hole.threadId)), direction, self.fillCount * 2) } else { return nil } @@ -838,66 +960,122 @@ public final class MessageHistoryView { self.fixedReadStates = mutableView.combinedReadStates - if let combinedReadStates = mutableView.combinedReadStates { - switch combinedReadStates { - case let .peer(states): - var hasUnread = false - for (_, readState) in states { - if readState.count > 0 { - hasUnread = true - break + switch mutableView.peerIds { + case .single, .associated: + if let combinedReadStates = mutableView.combinedReadStates { + switch combinedReadStates { + case let .peer(states): + var hasUnread = false + for (_, readState) in states { + if readState.count > 0 { + hasUnread = true + break + } } + + var maxIndex: MessageIndex? + + if hasUnread { + var peerIds = Set() + for entry in entries { + peerIds.insert(entry.index.id.peerId) + } + for peerId in peerIds { + if let combinedReadState = states[peerId] { + for (namespace, state) in combinedReadState.states { + var maxNamespaceIndex: MessageIndex? + var index = entries.count - 1 + for entry in entries.reversed() { + if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace && state.isIncomingMessageIndexRead(entry.index) { + maxNamespaceIndex = entry.index + break + } + index -= 1 + } + if maxNamespaceIndex == nil && index == -1 && entries.count != 0 { + index = 0 + for entry in entries { + if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace { + maxNamespaceIndex = entry.index.predecessor() + break + } + index += 1 + } + } + if let _ = maxNamespaceIndex , index + 1 < entries.count { + for i in index + 1 ..< entries.count { + if entries[i].message.flags.intersection(.IsIncomingMask).isEmpty { + maxNamespaceIndex = entries[i].message.index + } else { + break + } + } + } + if let maxNamespaceIndex = maxNamespaceIndex , maxIndex == nil || maxIndex! < maxNamespaceIndex { + maxIndex = maxNamespaceIndex + } + } + } + } + } + self.maxReadIndex = maxIndex } - + } else { + self.maxReadIndex = nil + } + case let .external(input): + if let maxReadMesageId = input.maxReadMessageId { var maxIndex: MessageIndex? + let hasUnread = true if hasUnread { var peerIds = Set() for entry in entries { peerIds.insert(entry.index.id.peerId) } for peerId in peerIds { - if let combinedReadState = states[peerId] { - for (namespace, state) in combinedReadState.states { - var maxNamespaceIndex: MessageIndex? - var index = entries.count - 1 - for entry in entries.reversed() { - if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace && state.isIncomingMessageIndexRead(entry.index) { - maxNamespaceIndex = entry.index - break - } - index -= 1 + if peerId != maxReadMesageId.peerId { + continue + } + let namespace = maxReadMesageId.namespace + + var maxNamespaceIndex: MessageIndex? + var index = entries.count - 1 + for entry in entries.reversed() { + if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace && entry.index.id <= maxReadMesageId { + maxNamespaceIndex = entry.index + break + } + index -= 1 + } + if maxNamespaceIndex == nil && index == -1 && entries.count != 0 { + index = 0 + for entry in entries { + if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace { + maxNamespaceIndex = entry.index.predecessor() + break } - if maxNamespaceIndex == nil && index == -1 && entries.count != 0 { - index = 0 - for entry in entries { - if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace { - maxNamespaceIndex = entry.index.predecessor() - break - } - index += 1 - } - } - if let _ = maxNamespaceIndex , index + 1 < entries.count { - for i in index + 1 ..< entries.count { - if entries[i].message.flags.intersection(.IsIncomingMask).isEmpty { - maxNamespaceIndex = entries[i].message.index - } else { - break - } - } - } - if let maxNamespaceIndex = maxNamespaceIndex , maxIndex == nil || maxIndex! < maxNamespaceIndex { - maxIndex = maxNamespaceIndex + index += 1 + } + } + if let _ = maxNamespaceIndex , index + 1 < entries.count { + for i in index + 1 ..< entries.count { + if entries[i].message.flags.intersection(.IsIncomingMask).isEmpty { + maxNamespaceIndex = entries[i].message.index + } else { + break } } } + if let maxNamespaceIndex = maxNamespaceIndex , maxIndex == nil || maxIndex! < maxNamespaceIndex { + maxIndex = maxNamespaceIndex + } } } self.maxReadIndex = maxIndex + } else { + self.maxReadIndex = nil } - } else { - self.maxReadIndex = nil } self.entries = entries diff --git a/submodules/Postbox/Sources/MessageHistoryViewState.swift b/submodules/Postbox/Sources/MessageHistoryViewState.swift index 7f46f97daa..6134e3d7a8 100644 --- a/submodules/Postbox/Sources/MessageHistoryViewState.swift +++ b/submodules/Postbox/Sources/MessageHistoryViewState.swift @@ -1,5 +1,43 @@ import Foundation +public enum MessageHistoryInput: Equatable, Hashable { + case automatic(MessageTags?) + case external(MessageHistoryViewExternalInput, MessageTags?) + + public func hash(into hasher: inout Hasher) { + switch self { + case .automatic: + hasher.combine(1) + case .external: + hasher.combine(2) + } + } +} + +private extension MessageHistoryInput { + func fetch(postbox: Postbox, peerId: PeerId, namespace: MessageId.Namespace, from fromIndex: MessageIndex, includeFrom: Bool, to toIndex: MessageIndex, limit: Int) -> [IntermediateMessage] { + switch self { + case let .automatic(tag): + return postbox.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: tag, threadId: nil, from: fromIndex, includeFrom: includeFrom, to: toIndex, limit: limit) + case let .external(input, tag): + return postbox.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: tag, threadId: input.threadId, from: fromIndex, includeFrom: includeFrom, to: toIndex, limit: limit) + } + } + + func getMessageCountInRange(postbox: Postbox, peerId: PeerId, namespace: MessageId.Namespace, lowerBound: MessageIndex, upperBound: MessageIndex) -> Int { + switch self { + case let .automatic(tag): + if let tag = tag { + return postbox.messageHistoryTagsTable.getMessageCountInRange(tag: tag, peerId: peerId, namespace: namespace, lowerBound: lowerBound, upperBound: upperBound) + } else { + return 0 + } + case .external: + return 0 + } + } +} + public struct PeerIdAndNamespace: Hashable { public let peerId: PeerId public let namespace: MessageId.Namespace @@ -10,11 +48,16 @@ public struct PeerIdAndNamespace: Hashable { } } -private func canContainHoles(_ peerIdAndNamespace: PeerIdAndNamespace, seedConfiguration: SeedConfiguration) -> Bool { - guard let messageNamespaces = seedConfiguration.messageHoles[peerIdAndNamespace.peerId.namespace] else { - return false +private func canContainHoles(_ peerIdAndNamespace: PeerIdAndNamespace, input: MessageHistoryInput, seedConfiguration: SeedConfiguration) -> Bool { + switch input { + case .automatic: + guard let messageNamespaces = seedConfiguration.messageHoles[peerIdAndNamespace.peerId.namespace] else { + return false + } + return messageNamespaces[peerIdAndNamespace.namespace] != nil + case .external: + return true } - return messageNamespaces[peerIdAndNamespace.namespace] != nil } private struct MessageMonthIndex: Equatable { @@ -246,6 +289,7 @@ struct SampledHistoryViewHole: Equatable { let peerId: PeerId let namespace: MessageId.Namespace let tag: MessageTags? + let threadId: Int64? let indices: IndexSet let startId: MessageId.Id let endId: MessageId.Id? @@ -291,22 +335,31 @@ private func isIndex(index: MessageIndex, closerTo anchor: HistoryViewAnchor, th } } -private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries], holes: HistoryViewHoles, anchor: HistoryViewAnchor, tag: MessageTags?, halfLimit: Int, seedConfiguration: SeedConfiguration) -> (clipRanges: [ClosedRange], sampledHole: SampledHistoryViewHole?) { +private func sampleHoleRanges(input: MessageHistoryInput, orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries], holes: HistoryViewHoles, anchor: HistoryViewAnchor, halfLimit: Int, seedConfiguration: SeedConfiguration) -> (clipRanges: [ClosedRange], sampledHole: SampledHistoryViewHole?) { var clipRanges: [ClosedRange] = [] var sampledHole: (distanceFromAnchor: Int?, hole: SampledHistoryViewHole)? + var tag: MessageTags? + var threadId: Int64? + switch input { + case let .automatic(value): + tag = value + case let .external(value, _): + threadId = value.threadId + } + for (space, indices) in holes.holesBySpace { if indices.isEmpty { continue } - assert(canContainHoles(space, seedConfiguration: seedConfiguration)) + assert(canContainHoles(space, input: input, seedConfiguration: seedConfiguration)) switch anchor { case .lowerBound, .upperBound: break case let .index(index): if index.id.peerId == space.peerId && index.id.namespace == space.namespace { if indices.contains(Int(index.id.id)) { - return ([MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound()], SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, indices: indices, startId: index.id.id, endId: nil)) + return ([MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound()], SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: index.id.id, endId: nil)) } } } @@ -319,9 +372,9 @@ private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: Ordere holeBounds = (Int32.max - 1, 1) } if case let .index(index) = anchor, index.id.peerId == space.peerId { - return ([MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound()], SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, indices: indices, startId: holeBounds.startId, endId: holeBounds.endId)) + return ([MessageIndex.absoluteLowerBound() ... MessageIndex.absoluteUpperBound()], SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: holeBounds.startId, endId: holeBounds.endId)) } else { - sampledHole = (nil, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, indices: indices, startId: holeBounds.startId, endId: holeBounds.endId)) + sampledHole = (nil, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: holeBounds.startId, endId: holeBounds.endId)) continue } } @@ -358,7 +411,7 @@ private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: Ordere } else { holeStartIndex = indices[indices.endIndex] } - lowerOrAtAnchorHole = (items.lowerOrAtAnchor.count - i, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, indices: indices, startId: Int32(holeStartIndex), endId: 1)) + lowerOrAtAnchorHole = (items.lowerOrAtAnchor.count - i, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: Int32(holeStartIndex), endId: 1)) if i == -1 { if items.lowerOrAtAnchor.count == 0 { @@ -426,7 +479,7 @@ private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: Ordere } else { holeStartIndex = indices[indices.startIndex] } - higherThanAnchorHole = (i, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, indices: indices, startId: Int32(holeStartIndex), endId: Int32.max - 1)) + higherThanAnchorHole = (i, SampledHistoryViewHole(peerId: space.peerId, namespace: space.namespace, tag: tag, threadId: threadId, indices: indices, startId: Int32(holeStartIndex), endId: Int32.max - 1)) if i == items.higherThanAnchor.count { if items.higherThanAnchor.count == 0 { @@ -772,8 +825,8 @@ struct HistoryViewLoadedSample { final class HistoryViewLoadedState { let anchor: HistoryViewAnchor - let tag: MessageTags? let namespaces: MessageIdNamespaces + let input: MessageHistoryInput let statistics: MessageHistoryViewOrderStatistics let halfLimit: Int let seedConfiguration: SeedConfiguration @@ -781,10 +834,9 @@ final class HistoryViewLoadedState { var holes: HistoryViewHoles var spacesWithRemovals = Set() - init(anchor: HistoryViewAnchor, tag: MessageTags?, namespaces: MessageIdNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewPeerIds, postbox: Postbox, holes: HistoryViewHoles) { + init(anchor: HistoryViewAnchor, tag: MessageTags?, namespaces: MessageIdNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewInput, postbox: Postbox, holes: HistoryViewHoles) { precondition(halfLimit >= 3) self.anchor = anchor - self.tag = tag self.namespaces = namespaces self.statistics = statistics self.halfLimit = halfLimit @@ -793,15 +845,22 @@ final class HistoryViewLoadedState { self.holes = holes var peerIds: [PeerId] = [] + let input: MessageHistoryInput switch locations { case let .single(peerId): peerIds.append(peerId) + input = .automatic(tag) case let .associated(peerId, associatedId): peerIds.append(peerId) if let associatedId = associatedId { peerIds.append(associatedId.peerId) } + input = .automatic(tag) + case let .external(external): + peerIds.append(external.peerId) + input = .external(external, tag) } + self.input = input var spaces: [PeerIdAndNamespace] = [] for peerId in peerIds { @@ -849,7 +908,7 @@ final class HistoryViewLoadedState { } else { nextLowerIndex = (anchorIndex, true) } - lowerOrAtAnchorMessages.append(contentsOf: postbox.messageHistoryTable.fetch(peerId: space.peerId, namespace: space.namespace, tag: self.tag, from: nextLowerIndex.index, includeFrom: nextLowerIndex.includeFrom, to: lowerBound, limit: self.halfLimit - lowerOrAtAnchorMessages.count).map(mapEntry)) + lowerOrAtAnchorMessages.append(contentsOf: self.input.fetch(postbox: postbox, peerId: space.peerId, namespace: space.namespace, from: nextLowerIndex.index, includeFrom: nextLowerIndex.includeFrom, to: lowerBound, limit: self.halfLimit - lowerOrAtAnchorMessages.count).map(mapEntry)) } if higherThanAnchorMessages.count < self.halfLimit { let nextHigherIndex: MessageIndex @@ -858,7 +917,7 @@ final class HistoryViewLoadedState { } else { nextHigherIndex = anchorIndex } - higherThanAnchorMessages.append(contentsOf: postbox.messageHistoryTable.fetch(peerId: space.peerId, namespace: space.namespace, tag: self.tag, from: nextHigherIndex, includeFrom: false, to: upperBound, limit: self.halfLimit - higherThanAnchorMessages.count).map(mapEntry)) + higherThanAnchorMessages.append(contentsOf: self.input.fetch(postbox: postbox, peerId: space.peerId, namespace: space.namespace, from: nextHigherIndex, includeFrom: false, to: upperBound, limit: self.halfLimit - higherThanAnchorMessages.count).map(mapEntry)) } lowerOrAtAnchorMessages.reverse() @@ -868,10 +927,10 @@ final class HistoryViewLoadedState { var entries = OrderedHistoryViewEntries(lowerOrAtAnchor: lowerOrAtAnchorMessages, higherThanAnchor: higherThanAnchorMessages) - if let tag = self.tag, self.statistics.contains(.combinedLocation), let first = entries.first { + if case let .automatic(tagValue) = self.input, let _ = tagValue, self.statistics.contains(.combinedLocation), let first = entries.first { let messageIndex = first.index - let previousCount = postbox.messageHistoryTagsTable.getMessageCountInRange(tag: tag, peerId: space.peerId, namespace: space.namespace, lowerBound: MessageIndex.lowerBound(peerId: space.peerId, namespace: space.namespace), upperBound: messageIndex) - let nextCount = postbox.messageHistoryTagsTable.getMessageCountInRange(tag: tag, peerId: space.peerId, namespace: space.namespace, lowerBound: messageIndex, upperBound: MessageIndex.upperBound(peerId: space.peerId, namespace: space.namespace)) + let previousCount = self.input.getMessageCountInRange(postbox: postbox, peerId: space.peerId, namespace: space.namespace, lowerBound: MessageIndex.lowerBound(peerId: space.peerId, namespace: space.namespace), upperBound: messageIndex) + let nextCount = self.input.getMessageCountInRange(postbox: postbox, peerId: space.peerId, namespace: space.namespace, lowerBound: messageIndex, upperBound: MessageIndex.upperBound(peerId: space.peerId, namespace: space.namespace)) let initialLocation = MessageHistoryEntryLocation(index: previousCount - 1, count: previousCount + nextCount - 1) var nextLocation = initialLocation @@ -887,10 +946,10 @@ final class HistoryViewLoadedState { } } - if let tag = self.tag, self.statistics.contains(.locationWithinMonth), let first = entries.first { + if case let .automatic(tagValue) = self.input, let _ = tagValue, self.statistics.contains(.locationWithinMonth), let first = entries.first { let messageIndex = first.index let monthIndex = MessageMonthIndex(timestamp: messageIndex.timestamp) - let count = postbox.messageHistoryTagsTable.getMessageCountInRange(tag: tag, peerId: space.peerId, namespace: space.namespace, lowerBound: messageIndex, upperBound: monthUpperBoundIndex(peerId: space.peerId, namespace: space.namespace, index: monthIndex)) + let count = self.input.getMessageCountInRange(postbox: postbox, peerId: space.peerId, namespace: space.namespace, lowerBound: messageIndex, upperBound: monthUpperBoundIndex(peerId: space.peerId, namespace: space.namespace, index: monthIndex)) var nextLocation: (MessageMonthIndex, Int) = (monthIndex, count - 1) @@ -911,19 +970,19 @@ final class HistoryViewLoadedState { } } - if canContainHoles(space, seedConfiguration: self.seedConfiguration) { + if canContainHoles(space, input: self.input, seedConfiguration: self.seedConfiguration) { entries.fixMonotony() } self.orderedEntriesBySpace[space] = entries } func insertHole(space: PeerIdAndNamespace, range: ClosedRange) -> Bool { - assert(canContainHoles(space, seedConfiguration: self.seedConfiguration)) + assert(canContainHoles(space, input: self.input, seedConfiguration: self.seedConfiguration)) return self.holes.insertHole(space: space, range: range) } func removeHole(space: PeerIdAndNamespace, range: ClosedRange) -> Bool { - assert(canContainHoles(space, seedConfiguration: self.seedConfiguration)) + assert(canContainHoles(space, input: self.input, seedConfiguration: self.seedConfiguration)) return self.holes.removeHole(space: space, range: range) } @@ -1025,7 +1084,7 @@ final class HistoryViewLoadedState { messageMedia.append(media) } } - let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: messageMedia, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) + let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: messageMedia, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers) } case .IntermediateMessageEntry: @@ -1104,6 +1163,28 @@ final class HistoryViewLoadedState { } } + func addAssociated(entry: MutableMessageHistoryEntry) -> Bool { + var updated = false + + for (space, _) in self.orderedEntriesBySpace { + if let associatedIndices = self.orderedEntriesBySpace[space]!.indicesForAssociatedMessageId(entry.index.id) { + for associatedIndex in associatedIndices { + let _ = self.orderedEntriesBySpace[space]!.update(index: associatedIndex, { current in + switch current { + case .IntermediateMessageEntry: + return current + case let .MessageEntry(messageEntry, _, reloadPeers): + updated = true + return .MessageEntry(messageEntry, reloadAssociatedMessages: true, reloadPeers: reloadPeers) + } + }) + } + } + } + + return updated + } + func remove(index: MessageIndex) -> Bool { let space = PeerIdAndNamespace(peerId: index.id.peerId, namespace: index.id.namespace) if self.orderedEntriesBySpace[space] == nil { @@ -1114,7 +1195,7 @@ final class HistoryViewLoadedState { if let associatedIndices = self.orderedEntriesBySpace[space]!.indicesForAssociatedMessageId(index.id) { for associatedIndex in associatedIndices { - self.orderedEntriesBySpace[space]!.update(index: associatedIndex, { current in + let _ = self.orderedEntriesBySpace[space]!.update(index: associatedIndex, { current in switch current { case .IntermediateMessageEntry: return current @@ -1147,7 +1228,7 @@ final class HistoryViewLoadedState { self.spacesWithRemovals.removeAll() } let combinedSpacesAndIndicesByDirection = sampleEntries(orderedEntriesBySpace: self.orderedEntriesBySpace, anchor: self.anchor, halfLimit: self.halfLimit) - let (clipRanges, sampledHole) = sampleHoleRanges(orderedEntriesBySpace: self.orderedEntriesBySpace, holes: self.holes, anchor: self.anchor, tag: self.tag, halfLimit: self.halfLimit, seedConfiguration: self.seedConfiguration) + let (clipRanges, sampledHole) = sampleHoleRanges(input: self.input, orderedEntriesBySpace: self.orderedEntriesBySpace, holes: self.holes, anchor: self.anchor, halfLimit: self.halfLimit, seedConfiguration: self.seedConfiguration) var holesToLower = false var holesToHigher = false @@ -1229,8 +1310,7 @@ final class HistoryViewLoadedState { } } -private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, namespaces: MessageIdNamespaces) -> [PeerIdAndNamespace: IndexSet] { - var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:] +private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewInput, tag: MessageTags?, namespaces: MessageIdNamespaces) -> [PeerIdAndNamespace: IndexSet] { var peerIds: [PeerId] = [] switch locations { case let .single(peerId): @@ -1240,37 +1320,59 @@ private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewPeerIds, if let associatedId = associatedId { peerIds.append(associatedId.peerId) } + case let .external(input): + peerIds.append(input.peerId) } - let holeSpace = tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere - for peerId in peerIds { - for namespace in postbox.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: holeSpace) { - if namespaces.contains(namespace) { - let indices = postbox.messageHistoryHoleIndexTable.closest(peerId: peerId, namespace: namespace, space: holeSpace, range: 1 ... (Int32.max - 1)) - if !indices.isEmpty { - let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace) - assert(canContainHoles(peerIdAndNamespace, seedConfiguration: postbox.seedConfiguration)) - holesBySpace[peerIdAndNamespace] = indices + switch locations { + case .single, .associated: + var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:] + let holeSpace = tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere + for peerId in peerIds { + for namespace in postbox.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: holeSpace) { + if namespaces.contains(namespace) { + let indices = postbox.messageHistoryHoleIndexTable.closest(peerId: peerId, namespace: namespace, space: holeSpace, range: 1 ... (Int32.max - 1)) + if !indices.isEmpty { + let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace) + assert(canContainHoles(peerIdAndNamespace, input: .automatic(tag), seedConfiguration: postbox.seedConfiguration)) + holesBySpace[peerIdAndNamespace] = indices + } } } } + return holesBySpace + case let .external(input): + var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:] + for peerId in peerIds { + for (namespace, indices) in input.holes { + if namespaces.contains(namespace) { + if !indices.isEmpty { + let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace) + assert(canContainHoles(peerIdAndNamespace, input: .external(input, tag), seedConfiguration: postbox.seedConfiguration)) + holesBySpace[peerIdAndNamespace] = indices + } + } + } + } + return holesBySpace } - return holesBySpace } enum HistoryViewLoadingSample { case ready(HistoryViewAnchor, HistoryViewHoles) - case loadHole(PeerId, MessageId.Namespace, MessageTags?, MessageId.Id) + case loadHole(PeerId, MessageId.Namespace, MessageTags?, Int64?, MessageId.Id) } final class HistoryViewLoadingState { var messageId: MessageId let tag: MessageTags? + let threadId: Int64? let halfLimit: Int var holes: HistoryViewHoles - init(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, namespaces: MessageIdNamespaces, messageId: MessageId, halfLimit: Int) { + init(postbox: Postbox, locations: MessageHistoryViewInput, tag: MessageTags?, threadId: Int64?, namespaces: MessageIdNamespaces, messageId: MessageId, halfLimit: Int) { self.messageId = messageId self.tag = tag + self.threadId = threadId self.halfLimit = halfLimit self.holes = HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces)) } @@ -1287,7 +1389,7 @@ final class HistoryViewLoadingState { while true { if let indices = self.holes.holesBySpace[PeerIdAndNamespace(peerId: self.messageId.peerId, namespace: self.messageId.namespace)] { if indices.contains(Int(messageId.id)) { - return .loadHole(messageId.peerId, messageId.namespace, self.tag, messageId.id) + return .loadHole(messageId.peerId, messageId.namespace, self.tag, self.threadId, messageId.id) } } @@ -1312,7 +1414,7 @@ enum HistoryViewState { case loaded(HistoryViewLoadedState) case loading(HistoryViewLoadingState) - init(postbox: Postbox, inputAnchor: HistoryViewInputAnchor, tag: MessageTags?, namespaces: MessageIdNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewPeerIds) { + init(postbox: Postbox, inputAnchor: HistoryViewInputAnchor, tag: MessageTags?, namespaces: MessageIdNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewInput) { switch inputAnchor { case let .index(index): self = .loaded(HistoryViewLoadedState(anchor: .index(index), tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces)))) @@ -1327,6 +1429,9 @@ enum HistoryViewState { anchorPeerId = peerId case let .associated(peerId, _): anchorPeerId = peerId + case .external: + self = .loaded(HistoryViewLoadedState(anchor: .upperBound, tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces)))) + return } if postbox.chatListIndexTable.get(peerId: anchorPeerId).includedIndex(peerId: anchorPeerId) != nil, let combinedState = postbox.readStateTable.getCombinedState(anchorPeerId) { var messageId: MessageId? @@ -1352,7 +1457,7 @@ enum HistoryViewState { } } if let messageId = messageId { - let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit) + let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, threadId: nil, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit) let sampledState = loadingState.checkAndSample(postbox: postbox) switch sampledState { case let .ready(anchor, holes): @@ -1367,7 +1472,14 @@ enum HistoryViewState { preconditionFailure() } case let .message(messageId): - let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit) + var threadId: Int64? + switch locations { + case let .external(input): + threadId = input.threadId + default: + break + } + let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, threadId: threadId, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit) let sampledState = loadingState.checkAndSample(postbox: postbox) switch sampledState { case let .ready(anchor, holes): diff --git a/submodules/Postbox/Sources/MessageOfInterestHolesView.swift b/submodules/Postbox/Sources/MessageOfInterestHolesView.swift index caa7f03362..79ebc808dc 100644 --- a/submodules/Postbox/Sources/MessageOfInterestHolesView.swift +++ b/submodules/Postbox/Sources/MessageOfInterestHolesView.swift @@ -33,7 +33,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { private let count: Int private var anchor: HistoryViewInputAnchor private var wrappedView: MutableMessageHistoryView - private var peerIds: MessageHistoryViewPeerIds + private var peerIds: MessageHistoryViewInput fileprivate var closestHole: MessageOfInterestHole? fileprivate var closestLaterMedia: [HolesViewMedia] = [] @@ -43,11 +43,11 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { self.count = count let mainPeerId: PeerId - let peerIds: MessageHistoryViewPeerIds + let peerIds: MessageHistoryViewInput switch self.location { case let .peer(id): mainPeerId = id - peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil) + peerIds = postbox.peerIdsForLocation(.peer(id)) } self.peerIds = peerIds var anchor: HistoryViewInputAnchor = .upperBound @@ -127,10 +127,10 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { if self.anchor != anchor { self.anchor = anchor - let peerIds: MessageHistoryViewPeerIds + let peerIds: MessageHistoryViewInput switch self.location { case let .peer(id): - peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil) + peerIds = postbox.peerIdsForLocation(.peer(id)) } self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0}) return self.updateFromView() @@ -146,6 +146,9 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { if let attachedMessageId = attachedMessageId { allPeerIds.append(attachedMessageId.peerId) } + case .external: + allPeerIds = [] + break } for (key, _) in transaction.currentPeerHoleOperations { if allPeerIds.contains(key.peerId) { @@ -155,10 +158,10 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { } } if reloadView { - let peerIds: MessageHistoryViewPeerIds + let peerIds: MessageHistoryViewInput switch self.location { case let .peer(id): - peerIds = postbox.peerIdsForLocation(.peer(id), tagMask: nil) + peerIds = postbox.peerIdsForLocation(.peer(id)) } self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0}) } diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 4dcf6d4dcc..1254573c07 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -38,6 +38,14 @@ public final class Transaction { } } + public func messageExists(id: MessageId) -> Bool { + if let postbox = self.postbox { + return postbox.messageHistoryIndexTable.exists(id) + } else { + return false + } + } + public func countIncomingMessage(id: MessageId) { assert(!self.disposed) if let postbox = self.postbox { @@ -940,6 +948,14 @@ public final class Transaction { return postbox.messageHistoryTagsTable.earlierIndices(tag: tag, peerId: peerId, namespace: namespace, index: nil, includeFrom: false, count: 1000) } + public func getMessagesWithThreadId(peerId: PeerId, namespace: MessageId.Namespace, threadId: Int64, from: MessageIndex, includeFrom: Bool, to: MessageIndex, limit: Int) -> [Message] { + assert(!self.disposed) + guard let postbox = self.postbox else { + return [] + } + return postbox.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: nil, threadId: threadId, from: from, includeFrom: includeFrom, to: to, limit: limit).map(postbox.renderIntermediateMessage(_:)) + } + public func scanMessages(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags, _ f: (Message) -> Bool) { assert(!self.disposed) self.postbox?.scanMessages(peerId: peerId, namespace: namespace, tag: tag, f) @@ -1220,6 +1236,7 @@ public final class Postbox { let messageHistoryUnsentTable: MessageHistoryUnsentTable let messageHistoryFailedTable: MessageHistoryFailedTable let messageHistoryTagsTable: MessageHistoryTagsTable + let messageHistoryThreadsTable: MessageHistoryThreadsTable let globalMessageHistoryTagsTable: GlobalMessageHistoryTagsTable let localMessageHistoryTagsTable: LocalMessageHistoryTagsTable let peerChatStateTable: PeerChatStateTable @@ -1302,6 +1319,7 @@ public final class Postbox { self.pendingMessageActionsMetadataTable = PendingMessageActionsMetadataTable(valueBox: self.valueBox, table: PendingMessageActionsMetadataTable.tableSpec(45)) self.pendingMessageActionsTable = PendingMessageActionsTable(valueBox: self.valueBox, table: PendingMessageActionsTable.tableSpec(46), metadataTable: self.pendingMessageActionsMetadataTable) self.messageHistoryTagsTable = MessageHistoryTagsTable(valueBox: self.valueBox, table: MessageHistoryTagsTable.tableSpec(12), seedConfiguration: self.seedConfiguration, summaryTable: self.messageHistoryTagsSummaryTable) + self.messageHistoryThreadsTable = MessageHistoryThreadsTable(valueBox: self.valueBox, table: MessageHistoryThreadsTable.tableSpec(62)) self.globalMessageHistoryTagsTable = GlobalMessageHistoryTagsTable(valueBox: self.valueBox, table: GlobalMessageHistoryTagsTable.tableSpec(39)) self.localMessageHistoryTagsTable = LocalMessageHistoryTagsTable(valueBox: self.valueBox, table: GlobalMessageHistoryTagsTable.tableSpec(52)) self.messageHistoryIndexTable = MessageHistoryIndexTable(valueBox: self.valueBox, table: MessageHistoryIndexTable.tableSpec(4), messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, globalMessageIdsTable: self.globalMessageIdsTable, metadataTable: self.messageHistoryMetadataTable, seedConfiguration: self.seedConfiguration) @@ -1313,7 +1331,7 @@ public final class Postbox { self.timestampBasedMessageAttributesTable = TimestampBasedMessageAttributesTable(valueBox: self.valueBox, table: TimestampBasedMessageAttributesTable.tableSpec(34), indexTable: self.timestampBasedMessageAttributesIndexTable) self.textIndexTable = MessageHistoryTextIndexTable(valueBox: self.valueBox, table: MessageHistoryTextIndexTable.tableSpec(41)) self.additionalChatListItemsTable = AdditionalChatListItemsTable(valueBox: self.valueBox, table: AdditionalChatListItemsTable.tableSpec(55)) - self.messageHistoryTable = MessageHistoryTable(valueBox: self.valueBox, table: MessageHistoryTable.tableSpec(7), seedConfiguration: seedConfiguration, messageHistoryIndexTable: self.messageHistoryIndexTable, messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, messageMediaTable: self.mediaTable, historyMetadataTable: self.messageHistoryMetadataTable, globallyUniqueMessageIdsTable: self.globallyUniqueMessageIdsTable, unsentTable: self.messageHistoryUnsentTable, failedTable: self.messageHistoryFailedTable, tagsTable: self.messageHistoryTagsTable, globalTagsTable: self.globalMessageHistoryTagsTable, localTagsTable: self.localMessageHistoryTagsTable, readStateTable: self.readStateTable, synchronizeReadStateTable: self.synchronizeReadStateTable, textIndexTable: self.textIndexTable, summaryTable: self.messageHistoryTagsSummaryTable, pendingActionsTable: self.pendingMessageActionsTable) + self.messageHistoryTable = MessageHistoryTable(valueBox: self.valueBox, table: MessageHistoryTable.tableSpec(7), seedConfiguration: seedConfiguration, messageHistoryIndexTable: self.messageHistoryIndexTable, messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, messageMediaTable: self.mediaTable, historyMetadataTable: self.messageHistoryMetadataTable, globallyUniqueMessageIdsTable: self.globallyUniqueMessageIdsTable, unsentTable: self.messageHistoryUnsentTable, failedTable: self.messageHistoryFailedTable, tagsTable: self.messageHistoryTagsTable, threadsTable: self.messageHistoryThreadsTable, globalTagsTable: self.globalMessageHistoryTagsTable, localTagsTable: self.localMessageHistoryTagsTable, readStateTable: self.readStateTable, synchronizeReadStateTable: self.synchronizeReadStateTable, textIndexTable: self.textIndexTable, summaryTable: self.messageHistoryTagsSummaryTable, pendingActionsTable: self.pendingMessageActionsTable) self.peerChatStateTable = PeerChatStateTable(valueBox: self.valueBox, table: PeerChatStateTable.tableSpec(13)) self.peerNameTokenIndexTable = ReverseIndexReferenceTable(valueBox: self.valueBox, table: ReverseIndexReferenceTable.tableSpec(26)) self.peerNameIndexTable = PeerNameIndexTable(valueBox: self.valueBox, table: PeerNameIndexTable.tableSpec(27), peerTable: self.peerTable, peerNameTokenIndexTable: self.peerNameTokenIndexTable) @@ -1355,6 +1373,7 @@ public final class Postbox { tables.append(self.messageHistoryUnsentTable) tables.append(self.messageHistoryFailedTable) tables.append(self.messageHistoryTagsTable) + tables.append(self.messageHistoryThreadsTable) tables.append(self.globalMessageHistoryTagsTable) tables.append(self.localMessageHistoryTagsTable) tables.append(self.messageHistoryIndexTable) @@ -1467,7 +1486,7 @@ public final class Postbox { if let forwardInfo = message.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, timestamp: message.timestamp, flags: flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: storeForwardInfo, authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media)) + return .update(StoreMessage(id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, timestamp: message.timestamp, flags: flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: storeForwardInfo, authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media)) } else { return .skip } @@ -1666,7 +1685,7 @@ public final class Postbox { } fileprivate func applyInteractiveReadMaxIndex(_ messageIndex: MessageIndex) -> [MessageId] { - let peerIds = self.peerIdsForLocation(.peer(messageIndex.id.peerId), tagMask: nil) + let peerIds = self.peerIdsForLocation(.peer(messageIndex.id.peerId)) switch peerIds { case let .associated(_, messageId): if let messageId = messageId, let readState = self.readStateTable.getCombinedState(messageId.peerId), readState.count != 0 { @@ -1682,7 +1701,7 @@ public final class Postbox { if let states = initialCombinedStates?.states { for (namespace, state) in states { if namespace != messageIndex.id.namespace && state.count != 0 { - if let item = self.messageHistoryTable.fetch(peerId: messageIndex.id.peerId, namespace: namespace, tag: nil, from: MessageIndex(id: MessageId(peerId: messageIndex.id.peerId, namespace: namespace, id: 1), timestamp: messageIndex.timestamp), includeFrom: true, to: MessageIndex.lowerBound(peerId: messageIndex.id.peerId, namespace: namespace), limit: 1).first { + if let item = self.messageHistoryTable.fetch(peerId: messageIndex.id.peerId, namespace: namespace, tag: nil, threadId: nil, from: MessageIndex(id: MessageId(peerId: messageIndex.id.peerId, namespace: namespace, id: 1), timestamp: messageIndex.timestamp), includeFrom: true, to: MessageIndex.lowerBound(peerId: messageIndex.id.peerId, namespace: namespace), limit: 1).first { resultIds.append(contentsOf: self.messageHistoryTable.applyInteractiveMaxReadIndex(postbox: self, messageIndex: item.index, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations)) } } @@ -2333,93 +2352,159 @@ public final class Postbox { } } - func peerIdsForLocation(_ chatLocation: ChatLocation, tagMask: MessageTags?) -> MessageHistoryViewPeerIds { - var peerIds: MessageHistoryViewPeerIds + func resolvedChatLocationInput(chatLocation: ChatLocationInput) -> Signal<(ResolvedChatLocationInput, Bool), NoError> { + switch chatLocation { + case let .peer(peerId): + return .single((.peer(peerId), false)) + case let .external(_, input): + var isHoleFill = false + return input + |> map { value -> (ResolvedChatLocationInput, Bool) in + let wasHoleFill = isHoleFill + isHoleFill = true + return (.external(value), wasHoleFill) + } + } + } + + func peerIdsForLocation(_ chatLocation: ResolvedChatLocationInput) -> MessageHistoryViewInput { + var peerIds: MessageHistoryViewInput switch chatLocation { case let .peer(peerId): peerIds = .single(peerId) if let associatedMessageId = self.cachedPeerDataTable.get(peerId)?.associatedHistoryMessageId, associatedMessageId.peerId != peerId { peerIds = .associated(peerId, associatedMessageId) } + case let .external(input): + peerIds = .external(input) } return peerIds } - public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocation, count: Int, clipHoles: Bool = true, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { - return self.transactionSignal(userInteractive: true, { subscriber, transaction in - let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask) + public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocationInput, count: Int, clipHoles: Bool = true, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + return self.resolvedChatLocationInput(chatLocation: chatLocation) + |> mapToSignal { chatLocationData in + let (chatLocation, isHoleFill) = chatLocationData - var anchor: HistoryViewInputAnchor = .upperBound - switch peerIds { - case let .single(peerId): - if self.chatListTable.getPeerChatListIndex(peerId: peerId) != nil { - if let combinedState = self.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 { - switch state.1 { - case let .idBased(maxIncomingReadId, _, _, _, _): - anchor = .message(MessageId(peerId: peerId, namespace: state.0, id: maxIncomingReadId)) - case let .indexBased(maxIncomingReadIndex, _, _, _): - anchor = .index(maxIncomingReadIndex) - } - } else if let scrollIndex = self.peerChatInterfaceStateTable.get(peerId)?.historyScrollMessageIndex { - anchor = .index(scrollIndex) - } - } - case let .associated(mainId, associatedId): - var ids: [PeerId] = [] - ids.append(mainId) - if let associatedId = associatedId { - ids.append(associatedId.peerId) - } + let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> = self.transactionSignal(userInteractive: true, { subscriber, transaction in + let peerIds = self.peerIdsForLocation(chatLocation) - var found = false - loop: for peerId in ids.reversed() { - if self.chatListTable.getPeerChatListIndex(peerId: mainId) != nil, let combinedState = self.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 { - found = true - switch state.1 { - case let .idBased(maxIncomingReadId, _, _, _, _): - anchor = .message(MessageId(peerId: peerId, namespace: state.0, id: maxIncomingReadId)) - case let .indexBased(maxIncomingReadIndex, _, _, _): - anchor = .index(maxIncomingReadIndex) + var anchor: HistoryViewInputAnchor = .upperBound + switch peerIds { + case let .single(peerId): + if self.chatListTable.getPeerChatListIndex(peerId: peerId) != nil { + if let combinedState = self.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 { + switch state.1 { + case let .idBased(maxIncomingReadId, _, _, _, _): + anchor = .message(MessageId(peerId: peerId, namespace: state.0, id: maxIncomingReadId)) + case let .indexBased(maxIncomingReadIndex, _, _, _): + anchor = .index(maxIncomingReadIndex) + } + } else if let scrollIndex = self.peerChatInterfaceStateTable.get(peerId)?.historyScrollMessageIndex { + anchor = .index(scrollIndex) } - break loop + } + case let .associated(mainId, associatedId): + var ids: [PeerId] = [] + ids.append(mainId) + if let associatedId = associatedId { + ids.append(associatedId.peerId) + } + + var found = false + loop: for peerId in ids.reversed() { + if self.chatListTable.getPeerChatListIndex(peerId: mainId) != nil, let combinedState = self.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 { + found = true + switch state.1 { + case let .idBased(maxIncomingReadId, _, _, _, _): + anchor = .message(MessageId(peerId: peerId, namespace: state.0, id: maxIncomingReadId)) + case let .indexBased(maxIncomingReadIndex, _, _, _): + anchor = .index(maxIncomingReadIndex) + } + break loop + } + } + + if !found { + if let scrollIndex = self.peerChatInterfaceStateTable.get(mainId)?.historyScrollMessageIndex { + anchor = .index(scrollIndex) + } + } + case let .external(input): + if let maxReadMessageId = input.maxReadMessageId { + anchor = .message(maxReadMessageId) + } else { + anchor = .upperBound } } - - if !found { - if let scrollIndex = self.peerChatInterfaceStateTable.get(mainId)?.historyScrollMessageIndex { - anchor = .index(scrollIndex) - } + return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) + }) + + return signal + |> map { (view, updateType, data) -> (MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?) in + if isHoleFill { + return (view, .FillHole, data) + } else { + return (view, updateType, data) } } - return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) - }) - } - - public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, clipHoles: Bool = true, messageId: MessageId, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { - return self.transactionSignal { subscriber, transaction in - let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask) - return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: .message(messageId), fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) } } - public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, anchor: HistoryViewInputAnchor, count: Int, clipHoles: Bool = true, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { - return self.transactionSignal { subscriber, transaction in - let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask) - - return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) + public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocationInput, count: Int, clipHoles: Bool = true, messageId: MessageId, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + return self.resolvedChatLocationInput(chatLocation: chatLocation) + |> mapToSignal { chatLocationData in + let (chatLocation, isHoleFill) = chatLocationData + let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> = self.transactionSignal { subscriber, transaction in + let peerIds = self.peerIdsForLocation(chatLocation) + return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: .message(messageId), fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) + } + + return signal + |> map { (view, updateType, data) -> (MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?) in + if isHoleFill { + return (view, .FillHole, data) + } else { + return (view, updateType, data) + } + } } } - private func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewPeerIds, count: Int, clipHoles: Bool, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable { + public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocationInput, anchor: HistoryViewInputAnchor, count: Int, clipHoles: Bool = true, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + return self.resolvedChatLocationInput(chatLocation: chatLocation) + |> mapToSignal { chatLocationData -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> in + let (chatLocation, isHoleFill) = chatLocationData + let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> = self.transactionSignal { subscriber, transaction in + let peerIds = self.peerIdsForLocation(chatLocation) + + return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, clipHoles: clipHoles, anchor: anchor, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData) + } + + return signal + |> map { viewData -> (MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?) in + let (view, updateType, data) = viewData + if isHoleFill { + return (view, .FillHole, data) + } else { + return (view, updateType, data) + } + } + } + } + + private func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewInput, count: Int, clipHoles: Bool, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable { var topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?] = [:] - var mainPeerId: PeerId? + var mainPeerIdForTopTaggedMessages: PeerId? switch peerIds { case let .single(id): - mainPeerId = id + mainPeerIdForTopTaggedMessages = id case let .associated(id, _): - mainPeerId = id + mainPeerIdForTopTaggedMessages = id + case .external: + mainPeerIdForTopTaggedMessages = nil } - if let peerId = mainPeerId { + if let peerId = mainPeerIdForTopTaggedMessages { for namespace in topTaggedMessageIdNamespaces { if let messageId = self.peerChatTopTaggedMessageIdsTable.get(peerId: peerId, namespace: namespace) { if let index = self.messageHistoryIndexTable.getIndex(messageId) { @@ -2453,6 +2538,9 @@ public final class Postbox { } } additionalDataEntries.append(.cachedPeerDataMessages(peerId, messages)) + case let .message(id): + let message = self.getMessage(id) + additionalDataEntries.append(.message(id, message)) case let .peerChatState(peerId): additionalDataEntries.append(.peerChatState(peerId, self.peerChatStateTable.get(peerId) as? PeerChatState)) case .totalUnreadState: @@ -2491,6 +2579,8 @@ public final class Postbox { if let readState = self.readStateTable.getCombinedState(peerId) { transientReadStates = .peer([peerId: readState]) } + case .external: + transientReadStates = nil } if let fixedCombinedReadStates = fixedCombinedReadStates { @@ -2517,6 +2607,8 @@ public final class Postbox { initialData = self.initialMessageHistoryData(peerId: peerId) case let .associated(peerId, _): initialData = self.initialMessageHistoryData(peerId: peerId) + case let .external(input): + initialData = self.initialMessageHistoryData(peerId: input.peerId) } subscriber.putNext((MessageHistoryView(mutableView), initialUpdateType, initialData)) @@ -3178,7 +3270,7 @@ public final class Postbox { var remainingLimit = limit var index = MessageIndex.upperBound(peerId: peerId, namespace: namespace) while remainingLimit > 0 { - let messages = self.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: nil, from: index, includeFrom: false, to: MessageIndex.lowerBound(peerId: peerId, namespace: namespace), limit: 32) + let messages = self.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: nil, threadId: nil, from: index, includeFrom: false, to: MessageIndex.lowerBound(peerId: peerId, namespace: namespace), limit: 32) for message in messages { let attributes = MessageHistoryTable.renderMessageAttributes(message) if !f(message.id, attributes) { diff --git a/submodules/Postbox/Sources/ViewTracker.swift b/submodules/Postbox/Sources/ViewTracker.swift index 6b7de365ee..0abae1dd7d 100644 --- a/submodules/Postbox/Sources/ViewTracker.swift +++ b/submodules/Postbox/Sources/ViewTracker.swift @@ -29,6 +29,9 @@ final class ViewTracker { private let messageHistoryHolesView = MutableMessageHistoryHolesView() private let messageHistoryHolesViewSubscribers = Bag>() + private let externalMessageHistoryHolesView = MutableMessageHistoryExternalHolesView() + private let externalMessageHistoryHolesViewSubscribers = Bag>() + private let chatListHolesView = MutableChatListHolesView() private let chatListHolesViewSubscribers = Bag>() @@ -309,7 +312,7 @@ final class ViewTracker { case .associated: var ids = Set() switch mutableView.peerIds { - case .single: + case .single, .external: assertionFailure() case let .associated(mainPeerId, associatedId): ids.insert(mainPeerId) @@ -326,6 +329,8 @@ final class ViewTracker { } } } + case .external: + break } mutableView.updatePeerIds(transaction: transaction) diff --git a/submodules/PresentationDataUtils/BUCK b/submodules/PresentationDataUtils/BUCK index 533f10d829..84fa4c2fdc 100644 --- a/submodules/PresentationDataUtils/BUCK +++ b/submodules/PresentationDataUtils/BUCK @@ -14,6 +14,7 @@ static_library( "//submodules/ItemListUI:ItemListUI", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/OverlayStatusController:OverlayStatusController", + "//submodules/UrlWhitelist:UrlWhitelist", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/PresentationDataUtils/BUILD b/submodules/PresentationDataUtils/BUILD index 022f9f42c3..18415391b2 100644 --- a/submodules/PresentationDataUtils/BUILD +++ b/submodules/PresentationDataUtils/BUILD @@ -15,6 +15,7 @@ swift_library( "//submodules/ItemListUI:ItemListUI", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/OverlayStatusController:OverlayStatusController", + "//submodules/UrlWhitelist:UrlWhitelist", ], visibility = [ "//visibility:public", diff --git a/submodules/PresentationDataUtils/Sources/OpenUrl.swift b/submodules/PresentationDataUtils/Sources/OpenUrl.swift new file mode 100644 index 0000000000..9a7f4ea544 --- /dev/null +++ b/submodules/PresentationDataUtils/Sources/OpenUrl.swift @@ -0,0 +1,62 @@ +import Foundation +import Display +import SwiftSignalKit +import AccountContext +import OverlayStatusController +import UrlWhitelist + +public func openUserGeneratedUrl(context: AccountContext, url: String, concealed: Bool, present: @escaping (ViewController) -> Void, openResolved: @escaping (ResolvedUrl) -> Void) { + var concealed = concealed + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + let openImpl: () -> Void = { + let disposable = MetaDisposable() + var cancelImpl: (() -> Void)? + let progressSignal = Signal { subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + present(controller) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + cancelImpl = { + disposable.dispose() + } + disposable.set((context.sharedContext.resolveUrl(account: context.account, url: url) + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + |> deliverOnMainQueue).start(next: { result in + openResolved(result) + })) + } + + let (parsedString, parsedConcealed) = parseUrl(url: url, wasConcealed: concealed) + concealed = parsedConcealed + + if concealed { + var rawDisplayUrl: String = parsedString + let maxLength = 180 + if rawDisplayUrl.count > maxLength { + rawDisplayUrl = String(rawDisplayUrl[.. }, opaque: false)?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0), topCapHeight: Int(diameter / 2.0)) } + +public struct SearchBarToken { + public struct Style { + public let backgroundColor: UIColor + public let foregroundColor: UIColor + public let strokeColor: UIColor + + public init(backgroundColor: UIColor, foregroundColor: UIColor, strokeColor: UIColor) { + self.backgroundColor = backgroundColor + self.foregroundColor = foregroundColor + self.strokeColor = strokeColor + } + } + + public let id: AnyHashable + public let icon: UIImage? + public let title: String + public let style: Style? + + public init(id: AnyHashable, icon: UIImage?, title: String, style: Style? = nil) { + self.id = id + self.icon = icon + self.title = title + self.style = style + } +} + +private final class TokenNode: ASDisplayNode { + var theme: SearchBarNodeTheme + let token: SearchBarToken + let iconNode: ASImageNode + let titleNode: ASTextNode + let backgroundNode: ASImageNode + + var isSelected: Bool = false + var isCollapsed: Bool = false + + var tapped: (() -> Void)? + + init(theme: SearchBarNodeTheme, token: SearchBarToken) { + self.theme = theme + self.token = token + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + self.iconNode.displayWithoutProcessing = true + self.titleNode = ASTextNode() + self.titleNode.isUserInteractionEnabled = false + self.titleNode.displaysAsynchronously = false + self.titleNode.maximumNumberOfLines = 1 + self.backgroundNode = ASImageNode() + self.backgroundNode.displaysAsynchronously = false + self.backgroundNode.displayWithoutProcessing = true + + super.init() + + self.clipsToBounds = true + + self.addSubnode(self.backgroundNode) + + let backgroundColor = token.style?.backgroundColor ?? theme.inputIcon + let strokeColor = token.style?.strokeColor ?? backgroundColor + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 8.0, color: backgroundColor, strokeColor: strokeColor, strokeWidth: UIScreenPixel, backgroundColor: nil) + + let foregroundColor = token.style?.foregroundColor ?? .white + self.iconNode.image = generateTintedImage(image: token.icon, color: foregroundColor) + self.addSubnode(self.iconNode) + + self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(17.0), textColor: foregroundColor) + self.addSubnode(self.titleNode) + } + + override func didLoad() { + super.didLoad() + + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture))) + } + + @objc private func tapGesture() { + self.tapped?() + } + + func animateIn() { + let targetFrame = self.frame + self.layer.animateFrame(from: CGRect(origin: targetFrame.origin, size: CGSize(width: 1.0, height: targetFrame.height)), to: targetFrame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + self.iconNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + self.titleNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + self.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + + func animateOut() { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { [weak self] _ in + self?.removeFromSupernode() + }) + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) + } + + func update(theme: SearchBarNodeTheme, token: SearchBarToken, isSelected: Bool, isCollapsed: Bool) { + let wasSelected = self.isSelected + self.isSelected = isSelected + self.isCollapsed = isCollapsed + + if theme !== self.theme || isSelected != wasSelected { + let backgroundColor = isSelected ? self.theme.accent : (token.style?.backgroundColor ?? self.theme.inputIcon) + let strokeColor = isSelected ? backgroundColor : (token.style?.strokeColor ?? backgroundColor) + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 8.0, color: backgroundColor, strokeColor: strokeColor, strokeWidth: UIScreenPixel, backgroundColor: nil) + + let foregroundColor = isSelected ? .white : (token.style?.foregroundColor ?? .white) + + if let image = token.icon { + self.iconNode.image = generateTintedImage(image: image, color: foregroundColor) + } + self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(17.0), textColor: foregroundColor) + } + } + + func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + let height: CGFloat = 24.0 + + var leftInset: CGFloat = 3.0 + if let icon = self.iconNode.image { + leftInset += 1.0 + transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: leftInset, y: floor((height - icon.size.height) / 2.0)), size: icon.size)) + leftInset += icon.size.width + 3.0 + } + + let iconSize = self.token.icon?.size ?? CGSize() + let titleSize = self.titleNode.measure(CGSize(width: constrainedSize.width - 6.0, height: constrainedSize.height)) + var width = titleSize.width + 6.0 + if !iconSize.width.isZero { + width += iconSize.width + 7.0 + } + + let size = CGSize(width: self.isCollapsed ? height : width, height: height) + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize)) + + return size + } +} + private class SearchBarTextField: UITextField { public var didDeleteBackwardWhileEmpty: (() -> Void)? @@ -37,6 +178,164 @@ private class SearchBarTextField: UITextField { } } + var tokenNodes: [AnyHashable: TokenNode] = [:] + var tokens: [SearchBarToken] = [] { + didSet { + self._selectedTokenIndex = nil + self.layoutTokens(transition: .animated(duration: 0.2, curve: .easeInOut)) + self.setNeedsLayout() + self.updateCursorColor() + } + } + + var _selectedTokenIndex: Int? + var selectedTokenIndex: Int? { + get { + return self._selectedTokenIndex + } + set { + _selectedTokenIndex = newValue + self.layoutTokens(transition: .animated(duration: 0.2, curve: .easeInOut)) + self.setNeedsLayout() + self.updateCursorColor() + } + } + + private func updateCursorColor() { + if self._selectedTokenIndex != nil { + super.tintColor = UIColor.clear + } else { + super.tintColor = self._tintColor + } + } + + var _tintColor: UIColor = .black + override var tintColor: UIColor! { + get { + return super.tintColor + } + set { + if newValue != UIColor.clear { + self._tintColor = newValue + + if self.selectedTokenIndex == nil { + super.tintColor = newValue + } + } + } + } + + var theme: SearchBarNodeTheme + + fileprivate func layoutTokens(transition: ContainedViewLayoutTransition = .immediate) { + for i in 0 ..< self.tokens.count { + let token = self.tokens[i] + + let tokenNode: TokenNode + if let current = self.tokenNodes[token.id] { + tokenNode = current + } else { + tokenNode = TokenNode(theme: self.theme, token: token) + self.tokenNodes[token.id] = tokenNode + } + tokenNode.tapped = { [weak self] in + self?.selectedTokenIndex = i + self?.becomeFirstResponder() + } + let isSelected = i == self.selectedTokenIndex + let isCollapsed = !isSelected && (i < self.tokens.count - 1 || !(self.text?.isEmpty ?? true)) + tokenNode.update(theme: self.theme, token: token, isSelected: isSelected, isCollapsed: isCollapsed) + } + var removeKeys: [AnyHashable] = [] + for (id, _) in self.tokenNodes { + if !self.tokens.contains(where: { $0.id == id }) { + removeKeys.append(id) + } + } + for id in removeKeys { + if let itemNode = self.tokenNodes.removeValue(forKey: id) { + if transition.isAnimated { + itemNode.animateOut() + } else { + itemNode.removeFromSupernode() + } + } + } + + var tokenSizes: [(AnyHashable, CGSize, TokenNode, Bool)] = [] + var totalRawTabSize: CGFloat = 0.0 + + for token in self.tokens { + guard let tokenNode = self.tokenNodes[token.id] else { + continue + } + let wasAdded = tokenNode.view.superview == nil + var tokenNodeTransition = transition + if wasAdded { + tokenNodeTransition = .immediate + self.addSubnode(tokenNode) + } + + let nodeSize = tokenNode.updateLayout(constrainedSize: self.bounds.size, transition: tokenNodeTransition) + tokenSizes.append((token.id, nodeSize, tokenNode, wasAdded)) + totalRawTabSize += nodeSize.width + } + + let minSpacing: CGFloat = 6.0 + + let resolvedSideInset: CGFloat = 10.0 + var leftOffset: CGFloat = 0.0 + if !tokenSizes.isEmpty { + leftOffset += resolvedSideInset + } + + var longTitlesWidth: CGFloat = resolvedSideInset + for i in 0 ..< tokenSizes.count { + let (_, paneNodeSize, _, _) = tokenSizes[i] + longTitlesWidth += paneNodeSize.width + if i != tokenSizes.count - 1 { + longTitlesWidth += minSpacing + } + } + longTitlesWidth += resolvedSideInset + + let verticalOffset: CGFloat = 0.0 + var horizontalOffset: CGFloat = 0.0 + for i in 0 ..< tokenSizes.count { + let (_, nodeSize, tokenNode, wasAdded) = tokenSizes[i] + let tokenNodeTransition = transition + + let nodeFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((self.frame.height - nodeSize.height) / 2.0) + verticalOffset), size: nodeSize) + + if wasAdded { + if horizontalOffset > 0.0 { + tokenNode.frame = nodeFrame.offsetBy(dx: horizontalOffset, dy: 0.0) + tokenNodeTransition.updatePosition(node: tokenNode, position: nodeFrame.center) + } else { + tokenNode.frame = nodeFrame + } + tokenNode.animateIn() + } else { + if nodeFrame.width < tokenNode.frame.width { + horizontalOffset += tokenNode.frame.width - nodeFrame.width + } + tokenNodeTransition.updateFrame(node: tokenNode, frame: nodeFrame) + } + + tokenNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -minSpacing / 2.0, bottom: 0.0, right: -minSpacing / 2.0) + + leftOffset += nodeSize.width + minSpacing + } + + if !tokenSizes.isEmpty { + leftOffset -= 6.0 + } + + self.tokensWidth = leftOffset + } + + private var tokensWidth: CGFloat = 0.0 + private let measurePrefixLabel: ImmediateTextNode let prefixLabel: ImmediateTextNode var prefixString: NSAttributedString? { @@ -47,7 +346,9 @@ private class SearchBarTextField: UITextField { } } - override init(frame: CGRect) { + init(theme: SearchBarNodeTheme) { + self.theme = theme + self.placeholderLabel = ImmediateTextNode() self.placeholderLabel.isUserInteractionEnabled = false self.placeholderLabel.displaysAsynchronously = false @@ -66,7 +367,7 @@ private class SearchBarTextField: UITextField { self.prefixLabel.maximumNumberOfLines = 1 self.prefixLabel.truncationMode = .byTruncatingTail - super.init(frame: frame) + super.init(frame: CGRect()) self.addSubnode(self.placeholderLabel) self.addSubnode(self.prefixLabel) @@ -96,7 +397,8 @@ private class SearchBarTextField: UITextField { if bounds.size.width.isZero { return CGRect(origin: CGPoint(), size: CGSize()) } - var rect = bounds.insetBy(dx: 4.0, dy: 4.0) + var rect = bounds.insetBy(dx: 7.0, dy: 4.0) + rect.origin.y += 1.0 let prefixSize = self.measurePrefixLabel.updateLayout(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height)) if !prefixSize.width.isZero { @@ -104,6 +406,10 @@ private class SearchBarTextField: UITextField { rect.origin.x += prefixOffset rect.size.width -= prefixOffset } + if !self.tokensWidth.isZero { + rect.origin.x += self.tokensWidth + rect.size.width -= self.tokensWidth + } rect.size.width = max(rect.size.width, 10.0) return rect } @@ -135,7 +441,16 @@ private class SearchBarTextField: UITextField { } override func deleteBackward() { - if self.text == nil || self.text!.isEmpty { + var processed = false + if let selectedRange = self.selectedTextRange { + let cursorPosition = self.offset(from: self.beginningOfDocument, to: selectedRange.start) + if cursorPosition == 0 && !self.tokens.isEmpty && self.selectedTokenIndex == nil { + self.selectedTokenIndex = self.tokens.count - 1 + processed = true + } + } + + if !processed && (self.text == nil || self.text!.isEmpty) { self.didDeleteBackwardWhileEmpty?() } super.deleteBackward() @@ -255,6 +570,9 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { public var textUpdated: ((String, String?) -> Void)? public var textReturned: ((String) -> Void)? public var clearPrefix: (() -> Void)? + public var clearTokens: (() -> Void)? + + public var tokensUpdated: (([SearchBarToken]) -> Void)? private let backgroundNode: ASDisplayNode private let separatorNode: ASDisplayNode @@ -273,6 +591,15 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { } } + public var tokens: [SearchBarToken] { + get { + return self.textField.tokens + } set { + self.textField.tokens = newValue + self.updateIsEmpty(animated: true) + } + } + public var prefixString: NSAttributedString? { get { return self.textField.prefixString @@ -362,7 +689,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.iconNode.displaysAsynchronously = false self.iconNode.displayWithoutProcessing = true - self.textField = SearchBarTextField() + self.textField = SearchBarTextField(theme: theme) self.textField.accessibilityTraits = .searchField self.textField.autocorrectionType = .no self.textField.returnKeyType = .search @@ -372,7 +699,6 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.clearButton.imageNode.displaysAsynchronously = false self.clearButton.imageNode.displayWithoutProcessing = true self.clearButton.displaysAsynchronously = false - self.clearButton.isHidden = true self.cancelButton = HighlightableButtonNode(pointerStyle: .default) self.cancelButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) @@ -393,15 +719,24 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.textField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged) self.textField.didDeleteBackwardWhileEmpty = { [weak self] in - self?.clearPressed() + guard let strongSelf = self else { + return + } + if let index = strongSelf.textField.selectedTokenIndex { + strongSelf.tokens.remove(at: index) + strongSelf.tokensUpdated?(strongSelf.tokens) + } else { + strongSelf.clearPressed() + } } self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside) self.clearButton.addTarget(self, action: #selector(self.clearPressed), forControlEvents: .touchUpInside) self.updateThemeAndStrings(theme: theme, strings: strings) + self.updateIsEmpty(animated: false) } - + public func updateThemeAndStrings(theme: SearchBarNodeTheme, strings: PresentationStrings) { if self.theme != theme || self.strings !== strings { self.cancelButton.setAttributedTitle(NSAttributedString(string: self.cancelText ?? strings.Common_Cancel, font: self.cancelText != nil ? Font.semibold(17.0) : Font.regular(17.0), textColor: theme.accent), for: []) @@ -452,16 +787,16 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { if let iconImage = self.iconNode.image { let iconSize = iconImage.size - transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 8.0, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - iconSize.height) / 2.0)), size: iconSize)) + transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 5.0, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - iconSize.height) / 2.0) - UIScreenPixel), size: iconSize)) } if let activityIndicator = self.activityIndicator { let indicatorSize = activityIndicator.measure(CGSize(width: 32.0, height: 32.0)) - transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 7.0, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - indicatorSize.height) / 2.0)), size: indicatorSize)) + transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 9.0 + UIScreenPixel, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - indicatorSize.height) / 2.0)), size: indicatorSize)) } let clearSize = self.clearButton.measure(CGSize(width: 100.0, height: 100.0)) - transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.maxX - 8.0 - clearSize.width, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - clearSize.height) / 2.0)), size: clearSize)) + transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.maxX - 6.0 - clearSize.width, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - clearSize.height) / 2.0)), size: clearSize)) self.textField.frame = textFrame } @@ -499,7 +834,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.textBackgroundNode.layer.animateFrame(from: initialTextBackgroundFrame, to: self.textBackgroundNode.frame, duration: duration, timingFunction: timingFunction) let textFieldFrame = self.textField.frame - let initialLabelNodeFrame = CGRect(origin: node.labelNode.frame.offsetBy(dx: initialTextBackgroundFrame.origin.x - 4.0, dy: initialTextBackgroundFrame.origin.y - 7.0).origin, size: textFieldFrame.size) + let initialLabelNodeFrame = CGRect(origin: node.labelNode.frame.offsetBy(dx: initialTextBackgroundFrame.origin.x - 7.0, dy: initialTextBackgroundFrame.origin.y - 8.0).origin, size: textFieldFrame.size) self.textField.layer.animateFrame(from: initialLabelNodeFrame, to: self.textField.frame, duration: duration, timingFunction: timingFunction) let iconFrame = self.iconNode.frame @@ -515,7 +850,9 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.textField.resignFirstResponder() if clear { self.textField.text = nil - self.textField.placeholderLabel.isHidden = false + self.textField.tokens = [] + self.textField.prefixString = nil + self.textField.placeholderLabel.alpha = 1.0 } } @@ -576,7 +913,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { transitionBackgroundNode.layer.animateFrame(from: self.textBackgroundNode.frame, to: targetTextBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) let textFieldFrame = self.textField.frame - let targetLabelNodeFrame = CGRect(origin: CGPoint(x: node.labelNode.frame.minX + targetTextBackgroundFrame.origin.x - 4.0, y: targetTextBackgroundFrame.minY + floorToScreenPixels((targetTextBackgroundFrame.size.height - textFieldFrame.size.height) / 2.0)), size: textFieldFrame.size) + let targetLabelNodeFrame = CGRect(origin: CGPoint(x: node.labelNode.frame.minX + targetTextBackgroundFrame.origin.x - 7.0, y: targetTextBackgroundFrame.minY + floorToScreenPixels((targetTextBackgroundFrame.size.height - textFieldFrame.size.height) / 2.0) - UIScreenPixel), size: textFieldFrame.size) self.textField.layer.animateFrame(from: self.textField.frame, to: targetLabelNodeFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { if let snapshot = node.labelNode.layer.snapshotContentTree() { @@ -626,22 +963,42 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { @objc private func textFieldDidChange(_ textField: UITextField) { self.updateIsEmpty() + if let _ = self.textField.selectedTokenIndex { + self.textField.selectedTokenIndex = nil + } if let textUpdated = self.textUpdated { textUpdated(textField.text ?? "", textField.textInputMode?.primaryLanguage) + self.textField.layoutTokens(transition: .animated(duration: 0.2, curve: .easeInOut)) } } + public func textFieldDidEndEditing(_ textField: UITextField) { + self.textField.selectedTokenIndex = nil + } + public func selectAll() { self.textField.becomeFirstResponder() self.textField.selectAll(nil) } - private func updateIsEmpty() { - let isEmpty = !(self.textField.text?.isEmpty ?? true) - if isEmpty != self.textField.placeholderLabel.isHidden { - self.textField.placeholderLabel.isHidden = isEmpty + public func selectLastToken() { + if !self.textField.tokens.isEmpty { + self.textField.selectedTokenIndex = self.textField.tokens.count - 1 + self.textField.becomeFirstResponder() } - self.clearButton.isHidden = !isEmpty && self.prefixString == nil + } + + private func updateIsEmpty(animated: Bool = false) { + let isEmpty = (self.textField.text?.isEmpty ?? true) && self.tokens.isEmpty + + let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .spring) : .immediate + let placeholderTransition = !isEmpty ? .immediate : transition + placeholderTransition.updateAlpha(node: self.textField.placeholderLabel, alpha: isEmpty ? 1.0 : 0.0) + + let clearIsHidden = isEmpty && self.prefixString == nil + transition.updateAlpha(node: self.clearButton, alpha: clearIsHidden ? 0.0 : 1.0) + transition.updateTransformScale(node: self.clearButton, scale: clearIsHidden ? 0.2 : 1.0) + self.clearButton.isUserInteractionEnabled = !clearIsHidden } @objc private func cancelPressed() { @@ -655,6 +1012,9 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { if self.prefixString != nil { self.clearPrefix?() } + if !self.tokens.isEmpty { + self.clearTokens?() + } } else { self.textField.text = "" self.textFieldDidChange(self.textField) diff --git a/submodules/SearchBarNode/Sources/SearchBarPlaceholderNode.swift b/submodules/SearchBarNode/Sources/SearchBarPlaceholderNode.swift index 78ad5c0b3f..499ee864fd 100644 --- a/submodules/SearchBarNode/Sources/SearchBarPlaceholderNode.swift +++ b/submodules/SearchBarNode/Sources/SearchBarPlaceholderNode.swift @@ -143,7 +143,7 @@ public class SearchBarPlaceholderNode: ASDisplayNode { var iconSize = CGSize() var totalWidth = labelLayoutResult.size.width - let spacing: CGFloat = 8.0 + let spacing: CGFloat = 6.0 if let iconImage = strongSelf.iconNode.image { iconSize = iconImage.size @@ -152,7 +152,7 @@ public class SearchBarPlaceholderNode: ASDisplayNode { } var textOffset: CGFloat = 0.0 if constrainedSize.height >= 36.0 { - textOffset += UIScreenPixel + textOffset += 1.0 } let labelFrame = CGRect(origin: CGPoint(x: floor((constrainedSize.width - totalWidth) / 2.0) + iconSize.width + spacing, y: floorToScreenPixels((height - labelLayoutResult.size.height) / 2.0) + textOffset), size: labelLayoutResult.size) transition.updateFrame(node: strongSelf.labelNode, frame: labelFrame) diff --git a/submodules/SearchUI/Sources/SearchDisplayController.swift b/submodules/SearchUI/Sources/SearchDisplayController.swift index e5bd24e29c..9843a4d8fd 100644 --- a/submodules/SearchUI/Sources/SearchDisplayController.swift +++ b/submodules/SearchUI/Sources/SearchDisplayController.swift @@ -14,6 +14,7 @@ public enum SearchDisplayControllerMode { public final class SearchDisplayController { private let searchBar: SearchBarNode private let mode: SearchDisplayControllerMode + private let backgroundNode: ASDisplayNode public let contentNode: SearchDisplayControllerContentNode private var hasSeparator: Bool @@ -25,6 +26,9 @@ public final class SearchDisplayController { public init(presentationData: PresentationData, mode: SearchDisplayControllerMode = .navigation, placeholder: String? = nil, hasSeparator: Bool = false, contentNode: SearchDisplayControllerContentNode, cancel: @escaping () -> Void) { self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: hasSeparator), strings: presentationData.strings, fieldStyle: .modern, forceSeparator: hasSeparator) + self.backgroundNode = ASDisplayNode() + self.backgroundNode.backgroundColor = presentationData.theme.chatList.backgroundColor + self.mode = mode self.contentNode = contentNode self.hasSeparator = hasSeparator @@ -32,6 +36,9 @@ public final class SearchDisplayController { self.searchBar.textUpdated = { [weak contentNode] text, _ in contentNode?.searchTextUpdated(text: text) } + self.searchBar.tokensUpdated = { [weak contentNode] tokens in + contentNode?.searchTokensUpdated(tokens: tokens) + } self.searchBar.cancel = { [weak self] in self?.isDeactivating = true cancel() @@ -39,6 +46,9 @@ public final class SearchDisplayController { self.searchBar.clearPrefix = { [weak contentNode] in contentNode?.searchTextClearPrefix() } + self.searchBar.clearTokens = { [weak contentNode] in + contentNode?.searchTextClearTokens() + } self.contentNode.cancel = { [weak self] in self?.isDeactivating = true cancel() @@ -46,9 +56,16 @@ public final class SearchDisplayController { self.contentNode.dismissInput = { [weak self] in self?.searchBar.deactivate(clear: false) } - self.contentNode.setQuery = { [weak self] prefix, query in - self?.searchBar.prefixString = prefix - self?.searchBar.text = query + self.contentNode.setQuery = { [weak self] prefix, tokens, query in + if let strongSelf = self { + strongSelf.searchBar.prefixString = prefix + let previousTokens = strongSelf.searchBar.tokens + strongSelf.searchBar.tokens = tokens + if previousTokens.count < tokens.count { + strongSelf.searchBar.selectLastToken() + } + strongSelf.searchBar.text = query + } } if let placeholder = placeholder { self.searchBar.placeholderString = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: presentationData.theme.rootController.navigationSearchBar.inputPlaceholderTextColor) @@ -72,6 +89,8 @@ public final class SearchDisplayController { public func updatePresentationData(_ presentationData: PresentationData) { self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: self.hasSeparator), strings: presentationData.strings) self.contentNode.updatePresentationData(presentationData) + + self.backgroundNode.backgroundColor = presentationData.theme.chatList.backgroundColor } public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { @@ -100,8 +119,9 @@ public final class SearchDisplayController { self.containerLayout = (layout, navigationBarFrame.maxY) + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: layout.size)) transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarFrame.maxY, transition: transition) + self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition) } public func activate(insertSubnode: (ASDisplayNode, Bool) -> Void, placeholder: SearchBarPlaceholderNode?) { @@ -109,6 +129,7 @@ public final class SearchDisplayController { return } + insertSubnode(self.backgroundNode, false) insertSubnode(self.contentNode, false) self.contentNode.frame = CGRect(origin: CGPoint(), size: layout.size) @@ -125,8 +146,10 @@ public final class SearchDisplayController { let contentNodePosition = self.contentNode.layer.position - self.contentNode.layer.animatePosition(from: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (initialTextBackgroundFrame.maxY + 8.0 - contentNavigationBarHeight)), to: contentNodePosition, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) +// self.contentNode.layer.animatePosition(from: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (initialTextBackgroundFrame.maxY + 8.0 - contentNavigationBarHeight)), to: contentNodePosition, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) + self.contentNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) self.searchBar.placeholderString = placeholder.placeholderString } @@ -178,6 +201,7 @@ public final class SearchDisplayController { }) } + let backgroundNode = self.backgroundNode let contentNode = self.contentNode if animated { if let placeholder = placeholder, let (layout, navigationBarHeight) = self.containerLayout { @@ -194,7 +218,11 @@ public final class SearchDisplayController { contentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak contentNode] _ in contentNode?.removeFromSupernode() }) + backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak backgroundNode] _ in + backgroundNode?.removeFromSupernode() + }) } else { + backgroundNode.removeFromSupernode() contentNode.removeFromSupernode() } } diff --git a/submodules/SearchUI/Sources/SearchDisplayControllerContentNode.swift b/submodules/SearchUI/Sources/SearchDisplayControllerContentNode.swift index 01e11f9db3..e9b8fd1fb8 100644 --- a/submodules/SearchUI/Sources/SearchDisplayControllerContentNode.swift +++ b/submodules/SearchUI/Sources/SearchDisplayControllerContentNode.swift @@ -4,11 +4,12 @@ import AsyncDisplayKit import Display import SwiftSignalKit import TelegramPresentationData +import SearchBarNode open class SearchDisplayControllerContentNode: ASDisplayNode { public final var dismissInput: (() -> Void)? public final var cancel: (() -> Void)? - public final var setQuery: ((NSAttributedString?, String) -> Void)? + public final var setQuery: ((NSAttributedString?, [SearchBarToken], String) -> Void)? public final var setPlaceholder: ((String) -> Void)? open var isSearching: Signal { @@ -25,9 +26,15 @@ open class SearchDisplayControllerContentNode: ASDisplayNode { open func searchTextUpdated(text: String) { } + open func searchTokensUpdated(tokens: [SearchBarToken]) { + } + open func searchTextClearPrefix() { } + open func searchTextClearTokens() { + } + open func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { } diff --git a/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift b/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift index 02eee416c4..77c5462bd3 100644 --- a/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift +++ b/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift @@ -149,6 +149,9 @@ public final class SelectablePeerNode: ASDisplayNode { if peer.peerId == context.account.peerId { text = strings.DialogList_SavedMessages overrideImage = .savedMessagesIcon + } else if peer.peerId.isReplies { + text = strings.DialogList_Replies + overrideImage = .repliesIcon } else { text = mainPeer.compactDisplayTitle if mainPeer.isDeleted { diff --git a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift index 600f8741f7..94145bbdb7 100644 --- a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift +++ b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift @@ -170,22 +170,22 @@ private final class BubbleSettingsControllerNode: ASDisplayNode, UIScrollViewDel peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) - let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message2], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) let waveformBase64 = "DAAOAAkACQAGAAwADwAMABAADQAPABsAGAALAA0AGAAfABoAHgATABgAGQAYABQADAAVABEAHwANAA0ACQAWABkACQAOAAwACQAfAAAAGQAVAAAAEwATAAAACAAfAAAAHAAAABwAHwAAABcAGQAAABQADgAAABQAHwAAAB8AHwAAAAwADwAAAB8AEwAAABoAFwAAAB8AFAAAAAAAHwAAAAAAHgAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAA=" let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))] let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes) - let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message3], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local), tapMessage: nil, clickThroughMessage: nil)) - let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message4], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) let width: CGFloat diff --git a/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift b/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift index 8fc2ba536c..30fecbd5b5 100644 --- a/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift +++ b/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift @@ -71,13 +71,13 @@ public func cachedFaqInstantPage(context: AccountContext) -> Signal Signal<[SettingsSearchableItem], NoError> { +func faqSearchableItems(context: AccountContext, resolvedUrl: Signal, suggestAccountDeletion: Bool) -> Signal<[SettingsSearchableItem], NoError> { let strings = context.sharedContext.currentPresentationData.with { $0 }.strings - return cachedFaqInstantPage(context: context) + return resolvedUrl |> map { resolvedUrl -> [SettingsSearchableItem] in var results: [SettingsSearchableItem] = [] var nextIndex: Int32 = 2 - if case let .instantView(webPage, _) = resolvedUrl { + if let resolvedUrl = resolvedUrl, case let .instantView(webPage, _) = resolvedUrl { if case let .Loaded(content) = webPage.content, let instantPage = content.instantPage { var processingQuestions = false var currentSection: String? diff --git a/submodules/SettingsUI/Sources/DebugController.swift b/submodules/SettingsUI/Sources/DebugController.swift index 4b505dae7d..80e166ea34 100644 --- a/submodules/SettingsUI/Sources/DebugController.swift +++ b/submodules/SettingsUI/Sources/DebugController.swift @@ -468,6 +468,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { actionSheet?.dismissAnimated() let databasePath = context.account.basePath + "/postbox/db" let _ = try? FileManager.default.removeItem(atPath: databasePath) + exit(0) preconditionFailure() }), ]), ActionSheetItemGroup(items: [ diff --git a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift index db6abc4344..47bdbbba0a 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift @@ -159,7 +159,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode { let forwardInfo = MessageForwardInfo(author: item.linkEnabled ? peers[peerId] : nil, source: nil, sourceMessageId: nil, date: 0, authorSignature: item.linkEnabled ? nil : item.peerName, psaType: nil) - let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: forwardInfo, author: nil, text: item.strings.Privacy_Forwards_PreviewMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil) + let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: forwardInfo, author: nil, text: item.strings.Privacy_Forwards_PreviewMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil) var node: ListViewItemNode? if let current = currentNode { diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift index 4aac556f72..eed38f2aee 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift @@ -74,6 +74,7 @@ final class SettingsSearchItem: ItemListControllerSearch { let presentController: (ViewController, Any?) -> Void let pushController: (ViewController) -> Void let getNavigationController: (() -> NavigationController?)? + let resolvedFaqUrl: Signal let exceptionsList: Signal let archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError> let privacySettings: Signal @@ -85,7 +86,7 @@ final class SettingsSearchItem: ItemListControllerSearch { private var activity: ValuePromise = ValuePromise(ignoreRepeated: false) private let activityDisposable = MetaDisposable() - init(context: AccountContext, theme: PresentationTheme, placeholder: String, activated: Bool, updateActivated: @escaping (Bool) -> Void, presentController: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, getNavigationController: (() -> NavigationController?)?, exceptionsList: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal, hasWallet: Signal, activeSessionsContext: Signal, webSessionsContext: Signal) { + init(context: AccountContext, theme: PresentationTheme, placeholder: String, activated: Bool, updateActivated: @escaping (Bool) -> Void, presentController: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, getNavigationController: (() -> NavigationController?)?, resolvedFaqUrl: Signal, exceptionsList: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal, hasWallet: Signal, activeSessionsContext: Signal, webSessionsContext: Signal) { self.context = context self.theme = theme self.placeholder = placeholder @@ -94,6 +95,7 @@ final class SettingsSearchItem: ItemListControllerSearch { self.presentController = presentController self.pushController = pushController self.getNavigationController = getNavigationController + self.resolvedFaqUrl = resolvedFaqUrl self.exceptionsList = exceptionsList self.archivedStickerPacks = archivedStickerPacks self.privacySettings = privacySettings @@ -163,7 +165,7 @@ final class SettingsSearchItem: ItemListControllerSearch { pushController(c) }, presentController: { c, a in presentController(c, a) - }, getNavigationController: self.getNavigationController, exceptionsList: self.exceptionsList, archivedStickerPacks: self.archivedStickerPacks, privacySettings: self.privacySettings, hasWallet: self.hasWallet, activeSessionsContext: self.activeSessionsContext, webSessionsContext: self.webSessionsContext) + }, getNavigationController: self.getNavigationController, resolvedFaqUrl: self.resolvedFaqUrl, exceptionsList: self.exceptionsList, archivedStickerPacks: self.archivedStickerPacks, privacySettings: self.privacySettings, hasWallet: self.hasWallet, activeSessionsContext: self.activeSessionsContext, webSessionsContext: self.webSessionsContext) } } } @@ -360,7 +362,7 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo private var presentationDataDisposable: Disposable? private let presentationDataPromise: Promise - public init(context: AccountContext, openResult: @escaping (SettingsSearchableItem) -> Void, exceptionsList: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal, hasWallet: Signal, activeSessionsContext: Signal, webSessionsContext: Signal) { + public init(context: AccountContext, openResult: @escaping (SettingsSearchableItem) -> Void, resolvedFaqUrl: Signal, exceptionsList: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal, hasWallet: Signal, activeSessionsContext: Signal, webSessionsContext: Signal) { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationDataPromise = Promise(self.presentationData) @@ -390,9 +392,9 @@ public final class SettingsSearchContainerNode: SearchDisplayControllerContentNo searchableItems.set(settingsSearchableItems(context: context, notificationExceptionsList: exceptionsList, archivedStickerPacks: archivedStickerPacks, privacySettings: privacySettings, hasWallet: hasWallet, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext)) let faqItems = Promise<[SettingsSearchableItem]>() - faqItems.set(faqSearchableItems(context: context, suggestAccountDeletion: false)) + faqItems.set(faqSearchableItems(context: context, resolvedUrl: resolvedFaqUrl, suggestAccountDeletion: false)) - let queryAndFoundItems = combineLatest(searchableItems.get(), faqSearchableItems(context: context, suggestAccountDeletion: true)) + let queryAndFoundItems = combineLatest(searchableItems.get(), faqSearchableItems(context: context, resolvedUrl: resolvedFaqUrl, suggestAccountDeletion: true)) |> mapToSignal { searchableItems, faqSearchableItems -> Signal<(String, [SettingsSearchableItem])?, NoError> in return self.searchQuery.get() |> mapToSignal { query -> Signal<(String, [SettingsSearchableItem])?, NoError> in @@ -656,6 +658,7 @@ private final class SettingsSearchItemNode: ItemListControllerSearchNode { let pushController: (ViewController) -> Void let presentController: (ViewController, Any?) -> Void let getNavigationController: (() -> NavigationController?)? + let resolvedFaqUrl: Signal let exceptionsList: Signal let archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError> let privacySettings: Signal @@ -665,13 +668,14 @@ private final class SettingsSearchItemNode: ItemListControllerSearchNode { var cancel: () -> Void - init(context: AccountContext, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: (() -> NavigationController?)?, exceptionsList: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal, hasWallet: Signal, activeSessionsContext: Signal, webSessionsContext: Signal) { + init(context: AccountContext, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: (() -> NavigationController?)?, resolvedFaqUrl: Signal, exceptionsList: Signal, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, privacySettings: Signal, hasWallet: Signal, activeSessionsContext: Signal, webSessionsContext: Signal) { self.context = context self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.cancel = cancel self.pushController = pushController self.presentController = presentController self.getNavigationController = getNavigationController + self.resolvedFaqUrl = resolvedFaqUrl self.exceptionsList = exceptionsList self.archivedStickerPacks = archivedStickerPacks self.privacySettings = privacySettings @@ -717,7 +721,7 @@ private final class SettingsSearchItemNode: ItemListControllerSearchNode { } }) } - }, exceptionsList: self.exceptionsList, archivedStickerPacks: self.archivedStickerPacks, privacySettings: self.privacySettings, hasWallet: self.hasWallet, activeSessionsContext: self.activeSessionsContext, webSessionsContext: self.webSessionsContext), cancel: { [weak self] in + }, resolvedFaqUrl: self.resolvedFaqUrl, exceptionsList: self.exceptionsList, archivedStickerPacks: self.archivedStickerPacks, privacySettings: self.privacySettings, hasWallet: self.hasWallet, activeSessionsContext: self.activeSessionsContext, webSessionsContext: self.webSessionsContext), cancel: { [weak self] in self?.cancel() }) diff --git a/submodules/SettingsUI/Sources/SettingsController.swift b/submodules/SettingsUI/Sources/SettingsController.swift index 19fb415862..66ec64ff69 100644 --- a/submodules/SettingsUI/Sources/SettingsController.swift +++ b/submodules/SettingsUI/Sources/SettingsController.swift @@ -1661,7 +1661,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM presentControllerImpl?(c, a) }, pushController: { c in pushControllerImpl?(c) - }, getNavigationController: getNavigationControllerImpl, exceptionsList: notifyExceptions.get(), archivedStickerPacks: archivedPacks.get(), privacySettings: privacySettings.get(), hasWallet: hasWallet, activeSessionsContext: activeSessionsContextAndCountSignal |> map { $0.0 } |> distinctUntilChanged(isEqual: { $0 === $1 }), webSessionsContext: activeSessionsContextAndCountSignal |> map { $0.2 } |> distinctUntilChanged(isEqual: { $0 === $1 })) + }, getNavigationController: getNavigationControllerImpl, resolvedFaqUrl: .complete(), exceptionsList: notifyExceptions.get(), archivedStickerPacks: archivedPacks.get(), privacySettings: privacySettings.get(), hasWallet: hasWallet, activeSessionsContext: activeSessionsContextAndCountSignal |> map { $0.0 } |> distinctUntilChanged(isEqual: { $0 === $1 }), webSessionsContext: activeSessionsContextAndCountSignal |> map { $0.2 } |> distinctUntilChanged(isEqual: { $0 === $1 })) let (hasWallet, hasPassport, hasWatchApp, enableQRLogin, enableFilters) = hasWalletPassportAndWatch let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: settingsEntries(account: context.account, presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, notifyExceptions: preferencesAndExceptions.1, notificationsAuthorizationStatus: preferencesAndExceptions.2, notificationsWarningSuppressed: preferencesAndExceptions.3, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, privacySettings: preferencesAndExceptions.4, hasWallet: hasWallet, hasPassport: hasPassport, hasWatchApp: hasWatchApp, accountsAndPeers: accountsAndPeers.1, inAppNotificationSettings: inAppNotificationSettings, experimentalUISettings: experimentalUISettings, displayPhoneNumberConfirmation: preferencesAndExceptions.5, otherSessionCount: otherSessionCount, enableQRLogin: enableQRLogin, enableFilters: enableFilters), style: .blocks, searchItem: searchItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)) diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 862a27987f..e98633d1cc 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -235,22 +235,22 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView let timestamp = self.referenceTimestamp let timestamp1 = timestamp + 120 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60) let timestamp2 = timestamp + 3660 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp3 = timestamp + 3200 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp4 = timestamp + 3000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp5 = timestamp + 1000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer5), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer5), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let width: CGFloat if case .regular = layout.metrics.widthClass { @@ -316,22 +316,22 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) - let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message2], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) let waveformBase64 = "DAAOAAkACQAGAAwADwAMABAADQAPABsAGAALAA0AGAAfABoAHgATABgAGQAYABQADAAVABEAHwANAA0ACQAWABkACQAOAAwACQAfAAAAGQAVAAAAEwATAAAACAAfAAAAHAAAABwAHwAAABcAGQAAABQADgAAABQAHwAAAB8AHwAAAAwADwAAAB8AEwAAABoAFwAAAB8AFAAAAAAAHwAAAAAAHgAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAA=" let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))] let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes) - let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message3], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local), tapMessage: nil, clickThroughMessage: nil)) - let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message4], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) let width: CGFloat diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 2830ef49ba..8cd04d4ec1 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -786,17 +786,17 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let timestamp = self.referenceTimestamp let timestamp1 = timestamp + 120 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60) let timestamp2 = timestamp + 3660 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp3 = timestamp + 3200 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp4 = timestamp + 3000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height) if let chatNodes = self.chatNodes { @@ -852,19 +852,19 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate var sampleMessages: [Message] = [] - let message1 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message1 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message1) - let message2 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message2 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message2) - let message3 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message3 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message3) - let message4 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message4 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message4) - let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) messages[message5.id] = message5 sampleMessages.append(message5) @@ -872,13 +872,13 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))] let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes) - let message6 = Message(stableId: 6, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message6 = Message(stableId: 6, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message6) - let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id, threadMessageId: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message7) - let message8 = Message(stableId: 8, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message8 = Message(stableId: 8, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message8) items = sampleMessages.reversed().map { message in diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchColorsItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchColorsItem.swift index 2fcc5a6656..c0dcf33d56 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchColorsItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchColorsItem.swift @@ -9,35 +9,6 @@ import SyncCore import TelegramPresentationData import ListSectionHeaderNode -private func nodeColor(for color: WallpaperSearchColor) -> UIColor { - switch color { - case .blue: - return UIColor(rgb: 0x0076ff) - case .red: - return UIColor(rgb: 0xff0000) - case .orange: - return UIColor(rgb: 0xff8a00) - case .yellow: - return UIColor(rgb: 0xffca00) - case .green: - return UIColor(rgb: 0x00e432) - case .teal: - return UIColor(rgb: 0x1fa9ab) - case .purple: - return UIColor(rgb: 0x7300aa) - case .pink: - return UIColor(rgb: 0xf9bec5) - case .brown: - return UIColor(rgb: 0x734021) - case .black: - return UIColor(rgb: 0x000000) - case .gray: - return UIColor(rgb: 0x5c585f) - case .white: - return UIColor(rgb: 0xffffff) - } -} - private class ThemeGridColorNode: HighlightableButtonNode { let action: () -> Void @@ -54,7 +25,7 @@ private class ThemeGridColorNode: HighlightableButtonNode { } else if color == .black && dark { image = generateFilledCircleImage(diameter: 42.0, color: .black, strokeColor: strokeColor, strokeWidth: 1.0) } else { - image = generateFilledCircleImage(diameter: 42.0, color: nodeColor(for: color)) + image = generateFilledCircleImage(diameter: 42.0, color: color.displayColor) } self.setImage(image, for: .normal) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift index 5574a938e9..b5801db755 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift @@ -12,6 +12,7 @@ import AccountContext import SearchUI import ChatListSearchItemHeader import WebSearchUI +import SearchBarNode enum WallpaperSearchColor: CaseIterable { case blue @@ -56,6 +57,35 @@ enum WallpaperSearchColor: CaseIterable { } } + var displayColor: UIColor { + switch self { + case .blue: + return UIColor(rgb: 0x0076ff) + case .red: + return UIColor(rgb: 0xff0000) + case .orange: + return UIColor(rgb: 0xff8a00) + case .yellow: + return UIColor(rgb: 0xffca00) + case .green: + return UIColor(rgb: 0x00e432) + case .teal: + return UIColor(rgb: 0x1fa9ab) + case .purple: + return UIColor(rgb: 0x7300aa) + case .pink: + return UIColor(rgb: 0xf9bec5) + case .brown: + return UIColor(rgb: 0x734021) + case .black: + return UIColor(rgb: 0x000000) + case .gray: + return UIColor(rgb: 0x5c585f) + case .white: + return UIColor(rgb: 0xffffff) + } + } + func localizedString(strings: PresentationStrings) -> String { switch self { case .blue: @@ -658,23 +688,30 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode { self.queryPromise.set(.single(query)) if updateInterface { - let prefix: NSAttributedString? + let tokens: [SearchBarToken] let text: String let placeholder: String switch query { case let .generic(query): - prefix = nil + tokens = [] text = query placeholder = self.presentationData.strings.Wallpaper_Search case let .color(color, query): - let prefixString = NSMutableAttributedString() - prefixString.append(NSAttributedString(string: self.presentationData.strings.WallpaperSearch_ColorPrefix, font: Font.regular(17.0), textColor: self.presentationData.theme.rootController.navigationSearchBar.inputTextColor)) - prefixString.append(NSAttributedString(string: "\(color.localizedString(strings: self.presentationData.strings)) ", font: Font.regular(17.0), textColor: self.presentationData.theme.rootController.navigationSearchBar.accentColor)) - prefix = prefixString + let backgroundColor = color.displayColor + let foregroundColor: UIColor + let strokeColor: UIColor + if color == .white { + foregroundColor = .black + strokeColor = self.presentationData.theme.rootController.navigationSearchBar.inputClearButtonColor + } else { + foregroundColor = .white + strokeColor = color.displayColor + } + tokens = [SearchBarToken(id: 0, icon: UIImage(bundleImageName: "Settings/WallpaperSearchColorIcon"), title: color.localizedString(strings: self.presentationData.strings), style: SearchBarToken.Style(backgroundColor: backgroundColor, foregroundColor: foregroundColor, strokeColor: strokeColor))] text = query placeholder = self.presentationData.strings.Wallpaper_SearchShort } - self.setQuery?(prefix, text) + self.setQuery?(nil, tokens, text) self.setPlaceholder?(placeholder) } } @@ -688,6 +725,10 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode { self.updateQuery({ $0.updatedWithColor(nil) }, updateInterface: true) } + override func searchTextClearTokens() { + self.updateQuery({ $0.updatedWithColor(nil) }, updateInterface: true) + } + private func enqueueRecentTransition(_ transition: ThemeGridSearchContainerRecentTransition, firstTime: Bool) { self.enqueuedRecentTransitions.append((transition, firstTime)) diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index e6a3736b1f..13125e369d 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -373,24 +373,24 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let timestamp = self.referenceTimestamp let timestamp1 = timestamp + 120 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60) let timestamp2 = timestamp + 3660 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer2), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp3 = timestamp + 3200 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp4 = timestamp + 3000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let timestamp5 = timestamp + 1000 - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer5), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer5), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) - items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer7.id, namespace: 0, id: 0), timestamp: timestamp - 420)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer7.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 420, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer7), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) + items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer7.id, namespace: 0, id: 0), timestamp: timestamp - 420)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer7.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp - 420, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [])], peer: RenderedPeer(peer: peer7), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)) let width: CGFloat if case .regular = layout.metrics.widthClass { @@ -457,19 +457,19 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { var sampleMessages: [Message] = [] - let message1 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message1 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message1) - let message2 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message2 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message2) - let message3 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message3 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message3) - let message4 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message4 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message4) - let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) messages[message5.id] = message5 sampleMessages.append(message5) @@ -477,13 +477,13 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: MemoryBuffer(data: Data(base64Encoded: waveformBase64)!))] let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes) - let message6 = Message(stableId: 6, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message6 = Message(stableId: 6, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message6) - let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id, threadMessageId: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message7) - let message8 = Message(stableId: 8, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message8 = Message(stableId: 8, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) sampleMessages.append(message8) items = sampleMessages.reversed().map { message in diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift index df4f629682..2a24f6b579 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift @@ -161,10 +161,10 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) if let (author, text) = messageItem.reply { peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: author, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) } - let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: messageItem.outgoing ? otherPeerId : peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: messageItem.outgoing ? TelegramUser(id: otherPeerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) : nil, text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId)] : [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: messageItem.outgoing ? otherPeerId : peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: messageItem.outgoing ? TelegramUser(id: otherPeerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) : nil, text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)] : [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index d489119662..c6abb1ff4e 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -839,10 +839,10 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let theme = self.presentationData.theme.withUpdated(preview: true) - let message1 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: bottomMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message1 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: bottomMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: theme, strings: self.presentationData.strings, wallpaper: currentWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) - let message2 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: topMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) + let message2 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: topMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message2], theme: theme, strings: self.presentationData.strings, wallpaper: currentWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil)) let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height) diff --git a/submodules/SyncCore/Sources/CachedChannelData.swift b/submodules/SyncCore/Sources/CachedChannelData.swift index 79019c8c40..4f7a4ac10e 100644 --- a/submodules/SyncCore/Sources/CachedChannelData.swift +++ b/submodules/SyncCore/Sources/CachedChannelData.swift @@ -150,6 +150,11 @@ public struct PeerGeoLocation: PostboxCoding, Equatable { } public final class CachedChannelData: CachedPeerData { + public enum LinkedDiscussionPeerId: Equatable { + case unknown + case known(PeerId?) + } + public let isNotAccessible: Bool public let flags: CachedChannelFlags public let about: String? @@ -161,7 +166,7 @@ public final class CachedChannelData: CachedPeerData { public let stickerPack: StickerPackCollectionInfo? public let minAvailableMessageId: MessageId? public let migrationReference: ChannelMigrationReference? - public let linkedDiscussionPeerId: PeerId? + public let linkedDiscussionPeerId: LinkedDiscussionPeerId public let peerGeoLocation: PeerGeoLocation? public let slowModeTimeout: Int32? public let slowModeValidUntilTimestamp: Int32? @@ -190,7 +195,7 @@ public final class CachedChannelData: CachedPeerData { self.stickerPack = nil self.minAvailableMessageId = nil self.migrationReference = nil - self.linkedDiscussionPeerId = nil + self.linkedDiscussionPeerId = .unknown self.peerGeoLocation = nil self.slowModeTimeout = nil self.slowModeValidUntilTimestamp = nil @@ -200,7 +205,7 @@ public final class CachedChannelData: CachedPeerData { self.photo = nil } - public init(isNotAccessible: Bool, flags: CachedChannelFlags, about: String?, participantsSummary: CachedChannelParticipantsSummary, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, stickerPack: StickerPackCollectionInfo?, minAvailableMessageId: MessageId?, migrationReference: ChannelMigrationReference?, linkedDiscussionPeerId: PeerId?, peerGeoLocation: PeerGeoLocation?, slowModeTimeout: Int32?, slowModeValidUntilTimestamp: Int32?, hasScheduledMessages: Bool, statsDatacenterId: Int32, invitedBy: PeerId?, photo: TelegramMediaImage?) { + public init(isNotAccessible: Bool, flags: CachedChannelFlags, about: String?, participantsSummary: CachedChannelParticipantsSummary, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, stickerPack: StickerPackCollectionInfo?, minAvailableMessageId: MessageId?, migrationReference: ChannelMigrationReference?, linkedDiscussionPeerId: LinkedDiscussionPeerId, peerGeoLocation: PeerGeoLocation?, slowModeTimeout: Int32?, slowModeValidUntilTimestamp: Int32?, hasScheduledMessages: Bool, statsDatacenterId: Int32, invitedBy: PeerId?, photo: TelegramMediaImage?) { self.isNotAccessible = isNotAccessible self.flags = flags self.about = about @@ -226,8 +231,10 @@ public final class CachedChannelData: CachedPeerData { peerIds.insert(botInfo.peerId) } - if let linkedDiscussionPeerId = linkedDiscussionPeerId { - peerIds.insert(linkedDiscussionPeerId) + if case let .known(linkedDiscussionPeerIdValue) = linkedDiscussionPeerId { + if let linkedDiscussionPeerIdValue = linkedDiscussionPeerIdValue { + peerIds.insert(linkedDiscussionPeerIdValue) + } } if let invitedBy = invitedBy { @@ -288,7 +295,7 @@ public final class CachedChannelData: CachedPeerData { return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) } - public func withUpdatedLinkedDiscussionPeerId(_ linkedDiscussionPeerId: PeerId?) -> CachedChannelData { + public func withUpdatedLinkedDiscussionPeerId(_ linkedDiscussionPeerId: LinkedDiscussionPeerId) -> CachedChannelData { return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) } @@ -361,9 +368,13 @@ public final class CachedChannelData: CachedPeerData { } if let linkedDiscussionPeerId = decoder.decodeOptionalInt64ForKey("dgi") { - self.linkedDiscussionPeerId = PeerId(linkedDiscussionPeerId) + if linkedDiscussionPeerId == 0 { + self.linkedDiscussionPeerId = .known(nil) + } else { + self.linkedDiscussionPeerId = .known(PeerId(linkedDiscussionPeerId)) + } } else { - self.linkedDiscussionPeerId = nil + self.linkedDiscussionPeerId = .unknown } if let peerGeoLocation = decoder.decodeObjectForKey("pgl", decoder: { PeerGeoLocation(decoder: $0) }) as? PeerGeoLocation { @@ -385,8 +396,10 @@ public final class CachedChannelData: CachedPeerData { self.photo = nil } - if let linkedDiscussionPeerId = self.linkedDiscussionPeerId { - peerIds.insert(linkedDiscussionPeerId) + if case let .known(linkedDiscussionPeerIdValue) = self.linkedDiscussionPeerId { + if let linkedDiscussionPeerIdValue = linkedDiscussionPeerIdValue { + peerIds.insert(linkedDiscussionPeerIdValue) + } } self.peerIds = peerIds @@ -446,10 +459,15 @@ public final class CachedChannelData: CachedPeerData { } else { encoder.encodeNil(forKey: "mr") } - if let linkedDiscussionPeerId = self.linkedDiscussionPeerId { - encoder.encodeInt64(linkedDiscussionPeerId.toInt64(), forKey: "dgi") - } else { + switch self.linkedDiscussionPeerId { + case .unknown: encoder.encodeNil(forKey: "dgi") + case let .known(value): + if let value = value { + encoder.encodeInt64(value.toInt64(), forKey: "dgi") + } else { + encoder.encodeInt64(0, forKey: "dgi") + } } if let peerGeoLocation = self.peerGeoLocation { encoder.encodeObject(peerGeoLocation, forKey: "pgl") diff --git a/submodules/SyncCore/Sources/ReplyMessageAttribute.swift b/submodules/SyncCore/Sources/ReplyMessageAttribute.swift index cbcc36b5fa..308c0ad9c1 100644 --- a/submodules/SyncCore/Sources/ReplyMessageAttribute.swift +++ b/submodules/SyncCore/Sources/ReplyMessageAttribute.swift @@ -3,23 +3,45 @@ import Postbox public class ReplyMessageAttribute: MessageAttribute { public let messageId: MessageId + public let threadMessageId: MessageId? public var associatedMessageIds: [MessageId] { return [self.messageId] } - public init(messageId: MessageId) { + public init(messageId: MessageId, threadMessageId: MessageId?) { self.messageId = messageId + self.threadMessageId = threadMessageId } required public init(decoder: PostboxDecoder) { let namespaceAndId: Int64 = decoder.decodeInt64ForKey("i", orElse: 0) self.messageId = MessageId(peerId: PeerId(decoder.decodeInt64ForKey("p", orElse: 0)), namespace: Int32(namespaceAndId & 0xffffffff), id: Int32((namespaceAndId >> 32) & 0xffffffff)) + + if let threadNamespaceAndId = decoder.decodeOptionalInt64ForKey("ti"), let threadPeerId = decoder.decodeOptionalInt64ForKey("tp") { + self.threadMessageId = MessageId(peerId: PeerId(threadPeerId), namespace: Int32(threadNamespaceAndId & 0xffffffff), id: Int32((threadNamespaceAndId >> 32) & 0xffffffff)) + } else { + self.threadMessageId = nil + } } public func encode(_ encoder: PostboxEncoder) { let namespaceAndId = Int64(self.messageId.namespace) | (Int64(self.messageId.id) << 32) encoder.encodeInt64(namespaceAndId, forKey: "i") encoder.encodeInt64(self.messageId.peerId.toInt64(), forKey: "p") + if let threadMessageId = self.threadMessageId { + let threadNamespaceAndId = Int64(threadMessageId.namespace) | (Int64(threadMessageId.id) << 32) + encoder.encodeInt64(threadNamespaceAndId, forKey: "ti") + encoder.encodeInt64(threadMessageId.peerId.toInt64(), forKey: "tp") + } + } +} + +public extension Message { + var effectiveReplyThreadMessageId: MessageId? { + if let threadId = self.threadId { + return makeThreadIdMessageId(peerId: self.id.peerId, threadId: threadId) + } + return nil } } diff --git a/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift b/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift new file mode 100644 index 0000000000..7ef8c3f65c --- /dev/null +++ b/submodules/SyncCore/Sources/ReplyThreadMessageAttribute.swift @@ -0,0 +1,34 @@ +import Foundation +import Postbox + +public class ReplyThreadMessageAttribute: MessageAttribute { + public let count: Int32 + public let latestUsers: [PeerId] + public let commentsPeerId: PeerId? + + public var associatedPeerIds: [PeerId] { + return self.latestUsers + } + + public init(count: Int32, latestUsers: [PeerId], commentsPeerId: PeerId?) { + self.count = count + self.latestUsers = latestUsers + self.commentsPeerId = commentsPeerId + } + + required public init(decoder: PostboxDecoder) { + self.count = decoder.decodeInt32ForKey("c", orElse: 0) + self.latestUsers = decoder.decodeInt64ArrayForKey("u").map(PeerId.init) + self.commentsPeerId = decoder.decodeOptionalInt64ForKey("cp").flatMap(PeerId.init) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.count, forKey: "c") + encoder.encodeInt64Array(self.latestUsers.map { $0.toInt64() }, forKey: "u") + if let commentsPeerId = self.commentsPeerId { + encoder.encodeInt64(commentsPeerId.toInt64(), forKey: "cp") + } else { + encoder.encodeNil(forKey: "cp") + } + } +} diff --git a/submodules/SyncCore/Sources/TelegramChatAdminRights.swift b/submodules/SyncCore/Sources/TelegramChatAdminRights.swift index 86abc3ec9f..1ff66bf62a 100644 --- a/submodules/SyncCore/Sources/TelegramChatAdminRights.swift +++ b/submodules/SyncCore/Sources/TelegramChatAdminRights.swift @@ -19,6 +19,7 @@ public struct TelegramChatAdminRightsFlags: OptionSet { public static let canInviteUsers = TelegramChatAdminRightsFlags(rawValue: 1 << 5) public static let canPinMessages = TelegramChatAdminRightsFlags(rawValue: 1 << 7) public static let canAddAdmins = TelegramChatAdminRightsFlags(rawValue: 1 << 9) + public static let canBeAnonymous = TelegramChatAdminRightsFlags(rawValue: 1 << 10) public static var groupSpecific: TelegramChatAdminRightsFlags = [ .canChangeInfo, @@ -26,6 +27,7 @@ public struct TelegramChatAdminRightsFlags: OptionSet { .canBanUsers, .canInviteUsers, .canPinMessages, + .canBeAnonymous, .canAddAdmins ] diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 1ea6c3776d..e8f963c62d 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -255,6 +255,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[889491791] = { return Api.Update.parse_updateDialogFilters($0) } dict[643940105] = { return Api.Update.parse_updatePhoneCallSignalingData($0) } dict[1708307556] = { return Api.Update.parse_updateChannelParticipant($0) } + dict[1854571743] = { return Api.Update.parse_updateChannelMessageForwards($0) } + dict[295679367] = { return Api.Update.parse_updateReadDiscussion($0) } dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) } dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) } dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) } @@ -262,7 +264,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1557620115] = { return Api.ChannelParticipant.parse_channelParticipantSelf($0) } dict[470789295] = { return Api.ChannelParticipant.parse_channelParticipantBanned($0) } dict[-859915345] = { return Api.ChannelParticipant.parse_channelParticipantAdmin($0) } - dict[-2138237532] = { return Api.ChannelParticipant.parse_channelParticipantCreator($0) } + dict[1149094475] = { return Api.ChannelParticipant.parse_channelParticipantCreator($0) } dict[-1567730343] = { return Api.MessageUserVote.parse_messageUserVote($0) } dict[909603888] = { return Api.MessageUserVote.parse_messageUserVoteInputOption($0) } dict[244310238] = { return Api.MessageUserVote.parse_messageUserVoteMultiple($0) } @@ -387,6 +389,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1694474197] = { return Api.messages.Chats.parse_chats($0) } dict[-1663561404] = { return Api.messages.Chats.parse_chatsSlice($0) } dict[482797855] = { return Api.InputSingleMedia.parse_inputSingleMedia($0) } + dict[1163625789] = { return Api.MessageViews.parse_messageViews($0) } dict[218751099] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowContacts($0) } dict[407582158] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowAll($0) } dict[320652927] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowUsers($0) } @@ -432,6 +435,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-886477832] = { return Api.LabeledPrice.parse_labeledPrice($0) } dict[-438840932] = { return Api.messages.ChatFull.parse_chatFull($0) } dict[-618540889] = { return Api.InputSecureValue.parse_inputSecureValue($0) } + dict[-765481584] = { return Api.messages.DiscussionMessage.parse_discussionMessage($0) } dict[1722786150] = { return Api.help.DeepLinkInfo.parse_deepLinkInfoEmpty($0) } dict[1783556146] = { return Api.help.DeepLinkInfo.parse_deepLinkInfo($0) } dict[-313079300] = { return Api.account.WebAuthorizations.parse_webAuthorizations($0) } @@ -485,6 +489,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1556570557] = { return Api.EmojiKeywordsDifference.parse_emojiKeywordsDifference($0) } dict[1493171408] = { return Api.HighScore.parse_highScore($0) } dict[-305282981] = { return Api.TopPeer.parse_topPeer($0) } + dict[-1495959709] = { return Api.MessageReplyHeader.parse_messageReplyHeader($0) } dict[411017418] = { return Api.SecureValue.parse_secureValue($0) } dict[-316748368] = { return Api.SecureValueHash.parse_secureValueHash($0) } dict[1444661369] = { return Api.ContactBlocked.parse_contactBlocked($0) } @@ -518,6 +523,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-402498398] = { return Api.messages.SavedGifs.parse_savedGifsNotModified($0) } dict[772213157] = { return Api.messages.SavedGifs.parse_savedGifs($0) } dict[-914167110] = { return Api.CdnPublicKey.parse_cdnPublicKey($0) } + dict[1093204652] = { return Api.MessageReplies.parse_messageReplies($0) } dict[53231223] = { return Api.InputGame.parse_inputGameID($0) } dict[-1020139510] = { return Api.InputGame.parse_inputGameShortName($0) } dict[1107543535] = { return Api.help.CountryCode.parse_countryCode($0) } @@ -539,16 +545,17 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[480546647] = { return Api.InputChatPhoto.parse_inputChatPhotoEmpty($0) } dict[-1991004873] = { return Api.InputChatPhoto.parse_inputChatPhoto($0) } dict[-968723890] = { return Api.InputChatPhoto.parse_inputChatUploadedPhoto($0) } + dict[742337250] = { return Api.messages.MessageViews.parse_messageViews($0) } dict[-368917890] = { return Api.PaymentCharge.parse_paymentCharge($0) } dict[-1387279939] = { return Api.MessageInteractionCounters.parse_messageInteractionCounters($0) } dict[-1107852396] = { return Api.stats.BroadcastStats.parse_broadcastStats($0) } dict[-484987010] = { return Api.Updates.parse_updatesTooLong($0) } - dict[-1857044719] = { return Api.Updates.parse_updateShortMessage($0) } - dict[377562760] = { return Api.Updates.parse_updateShortChatMessage($0) } dict[2027216577] = { return Api.Updates.parse_updateShort($0) } dict[1918567619] = { return Api.Updates.parse_updatesCombined($0) } dict[1957577280] = { return Api.Updates.parse_updates($0) } dict[301019932] = { return Api.Updates.parse_updateShortSentMessage($0) } + dict[580309704] = { return Api.Updates.parse_updateShortMessage($0) } + dict[1076714939] = { return Api.Updates.parse_updateShortChatMessage($0) } dict[-276825834] = { return Api.stats.MegagroupStats.parse_megagroupStats($0) } dict[-884757282] = { return Api.StatsAbsValueAndPrev.parse_statsAbsValueAndPrev($0) } dict[1038967584] = { return Api.MessageMedia.parse_messageMediaEmpty($0) } @@ -599,8 +606,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[537022650] = { return Api.User.parse_userEmpty($0) } dict[-1820043071] = { return Api.User.parse_user($0) } dict[-2082087340] = { return Api.Message.parse_messageEmpty($0) } - dict[-1642487306] = { return Api.Message.parse_messageService($0) } - dict[1160515173] = { return Api.Message.parse_message($0) } + dict[1487813065] = { return Api.Message.parse_message($0) } + dict[678405636] = { return Api.Message.parse_messageService($0) } dict[831924812] = { return Api.StatsGroupTopInviter.parse_statsGroupTopInviter($0) } dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) } dict[586395571] = { return Api.messages.RecentStickers.parse_recentStickers($0) } @@ -657,7 +664,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1036396922] = { return Api.InputWebFileLocation.parse_inputWebFileLocation($0) } dict[1430205163] = { return Api.InputWebFileLocation.parse_inputWebFileGeoMessageLocation($0) } dict[-1625153079] = { return Api.InputWebFileLocation.parse_inputWebFileGeoPointLocation($0) } - dict[893020267] = { return Api.MessageFwdHeader.parse_messageFwdHeader($0) } + dict[1601666510] = { return Api.MessageFwdHeader.parse_messageFwdHeader($0) } dict[-1012849566] = { return Api.BaseTheme.parse_baseThemeClassic($0) } dict[-69724536] = { return Api.BaseTheme.parse_baseThemeDay($0) } dict[-1212997976] = { return Api.BaseTheme.parse_baseThemeNight($0) } @@ -685,6 +692,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[364538944] = { return Api.messages.Dialogs.parse_dialogs($0) } dict[1910543603] = { return Api.messages.Dialogs.parse_dialogsSlice($0) } dict[-253500010] = { return Api.messages.Dialogs.parse_dialogsNotModified($0) } + dict[-1986399595] = { return Api.stats.MessageStats.parse_messageStats($0) } dict[-709641735] = { return Api.EmojiKeyword.parse_emojiKeyword($0) } dict[594408994] = { return Api.EmojiKeyword.parse_emojiKeywordDeleted($0) } dict[-290921362] = { return Api.upload.CdnFile.parse_cdnFileReuploadNeeded($0) } @@ -1110,6 +1118,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.InputSingleMedia: _1.serialize(buffer, boxed) + case let _1 as Api.MessageViews: + _1.serialize(buffer, boxed) case let _1 as Api.InputPrivacyRule: _1.serialize(buffer, boxed) case let _1 as Api.messages.DhConfig: @@ -1138,6 +1148,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.InputSecureValue: _1.serialize(buffer, boxed) + case let _1 as Api.messages.DiscussionMessage: + _1.serialize(buffer, boxed) case let _1 as Api.help.DeepLinkInfo: _1.serialize(buffer, boxed) case let _1 as Api.account.WebAuthorizations: @@ -1182,6 +1194,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.TopPeer: _1.serialize(buffer, boxed) + case let _1 as Api.MessageReplyHeader: + _1.serialize(buffer, boxed) case let _1 as Api.SecureValue: _1.serialize(buffer, boxed) case let _1 as Api.SecureValueHash: @@ -1230,6 +1244,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.CdnPublicKey: _1.serialize(buffer, boxed) + case let _1 as Api.MessageReplies: + _1.serialize(buffer, boxed) case let _1 as Api.InputGame: _1.serialize(buffer, boxed) case let _1 as Api.help.CountryCode: @@ -1254,6 +1270,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.InputChatPhoto: _1.serialize(buffer, boxed) + case let _1 as Api.messages.MessageViews: + _1.serialize(buffer, boxed) case let _1 as Api.PaymentCharge: _1.serialize(buffer, boxed) case let _1 as Api.MessageInteractionCounters: @@ -1374,6 +1392,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.Dialogs: _1.serialize(buffer, boxed) + case let _1 as Api.stats.MessageStats: + _1.serialize(buffer, boxed) case let _1 as Api.EmojiKeyword: _1.serialize(buffer, boxed) case let _1 as Api.upload.CdnFile: diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index cb22e0f124..1a0b5f1fd4 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -823,6 +823,66 @@ public struct messages { } } + } + public enum DiscussionMessage: TypeConstructorDescription { + case discussionMessage(message: Api.Message, readMaxId: Int32, chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .discussionMessage(let message, let readMaxId, let chats, let users): + if boxed { + buffer.appendInt32(-765481584) + } + message.serialize(buffer, true) + serializeInt32(readMaxId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .discussionMessage(let message, let readMaxId, let chats, let users): + return ("discussionMessage", [("message", message), ("readMaxId", readMaxId), ("chats", chats), ("users", users)]) + } + } + + public static func parse_discussionMessage(_ reader: BufferReader) -> DiscussionMessage? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.DiscussionMessage.discussionMessage(message: _1!, readMaxId: _2!, chats: _3!, users: _4!) + } + else { + return nil + } + } + } public enum SearchCounter: TypeConstructorDescription { case searchCounter(flags: Int32, filter: Api.MessagesFilter, count: Int32) @@ -1209,6 +1269,56 @@ public struct messages { } } + } + public enum MessageViews: TypeConstructorDescription { + case messageViews(views: [Api.MessageViews], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageViews(let views, let users): + if boxed { + buffer.appendInt32(742337250) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(views.count)) + for item in views { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .messageViews(let views, let users): + return ("messageViews", [("views", views), ("users", users)]) + } + } + + public static func parse_messageViews(_ reader: BufferReader) -> MessageViews? { + var _1: [Api.MessageViews]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageViews.self) + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.MessageViews.messageViews(views: _1!, users: _2!) + } + else { + return nil + } + } + } public enum PeerDialogs: TypeConstructorDescription { case peerDialogs(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], state: Api.updates.State) @@ -6038,6 +6148,8 @@ public extension Api { case updateDialogFilters case updatePhoneCallSignalingData(phoneCallId: Int64, data: Buffer) case updateChannelParticipant(flags: Int32, channelId: Int32, date: Int32, userId: Int32, prevParticipant: Api.ChannelParticipant?, newParticipant: Api.ChannelParticipant?, qts: Int32) + case updateChannelMessageForwards(channelId: Int32, id: Int32, forwards: Int32) + case updateReadDiscussion(peer: Api.Peer, msgId: Int32, readMaxId: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -6730,6 +6842,22 @@ public extension Api { if Int(flags) & Int(1 << 1) != 0 {newParticipant!.serialize(buffer, true)} serializeInt32(qts, buffer: buffer, boxed: false) break + case .updateChannelMessageForwards(let channelId, let id, let forwards): + if boxed { + buffer.appendInt32(1854571743) + } + serializeInt32(channelId, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt32(forwards, buffer: buffer, boxed: false) + break + case .updateReadDiscussion(let peer, let msgId, let readMaxId): + if boxed { + buffer.appendInt32(295679367) + } + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(readMaxId, buffer: buffer, boxed: false) + break } } @@ -6899,6 +7027,10 @@ public extension Api { return ("updatePhoneCallSignalingData", [("phoneCallId", phoneCallId), ("data", data)]) case .updateChannelParticipant(let flags, let channelId, let date, let userId, let prevParticipant, let newParticipant, let qts): return ("updateChannelParticipant", [("flags", flags), ("channelId", channelId), ("date", date), ("userId", userId), ("prevParticipant", prevParticipant), ("newParticipant", newParticipant), ("qts", qts)]) + case .updateChannelMessageForwards(let channelId, let id, let forwards): + return ("updateChannelMessageForwards", [("channelId", channelId), ("id", id), ("forwards", forwards)]) + case .updateReadDiscussion(let peer, let msgId, let readMaxId): + return ("updateReadDiscussion", [("peer", peer), ("msgId", msgId), ("readMaxId", readMaxId)]) } } @@ -8277,6 +8409,42 @@ public extension Api { return nil } } + public static func parse_updateChannelMessageForwards(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateChannelMessageForwards(channelId: _1!, id: _2!, forwards: _3!) + } + else { + return nil + } + } + public static func parse_updateReadDiscussion(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateReadDiscussion(peer: _1!, msgId: _2!, readMaxId: _3!) + } + else { + return nil + } + } } public enum PopularContact: TypeConstructorDescription { @@ -8362,7 +8530,7 @@ public extension Api { case channelParticipantSelf(userId: Int32, inviterId: Int32, date: Int32) case channelParticipantBanned(flags: Int32, userId: Int32, kickedBy: Int32, date: Int32, bannedRights: Api.ChatBannedRights) case channelParticipantAdmin(flags: Int32, userId: Int32, inviterId: Int32?, promotedBy: Int32, date: Int32, adminRights: Api.ChatAdminRights, rank: String?) - case channelParticipantCreator(flags: Int32, userId: Int32, rank: String?) + case channelParticipantCreator(flags: Int32, userId: Int32, adminRights: Api.ChatAdminRights, rank: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -8403,12 +8571,13 @@ public extension Api { adminRights.serialize(buffer, true) if Int(flags) & Int(1 << 2) != 0 {serializeString(rank!, buffer: buffer, boxed: false)} break - case .channelParticipantCreator(let flags, let userId, let rank): + case .channelParticipantCreator(let flags, let userId, let adminRights, let rank): if boxed { - buffer.appendInt32(-2138237532) + buffer.appendInt32(1149094475) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(userId, buffer: buffer, boxed: false) + adminRights.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {serializeString(rank!, buffer: buffer, boxed: false)} break } @@ -8424,8 +8593,8 @@ public extension Api { return ("channelParticipantBanned", [("flags", flags), ("userId", userId), ("kickedBy", kickedBy), ("date", date), ("bannedRights", bannedRights)]) case .channelParticipantAdmin(let flags, let userId, let inviterId, let promotedBy, let date, let adminRights, let rank): return ("channelParticipantAdmin", [("flags", flags), ("userId", userId), ("inviterId", inviterId), ("promotedBy", promotedBy), ("date", date), ("adminRights", adminRights), ("rank", rank)]) - case .channelParticipantCreator(let flags, let userId, let rank): - return ("channelParticipantCreator", [("flags", flags), ("userId", userId), ("rank", rank)]) + case .channelParticipantCreator(let flags, let userId, let adminRights, let rank): + return ("channelParticipantCreator", [("flags", flags), ("userId", userId), ("adminRights", adminRights), ("rank", rank)]) } } @@ -8521,13 +8690,18 @@ public extension Api { _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + var _3: Api.ChatAdminRights? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights + } + var _4: String? + if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.ChannelParticipant.channelParticipantCreator(flags: _1!, userId: _2!, rank: _3) + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.ChannelParticipant.channelParticipantCreator(flags: _1!, userId: _2!, adminRights: _3!, rank: _4) } else { return nil @@ -11722,6 +11896,54 @@ public extension Api { } } + } + public enum MessageViews: TypeConstructorDescription { + case messageViews(flags: Int32, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageViews(let flags, let views, let forwards, let replies): + if boxed { + buffer.appendInt32(1163625789) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(views!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(forwards!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {replies!.serialize(buffer, true)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .messageViews(let flags, let views, let forwards, let replies): + return ("messageViews", [("flags", flags), ("views", views), ("forwards", forwards), ("replies", replies)]) + } + } + + public static func parse_messageViews(_ reader: BufferReader) -> MessageViews? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } + var _3: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() } + var _4: Api.MessageReplies? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.MessageReplies + } } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageViews.messageViews(flags: _1!, views: _2, forwards: _3, replies: _4) + } + else { + return nil + } + } + } public enum InputPrivacyRule: TypeConstructorDescription { case inputPrivacyValueAllowContacts @@ -13876,6 +14098,54 @@ public extension Api { } } + } + public enum MessageReplyHeader: TypeConstructorDescription { + case messageReplyHeader(flags: Int32, replyToMsgId: Int32, replyToPeerId: Api.Peer?, replyToTopId: Int32?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyToTopId): + if boxed { + buffer.appendInt32(-1495959709) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(replyToMsgId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {replyToPeerId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(replyToTopId!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyToTopId): + return ("messageReplyHeader", [("flags", flags), ("replyToMsgId", replyToMsgId), ("replyToPeerId", replyToPeerId), ("replyToTopId", replyToTopId)]) + } + } + + public static func parse_messageReplyHeader(_ reader: BufferReader) -> MessageReplyHeader? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.Peer? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _4: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageReplyHeader.messageReplyHeader(flags: _1!, replyToMsgId: _2!, replyToPeerId: _3, replyToTopId: _4) + } + else { + return nil + } + } + } public enum SecureValue: TypeConstructorDescription { case secureValue(flags: Int32, type: Api.SecureValueType, data: Api.SecureData?, frontSide: Api.SecureFile?, reverseSide: Api.SecureFile?, selfie: Api.SecureFile?, translation: [Api.SecureFile]?, files: [Api.SecureFile]?, plainData: Api.SecurePlainData?, hash: Buffer) @@ -14924,6 +15194,70 @@ public extension Api { } } + } + public enum MessageReplies: TypeConstructorDescription { + case messageReplies(flags: Int32, replies: Int32, repliesPts: Int32, recentRepliers: [Api.Peer]?, channelId: Int32?, maxId: Int32?, readMaxId: Int32?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageReplies(let flags, let replies, let repliesPts, let recentRepliers, let channelId, let maxId, let readMaxId): + if boxed { + buffer.appendInt32(1093204652) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(replies, buffer: buffer, boxed: false) + serializeInt32(repliesPts, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(recentRepliers!.count)) + for item in recentRepliers! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(channelId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(maxId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(readMaxId!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .messageReplies(let flags, let replies, let repliesPts, let recentRepliers, let channelId, let maxId, let readMaxId): + return ("messageReplies", [("flags", flags), ("replies", replies), ("repliesPts", repliesPts), ("recentRepliers", recentRepliers), ("channelId", channelId), ("maxId", maxId), ("readMaxId", readMaxId)]) + } + } + + public static func parse_messageReplies(_ reader: BufferReader) -> MessageReplies? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: [Api.Peer]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } } + var _5: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } + var _6: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_6 = reader.readInt32() } + var _7: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_7 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.MessageReplies.messageReplies(flags: _1!, replies: _2!, repliesPts: _3!, recentRepliers: _4, channelId: _5, maxId: _6, readMaxId: _7) + } + else { + return nil + } + } + } public enum InputGame: TypeConstructorDescription { case inputGameID(id: Int64, accessHash: Int64) @@ -15515,12 +15849,12 @@ public extension Api { } public enum Updates: TypeConstructorDescription { case updatesTooLong - case updateShortMessage(flags: Int32, id: Int32, userId: Int32, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, entities: [Api.MessageEntity]?) - case updateShortChatMessage(flags: Int32, id: Int32, fromId: Int32, chatId: Int32, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, entities: [Api.MessageEntity]?) case updateShort(update: Api.Update, date: Int32) case updatesCombined(updates: [Api.Update], users: [Api.User], chats: [Api.Chat], date: Int32, seqStart: Int32, seq: Int32) case updates(updates: [Api.Update], users: [Api.User], chats: [Api.Chat], date: Int32, seq: Int32) case updateShortSentMessage(flags: Int32, id: Int32, pts: Int32, ptsCount: Int32, date: Int32, media: Api.MessageMedia?, entities: [Api.MessageEntity]?) + case updateShortMessage(flags: Int32, id: Int32, userId: Int32, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyTo: Api.MessageReplyHeader?, entities: [Api.MessageEntity]?) + case updateShortChatMessage(flags: Int32, id: Int32, fromId: Int32, chatId: Int32, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyTo: Api.MessageReplyHeader?, entities: [Api.MessageEntity]?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -15529,47 +15863,6 @@ public extension Api { buffer.appendInt32(-484987010) } - break - case .updateShortMessage(let flags, let id, let userId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyToMsgId, let entities): - if boxed { - buffer.appendInt32(-1857044719) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt32(userId, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} - if Int(flags) & Int(1 << 11) != 0 {serializeInt32(viaBotId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - break - case .updateShortChatMessage(let flags, let id, let fromId, let chatId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyToMsgId, let entities): - if boxed { - buffer.appendInt32(377562760) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt32(fromId, buffer: buffer, boxed: false) - serializeInt32(chatId, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(ptsCount, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} - if Int(flags) & Int(1 << 11) != 0 {serializeInt32(viaBotId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} break case .updateShort(let update, let date): if boxed { @@ -15639,6 +15932,47 @@ public extension Api { item.serialize(buffer, true) }} break + case .updateShortMessage(let flags, let id, let userId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities): + if boxed { + buffer.appendInt32(580309704) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} + if Int(flags) & Int(1 << 11) != 0 {serializeInt32(viaBotId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + break + case .updateShortChatMessage(let flags, let id, let fromId, let chatId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities): + if boxed { + buffer.appendInt32(1076714939) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt32(fromId, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(ptsCount, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} + if Int(flags) & Int(1 << 11) != 0 {serializeInt32(viaBotId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + break } } @@ -15646,10 +15980,6 @@ public extension Api { switch self { case .updatesTooLong: return ("updatesTooLong", []) - case .updateShortMessage(let flags, let id, let userId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyToMsgId, let entities): - return ("updateShortMessage", [("flags", flags), ("id", id), ("userId", userId), ("message", message), ("pts", pts), ("ptsCount", ptsCount), ("date", date), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("entities", entities)]) - case .updateShortChatMessage(let flags, let id, let fromId, let chatId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyToMsgId, let entities): - return ("updateShortChatMessage", [("flags", flags), ("id", id), ("fromId", fromId), ("chatId", chatId), ("message", message), ("pts", pts), ("ptsCount", ptsCount), ("date", date), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("entities", entities)]) case .updateShort(let update, let date): return ("updateShort", [("update", update), ("date", date)]) case .updatesCombined(let updates, let users, let chats, let date, let seqStart, let seq): @@ -15658,105 +15988,16 @@ public extension Api { return ("updates", [("updates", updates), ("users", users), ("chats", chats), ("date", date), ("seq", seq)]) case .updateShortSentMessage(let flags, let id, let pts, let ptsCount, let date, let media, let entities): return ("updateShortSentMessage", [("flags", flags), ("id", id), ("pts", pts), ("ptsCount", ptsCount), ("date", date), ("media", media), ("entities", entities)]) + case .updateShortMessage(let flags, let id, let userId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities): + return ("updateShortMessage", [("flags", flags), ("id", id), ("userId", userId), ("message", message), ("pts", pts), ("ptsCount", ptsCount), ("date", date), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyTo", replyTo), ("entities", entities)]) + case .updateShortChatMessage(let flags, let id, let fromId, let chatId, let message, let pts, let ptsCount, let date, let fwdFrom, let viaBotId, let replyTo, let entities): + return ("updateShortChatMessage", [("flags", flags), ("id", id), ("fromId", fromId), ("chatId", chatId), ("message", message), ("pts", pts), ("ptsCount", ptsCount), ("date", date), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyTo", replyTo), ("entities", entities)]) } } public static func parse_updatesTooLong(_ reader: BufferReader) -> Updates? { return Api.Updates.updatesTooLong } - public static func parse_updateShortMessage(_ reader: BufferReader) -> Updates? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - _7 = reader.readInt32() - var _8: Api.MessageFwdHeader? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader - } } - var _9: Int32? - if Int(_1!) & Int(1 << 11) != 0 {_9 = reader.readInt32() } - var _10: Int32? - if Int(_1!) & Int(1 << 3) != 0 {_10 = reader.readInt32() } - var _11: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 11) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 3) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.Updates.updateShortMessage(flags: _1!, id: _2!, userId: _3!, message: _4!, pts: _5!, ptsCount: _6!, date: _7!, fwdFrom: _8, viaBotId: _9, replyToMsgId: _10, entities: _11) - } - else { - return nil - } - } - public static func parse_updateShortChatMessage(_ reader: BufferReader) -> Updates? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: String? - _5 = parseString(reader) - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - _7 = reader.readInt32() - var _8: Int32? - _8 = reader.readInt32() - var _9: Api.MessageFwdHeader? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader - } } - var _10: Int32? - if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt32() } - var _11: Int32? - if Int(_1!) & Int(1 << 3) != 0 {_11 = reader.readInt32() } - var _12: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 2) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.Updates.updateShortChatMessage(flags: _1!, id: _2!, fromId: _3!, chatId: _4!, message: _5!, pts: _6!, ptsCount: _7!, date: _8!, fwdFrom: _9, viaBotId: _10, replyToMsgId: _11, entities: _12) - } - else { - return nil - } - } public static func parse_updateShort(_ reader: BufferReader) -> Updates? { var _1: Api.Update? if let signature = reader.readInt32() { @@ -15867,6 +16108,103 @@ public extension Api { return nil } } + public static func parse_updateShortMessage(_ reader: BufferReader) -> Updates? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Api.MessageFwdHeader? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader + } } + var _9: Int32? + if Int(_1!) & Int(1 << 11) != 0 {_9 = reader.readInt32() } + var _10: Api.MessageReplyHeader? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader + } } + var _11: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { + _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 11) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 3) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return Api.Updates.updateShortMessage(flags: _1!, id: _2!, userId: _3!, message: _4!, pts: _5!, ptsCount: _6!, date: _7!, fwdFrom: _8, viaBotId: _9, replyTo: _10, entities: _11) + } + else { + return nil + } + } + public static func parse_updateShortChatMessage(_ reader: BufferReader) -> Updates? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + var _9: Api.MessageFwdHeader? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader + } } + var _10: Int32? + if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt32() } + var _11: Api.MessageReplyHeader? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _11 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader + } } + var _12: [Api.MessageEntity]? + if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { + _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1!) & Int(1 << 2) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.Updates.updateShortChatMessage(flags: _1!, id: _2!, fromId: _3!, chatId: _4!, message: _5!, pts: _6!, ptsCount: _7!, date: _8!, fwdFrom: _9, viaBotId: _10, replyTo: _11, entities: _12) + } + else { + return nil + } + } } public enum StatsAbsValueAndPrev: TypeConstructorDescription { @@ -17095,8 +17433,8 @@ public extension Api { } public enum Message: TypeConstructorDescription { case messageEmpty(id: Int32) - case messageService(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, replyToMsgId: Int32?, date: Int32, action: Api.MessageAction) - case message(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?) + case message(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?) + case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -17106,29 +17444,17 @@ public extension Api { } serializeInt32(id, buffer: buffer, boxed: false) break - case .messageService(let flags, let id, let fromId, let toId, let replyToMsgId, let date, let action): + case .message(let flags, let id, let fromId, let peerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let restrictionReason): if boxed { - buffer.appendInt32(-1642487306) + buffer.appendInt32(1487813065) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 8) != 0 {serializeInt32(fromId!, buffer: buffer, boxed: false)} - toId.serialize(buffer, true) - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} - serializeInt32(date, buffer: buffer, boxed: false) - action.serialize(buffer, true) - break - case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let editDate, let postAuthor, let groupedId, let restrictionReason): - if boxed { - buffer.appendInt32(1160515173) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 8) != 0 {serializeInt32(fromId!, buffer: buffer, boxed: false)} - toId.serialize(buffer, true) + if Int(flags) & Int(1 << 8) != 0 {fromId!.serialize(buffer, true)} + peerId.serialize(buffer, true) if Int(flags) & Int(1 << 2) != 0 {fwdFrom!.serialize(buffer, true)} if Int(flags) & Int(1 << 11) != 0 {serializeInt32(viaBotId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} serializeInt32(date, buffer: buffer, boxed: false) serializeString(message, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 9) != 0 {media!.serialize(buffer, true)} @@ -17139,6 +17465,8 @@ public extension Api { item.serialize(buffer, true) }} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(views!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(forwards!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 23) != 0 {replies!.serialize(buffer, true)} if Int(flags) & Int(1 << 15) != 0 {serializeInt32(editDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 16) != 0 {serializeString(postAuthor!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 17) != 0 {serializeInt64(groupedId!, buffer: buffer, boxed: false)} @@ -17148,6 +17476,18 @@ public extension Api { item.serialize(buffer, true) }} break + case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action): + if boxed { + buffer.appendInt32(678405636) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 8) != 0 {fromId!.serialize(buffer, true)} + peerId.serialize(buffer, true) + if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)} + serializeInt32(date, buffer: buffer, boxed: false) + action.serialize(buffer, true) + break } } @@ -17155,10 +17495,10 @@ public extension Api { switch self { case .messageEmpty(let id): return ("messageEmpty", [("id", id)]) - case .messageService(let flags, let id, let fromId, let toId, let replyToMsgId, let date, let action): - return ("messageService", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("replyToMsgId", replyToMsgId), ("date", date), ("action", action)]) - case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let editDate, let postAuthor, let groupedId, let restrictionReason): - return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("restrictionReason", restrictionReason)]) + case .message(let flags, let id, let fromId, let peerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let restrictionReason): + return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("peerId", peerId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyTo", replyTo), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("forwards", forwards), ("replies", replies), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("restrictionReason", restrictionReason)]) + case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action): + return ("messageService", [("flags", flags), ("id", id), ("fromId", fromId), ("peerId", peerId), ("replyTo", replyTo), ("date", date), ("action", action)]) } } @@ -17173,46 +17513,15 @@ public extension Api { return nil } } - public static func parse_messageService(_ reader: BufferReader) -> Message? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - if Int(_1!) & Int(1 << 8) != 0 {_3 = reader.readInt32() } - var _4: Api.Peer? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _5: Int32? - if Int(_1!) & Int(1 << 3) != 0 {_5 = reader.readInt32() } - var _6: Int32? - _6 = reader.readInt32() - var _7: Api.MessageAction? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.MessageAction - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.Message.messageService(flags: _1!, id: _2!, fromId: _3, toId: _4!, replyToMsgId: _5, date: _6!, action: _7!) - } - else { - return nil - } - } public static func parse_message(_ reader: BufferReader) -> Message? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: Int32? - if Int(_1!) & Int(1 << 8) != 0 {_3 = reader.readInt32() } + var _3: Api.Peer? + if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } } var _4: Api.Peer? if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.Peer @@ -17223,8 +17532,10 @@ public extension Api { } } var _6: Int32? if Int(_1!) & Int(1 << 11) != 0 {_6 = reader.readInt32() } - var _7: Int32? - if Int(_1!) & Int(1 << 3) != 0 {_7 = reader.readInt32() } + var _7: Api.MessageReplyHeader? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader + } } var _8: Int32? _8 = reader.readInt32() var _9: String? @@ -17244,14 +17555,20 @@ public extension Api { var _13: Int32? if Int(_1!) & Int(1 << 10) != 0 {_13 = reader.readInt32() } var _14: Int32? - if Int(_1!) & Int(1 << 15) != 0 {_14 = reader.readInt32() } - var _15: String? - if Int(_1!) & Int(1 << 16) != 0 {_15 = parseString(reader) } - var _16: Int64? - if Int(_1!) & Int(1 << 17) != 0 {_16 = reader.readInt64() } - var _17: [Api.RestrictionReason]? + if Int(_1!) & Int(1 << 10) != 0 {_14 = reader.readInt32() } + var _15: Api.MessageReplies? + if Int(_1!) & Int(1 << 23) != 0 {if let signature = reader.readInt32() { + _15 = Api.parse(reader, signature: signature) as? Api.MessageReplies + } } + var _16: Int32? + if Int(_1!) & Int(1 << 15) != 0 {_16 = reader.readInt32() } + var _17: String? + if Int(_1!) & Int(1 << 16) != 0 {_17 = parseString(reader) } + var _18: Int64? + if Int(_1!) & Int(1 << 17) != 0 {_18 = reader.readInt64() } + var _19: [Api.RestrictionReason]? if Int(_1!) & Int(1 << 22) != 0 {if let _ = reader.readInt32() { - _17 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) + _19 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) } } let _c1 = _1 != nil let _c2 = _2 != nil @@ -17266,12 +17583,51 @@ public extension Api { let _c11 = (Int(_1!) & Int(1 << 6) == 0) || _11 != nil let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil let _c13 = (Int(_1!) & Int(1 << 10) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 15) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 16) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 17) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 22) == 0) || _17 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 { - return Api.Message.message(flags: _1!, id: _2!, fromId: _3, toId: _4!, fwdFrom: _5, viaBotId: _6, replyToMsgId: _7, date: _8!, message: _9!, media: _10, replyMarkup: _11, entities: _12, views: _13, editDate: _14, postAuthor: _15, groupedId: _16, restrictionReason: _17) + let _c14 = (Int(_1!) & Int(1 << 10) == 0) || _14 != nil + let _c15 = (Int(_1!) & Int(1 << 23) == 0) || _15 != nil + let _c16 = (Int(_1!) & Int(1 << 15) == 0) || _16 != nil + let _c17 = (Int(_1!) & Int(1 << 16) == 0) || _17 != nil + let _c18 = (Int(_1!) & Int(1 << 17) == 0) || _18 != nil + let _c19 = (Int(_1!) & Int(1 << 22) == 0) || _19 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 { + return Api.Message.message(flags: _1!, id: _2!, fromId: _3, peerId: _4!, fwdFrom: _5, viaBotId: _6, replyTo: _7, date: _8!, message: _9!, media: _10, replyMarkup: _11, entities: _12, views: _13, forwards: _14, replies: _15, editDate: _16, postAuthor: _17, groupedId: _18, restrictionReason: _19) + } + else { + return nil + } + } + public static func parse_messageService(_ reader: BufferReader) -> Message? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.Peer? + if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _4: Api.Peer? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _5: Api.MessageReplyHeader? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader + } } + var _6: Int32? + _6 = reader.readInt32() + var _7: Api.MessageAction? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.MessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.Message.messageService(flags: _1!, id: _2!, fromId: _3, peerId: _4!, replyTo: _5, date: _6!, action: _7!) } else { return nil @@ -18938,19 +19294,18 @@ public extension Api { } public enum MessageFwdHeader: TypeConstructorDescription { - case messageFwdHeader(flags: Int32, fromId: Int32?, fromName: String?, date: Int32, channelId: Int32?, channelPost: Int32?, postAuthor: String?, savedFromPeer: Api.Peer?, savedFromMsgId: Int32?, psaType: String?) + case messageFwdHeader(flags: Int32, fromId: Api.Peer?, fromName: String?, date: Int32, channelPost: Int32?, postAuthor: String?, savedFromPeer: Api.Peer?, savedFromMsgId: Int32?, psaType: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageFwdHeader(let flags, let fromId, let fromName, let date, let channelId, let channelPost, let postAuthor, let savedFromPeer, let savedFromMsgId, let psaType): + case .messageFwdHeader(let flags, let fromId, let fromName, let date, let channelPost, let postAuthor, let savedFromPeer, let savedFromMsgId, let psaType): if boxed { - buffer.appendInt32(893020267) + buffer.appendInt32(1601666510) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(fromId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {fromId!.serialize(buffer, true)} if Int(flags) & Int(1 << 5) != 0 {serializeString(fromName!, buffer: buffer, boxed: false)} serializeInt32(date, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeInt32(channelId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {serializeInt32(channelPost!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {serializeString(postAuthor!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 4) != 0 {savedFromPeer!.serialize(buffer, true)} @@ -18962,46 +19317,45 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messageFwdHeader(let flags, let fromId, let fromName, let date, let channelId, let channelPost, let postAuthor, let savedFromPeer, let savedFromMsgId, let psaType): - return ("messageFwdHeader", [("flags", flags), ("fromId", fromId), ("fromName", fromName), ("date", date), ("channelId", channelId), ("channelPost", channelPost), ("postAuthor", postAuthor), ("savedFromPeer", savedFromPeer), ("savedFromMsgId", savedFromMsgId), ("psaType", psaType)]) + case .messageFwdHeader(let flags, let fromId, let fromName, let date, let channelPost, let postAuthor, let savedFromPeer, let savedFromMsgId, let psaType): + return ("messageFwdHeader", [("flags", flags), ("fromId", fromId), ("fromName", fromName), ("date", date), ("channelPost", channelPost), ("postAuthor", postAuthor), ("savedFromPeer", savedFromPeer), ("savedFromMsgId", savedFromMsgId), ("psaType", psaType)]) } } public static func parse_messageFwdHeader(_ reader: BufferReader) -> MessageFwdHeader? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } + var _2: Api.Peer? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } } var _3: String? if Int(_1!) & Int(1 << 5) != 0 {_3 = parseString(reader) } var _4: Int32? _4 = reader.readInt32() var _5: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() } - var _6: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_6 = reader.readInt32() } - var _7: String? - if Int(_1!) & Int(1 << 3) != 0 {_7 = parseString(reader) } - var _8: Api.Peer? + if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() } + var _6: String? + if Int(_1!) & Int(1 << 3) != 0 {_6 = parseString(reader) } + var _7: Api.Peer? if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.Peer + _7 = Api.parse(reader, signature: signature) as? Api.Peer } } - var _9: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_9 = reader.readInt32() } - var _10: String? - if Int(_1!) & Int(1 << 6) != 0 {_10 = parseString(reader) } + var _8: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_8 = reader.readInt32() } + var _9: String? + if Int(_1!) & Int(1 << 6) != 0 {_9 = parseString(reader) } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 5) == 0) || _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil + let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 6) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.MessageFwdHeader.messageFwdHeader(flags: _1!, fromId: _2, fromName: _3, date: _4!, channelId: _5, channelPost: _6, postAuthor: _7, savedFromPeer: _8, savedFromMsgId: _9, psaType: _10) + let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.MessageFwdHeader.messageFwdHeader(flags: _1!, fromId: _2, fromName: _3, date: _4!, channelPost: _5, postAuthor: _6, savedFromPeer: _7, savedFromMsgId: _8, psaType: _9) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index aad07e69b9..a47b7c7b83 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -810,6 +810,42 @@ public struct stats { } } + public enum MessageStats: TypeConstructorDescription { + case messageStats(viewsGraph: Api.StatsGraph) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageStats(let viewsGraph): + if boxed { + buffer.appendInt32(-1986399595) + } + viewsGraph.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .messageStats(let viewsGraph): + return ("messageStats", [("viewsGraph", viewsGraph)]) + } + } + + public static func parse_messageStats(_ reader: BufferReader) -> MessageStats? { + var _1: Api.StatsGraph? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + let _c1 = _1 != nil + if _c1 { + return Api.stats.MessageStats.messageStats(viewsGraph: _1!) + } + else { + return nil + } + } + + } } } public extension Api { diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 238ea08cbb..0f9ff2daa3 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -2210,26 +2210,6 @@ public extension Api { }) } - public static func getMessagesViews(peer: Api.InputPeer, id: [Int32], increment: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { - let buffer = Buffer() - buffer.appendInt32(-993483427) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - increment.serialize(buffer, true) - return (FunctionDescription(name: "messages.getMessagesViews", parameters: [("peer", peer), ("id", id), ("increment", increment)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in - let reader = BufferReader(buffer) - var result: [Int32]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - return result - }) - } - public static func editChatAdmin(chatId: Int32, userId: Api.InputUser, isAdmin: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(-1444503762) @@ -2905,32 +2885,6 @@ public extension Api { }) } - public static func search(flags: Int32, peer: Api.InputPeer, q: String, fromId: Api.InputUser?, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2045448344) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeString(q, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {fromId!.serialize(buffer, true)} - filter.serialize(buffer, true) - serializeInt32(minDate, buffer: buffer, boxed: false) - serializeInt32(maxDate, buffer: buffer, boxed: false) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.search", parameters: [("flags", flags), ("peer", peer), ("q", q), ("fromId", fromId), ("filter", filter), ("minDate", minDate), ("maxDate", maxDate), ("offsetId", offsetId), ("addOffset", addOffset), ("limit", limit), ("maxId", maxId), ("minId", minId), ("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } - public static func toggleDialogPin(flags: Int32, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(-1489903017) @@ -3335,26 +3289,6 @@ public extension Api { }) } - public static func searchGlobal(flags: Int32, folderId: Int32?, q: String, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1083038300) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} - serializeString(q, buffer: buffer, boxed: false) - serializeInt32(offsetRate, buffer: buffer, boxed: false) - offsetPeer.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.searchGlobal", parameters: [("flags", flags), ("folderId", folderId), ("q", q), ("offsetRate", offsetRate), ("offsetPeer", offsetPeer), ("offsetId", offsetId), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } - public static func sendMessage(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(1376532592) @@ -3721,6 +3655,128 @@ public extension Api { return result }) } + + public static func getReplies(peer: Api.InputPeer, msgId: Int32, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-39505956) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getReplies", parameters: [("peer", peer), ("msgId", msgId), ("offsetId", offsetId), ("addOffset", addOffset), ("limit", limit), ("maxId", maxId), ("minId", minId), ("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } + + public static func getDiscussionMessage(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1147761405) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getDiscussionMessage", parameters: [("peer", peer), ("msgId", msgId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DiscussionMessage? in + let reader = BufferReader(buffer) + var result: Api.messages.DiscussionMessage? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.DiscussionMessage + } + return result + }) + } + + public static func readDiscussion(peer: Api.InputPeer, msgId: Int32, readMaxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-147740172) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(readMaxId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.readDiscussion", parameters: [("peer", peer), ("msgId", msgId), ("readMaxId", readMaxId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } + + public static func getMessagesViews(peer: Api.InputPeer, id: [Int32], increment: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1468322785) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + increment.serialize(buffer, true) + return (FunctionDescription(name: "messages.getMessagesViews", parameters: [("peer", peer), ("id", id), ("increment", increment)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageViews? in + let reader = BufferReader(buffer) + var result: Api.messages.MessageViews? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.MessageViews + } + return result + }) + } + + public static func searchGlobal(flags: Int32, folderId: Int32?, q: String, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1271290010) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} + serializeString(q, buffer: buffer, boxed: false) + filter.serialize(buffer, true) + serializeInt32(minDate, buffer: buffer, boxed: false) + serializeInt32(maxDate, buffer: buffer, boxed: false) + serializeInt32(offsetRate, buffer: buffer, boxed: false) + offsetPeer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.searchGlobal", parameters: [("flags", flags), ("folderId", folderId), ("q", q), ("filter", filter), ("minDate", minDate), ("maxDate", maxDate), ("offsetRate", offsetRate), ("offsetPeer", offsetPeer), ("offsetId", offsetId), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } + + public static func search(flags: Int32, peer: Api.InputPeer, q: String, fromId: Api.InputUser?, topMsgId: Int32?, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1310163211) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(q, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {fromId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + filter.serialize(buffer, true) + serializeInt32(minDate, buffer: buffer, boxed: false) + serializeInt32(maxDate, buffer: buffer, boxed: false) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.search", parameters: [("flags", flags), ("peer", peer), ("q", q), ("fromId", fromId), ("topMsgId", topMsgId), ("filter", filter), ("minDate", minDate), ("maxDate", maxDate), ("offsetId", offsetId), ("addOffset", addOffset), ("limit", limit), ("maxId", maxId), ("minId", minId), ("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } } public struct channels { public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { @@ -4453,6 +4509,41 @@ public extension Api { return result }) } + + public static func getMessagePublicForwards(channel: Api.InputChannel, msgId: Int32, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1445996571) + channel.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(offsetRate, buffer: buffer, boxed: false) + offsetPeer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stats.getMessagePublicForwards", parameters: [("channel", channel), ("msgId", msgId), ("offsetRate", offsetRate), ("offsetPeer", offsetPeer), ("offsetId", offsetId), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } + + public static func getMessageStats(flags: Int32, channel: Api.InputChannel, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1226791947) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stats.getMessageStats", parameters: [("flags", flags), ("channel", channel), ("msgId", msgId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.MessageStats? in + let reader = BufferReader(buffer) + var result: Api.stats.MessageStats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.MessageStats + } + return result + }) + } } public struct auth { public static func checkPhone(phoneNumber: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index b83bf9492b..d248455df0 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -28,7 +28,7 @@ public enum LocationBroadcastPanelSource { private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) { let presentImpl: (Message?) -> Void = { [weak controller] message in if let message = message, let strongController = controller { - let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongController.navigationController as? NavigationController, modal: true, dismissInput: { + let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongController.navigationController as? NavigationController, modal: true, dismissInput: { controller?.view.endEditing(true) }, present: { c, a in controller?.present(c, in: .window(.root), with: a, blockInteraction: true) @@ -557,7 +557,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { } if let id = state.id as? PeerMessagesMediaPlaylistItemId { if type == .music { - let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), account: account, chatLocation: .peer(id.messageId.peerId), tagMask: MessageTags.music) + let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), context: strongSelf.context, chatLocation: .peer(id.messageId.peerId), chatLocationContextHolder: Atomic(value: nil), tagMask: MessageTags.music) var cancelImpl: (() -> Void)? let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } @@ -594,7 +594,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { } else { controllerContext = strongSelf.context.sharedContext.makeTempAccountContext(account: account) } - let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, peerId: id.messageId.peerId, type: type, initialMessageId: id.messageId, initialOrder: order, parentNavigationController: strongSelf.navigationController as? NavigationController) + let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, peerId: id.messageId.peerId, type: type, initialMessageId: id.messageId, initialOrder: order, isGlobalSearch: false, parentNavigationController: strongSelf.navigationController as? NavigationController) strongSelf.displayNode.view.window?.endEditing(true) strongSelf.present(controller, in: .window(.root)) } else if index.1 { diff --git a/submodules/TelegramCore/Sources/Account.swift b/submodules/TelegramCore/Sources/Account.swift index 24194959ca..cad791bbc4 100644 --- a/submodules/TelegramCore/Sources/Account.swift +++ b/submodules/TelegramCore/Sources/Account.swift @@ -103,8 +103,8 @@ public class UnauthorizedAccount { datacenterIds.append(contentsOf: [4]) } for id in datacenterIds { - if network.context.authInfoForDatacenter(withId: id) == nil { - network.context.authInfoForDatacenter(withIdRequired: id, isCdn: false) + if network.context.authInfoForDatacenter(withId: id, selector: .persistent) == nil { + network.context.authInfoForDatacenter(withIdRequired: id, isCdn: false, selector: .ephemeralMain) } } network.context.beginExplicitBackupAddressDiscovery() @@ -128,7 +128,7 @@ public class UnauthorizedAccount { } } |> mapToSignal { (localizationSettings, proxySettings, networkSettings) -> Signal in - return initializedNetwork(arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) + return initializedNetwork(accountId: self.id, arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) |> map { network in let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network) updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get()) @@ -243,7 +243,7 @@ public func accountWithId(accountManager: AccountManager, networkArguments: Netw let backupState = AuthorizedAccountState(isTestingEnvironment: beginWithTestingEnvironment, masterDatacenterId: backupData.masterDatacenterId, peerId: PeerId(backupData.peerId), state: nil) state = backupState let dict = NSMutableDictionary() - dict.setObject(MTDatacenterAuthInfo(authKey: backupData.masterDatacenterKey, authKeyId: backupData.masterDatacenterKeyId, saltSet: [], authKeyAttributes: [:], mainTempAuthKey: nil, mediaTempAuthKey: nil), forKey: backupData.masterDatacenterId as NSNumber) + dict.setObject(MTDatacenterAuthInfo(authKey: backupData.masterDatacenterKey, authKeyId: backupData.masterDatacenterKeyId, saltSet: [], authKeyAttributes: [:]), forKey: backupData.masterDatacenterId as NSNumber) let data = NSKeyedArchiver.archivedData(withRootObject: dict) transaction.setState(backupState) transaction.setKeychainEntry(data, forKey: "persistent:datacenterAuthInfoById") @@ -257,7 +257,7 @@ public func accountWithId(accountManager: AccountManager, networkArguments: Netw if let accountState = accountState { switch accountState { case let unauthorizedState as UnauthorizedAccountState: - return initializedNetwork(arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) + return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) |> map { network -> AccountResult in return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) } @@ -266,7 +266,7 @@ public func accountWithId(accountManager: AccountManager, networkArguments: Netw return (transaction.getPeer(authorizedState.peerId) as? TelegramUser)?.phone } |> mapToSignal { phoneNumber in - return initializedNetwork(arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber) + return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber) |> map { network -> AccountResult in return .authorized(Account(accountManager: accountManager, id: id, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, postbox: postbox, network: network, networkArguments: networkArguments, peerId: authorizedState.peerId, auxiliaryMethods: auxiliaryMethods, supplementary: supplementary)) } @@ -276,7 +276,7 @@ public func accountWithId(accountManager: AccountManager, networkArguments: Netw } } - return initializedNetwork(arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) + return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) |> map { network -> AccountResult in return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) } diff --git a/submodules/TelegramCore/Sources/AccountManager.swift b/submodules/TelegramCore/Sources/AccountManager.swift index d2a4eea491..e604d4f956 100644 --- a/submodules/TelegramCore/Sources/AccountManager.swift +++ b/submodules/TelegramCore/Sources/AccountManager.swift @@ -30,6 +30,7 @@ private var declaredEncodables: Void = { declareEncodable(InlineBotMessageAttribute.self, f: { InlineBotMessageAttribute(decoder: $0) }) declareEncodable(TextEntitiesMessageAttribute.self, f: { TextEntitiesMessageAttribute(decoder: $0) }) declareEncodable(ReplyMessageAttribute.self, f: { ReplyMessageAttribute(decoder: $0) }) + declareEncodable(ReplyThreadMessageAttribute.self, f: { ReplyThreadMessageAttribute(decoder: $0) }) declareEncodable(ReactionsMessageAttribute.self, f: { ReactionsMessageAttribute(decoder: $0) }) declareEncodable(PendingReactionsMessageAttribute.self, f: { PendingReactionsMessageAttribute(decoder: $0) }) declareEncodable(CloudDocumentMediaResource.self, f: { CloudDocumentMediaResource(decoder: $0) }) diff --git a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift index 57c725bf36..a25c5cf3a5 100644 --- a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift @@ -975,7 +975,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo updatedState.authorizationListUpdated = true } - let message = StoreMessage(peerId: peerId, namespace: Namespaces.Message.Local, globallyUniqueId: nil, groupingKey: nil, timestamp: date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: peerId, text: messageText, attributes: attributes, media: medias) + let message = StoreMessage(peerId: peerId, namespace: Namespaces.Message.Local, globallyUniqueId: nil, groupingKey: nil, threadId: nil, timestamp: date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: peerId, text: messageText, attributes: attributes, media: medias) updatedState.addMessages([message], location: .UpperHistoryBlock) } } @@ -2274,9 +2274,44 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP var topUpperHistoryBlockMessages: [PeerIdAndMessageNamespace: MessageId.Id] = [:] + final class MessageThreadStatsRecord { + var count: Int = 0 + var peers: [PeerId] = [] + } + var messageThreadStatsDifferences: [MessageId: MessageThreadStatsRecord] = [:] + func addMessageThreadStatsDifference(threadMessageId: MessageId, add: Int, remove: Int, addedMessagePeer: PeerId?) { + if let value = messageThreadStatsDifferences[threadMessageId] { + value.count += add - remove + if let addedMessagePeer = addedMessagePeer { + value.peers.append(addedMessagePeer) + } + } else { + let value = MessageThreadStatsRecord() + messageThreadStatsDifferences[threadMessageId] = value + value.count = add - remove + if let addedMessagePeer = addedMessagePeer { + value.peers.append(addedMessagePeer) + } + } + } + for operation in optimizedOperations(finalState.state.operations) { switch operation { case let .AddMessages(messages, location): + if case .UpperHistoryBlock = location { + for message in messages { + if case let .Id(id) = message.id { + if let threadId = message.threadId { + let messageThreadId = makeThreadIdMessageId(peerId: message.id.peerId, threadId: threadId) + if id.peerId.namespace == Namespaces.Peer.CloudChannel { + if !transaction.messageExists(id: id) { + addMessageThreadStatsDifference(threadMessageId: messageThreadId, add: 1, remove: 0, addedMessagePeer: message.authorId) + } + } + } + } + } + } let _ = transaction.addMessages(messages, location: location) if case .UpperHistoryBlock = location { for message in messages { @@ -2384,7 +2419,9 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP let _ = mediaBox.removeCachedResources(Set(resourceIds)).start() } case let .DeleteMessages(ids): - deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids) + deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids, manualAddMessageThreadStatsDifference: { id, add, remove in + addMessageThreadStatsDifference(threadMessageId: id, add: add, remove: remove, addedMessagePeer: nil) + }) case let .UpdateMinAvailableMessage(id): if let message = transaction.getMessage(id) { updatePeerChatInclusionWithMinTimestamp(transaction: transaction, id: id.peerId, minTimestamp: message.timestamp, forceRootGroupIfNotExists: false) @@ -2775,7 +2812,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP break loop } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) case let .UpdateMessageForwardsCount(id, count): transaction.updateMessage(id, update: { currentMessage in @@ -2790,7 +2827,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP break loop } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) case let .UpdateInstalledStickerPacks(operation): stickerPackOperations.append(operation) @@ -2876,36 +2913,41 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP Logger.shared.log("State", "not adding hole for peer \(messageId.peerId), \(upperId) >= \(messageId.id) = false") } } +//TODO Please do not forget fix holes space. // could be the reason for unbounded slowdown, needs investigation - for (peerIdAndNamespace, pts) in clearHolesFromPreviousStateForChannelMessagesWithPts { - var upperMessageId: Int32? - var lowerMessageId: Int32? - transaction.scanMessageAttributes(peerId: peerIdAndNamespace.peerId, namespace: peerIdAndNamespace.namespace, limit: 200, { id, attributes in - for attribute in attributes { - if let attribute = attribute as? ChannelMessageStateVersionAttribute { - if attribute.pts >= pts { - if upperMessageId == nil { - upperMessageId = id.id - } - if let lowerMessageIdValue = lowerMessageId { - lowerMessageId = min(id.id, lowerMessageIdValue) - } else { - lowerMessageId = id.id - } - return true - } else { - return false - } - } - } - return false - }) - if let upperMessageId = upperMessageId, let lowerMessageId = lowerMessageId { - if upperMessageId != lowerMessageId { - transaction.removeHole(peerId: peerIdAndNamespace.peerId, namespace: peerIdAndNamespace.namespace, space: .everywhere, range: lowerMessageId ... upperMessageId) - } - } +// for (peerIdAndNamespace, pts) in clearHolesFromPreviousStateForChannelMessagesWithPts { +// var upperMessageId: Int32? +// var lowerMessageId: Int32? +// transaction.scanMessageAttributes(peerId: peerIdAndNamespace.peerId, namespace: peerIdAndNamespace.namespace, limit: 200, { id, attributes in +// for attribute in attributes { +// if let attribute = attribute as? ChannelMessageStateVersionAttribute { +// if attribute.pts >= pts { +// if upperMessageId == nil { +// upperMessageId = id.id +// } +// if let lowerMessageIdValue = lowerMessageId { +// lowerMessageId = min(id.id, lowerMessageIdValue) +// } else { +// lowerMessageId = id.id +// } +// return true +// } else { +// return false +// } +// } +// } +// return false +// }) +// if let upperMessageId = upperMessageId, let lowerMessageId = lowerMessageId { +// if upperMessageId != lowerMessageId { +// transaction.removeHole(peerId: peerIdAndNamespace.peerId, namespace: peerIdAndNamespace.namespace, space: .everywhere, range: lowerMessageId ... upperMessageId) +// } +// } +// } + + for (threadMessageId, difference) in messageThreadStatsDifferences { + updateMessageThreadStats(transaction: transaction, threadMessageId: threadMessageId, difference: difference.count, addedMessagePeers: difference.peers) } if !peerActivityTimestamps.isEmpty { diff --git a/submodules/TelegramCore/Sources/AccountViewTracker.swift b/submodules/TelegramCore/Sources/AccountViewTracker.swift index cbabb0e832..af888300f2 100644 --- a/submodules/TelegramCore/Sources/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/AccountViewTracker.swift @@ -180,7 +180,7 @@ private func fetchPoll(account: Account, messageId: MessageId) -> Signal [AdditionalMessageHistoryViewData] { +private func wrappedHistoryViewAdditionalData(chatLocation: ChatLocationInput, additionalData: [AdditionalMessageHistoryViewData]) -> [AdditionalMessageHistoryViewData] { var result = additionalData switch chatLocation { case let .peer(peerId): @@ -189,6 +189,12 @@ private func wrappedHistoryViewAdditionalData(chatLocation: ChatLocation, additi result.append(.peerChatState(peerId)) } } + case let .external(peerId, _): + if peerId.namespace == Namespaces.Peer.CloudChannel { + if result.firstIndex(where: { if case .peerChatState = $0 { return true } else { return false } }) == nil { + result.append(.peerChatState(peerId)) + } + } } return result } @@ -375,7 +381,7 @@ public final class AccountViewTracker { break } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: media)) }) } } @@ -591,35 +597,63 @@ public final class AccountViewTracker { if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { return account.network.request(Api.functions.messages.getMessagesViews(peer: inputPeer, id: messageIds.map { $0.id }, increment: .boolTrue)) |> map(Optional.init) - |> `catch` { _ -> Signal<[Int32]?, NoError> in + |> `catch` { _ -> Signal in return .single(nil) } - |> mapToSignal { viewCounts -> Signal in - if let viewCounts = viewCounts { - return account.postbox.transaction { transaction -> Void in - for i in 0 ..< messageIds.count { - if i < viewCounts.count { - /*if case let .messageViews(views, forwards) = viewCounts[i] {*/ - let views = viewCounts[i] - transaction.updateMessage(messageIds[i], update: { currentMessage in - let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) - var attributes = currentMessage.attributes - loop: for j in 0 ..< attributes.count { - if let attribute = attributes[j] as? ViewCountMessageAttribute { + |> mapToSignal { result -> Signal in + guard case let .messageViews(viewCounts, _)? = result else { + return .complete() + } + + return account.postbox.transaction { transaction -> Void in + for i in 0 ..< messageIds.count { + if i < viewCounts.count { + if case let .messageViews(_, views, forwards, replies) = viewCounts[i] { + transaction.updateMessage(messageIds[i], update: { currentMessage in + let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) + var attributes = currentMessage.attributes + var foundReplies = false + var commentsChannelId: PeerId? + var recentRepliersPeerIds: [PeerId]? + var repliesCount: Int32? + if let replies = replies { + switch replies { + case let .messageReplies(_, repliesCountValue, _, recentRepliers, channelId, _, _): + if let channelId = channelId { + commentsChannelId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + } + repliesCount = repliesCountValue + if let recentRepliers = recentRepliers { + recentRepliersPeerIds = recentRepliers.map { $0.peerId } + } else { + recentRepliersPeerIds = nil + } + } + } + loop: for j in 0 ..< attributes.count { + if let attribute = attributes[j] as? ViewCountMessageAttribute { + if let views = views { attributes[j] = ViewCountMessageAttribute(count: max(attribute.count, Int(views))) } - /*if let _ = attributes[j] as? ForwardCountMessageAttribute { + } else if let _ = attributes[j] as? ForwardCountMessageAttribute { + if let forwards = forwards { attributes[j] = ForwardCountMessageAttribute(count: Int(forwards)) - }*/ + } + } else if let _ = attributes[j] as? ReplyThreadMessageAttribute { + foundReplies = true + if let repliesCount = repliesCount { + attributes[j] = ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsChannelId) + } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) - }) - //} + } + if !foundReplies, let repliesCount = repliesCount { + attributes.append(ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsChannelId)) + } + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) } } } - } else { - return .complete() } } } else { @@ -941,7 +975,7 @@ public final class AccountViewTracker { break loop } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) transaction.setPendingMessageAction(type: .consumeUnseenPersonalMessage, id: id, action: ConsumePersonalMessageAction()) @@ -1074,7 +1108,7 @@ public final class AccountViewTracker { } } - func wrappedMessageHistorySignal(chatLocation: ChatLocation, signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, addHoleIfNeeded: Bool) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + func wrappedMessageHistorySignal(chatLocation: ChatLocationInput, signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, addHoleIfNeeded: Bool) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { let history = withState(signal, { [weak self] () -> Int32 in if let strongSelf = self { return OSAtomicIncrement32(&strongSelf.nextViewId) @@ -1103,6 +1137,10 @@ public final class AccountViewTracker { if peerId.namespace == Namespaces.Peer.CloudChannel { strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil) } + case let .external(peerId, _): + if peerId.namespace == Namespaces.Peer.CloudChannel { + strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil) + } } } } @@ -1149,7 +1187,7 @@ public final class AccountViewTracker { } } - public func scheduledMessagesViewForLocation(_ chatLocation: ChatLocation, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func scheduledMessagesViewForLocation(_ chatLocation: ChatLocationInput, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, anchor: .upperBound, count: 200, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: nil, namespaces: .just(Namespaces.Message.allScheduled), orderStatistics: [], additionalData: additionalData) return withState(signal, { [weak self] () -> Int32 in @@ -1179,7 +1217,7 @@ public final class AccountViewTracker { } } - public func aroundMessageOfInterestHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func aroundMessageOfInterestHistoryViewForLocation(_ chatLocation: ChatLocationInput, count: Int, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { let signal = account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData)) return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal, addHoleIfNeeded: true) @@ -1188,7 +1226,7 @@ public final class AccountViewTracker { } } - public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, messageId: MessageId, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocationInput, count: Int, messageId: MessageId, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { let signal = account.postbox.aroundIdMessageHistoryViewForLocation(chatLocation, count: count, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData)) return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal, addHoleIfNeeded: false) @@ -1197,7 +1235,7 @@ public final class AccountViewTracker { } } - public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, clipHoles: Bool = true, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocationInput, index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, clipHoles: Bool = true, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { let inputAnchor: HistoryViewInputAnchor switch index { diff --git a/submodules/TelegramCore/Sources/ApplyMaxReadIndexInteractively.swift b/submodules/TelegramCore/Sources/ApplyMaxReadIndexInteractively.swift index 84974b2b75..6de3383d3a 100644 --- a/submodules/TelegramCore/Sources/ApplyMaxReadIndexInteractively.swift +++ b/submodules/TelegramCore/Sources/ApplyMaxReadIndexInteractively.swift @@ -32,7 +32,7 @@ func applyMaxReadIndexInteractively(transaction: Transaction, stateManager: Acco return currentAttribute } }) - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) }) transaction.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: id) } @@ -101,7 +101,7 @@ func applySecretOutgoingMessageReadActions(transaction: Transaction, id: Message return currentAttribute } }) - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) }) transaction.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: id) } diff --git a/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift index 9d09f0de6e..be58039f8e 100644 --- a/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/ApplyUpdateMessage.swift @@ -42,7 +42,7 @@ func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox, force: } } -func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, message: Message, result: Api.Updates) -> Signal { +func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, message: Message, result: Api.Updates, accountPeerId: PeerId) -> Signal { return postbox.transaction { transaction -> Void in let messageId: Int32? var apiMessage: Api.Message? @@ -90,6 +90,8 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes transaction.offsetPendingMessagesTimestamps(lowerBound: message.id, excludeIds: Set([message.id]), timestamp: updatedTimestamp) } + var updatedMessage: StoreMessage? + transaction.updateMessage(message.id, update: { currentMessage in let updatedId: MessageId if let messageId = messageId { @@ -222,8 +224,19 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes } } - return .update(StoreMessage(id: updatedId, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: updatedTimestamp ?? currentMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: forwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media)) + let updatedMessageValue = StoreMessage(id: updatedId, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: updatedTimestamp ?? currentMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: forwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media) + updatedMessage = updatedMessageValue + + return .update(updatedMessageValue) }) + if let updatedMessage = updatedMessage, case let .Id(updatedId) = updatedMessage.id { + if message.id.namespace == Namespaces.Message.Local && updatedId.namespace == Namespaces.Message.Cloud && updatedId.peerId.namespace == Namespaces.Peer.CloudChannel { + if let threadId = updatedMessage.threadId { + let messageThreadId = makeThreadIdMessageId(peerId: updatedMessage.id.peerId, threadId: threadId) + updateMessageThreadStats(transaction: transaction, threadMessageId: messageThreadId, difference: 1, addedMessagePeers: [accountPeerId]) + } + } + } for file in sentStickers { transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20) } @@ -288,20 +301,21 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage var sentStickers: [TelegramMediaFile] = [] var sentGifs: [TelegramMediaFile] = [] - var updatedGroupingKey: Int64? - for (_, _, updatedMessage) in mapping { - if let updatedGroupingKey = updatedGroupingKey { - assert(updatedGroupingKey == updatedMessage.groupingKey) + var updatedGroupingKey: [Int64 : [MessageId]] = [:] + for (message, _, updatedMessage) in mapping { + if let groupingKey = updatedMessage.groupingKey { + var ids = updatedGroupingKey[groupingKey] ?? [] + ids.append(message.id) + updatedGroupingKey[groupingKey] = ids } - updatedGroupingKey = updatedMessage.groupingKey } if let latestPreviousId = latestPreviousId, let latestIndex = mapping.last?.1 { transaction.offsetPendingMessagesTimestamps(lowerBound: latestPreviousId, excludeIds: Set(mapping.map { $0.0.id }), timestamp: latestIndex.timestamp) } - if let updatedGroupingKey = updatedGroupingKey { - transaction.updateMessageGroupingKeysAtomically(mapping.map { $0.0.id }, groupingKey: updatedGroupingKey) + for (key, ids) in updatedGroupingKey { + transaction.updateMessageGroupingKeysAtomically(ids, groupingKey: key) } for (message, _, updatedMessage) in mapping { @@ -360,7 +374,7 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage let (tags, globalTags) = tagsForStoreMessage(incoming: currentMessage.flags.contains(.Incoming), attributes: attributes, media: media, textEntities: entitiesAttribute?.entities) - return .update(StoreMessage(id: updatedId, globallyUniqueId: nil, groupingKey: currentMessage.groupingKey, timestamp: updatedMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media)) + return .update(StoreMessage(id: updatedId, globallyUniqueId: nil, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: updatedMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media)) }) } diff --git a/submodules/TelegramCore/Sources/CachedChannelParticipants.swift b/submodules/TelegramCore/Sources/CachedChannelParticipants.swift index 8b6b1b79d8..ea72e40a81 100644 --- a/submodules/TelegramCore/Sources/CachedChannelParticipants.swift +++ b/submodules/TelegramCore/Sources/CachedChannelParticipants.swift @@ -72,12 +72,12 @@ public struct ChannelParticipantBannedInfo: PostboxCoding, Equatable { } public enum ChannelParticipant: PostboxCoding, Equatable { - case creator(id: PeerId, rank: String?) + case creator(id: PeerId, adminInfo: ChannelParticipantAdminInfo?, rank: String?) case member(id: PeerId, invitedAt: Int32, adminInfo: ChannelParticipantAdminInfo?, banInfo: ChannelParticipantBannedInfo?, rank: String?) public var peerId: PeerId { switch self { - case let .creator(id, _): + case let .creator(id, _, _): return id case let .member(id, _, _, _, _): return id @@ -86,7 +86,7 @@ public enum ChannelParticipant: PostboxCoding, Equatable { public var rank: String? { switch self { - case let .creator(_, rank): + case let .creator(_, _, rank): return rank case let .member(_, _, _, _, rank): return rank @@ -116,8 +116,8 @@ public enum ChannelParticipant: PostboxCoding, Equatable { } else { return false } - case let .creator(id, rank): - if case .creator(id, rank) = rhs { + case let .creator(id, adminInfo, rank): + if case .creator(id, adminInfo, rank) = rhs { return true } else { return false @@ -130,7 +130,7 @@ public enum ChannelParticipant: PostboxCoding, Equatable { case ChannelParticipantValue.member.rawValue: self = .member(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), invitedAt: decoder.decodeInt32ForKey("t", orElse: 0), adminInfo: decoder.decodeObjectForKey("ai", decoder: { ChannelParticipantAdminInfo(decoder: $0) }) as? ChannelParticipantAdminInfo, banInfo: decoder.decodeObjectForKey("bi", decoder: { ChannelParticipantBannedInfo(decoder: $0) }) as? ChannelParticipantBannedInfo, rank: decoder.decodeOptionalStringForKey("rank")) case ChannelParticipantValue.creator.rawValue: - self = .creator(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), rank: decoder.decodeOptionalStringForKey("rank")) + self = .creator(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), adminInfo: decoder.decodeObjectForKey("ai", decoder: { ChannelParticipantAdminInfo(decoder: $0) }) as? ChannelParticipantAdminInfo, rank: decoder.decodeOptionalStringForKey("rank")) default: self = .member(id: PeerId(decoder.decodeInt64ForKey("i", orElse: 0)), invitedAt: decoder.decodeInt32ForKey("t", orElse: 0), adminInfo: nil, banInfo: nil, rank: nil) } @@ -157,9 +157,14 @@ public enum ChannelParticipant: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "rank") } - case let .creator(id, rank): + case let .creator(id, adminInfo, rank): encoder.encodeInt32(ChannelParticipantValue.creator.rawValue, forKey: "r") encoder.encodeInt64(id.toInt64(), forKey: "i") + if let adminInfo = adminInfo { + encoder.encodeObject(adminInfo, forKey: "ai") + } else { + encoder.encodeNil(forKey: "ai") + } if let rank = rank { encoder.encodeString(rank, forKey: "rank") } else { @@ -195,8 +200,8 @@ extension ChannelParticipant { switch apiParticipant { case let .channelParticipant(userId, date): self = .member(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), invitedAt: date, adminInfo: nil, banInfo: nil, rank: nil) - case let .channelParticipantCreator(_, userId, rank): - self = .creator(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), rank: rank) + case let .channelParticipantCreator(_, userId, adminRights, rank): + self = .creator(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(apiAdminRights: adminRights), promotedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), canBeEditedByAccountPeer: true), rank: rank) case let .channelParticipantBanned(flags, userId, restrictedBy, date, bannedRights): let hasLeft = (flags & (1 << 0)) != 0 let banInfo = ChannelParticipantBannedInfo(rights: TelegramChatBannedRights(apiBannedRights: bannedRights), restrictedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: restrictedBy), timestamp: date, isMember: !hasLeft) diff --git a/submodules/TelegramCore/Sources/ChannelAdmins.swift b/submodules/TelegramCore/Sources/ChannelAdmins.swift index 10d4591c3c..2557ff7ede 100644 --- a/submodules/TelegramCore/Sources/ChannelAdmins.swift +++ b/submodules/TelegramCore/Sources/ChannelAdmins.swift @@ -65,7 +65,7 @@ public func channelAdminIds(postbox: Postbox, network: Network, peerId: PeerId, switch participant { case let .channelParticipantAdmin(_, userId, _, _, _, _, _): return user.peerId.id == userId - case let .channelParticipantCreator(_, userId, _): + case let .channelParticipantCreator(_, userId, _, _): return user.peerId.id == userId default: return false diff --git a/submodules/TelegramCore/Sources/ChannelOwnershipTransfer.swift b/submodules/TelegramCore/Sources/ChannelOwnershipTransfer.swift index 023130be15..8c5d7d4357 100644 --- a/submodules/TelegramCore/Sources/ChannelOwnershipTransfer.swift +++ b/submodules/TelegramCore/Sources/ChannelOwnershipTransfer.swift @@ -92,7 +92,7 @@ public func updateChannelOwnership(account: Account, accountStateManager: Accoun flags = TelegramChatAdminRightsFlags.groupSpecific } - let updatedParticipant = ChannelParticipant.creator(id: user.id, rank: currentParticipant?.rank) + let updatedParticipant = ChannelParticipant.creator(id: user.id, adminInfo: nil, rank: currentParticipant?.rank) let updatedPreviousCreator = ChannelParticipant.member(id: accountUser.id, invitedAt: Int32(Date().timeIntervalSince1970), adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(flags: flags), promotedBy: accountUser.id, canBeEditedByAccountPeer: false), banInfo: nil, rank: currentCreator?.rank) let checkPassword = twoStepAuthData(account.network) diff --git a/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift b/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift index dfb6619eaa..5c3cbde19c 100644 --- a/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift +++ b/submodules/TelegramCore/Sources/ChatHistoryPreloadManager.swift @@ -102,7 +102,8 @@ private final class HistoryPreloadEntry: Comparable { |> mapToSignal { download -> Signal in switch hole.hole { case let .peer(peerHole): - return fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .download(download), postbox: postbox, peerId: peerHole.peerId, namespace: peerHole.namespace, direction: hole.direction, space: .everywhere, count: 60) + return fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .download(download), postbox: postbox, peerId: peerHole.peerId, namespace: peerHole.namespace, direction: hole.direction, space: .everywhere, threadId: nil, count: 60) + |> ignoreValues } } ) diff --git a/submodules/TelegramCore/Sources/DeleteMessages.swift b/submodules/TelegramCore/Sources/DeleteMessages.swift index 14dfab3fd3..c8c06b4de4 100644 --- a/submodules/TelegramCore/Sources/DeleteMessages.swift +++ b/submodules/TelegramCore/Sources/DeleteMessages.swift @@ -23,7 +23,7 @@ func addMessageMediaResourceIdsToRemove(message: Message, resourceIds: inout [Wr } } -public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [MessageId], deleteMedia: Bool = true) { +public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [MessageId], deleteMedia: Bool = true, manualAddMessageThreadStatsDifference: ((MessageId, Int, Int) -> Void)? = nil) { var resourceIds: [WrappedMediaResourceId] = [] if deleteMedia { for id in ids { @@ -37,6 +37,22 @@ public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [M if !resourceIds.isEmpty { let _ = mediaBox.removeCachedResources(Set(resourceIds)).start() } + for id in ids { + if id.peerId.namespace == Namespaces.Peer.CloudChannel && id.namespace == Namespaces.Message.Cloud { + if let message = transaction.getMessage(id) { + if let threadId = message.threadId { + let messageThreadId = makeThreadIdMessageId(peerId: message.id.peerId, threadId: threadId) + if id.peerId.namespace == Namespaces.Peer.CloudChannel { + if let manualAddMessageThreadStatsDifference = manualAddMessageThreadStatsDifference { + manualAddMessageThreadStatsDifference(messageThreadId, 0, 1) + } else { + updateMessageThreadStats(transaction: transaction, threadMessageId: messageThreadId, difference: -1, addedMessagePeers: []) + } + } + } + } + } + } transaction.deleteMessages(ids, forEachMedia: { _ in }) } diff --git a/submodules/TelegramCore/Sources/DeleteMessagesInteractively.swift b/submodules/TelegramCore/Sources/DeleteMessagesInteractively.swift index 12a257f601..e206ada74b 100644 --- a/submodules/TelegramCore/Sources/DeleteMessagesInteractively.swift +++ b/submodules/TelegramCore/Sources/DeleteMessagesInteractively.swift @@ -114,7 +114,7 @@ public func clearHistoryInteractively(postbox: Postbox, peerId: PeerId, type: In } if let topIndex = topIndex { if peerId.namespace == Namespaces.Peer.CloudUser { - let _ = transaction.addMessages([StoreMessage(id: topIndex.id, globallyUniqueId: nil, groupingKey: nil, timestamp: topIndex.timestamp, flags: StoreMessageFlags(), tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: nil, text: "", attributes: [], media: [TelegramMediaAction(action: .historyCleared)])], location: .Random) + let _ = transaction.addMessages([StoreMessage(id: topIndex.id, globallyUniqueId: nil, groupingKey: nil, threadId: nil, timestamp: topIndex.timestamp, flags: StoreMessageFlags(), tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: nil, text: "", attributes: [], media: [TelegramMediaAction(action: .historyCleared)])], location: .Random) } else { updatePeerChatInclusionWithMinTimestamp(transaction: transaction, id: peerId, minTimestamp: topIndex.timestamp, forceRootGroupIfNotExists: false) } diff --git a/submodules/TelegramCore/Sources/EnqueueMessage.swift b/submodules/TelegramCore/Sources/EnqueueMessage.swift index b1fc81bf18..56e46a2933 100644 --- a/submodules/TelegramCore/Sources/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/EnqueueMessage.swift @@ -316,7 +316,11 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, attributes.append(contentsOf: filterMessageAttributesForOutgoingMessage(requestedAttributes)) if let replyToMessageId = replyToMessageId, replyToMessageId.peerId == peerId { - attributes.append(ReplyMessageAttribute(messageId: replyToMessageId)) + var threadMessageId: MessageId? + if let replyMessage = transaction.getMessage(replyToMessageId) { + threadMessageId = replyMessage.effectiveReplyThreadMessageId + } + attributes.append(ReplyMessageAttribute(messageId: replyToMessageId, threadMessageId: threadMessageId)) } var mediaList: [Media] = [] if let mediaReference = mediaReference { @@ -362,8 +366,14 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, } let authorId: PeerId? - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { - authorId = peer.id + if let peer = peer as? TelegramChannel { + if case .broadcast = peer.info { + authorId = peer.id + } else if case .group = peer.info, peer.hasPermission(.canBeAnonymous) { + authorId = peer.id + } else { + authorId = account.peerId + } } else { authorId = account.peerId } @@ -408,7 +418,18 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, } } - storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: mediaList)) + var threadId: Int64? + if let replyToMessageId = replyToMessageId { + if let message = transaction.getMessage(replyToMessageId) { + if let threadIdValue = message.threadId { + threadId = threadIdValue + } else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { + threadId = makeMessageThreadId(replyToMessageId) + } + } + } + + storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, threadId: threadId, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: mediaList)) case let .forward(source, grouping, requestedAttributes): let sourceMessage = transaction.getMessage(source) if let sourceMessage = sourceMessage, let author = sourceMessage.author ?? sourceMessage.peers[sourceMessage.id.peerId] { @@ -514,8 +535,14 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, } let authorId: PeerId? - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { - authorId = peer.id + if let peer = peer as? TelegramChannel { + if case .broadcast = peer.info { + authorId = peer.id + } else if case .group = peer.info, peer.hasPermission(.canBeAnonymous) { + authorId = peer.id + } else { + authorId = account.peerId + } } else { authorId = account.peerId } @@ -523,16 +550,20 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, var messageNamespace = Namespaces.Message.Local var entitiesAttribute: TextEntitiesMessageAttribute? var effectiveTimestamp = timestamp + var threadId: Int64? for attribute in attributes { if let attribute = attribute as? TextEntitiesMessageAttribute { entitiesAttribute = attribute - } - if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute { + } else if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute { if attribute.scheduleTime == scheduleWhenOnlineTimestamp, let presence = peerPresence as? TelegramUserPresence, case let .present(statusTimestamp) = presence.status, statusTimestamp >= timestamp { } else { messageNamespace = Namespaces.Message.ScheduledLocal effectiveTimestamp = attribute.scheduleTime } + } else if let attribute = attribute as? ReplyMessageAttribute { + if let threadMessageId = attribute.threadMessageId { + threadId = makeMessageThreadId(threadMessageId) + } } } @@ -568,7 +599,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, augmentedMediaList = augmentedMediaList.map(convertForwardedMediaForSecretChat) } - storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: sourceMessage.text, attributes: attributes, media: augmentedMediaList)) + storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, threadId: threadId, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: sourceMessage.text, attributes: attributes, media: augmentedMediaList)) } } } diff --git a/submodules/TelegramCore/Sources/ExportMessageLink.swift b/submodules/TelegramCore/Sources/ExportMessageLink.swift index 75be0fce4c..a75088a9bb 100644 --- a/submodules/TelegramCore/Sources/ExportMessageLink.swift +++ b/submodules/TelegramCore/Sources/ExportMessageLink.swift @@ -3,16 +3,37 @@ import Postbox import TelegramApi import SwiftSignalKit -public func exportMessageLink(account: Account, peerId: PeerId, messageId: MessageId) -> Signal { - return account.postbox.transaction { transaction -> Peer? in - return transaction.getPeer(peerId) +public func exportMessageLink(account: Account, peerId: PeerId, messageId: MessageId, threadMessageId: MessageId? = nil) -> Signal { + return account.postbox.transaction { transaction -> (Peer, MessageId)? in + var peer: Peer? + var messageId = messageId + if let threadMessageId = threadMessageId { + messageId = threadMessageId + if let message = transaction.getMessage(threadMessageId), let sourceReference = message.sourceReference { + peer = transaction.getPeer(sourceReference.messageId.peerId) + messageId = sourceReference.messageId + } + } else { + peer = transaction.getPeer(messageId.peerId) + } + if let peer = peer { + return (peer, messageId) + } else { + return nil + } } - |> mapToSignal { peer -> Signal in - if let peer = peer, let input = apiInputChannel(peer) { - return account.network.request(Api.functions.channels.exportMessageLink(channel: input, id: messageId.id, grouped: .boolTrue)) |> mapError { _ in return } + |> mapToSignal { data -> Signal in + guard let (peer, sourceMessageId) = data else { + return .single(nil) + } + if let input = apiInputChannel(peer) { + return account.network.request(Api.functions.channels.exportMessageLink(channel: input, id: sourceMessageId.id, grouped: .boolTrue)) |> mapError { _ in return } |> map { res in switch res { case let .exportedMessageLink(link, _): + if let _ = threadMessageId { + return "\(link)?comment=\(messageId.id)" + } return link } } |> `catch` { _ -> Signal in diff --git a/submodules/TelegramCore/Sources/HistoryViewStateValidation.swift b/submodules/TelegramCore/Sources/HistoryViewStateValidation.swift index da433663bf..590ad6d6d1 100644 --- a/submodules/TelegramCore/Sources/HistoryViewStateValidation.swift +++ b/submodules/TelegramCore/Sources/HistoryViewStateValidation.swift @@ -138,7 +138,7 @@ final class HistoryViewStateValidationContexts { self.accountPeerId = accountPeerId } - func updateView(id: Int32, view: MessageHistoryView?, location: ChatLocation? = nil) { + func updateView(id: Int32, view: MessageHistoryView?, location: ChatLocationInput? = nil) { assert(self.queue.isCurrent()) guard let view = view, view.tagMask == nil || view.tagMask == MessageTags.unseenPersonalMessage || view.tagMask == MessageTags.music else { if self.contexts[id] != nil { @@ -375,7 +375,7 @@ private func validateChannelMessagesBatch(postbox: Postbox, network: Network, ac if tag == MessageTags.unseenPersonalMessage { requestSignal = network.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1)) } else if let filter = messageFilterForTagMask(tag) { - requestSignal = network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1, hash: hash)) + requestSignal = network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, topMsgId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1, hash: hash)) } else { assertionFailure() requestSignal = .complete() @@ -600,7 +600,7 @@ private func validateBatch(postbox: Postbox, network: Network, transaction: Tran let updatedFlags = StoreMessageFlags(currentMessage.flags) - return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: updatedFlags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: updatedFlags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) } }) @@ -640,7 +640,7 @@ private func validateBatch(postbox: Postbox, network: Network, transaction: Tran default: break } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) } } @@ -673,7 +673,7 @@ private func validateBatch(postbox: Postbox, network: Network, transaction: Tran default: break } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) } else { deleteMessages(transaction: transaction, mediaBox: postbox.mediaBox, ids: [id]) @@ -708,7 +708,7 @@ private func validateBatch(postbox: Postbox, network: Network, transaction: Tran default: break } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) } } diff --git a/submodules/TelegramCore/Sources/Holes.swift b/submodules/TelegramCore/Sources/Holes.swift index 53c07bf180..ada0911e65 100644 --- a/submodules/TelegramCore/Sources/Holes.swift +++ b/submodules/TelegramCore/Sources/Holes.swift @@ -42,8 +42,8 @@ enum FetchMessageHistoryHoleSource { } } -func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistoryHoleSource, peers: [PeerId: Peer], storeMessages: [StoreMessage], _ f: @escaping (Transaction, [Peer], [StoreMessage]) -> Void) -> Signal { - return postbox.transaction { transaction -> Signal in +func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistoryHoleSource, peers: [PeerId: Peer], storeMessages: [StoreMessage], _ f: @escaping (Transaction, [Peer], [StoreMessage]) -> T) -> Signal { + return postbox.transaction { transaction -> Signal in var storedIds = Set() var referencedIds = Set() for message in storeMessages { @@ -59,8 +59,7 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistor referencedIds.subtract(transaction.filterStoredMessageIds(referencedIds)) if referencedIds.isEmpty { - f(transaction, [], []) - return .complete() + return .single(f(transaction, [], [])) } else { var signals: [Signal<([Api.Message], [Api.Chat], [Api.User]), NoError>] = [] for (peerId, messageIds) in messagesIdsGroupedByPeerId(referencedIds) { @@ -97,7 +96,7 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistor let fetchMessages = combineLatest(signals) return fetchMessages - |> mapToSignal { results -> Signal in + |> mapToSignal { results -> Signal in var additionalPeers: [Peer] = [] var additionalMessages: [StoreMessage] = [] for (messages, chats, users) in results { @@ -117,17 +116,16 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistor additionalPeers.append(TelegramUser(user: user)) } } - return postbox.transaction { transaction -> Void in - f(transaction, additionalPeers, additionalMessages) + return postbox.transaction { transaction -> T in + return f(transaction, additionalPeers, additionalMessages) } - |> ignoreValues } } } |> switchToLatest } -func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryHoleSource, postbox: Postbox, peerId: PeerId, namespace: MessageId.Namespace, direction: MessageHistoryViewRelativeHoleDirection, space: MessageHistoryHoleSpace, count rawCount: Int) -> Signal { +func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryHoleSource, postbox: Postbox, peerId: PeerId, namespace: MessageId.Namespace, direction: MessageHistoryViewRelativeHoleDirection, space: MessageHistoryHoleSpace, threadId: MessageId?, count rawCount: Int) -> Signal { let count = min(100, rawCount) return postbox.stateView() @@ -139,10 +137,50 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH } } |> take(1) - |> mapToSignal { _ -> Signal in - return postbox.loadedPeerWithId(peerId) - |> take(1) - |> mapToSignal { peer in + |> mapToSignal { _ -> Signal in + return postbox.transaction { transaction -> (Peer?, Int32) in + var hash: Int32 = 0 + switch space { + case .everywhere: + if let threadId = threadId { + let offsetId: Int32 + let addOffset: Int32 + let selectedLimit = count + let maxId: Int32 + let minId: Int32 + + switch direction { + case let .range(start, end): + if start.id <= end.id { + offsetId = start.id <= 1 ? 1 : (start.id - 1) + addOffset = Int32(-selectedLimit) + maxId = end.id + minId = start.id - 1 + } else { + offsetId = start.id == Int32.max ? start.id : (start.id + 1) + addOffset = 0 + maxId = start.id == Int32.max ? start.id : (start.id + 1) + minId = end.id + } + case let .aroundId(id): + offsetId = id.id + addOffset = Int32(-selectedLimit / 2) + maxId = Int32.max + minId = 1 + } + + //request = source.request(Api.functions.messages.getReplies(peer: inputPeer, msgId: threadId.id, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0)) + } + default: + break + } + + return (transaction.getPeer(peerId), hash) + } + |> mapToSignal { (peer, hash) -> Signal in + guard let peer = peer else { + return .single(IndexSet()) + } if let inputPeer = forceApiInputPeer(peer) { print("fetchMessageHistoryHole for \(peer.id) \(peer.debugDisplayTitle) \(direction) space \(space)") Logger.shared.log("fetchMessageHistoryHole", "fetch for \(peer.id) \(peer.debugDisplayTitle) \(direction) space \(space)") @@ -152,52 +190,102 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH switch space { case .everywhere: - let offsetId: Int32 - let addOffset: Int32 - let selectedLimit = count - let maxId: Int32 - let minId: Int32 - - switch direction { - case let .range(start, end): - if start.id <= end.id { - offsetId = start.id <= 1 ? 1 : (start.id - 1) - addOffset = Int32(-selectedLimit) - maxId = end.id - minId = start.id - 1 - - let rangeStartId = start.id - let rangeEndId = min(end.id, Int32.max - 1) - if rangeStartId <= rangeEndId { - minMaxRange = rangeStartId ... rangeEndId + if let threadId = threadId { + let offsetId: Int32 + let addOffset: Int32 + let selectedLimit = count + let maxId: Int32 + let minId: Int32 + + switch direction { + case let .range(start, end): + if start.id <= end.id { + offsetId = start.id <= 1 ? 1 : (start.id - 1) + addOffset = Int32(-selectedLimit) + maxId = end.id + minId = start.id - 1 + + let rangeStartId = start.id + let rangeEndId = min(end.id, Int32.max - 1) + if rangeStartId <= rangeEndId { + minMaxRange = rangeStartId ... rangeEndId + } else { + minMaxRange = rangeStartId ... rangeStartId + assertionFailure() + } } else { - minMaxRange = rangeStartId ... rangeStartId - assertionFailure() + offsetId = start.id == Int32.max ? start.id : (start.id + 1) + addOffset = 0 + maxId = start.id == Int32.max ? start.id : (start.id + 1) + minId = end.id + + let rangeStartId = end.id + let rangeEndId = min(start.id, Int32.max - 1) + if rangeStartId <= rangeEndId { + minMaxRange = rangeStartId ... rangeEndId + } else { + minMaxRange = rangeStartId ... rangeStartId + assertionFailure() + } } - } else { - offsetId = start.id == Int32.max ? start.id : (start.id + 1) - addOffset = 0 - maxId = start.id == Int32.max ? start.id : (start.id + 1) - minId = end.id - - let rangeStartId = end.id - let rangeEndId = min(start.id, Int32.max - 1) - if rangeStartId <= rangeEndId { - minMaxRange = rangeStartId ... rangeEndId + case let .aroundId(id): + offsetId = id.id + addOffset = Int32(-selectedLimit / 2) + maxId = Int32.max + minId = 1 + + minMaxRange = 1 ... (Int32.max - 1) + } + + request = source.request(Api.functions.messages.getReplies(peer: inputPeer, msgId: threadId.id, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: hash)) + } else { + let offsetId: Int32 + let addOffset: Int32 + let selectedLimit = count + let maxId: Int32 + let minId: Int32 + + switch direction { + case let .range(start, end): + if start.id <= end.id { + offsetId = start.id <= 1 ? 1 : (start.id - 1) + addOffset = Int32(-selectedLimit) + maxId = end.id + minId = start.id - 1 + + let rangeStartId = start.id + let rangeEndId = min(end.id, Int32.max - 1) + if rangeStartId <= rangeEndId { + minMaxRange = rangeStartId ... rangeEndId + } else { + minMaxRange = rangeStartId ... rangeStartId + assertionFailure() + } } else { - minMaxRange = rangeStartId ... rangeStartId - assertionFailure() + offsetId = start.id == Int32.max ? start.id : (start.id + 1) + addOffset = 0 + maxId = start.id == Int32.max ? start.id : (start.id + 1) + minId = end.id + + let rangeStartId = end.id + let rangeEndId = min(start.id, Int32.max - 1) + if rangeStartId <= rangeEndId { + minMaxRange = rangeStartId ... rangeEndId + } else { + minMaxRange = rangeStartId ... rangeStartId + assertionFailure() + } } - } - case let .aroundId(id): - offsetId = id.id - addOffset = Int32(-selectedLimit / 2) - maxId = Int32.max - minId = 1 - minMaxRange = 1 ... Int32.max - 1 + case let .aroundId(id): + offsetId = id.id + addOffset = Int32(-selectedLimit / 2) + maxId = Int32.max + minId = 1 + minMaxRange = 1 ... Int32.max - 1 + } + + request = source.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: offsetId, offsetDate: 0, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0)) } - - request = source.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: offsetId, offsetDate: 0, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0)) case let .tag(tag): assert(tag.containsSingleElement) if tag == .unseenPersonalMessage { @@ -304,17 +392,17 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH minMaxRange = 1 ... (Int32.max - 1) } - request = source.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0)) + request = source.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, topMsgId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0)) } else { assertionFailure() minMaxRange = 1 ... 1 request = .never() - } + } } return request |> retryRequest - |> mapToSignal { result -> Signal in + |> mapToSignal { result -> Signal in let messages: [Api.Message] let chats: [Api.Chat] let users: [Api.User] @@ -368,10 +456,10 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH } } - return withResolvedAssociatedMessages(postbox: postbox, source: source, peers: Dictionary(peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages in + return withResolvedAssociatedMessages(postbox: postbox, source: source, peers: Dictionary(peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages -> IndexSet in let _ = transaction.addMessages(storeMessages, location: .Random) let _ = transaction.addMessages(additionalMessages, location: .Random) - let filledRange: ClosedRange + var filledRange: ClosedRange let ids = storeMessages.compactMap { message -> MessageId.Id? in switch message.id { case let .Id(id): @@ -396,6 +484,11 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH switch direction { case let .aroundId(aroundId): filledRange = min(aroundId.id, messageRange.lowerBound) ... max(aroundId.id, messageRange.upperBound) + if threadId != nil { + if ids.count <= count / 2 - 1 { + filledRange = minMaxRange + } + } case let .range(start, end): if start.id <= end.id { let minBound = start.id @@ -408,16 +501,19 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH } } } - transaction.removeHole(peerId: peerId, namespace: namespace, space: space, range: filledRange) + + if threadId == nil { + transaction.removeHole(peerId: peerId, namespace: namespace, space: space, range: filledRange) + } updatePeers(transaction: transaction, peers: peers + additionalPeers, update: { _, updated -> Peer in return updated }) updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences) - print("fetchMessageHistoryHole for \(peer.id) \(peer.debugDisplayTitle) space \(space) done") + print("fetchMessageHistoryHole for \(peer.id) \(peer.debugDisplayTitle) space \(space) threadId: \(String(describing: threadId)) done") - return + return IndexSet(integersIn: Int(filledRange.lowerBound) ... Int(filledRange.upperBound)) }) } } else { @@ -504,7 +600,10 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId for (groupId, summary) in fetchedChats.folderSummaries { transaction.resetPeerGroupSummary(groupId: groupId, namespace: Namespaces.Message.Cloud, summary: summary) } + + return }) + |> ignoreValues } } @@ -513,7 +612,7 @@ func fetchCallListHole(network: Network, postbox: Postbox, accountPeerId: PeerId offset = single((holeIndex.timestamp, min(holeIndex.id.id, Int32.max - 1) + 1, Api.InputPeer.inputPeerEmpty), NoError.self) return offset |> mapToSignal { (timestamp, id, peer) -> Signal in - let searchResult = network.request(Api.functions.messages.search(flags: 0, peer: .inputPeerEmpty, q: "", fromId: nil, filter: .inputMessagesFilterPhoneCalls(flags: 0), minDate: 0, maxDate: holeIndex.timestamp, offsetId: 0, addOffset: 0, limit: limit, maxId: holeIndex.id.id, minId: 0, hash: 0)) + let searchResult = network.request(Api.functions.messages.search(flags: 0, peer: .inputPeerEmpty, q: "", fromId: nil, topMsgId: nil, filter: .inputMessagesFilterPhoneCalls(flags: 0), minDate: 0, maxDate: holeIndex.timestamp, offsetId: 0, addOffset: 0, limit: limit, maxId: holeIndex.id.id, minId: 0, hash: 0)) |> retryRequest |> mapToSignal { result -> Signal in let messages: [Api.Message] diff --git a/submodules/TelegramCore/Sources/InstallInteractiveReadMessagesAction.swift b/submodules/TelegramCore/Sources/InstallInteractiveReadMessagesAction.swift index d24d8915ef..ec862edad0 100644 --- a/submodules/TelegramCore/Sources/InstallInteractiveReadMessagesAction.swift +++ b/submodules/TelegramCore/Sources/InstallInteractiveReadMessagesAction.swift @@ -49,7 +49,7 @@ public func installInteractiveReadMessagesAction(postbox: Postbox, stateManager: break loop } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) transaction.setPendingMessageAction(type: .consumeUnseenPersonalMessage, id: id, action: ConsumePersonalMessageAction()) diff --git a/submodules/TelegramCore/Sources/ManageChannelDiscussionGroup.swift b/submodules/TelegramCore/Sources/ManageChannelDiscussionGroup.swift index ab99f17e84..e8fc694411 100644 --- a/submodules/TelegramCore/Sources/ManageChannelDiscussionGroup.swift +++ b/submodules/TelegramCore/Sources/ManageChannelDiscussionGroup.swift @@ -72,30 +72,30 @@ public func updateGroupDiscussionForChannel(network: Network, postbox: Postbox, if result { return postbox.transaction { transaction in if let channelId = channelId { - var previousGroupId: PeerId? + var previousGroupId: CachedChannelData.LinkedDiscussionPeerId? transaction.updatePeerCachedData(peerIds: Set([channelId]), update: { (_, current) -> CachedPeerData? in let current: CachedChannelData = current as? CachedChannelData ?? CachedChannelData() previousGroupId = current.linkedDiscussionPeerId - return current.withUpdatedLinkedDiscussionPeerId(groupId) + return current.withUpdatedLinkedDiscussionPeerId(.known(groupId)) }) - if let previousGroupId = previousGroupId, previousGroupId != groupId { - transaction.updatePeerCachedData(peerIds: Set([previousGroupId]), update: { (_, current) -> CachedPeerData? in + if case let .known(maybeValue)? = previousGroupId, let value = maybeValue, value != groupId { + transaction.updatePeerCachedData(peerIds: Set([value]), update: { (_, current) -> CachedPeerData? in let cachedData = (current as? CachedChannelData ?? CachedChannelData()) - return cachedData.withUpdatedLinkedDiscussionPeerId(nil) + return cachedData.withUpdatedLinkedDiscussionPeerId(.known(nil)) }) } } if let groupId = groupId { - var previousChannelId: PeerId? + var previousChannelId: CachedChannelData.LinkedDiscussionPeerId? transaction.updatePeerCachedData(peerIds: Set([groupId]), update: { (_, current) -> CachedPeerData? in let current: CachedChannelData = current as? CachedChannelData ?? CachedChannelData() previousChannelId = current.linkedDiscussionPeerId - return current.withUpdatedLinkedDiscussionPeerId(channelId) + return current.withUpdatedLinkedDiscussionPeerId(.known(channelId)) }) - if let previousChannelId = previousChannelId, previousChannelId != channelId { - transaction.updatePeerCachedData(peerIds: Set([previousChannelId]), update: { (_, current) -> CachedPeerData? in + if case let .known(maybeValue)? = previousChannelId, let value = maybeValue, value != channelId { + transaction.updatePeerCachedData(peerIds: Set([value]), update: { (_, current) -> CachedPeerData? in let cachedData = (current as? CachedChannelData ?? CachedChannelData()) - return cachedData.withUpdatedLinkedDiscussionPeerId(nil) + return cachedData.withUpdatedLinkedDiscussionPeerId(.known(nil)) }) } } diff --git a/submodules/TelegramCore/Sources/ManagedAutoremoveMessageOperations.swift b/submodules/TelegramCore/Sources/ManagedAutoremoveMessageOperations.swift index fd094efc44..76c4ac24b6 100644 --- a/submodules/TelegramCore/Sources/ManagedAutoremoveMessageOperations.swift +++ b/submodules/TelegramCore/Sources/ManagedAutoremoveMessageOperations.swift @@ -74,7 +74,7 @@ func managedAutoremoveMessageOperations(postbox: Postbox) -> Signal ignoreValues @@ -220,7 +220,7 @@ private func synchronizeMessageReactions(transaction: Transaction, postbox: Post break loop } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) } |> ignoreValues diff --git a/submodules/TelegramCore/Sources/MessageUtils.swift b/submodules/TelegramCore/Sources/MessageUtils.swift index 1f09a7143a..3c84ab7aa9 100644 --- a/submodules/TelegramCore/Sources/MessageUtils.swift +++ b/submodules/TelegramCore/Sources/MessageUtils.swift @@ -168,7 +168,11 @@ func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer]) -> Mes } } - return Message(stableId: 0, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + var hash: Int32 = id.id + hash = hash &* 31 &+ id.peerId.id + let stableId = UInt32(clamping: hash) + + return Message(stableId: stableId, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) } public extension Message { diff --git a/submodules/TelegramCore/Sources/Network.swift b/submodules/TelegramCore/Sources/Network.swift index 7d0313466e..4e7a4a2c60 100644 --- a/submodules/TelegramCore/Sources/Network.swift +++ b/submodules/TelegramCore/Sources/Network.swift @@ -424,7 +424,17 @@ public struct NetworkInitializationArguments { private let cloudDataContext = Atomic(value: nil) #endif -func initializedNetwork(arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?) -> Signal { +private final class SharedContextStore { + struct Key: Hashable { + var accountId: AccountRecordId + } + + var contexts: [Key: MTContext] = [:] +} + +private let sharedContexts = Atomic(value: SharedContextStore()) + +func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?) -> Signal { return Signal { subscriber in let queue = Queue() queue.async { @@ -464,7 +474,22 @@ func initializedNetwork(arguments: NetworkInitializationArguments, supplementary } } - let context = MTContext(serialization: serialization, encryptionProvider: arguments.encryptionProvider, apiEnvironment: apiEnvironment, isTestingEnvironment: testingEnvironment, useTempAuthKeys: false)! + var contextValue: MTContext? + sharedContexts.with { store in + let key = SharedContextStore.Key(accountId: accountId) + + let context: MTContext + if let current = store.contexts[key] { + context = current + context.updateApiEnvironment({ _ in return apiEnvironment}) + } else { + context = MTContext(serialization: serialization, encryptionProvider: arguments.encryptionProvider, apiEnvironment: apiEnvironment, isTestingEnvironment: testingEnvironment, useTempAuthKeys: true)! + store.contexts[key] = context + } + contextValue = context + } + + let context = contextValue! let seedAddressList: [Int: [String]] diff --git a/submodules/TelegramCore/Sources/PeerAdmins.swift b/submodules/TelegramCore/Sources/PeerAdmins.swift index 416a0596e0..3ddfdf6dad 100644 --- a/submodules/TelegramCore/Sources/PeerAdmins.swift +++ b/submodules/TelegramCore/Sources/PeerAdmins.swift @@ -165,7 +165,13 @@ public func updateChannelAdminRights(account: Account, peerId: PeerId, adminId: } updatedParticipant = .member(id: adminId, invitedAt: invitedAt, adminInfo: adminInfo, banInfo: nil, rank: rank) } else if let currentParticipant = currentParticipant, case .creator = currentParticipant { - updatedParticipant = .creator(id: adminId, rank: rank) + let adminInfo: ChannelParticipantAdminInfo? + if !rights.flags.isEmpty { + adminInfo = ChannelParticipantAdminInfo(rights: rights, promotedBy: account.peerId, canBeEditedByAccountPeer: true) + } else { + adminInfo = nil + } + updatedParticipant = .creator(id: adminId, adminInfo: adminInfo, rank: rank) } else { let adminInfo: ChannelParticipantAdminInfo? if !rights.flags.isEmpty { diff --git a/submodules/TelegramCore/Sources/PeerUtils.swift b/submodules/TelegramCore/Sources/PeerUtils.swift index 28571aa2ea..23f84731d4 100644 --- a/submodules/TelegramCore/Sources/PeerUtils.swift +++ b/submodules/TelegramCore/Sources/PeerUtils.swift @@ -231,3 +231,24 @@ public func isServicePeer(_ peer: Peer) -> Bool { } return false } + +public extension PeerId { + var isReplies: Bool { + if self.namespace == Namespaces.Peer.CloudUser { + if self.id == 708513 || self.id == 1271266957 { + return true + } + } + return false + } + + func isRepliesOrSavedMessages(accountPeerId: PeerId) -> Bool { + if accountPeerId == self { + return true + } else if self.isReplies { + return true + } else { + return false + } + } +} diff --git a/submodules/TelegramCore/Sources/PendingMessageManager.swift b/submodules/TelegramCore/Sources/PendingMessageManager.swift index a1cc5d6fc5..500e9f32e8 100644 --- a/submodules/TelegramCore/Sources/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/PendingMessageManager.swift @@ -111,7 +111,7 @@ private func failMessages(postbox: Postbox, ids: [MessageId]) -> Signal if isForward { - if !messages.contains(where: { $0.0.groupingKey == nil }) { + if messages.contains(where: { $0.0.groupingKey != nil }) { flags |= (1 << 9) } @@ -906,7 +906,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } else { let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: message.id.peerId, operation: .sendMessage(layer: layer, id: message.id, file: secretFile), state: state) @@ -922,7 +922,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } } else { @@ -931,7 +931,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } } @@ -1072,7 +1072,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) }).start() } @@ -1086,7 +1086,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } } @@ -1114,7 +1114,7 @@ public final class PendingMessageManager { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) } } @@ -1135,7 +1135,7 @@ public final class PendingMessageManager { namespace = id.namespace } - return applyUpdateMessage(postbox: postbox, stateManager: stateManager, message: message, result: result) + return applyUpdateMessage(postbox: postbox, stateManager: stateManager, message: message, result: result, accountPeerId: self.accountPeerId) |> afterDisposed { [weak self] in if let strongSelf = self { strongSelf.queue.async { diff --git a/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift index 16b490b314..9feef62447 100644 --- a/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift @@ -352,7 +352,7 @@ private func uploadedMediaImageContent(network: Network, postbox: Postbox, trans } else { updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: arc4random64(), flags: [.transformedMedia], acknowledged: false)) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) }) } return .done(media) @@ -641,7 +641,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili } else { updatedAttributes.append(OutgoingMessageInfoAttribute(uniqueId: arc4random64(), flags: [.transformedMedia], acknowledged: false)) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) }) } return .done(media) diff --git a/submodules/TelegramCore/Sources/ProcessSecretChatIncomingDecryptedOperations.swift b/submodules/TelegramCore/Sources/ProcessSecretChatIncomingDecryptedOperations.swift index b86d428398..431af1370b 100644 --- a/submodules/TelegramCore/Sources/ProcessSecretChatIncomingDecryptedOperations.swift +++ b/submodules/TelegramCore/Sources/ProcessSecretChatIncomingDecryptedOperations.swift @@ -512,7 +512,7 @@ extension StoreMessage { convenience init?(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi8.DecryptedMessage, file: SecretChatFileReference?) { switch apiMessage { case let .decryptedMessage(randomId, _, message, media): - self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: message, attributes: [], media: []) + self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: message, attributes: [], media: []) case let .decryptedMessageService(randomId, _, action): switch action { case let .decryptedMessageActionDeleteMessages(randomIds): @@ -524,9 +524,9 @@ extension StoreMessage { case let .decryptedMessageActionReadMessages(randomIds): return nil case .decryptedMessageActionScreenshotMessages: - self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]) + self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]) + self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]) } } } @@ -809,7 +809,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) { - attributes.append(ReplyMessageAttribute(messageId: replyMessageId)) + attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)) } var entitiesAttribute: TextEntitiesMessageAttribute? @@ -822,7 +822,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 let (tags, globalTags) = tagsForStoreMessage(incoming: true, attributes: attributes, media: parsedMedia, textEntities: entitiesAttribute?.entities) - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) case let .decryptedMessageService(randomId, action): switch action { case let .decryptedMessageActionDeleteMessages(randomIds): @@ -834,9 +834,9 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 case .decryptedMessageActionReadMessages: return nil case .decryptedMessageActionScreenshotMessages: - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) case .decryptedMessageActionResend: return nil case .decryptedMessageActionRequestKey: @@ -1041,7 +1041,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) { - attributes.append(ReplyMessageAttribute(messageId: replyMessageId)) + attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)) } var entitiesAttribute: TextEntitiesMessageAttribute? @@ -1054,7 +1054,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 let (tags, globalTags) = tagsForStoreMessage(incoming: true, attributes: attributes, media: parsedMedia, textEntities: entitiesAttribute?.entities) - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: groupingKey, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: groupingKey, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) case let .decryptedMessageService(randomId, action): switch action { case .decryptedMessageActionDeleteMessages: @@ -1066,9 +1066,9 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 case .decryptedMessageActionReadMessages: return nil case .decryptedMessageActionScreenshotMessages: - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) case .decryptedMessageActionResend: return nil case .decryptedMessageActionRequestKey: @@ -1279,7 +1279,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) { - attributes.append(ReplyMessageAttribute(messageId: replyMessageId)) + attributes.append(ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil)) } var entitiesAttribute: TextEntitiesMessageAttribute? @@ -1292,7 +1292,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 let (tags, globalTags) = tagsForStoreMessage(incoming: true, attributes: attributes, media: parsedMedia, textEntities: entitiesAttribute?.entities) - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: groupingKey, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: groupingKey, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources) case let .decryptedMessageService(randomId, action): switch action { case .decryptedMessageActionDeleteMessages: @@ -1304,9 +1304,9 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 case .decryptedMessageActionReadMessages: return nil case .decryptedMessageActionScreenshotMessages: - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), []) case let .decryptedMessageActionSetMessageTTL(ttlSeconds): - return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) + return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), []) case .decryptedMessageActionResend: return nil case .decryptedMessageActionRequestKey: diff --git a/submodules/TelegramCore/Sources/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/ReplyThreadHistory.swift new file mode 100644 index 0000000000..25507264eb --- /dev/null +++ b/submodules/TelegramCore/Sources/ReplyThreadHistory.swift @@ -0,0 +1,236 @@ +import Foundation +import SyncCore +import Postbox +import SwiftSignalKit +import TelegramApi + +private class ReplyThreadHistoryContextImpl { + private let queue: Queue + private let account: Account + private let messageId: MessageId + + private var currentHole: (MessageHistoryHolesViewEntry, Disposable)? + + struct State: Equatable { + var messageId: MessageId + var holeIndices: [MessageId.Namespace: IndexSet] + var maxReadMessageId: MessageId? + } + + let state = Promise() + private var stateValue: State { + didSet { + if self.stateValue != oldValue { + self.state.set(.single(self.stateValue)) + } + } + } + + private var holesDisposable: Disposable? + private let readDisposable = MetaDisposable() + + init(queue: Queue, account: Account, messageId: MessageId, maxReadMessageId: MessageId?) { + self.queue = queue + self.account = account + self.messageId = messageId + + self.stateValue = State(messageId: self.messageId, holeIndices: [Namespaces.Message.Cloud: IndexSet(integersIn: 1 ..< Int(Int32.max))], maxReadMessageId: maxReadMessageId) + self.state.set(.single(self.stateValue)) + + let threadId = makeMessageThreadId(messageId) + + self.holesDisposable = (account.postbox.messageHistoryHolesView() + |> map { view -> MessageHistoryHolesViewEntry? in + for entry in view.entries { + switch entry.hole { + case let .peer(hole): + if hole.threadId == threadId { + return entry + } + } + } + return nil + } + |> distinctUntilChanged + |> deliverOn(self.queue)).start(next: { [weak self] entry in + guard let strongSelf = self else { + return + } + strongSelf.setCurrentHole(entry: entry) + }) + } + + deinit { + self.holesDisposable?.dispose() + self.readDisposable.dispose() + } + + func setCurrentHole(entry: MessageHistoryHolesViewEntry?) { + if self.currentHole?.0 != entry { + self.currentHole?.1.dispose() + if let entry = entry { + self.currentHole = (entry, self.fetchHole(entry: entry).start(next: { [weak self] removedHoleIndices in + guard let strongSelf = self else { + return + } + if var currentHoles = strongSelf.stateValue.holeIndices[Namespaces.Message.Cloud] { + currentHoles.subtract(removedHoleIndices) + strongSelf.stateValue.holeIndices[Namespaces.Message.Cloud] = currentHoles + } + })) + } else { + self.currentHole = nil + } + } + } + + private func fetchHole(entry: MessageHistoryHolesViewEntry) -> Signal { + switch entry.hole { + case let .peer(hole): + let fetchCount = min(entry.count, 100) + return fetchMessageHistoryHole(accountPeerId: self.account.peerId, source: .network(self.account.network), postbox: self.account.postbox, peerId: hole.peerId, namespace: hole.namespace, direction: entry.direction, space: entry.space, threadId: hole.threadId.flatMap { makeThreadIdMessageId(peerId: self.messageId.peerId, threadId: $0) }, count: fetchCount) + } + } + + func applyMaxReadIndex(messageIndex: MessageIndex) { + let account = self.account + let messageId = self.messageId + + if messageIndex.id.namespace != messageId.namespace { + return + } + + let signal = self.account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(messageIndex.id.peerId).flatMap(apiInputPeer) + } + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer = inputPeer else { + return .complete() + } + return account.network.request(Api.functions.messages.readDiscussion(peer: inputPeer, msgId: messageId.id, readMaxId: messageIndex.id.id)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> ignoreValues + } + self.readDisposable.set(signal.start()) + } +} + +public class ReplyThreadHistoryContext { + fileprivate final class GuardReference { + private let deallocated: () -> Void + + init(deallocated: @escaping () -> Void) { + self.deallocated = deallocated + } + + deinit { + self.deallocated() + } + } + + private let queue = Queue() + private let impl: QueueLocalObject + + public var state: Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + let stateDisposable = impl.state.get().start(next: { state in + subscriber.putNext(MessageHistoryViewExternalInput( + peerId: state.messageId.peerId, + threadId: makeMessageThreadId(state.messageId), + maxReadMessageId: state.maxReadMessageId, + holes: state.holeIndices + )) + }) + disposable.set(stateDisposable) + } + + return disposable + } + } + + public init(account: Account, peerId: PeerId, threadMessageId: MessageId, maxReadMessageId: MessageId?) { + let queue = self.queue + self.impl = QueueLocalObject(queue: queue, generate: { + return ReplyThreadHistoryContextImpl(queue: queue, account: account, messageId: threadMessageId, maxReadMessageId: maxReadMessageId) + }) + } + + public func applyMaxReadIndex(messageIndex: MessageIndex) { + self.impl.with { impl in + impl.applyMaxReadIndex(messageIndex: messageIndex) + } + } +} + +public struct ChatReplyThreadMessage { + public var messageId: MessageId + public var maxReadMessageId: MessageId? + + public init(messageId: MessageId, maxReadMessageId: MessageId?) { + self.messageId = messageId + self.maxReadMessageId = maxReadMessageId + } +} + +public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageId) -> Signal { + return account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) + } + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer = inputPeer else { + return .single(nil) + } + return account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: messageId.id)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result = result else { + return .single(nil) + } + return account.postbox.transaction { transaction -> ChatReplyThreadMessage? in + switch result { + case let .discussionMessage(message, readMaxId, chats, users): + guard let parsedMessage = StoreMessage(apiMessage: message), let parsedIndex = parsedMessage.index else { + return nil + } + + var peers: [Peer] = [] + var peerPresences: [PeerId: PeerPresence] = [:] + + for chat in chats { + if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { + peers.append(groupOrChannel) + } + } + for user in users { + let telegramUser = TelegramUser(user: user) + peers.append(telegramUser) + if let presence = TelegramUserPresence(apiUser: user) { + peerPresences[telegramUser.id] = presence + } + } + + let _ = transaction.addMessages([parsedMessage], location: .Random) + + updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in + return updated + }) + + updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences) + + return ChatReplyThreadMessage( + messageId: parsedIndex.id, + maxReadMessageId: MessageId(peerId: parsedIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: readMaxId) + ) + } + } + } + } +} diff --git a/submodules/TelegramCore/Sources/RequestEditMessage.swift b/submodules/TelegramCore/Sources/RequestEditMessage.swift index 495e7401c0..e266c1bc41 100644 --- a/submodules/TelegramCore/Sources/RequestEditMessage.swift +++ b/submodules/TelegramCore/Sources/RequestEditMessage.swift @@ -295,7 +295,7 @@ public func requestEditLiveLocation(postbox: Postbox, network: Network, stateMan } var updatedLocalTags = currentMessage.localTags updatedLocalTags.remove(.OutgoingLiveLocation) - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: updatedLocalTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: updatedLocalTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } } else { diff --git a/submodules/TelegramCore/Sources/RequestUserPhotos.swift b/submodules/TelegramCore/Sources/RequestUserPhotos.swift index c77b20abdf..5d4f40d963 100644 --- a/submodules/TelegramCore/Sources/RequestUserPhotos.swift +++ b/submodules/TelegramCore/Sources/RequestUserPhotos.swift @@ -61,7 +61,7 @@ public func requestPeerPhotos(postbox: Postbox, network: Network, peerId: PeerId } } } else if let peer = peer, let inputPeer = apiInputPeer(peer) { - return network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, filter: .inputMessagesFilterChatPhotos, minDate: 0, maxDate: 0, offsetId: 0, addOffset: 0, limit: 1000, maxId: 0, minId: 0, hash: 0)) + return network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, topMsgId: nil, filter: .inputMessagesFilterChatPhotos, minDate: 0, maxDate: 0, offsetId: 0, addOffset: 0, limit: 1000, maxId: 0, minId: 0, hash: 0)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -121,7 +121,7 @@ public func requestPeerPhotos(postbox: Postbox, network: Network, peerId: PeerId switch media.action { case let .photoUpdated(image): if let image = image { - photos.append(TelegramPeerPhoto(image: image, reference: nil, date: message.timestamp, index: index, totalCount: messages.count, messageId: message.id)) + photos.append(TelegramPeerPhoto(image: image, reference: image.reference, date: message.timestamp, index: index, totalCount: messages.count, messageId: message.id)) } default: break diff --git a/submodules/TelegramCore/Sources/SearchMessages.swift b/submodules/TelegramCore/Sources/SearchMessages.swift index ac1ec30e2d..4e0ce26fb7 100644 --- a/submodules/TelegramCore/Sources/SearchMessages.swift +++ b/submodules/TelegramCore/Sources/SearchMessages.swift @@ -7,9 +7,9 @@ import MtProtoKit import SyncCore public enum SearchMessagesLocation: Equatable { - case general(tags: MessageTags?) + case general(tags: MessageTags?, minDate: Int32?, maxDate: Int32?) case group(PeerGroupId) - case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?) + case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?, topMsgId: MessageId?, minDate: Int32?, maxDate: Int32?) case publicForwards(messageId: MessageId, datacenterId: Int?) } @@ -47,6 +47,13 @@ public struct SearchMessagesResult { public let readStates: [PeerId: CombinedPeerReadState] public let totalCount: Int32 public let completed: Bool + + public init(messages: [Message], readStates: [PeerId: CombinedPeerReadState], totalCount: Int32, completed: Bool) { + self.messages = messages + self.readStates = readStates + self.totalCount = totalCount + self.completed = completed + } } public struct SearchMessagesState: Equatable { @@ -180,7 +187,7 @@ private func mergedResult(_ state: SearchMessagesState) -> SearchMessagesResult public func searchMessages(account: Account, location: SearchMessagesLocation, query: String, state: SearchMessagesState?, limit: Int32 = 100) -> Signal<(SearchMessagesResult, SearchMessagesState), NoError> { let remoteSearchResult: Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError> switch location { - case let .peer(peerId, fromId, tags): + case let .peer(peerId, fromId, tags, topMsgId, minDate, maxDate): if peerId.namespace == Namespaces.Peer.SecretChat { return account.postbox.transaction { transaction -> (SearchMessagesResult, SearchMessagesState) in var readStates: [PeerId: CombinedPeerReadState] = [:] @@ -222,12 +229,15 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q flags |= (1 << 0) } } + if let _ = topMsgId { + flags |= (1 << 1) + } let peerMessages: Signal if let completed = state?.main.completed, completed { peerMessages = .single(nil) } else { let lowerBound = state?.main.messages.last.flatMap({ $0.index }) - peerMessages = account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: query, fromId: fromInputUser, filter: filter, minDate: 0, maxDate: Int32.max - 1, offsetId: lowerBound?.id.id ?? 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0)) + peerMessages = account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: query, fromId: fromInputUser, topMsgId: topMsgId?.id, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetId: lowerBound?.id.id ?? 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -241,7 +251,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q additionalPeerMessages = .single(nil) } else if mainCompleted || !hasAdditional { let lowerBound = state?.additional?.messages.last.flatMap({ $0.index }) - additionalPeerMessages = account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: query, fromId: fromInputUser, filter: filter, minDate: 0, maxDate: Int32.max - 1, offsetId: lowerBound?.id.id ?? 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0)) + additionalPeerMessages = account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: query, fromId: fromInputUser, topMsgId: topMsgId?.id, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetId: lowerBound?.id.id ?? 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -256,7 +266,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q } case .group: remoteSearchResult = .single((nil, nil)) - case let .general(tags): + case let .general(tags, minDate, maxDate): let filter: Api.MessagesFilter = tags.flatMap { messageFilterForTagMask($0) } ?? .inputMessagesFilterEmpty remoteSearchResult = account.postbox.transaction { transaction -> (Int32, MessageIndex?, Api.InputPeer) in var lowerBound: MessageIndex? @@ -270,7 +280,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q } } |> mapToSignal { (nextRate, lowerBound, inputPeer) in - return account.network.request(Api.functions.messages.searchGlobal(flags: 0, folderId: nil, q: query, offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit), automaticFloodWait: false) + return account.network.request(Api.functions.messages.searchGlobal(flags: 0, folderId: nil, q: query, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit), automaticFloodWait: false) |> map { result -> (Api.messages.Messages?, Api.messages.Messages?) in return (result, nil) } @@ -293,13 +303,12 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q return (inputChannel, 0, lowerBound, .inputPeerEmpty) } } - |> mapToSignal { (inputChannel, nextRate, lowerBound, inputPeer) -> Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError> in + |> mapToSignal { (inputChannel, nextRate, lowerBound, inputPeer) in guard let inputChannel = inputChannel else { return .complete() } - return .single((nil, nil)) - /*let request = Api.functions.stats.getMessagePublicForwards(channel: inputChannel, msgId: messageId.id, offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit) + let request = Api.functions.stats.getMessagePublicForwards(channel: inputChannel, msgId: messageId.id, offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit) let signal: Signal if let datacenterId = datacenterId, account.network.datacenterId != datacenterId { signal = account.network.download(datacenterId: datacenterId, isMedia: false, tag: nil) @@ -316,7 +325,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q } |> `catch` { _ -> Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError> in return .single((nil, nil)) - }*/ + } } } @@ -324,7 +333,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q |> mapToSignal { result, additionalResult -> Signal<(SearchMessagesResult, SearchMessagesState), NoError> in return account.postbox.transaction { transaction -> (SearchMessagesResult, SearchMessagesState) in var additional: SearchMessagesPeerState? = mergedState(transaction: transaction, state: state?.additional, result: additionalResult) - if state?.additional == nil, case let .general(tags) = location { + if state?.additional == nil, case let .general(tags, _, _) = location { let secretMessages = transaction.searchMessages(peerId: nil, query: query, tags: tags) var readStates: [PeerId: CombinedPeerReadState] = [:] for message in secretMessages { diff --git a/submodules/TelegramCore/Sources/Serialization.swift b/submodules/TelegramCore/Sources/Serialization.swift index 9d7dec93c3..94cd1882c6 100644 --- a/submodules/TelegramCore/Sources/Serialization.swift +++ b/submodules/TelegramCore/Sources/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 118 + return 119 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/SplitTest.swift b/submodules/TelegramCore/Sources/SplitTest.swift index 10cac1b41e..c0f8938470 100644 --- a/submodules/TelegramCore/Sources/SplitTest.swift +++ b/submodules/TelegramCore/Sources/SplitTest.swift @@ -25,7 +25,7 @@ extension SplitTest { public func addEvent(_ event: Self.Event, data: JSON = []) { if let bucket = self.bucket { //TODO: merge additional data - addAppLogEvent(postbox: self.postbox, time: Date().timeIntervalSince1970, type: event.rawValue, peerId: nil, data: ["bucket": bucket]) + addAppLogEvent(postbox: self.postbox, type: event.rawValue, data: ["bucket": bucket]) } } } diff --git a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift index 0ae53cfaa9..edb29aae1b 100644 --- a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift @@ -110,58 +110,33 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { switch messsage { case let .message(message): - let flags = message.flags - let fromId = message.fromId - let toId = message.toId - switch toId { - case let .peerUser(userId): - return PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) - case let .peerChat(chatId): - return PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) - case let .peerChannel(channelId): - return PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) - } + let chatPeerId = message.peerId + return chatPeerId.peerId case .messageEmpty: return nil - case let .messageService(flags, _, fromId, toId, _, _, _): - switch toId { - case let .peerUser(userId): - return PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) - case let .peerChat(chatId): - return PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) - case let .peerChannel(channelId): - return PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) - } + case let .messageService(flags, _, fromId, chatPeerId, _, _, _): + return chatPeerId.peerId } } func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { switch message { - case let .message(flags, _, fromId, toId, fwdHeader, viaBotId, _, _, _, media, _, entities, _, _, _, _, _): - let peerId: PeerId - switch toId { - case let .peerUser(userId): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) - case let .peerChat(chatId): - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) - case let .peerChannel(channelId): - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) - } + case let .message(flags, _, fromId, chatPeerId, fwdHeader, viaBotId, _, _, _, media, _, entities, _, _, _, _, _, _, _): + let peerId: PeerId = chatPeerId.peerId var result = [peerId] - if let fromId = fromId, PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) != peerId { - result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId)) + let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId + + if resolvedFromId != peerId { + result.append(resolvedFromId) } if let fwdHeader = fwdHeader { switch fwdHeader { case let .messageFwdHeader(messageFwdHeader): - if let channelId = messageFwdHeader.channelId { - result.append(PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)) - } if let fromId = messageFwdHeader.fromId { - result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId)) + result.append(fromId.peerId) } if let savedFromPeer = messageFwdHeader.savedFromPeer { result.append(savedFromPeer.peerId) @@ -198,20 +173,14 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { return result case .messageEmpty: return [] - case let .messageService(flags, _, fromId, toId, _, _, action): - let peerId: PeerId - switch toId { - case let .peerUser(userId): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) - case let .peerChat(chatId): - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) - case let .peerChannel(channelId): - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) - } + case let .messageService(flags, _, fromId, chatPeerId, _, _, action): + let peerId: PeerId = chatPeerId.peerId var result = [peerId] - if let fromId = fromId, PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) != peerId { - result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId)) + let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId + + if resolvedFromId != peerId { + result.append(resolvedFromId) } switch action { @@ -241,35 +210,23 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { func apiMessageAssociatedMessageIds(_ message: Api.Message) -> [MessageId]? { switch message { - case let .message(flags, _, fromId, toId, _, _, replyToMsgId, _, _, _, _, _, _, _, _, _, _): - if let replyToMsgId = replyToMsgId { - let peerId: PeerId - switch toId { - case let .peerUser(userId): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) - case let .peerChat(chatId): - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) - case let .peerChannel(channelId): - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) - } + case let .message(flags, _, fromId, chatPeerId, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _): + if let replyTo = replyTo { + let peerId: PeerId = chatPeerId.peerId - return [MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId)] + switch replyTo { + case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, _): + return [MessageId(peerId: replyToPeerId?.peerId ?? peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId)] + } } case .messageEmpty: break - case let .messageService(flags, _, fromId, toId, replyToMsgId, _, _): - if let replyToMsgId = replyToMsgId { - let peerId: PeerId - switch toId { - case let .peerUser(userId): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) - case let .peerChat(chatId): - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) - case let .peerChannel(channelId): - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + case let .messageService(flags, _, fromId, chatPeerId, replyHeader, _, _): + if let replyHeader = replyHeader { + switch replyHeader { + case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, _): + return [MessageId(peerId: replyToPeerId?.peerId ?? chatPeerId.peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId)] } - - return [MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId)] } } return nil @@ -399,31 +356,21 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { - case let .message(flags, id, fromId, toId, fwdFrom, viaBotId, replyToMsgId, date, message, media, replyMarkup, entities, views, editDate, postAuthor, groupingId, restrictionReason): + case let .message(flags, id, fromId, chatPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, restrictionReason): + let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId + let peerId: PeerId var authorId: PeerId? - switch toId { - case let .peerUser(userId): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) - if let fromId = fromId { - authorId = PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) - } else { - authorId = peerId - } + switch chatPeerId { + case .peerUser: + peerId = chatPeerId.peerId + authorId = resolvedFromId case let .peerChat(chatId): peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) - if let fromId = fromId { - authorId = PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) - } else { - authorId = peerId - } + authorId = resolvedFromId case let .peerChannel(channelId): peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) - if let fromId = fromId { - authorId = PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) - } else { - authorId = peerId - } + authorId = resolvedFromId } var attributes: [MessageAttribute] = [] @@ -431,20 +378,22 @@ extension StoreMessage { var forwardInfo: StoreMessageForwardInfo? if let fwdFrom = fwdFrom { switch fwdFrom { - case let .messageFwdHeader(_, fromId, fromName, date, channelId, channelPost, postAuthor, savedFromPeer, savedFromMsgId, psaType): + case let .messageFwdHeader(_, fromId, fromName, date, channelPost, postAuthor, savedFromPeer, savedFromMsgId, psaType): var authorId: PeerId? var sourceId: PeerId? var sourceMessageId: MessageId? if let fromId = fromId { - authorId = PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) - } - if let channelId = channelId { - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) - sourceId = peerId - - if let channelPost = channelPost { - sourceMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: channelPost) + switch fromId { + case .peerChannel: + let peerId = fromId.peerId + sourceId = peerId + + if let channelPost = channelPost { + sourceMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: channelPost) + } + default: + authorId = fromId.peerId } } @@ -514,8 +463,23 @@ extension StoreMessage { attributes.append(InlineBotMessageAttribute(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: viaBotId), title: nil)) } - if let replyToMsgId = replyToMsgId { - attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId))) + var threadId: Int64? + if let replyTo = replyTo { + var threadMessageId: MessageId? + switch replyTo { + case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyToTopId): + let replyPeerId = replyToPeerId?.peerId ?? peerId + if let replyToTopId = replyToTopId { + let threadIdValue = MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToTopId) + threadMessageId = threadIdValue + threadId = makeMessageThreadId(threadIdValue) + } else if peerId.namespace == Namespaces.Peer.CloudChannel { + let threadIdValue = MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId) + threadMessageId = threadIdValue + threadId = makeMessageThreadId(threadIdValue) + } + attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId)) + } } if namespace != Namespaces.Message.ScheduledCloud { @@ -523,9 +487,9 @@ extension StoreMessage { attributes.append(ViewCountMessageAttribute(count: Int(views))) } - /*if let forwards = forwards { + if let forwards = forwards { attributes.append(ForwardCountMessageAttribute(count: Int(forwards))) - }*/ + } } if let editDate = editDate { @@ -564,6 +528,22 @@ extension StoreMessage { attributes.append(ReactionsMessageAttribute(apiReactions: reactions)) }*/ + if let replies = replies { + let recentRepliersPeerIds: [PeerId]? + switch replies { + case let .messageReplies(_, repliesCount, _, recentRepliers, channelId, _, _): + if let recentRepliers = recentRepliers { + recentRepliersPeerIds = recentRepliers.map { $0.peerId } + } else { + recentRepliersPeerIds = nil + } + + let commentsPeerId = channelId.flatMap { PeerId(namespace: Namespaces.Peer.CloudChannel, id: $0) } + + attributes.append(ReplyThreadMessageAttribute(count: repliesCount, latestUsers: recentRepliersPeerIds ?? [], commentsPeerId: commentsPeerId)) + } + } + if let restrictionReason = restrictionReason { attributes.append(RestrictedContentMessageAttribute(rules: restrictionReason.map(RestrictionRule.init(apiReason:)))) } @@ -604,46 +584,38 @@ extension StoreMessage { storeFlags.insert(.CanBeGroupedIntoFeed) - self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: groupingId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: messageText, attributes: attributes, media: medias) + self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: groupingId, threadId: threadId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: messageText, attributes: attributes, media: medias) case .messageEmpty: return nil - case let .messageService(flags, id, fromId, toId, replyToMsgId, date, action): - let peerId: PeerId - var authorId: PeerId? - switch toId { - case let .peerUser(userId): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) - if let fromId = fromId { - authorId = PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) - } else { - authorId = peerId - } - case let .peerChat(chatId): - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) - if let fromId = fromId { - authorId = PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) - } else { - authorId = peerId - } - case let .peerChannel(channelId): - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) - if let fromId = fromId { - authorId = PeerId(namespace: Namespaces.Peer.CloudUser, id: fromId) - } else { - authorId = peerId - } - } + case let .messageService(flags, id, fromId, chatPeerId, replyTo, date, action): + let peerId: PeerId = chatPeerId.peerId + var authorId: PeerId? = fromId?.peerId ?? chatPeerId.peerId var attributes: [MessageAttribute] = [] - if let replyToMsgId = replyToMsgId { - attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId))) + + var threadId: Int64? + if let replyTo = replyTo { + var threadMessageId: MessageId? + switch replyTo { + case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyToTopId): + let replyPeerId = replyToPeerId?.peerId ?? peerId + if let replyToTopId = replyToTopId { + let threadIdValue = MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToTopId) + threadMessageId = threadIdValue + threadId = makeMessageThreadId(threadIdValue) + } else if peerId.namespace == Namespaces.Peer.CloudChannel { + let threadIdValue = MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId) + threadMessageId = threadIdValue + threadId = makeMessageThreadId(threadIdValue) + } + attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId)) + } } if (flags & (1 << 17)) != 0 { attributes.append(ContentRequiresValidationMessageAttribute()) } - var storeFlags = StoreMessageFlags() if (flags & 2) == 0 { let _ = storeFlags.insert(.Incoming) @@ -673,7 +645,7 @@ extension StoreMessage { storeFlags.insert(.WasScheduled) } - self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: nil, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: attributes, media: media) + self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: nil, threadId: threadId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: attributes, media: media) } } } diff --git a/submodules/TelegramCore/Sources/SynchronizeAppLogEventsOperation.swift b/submodules/TelegramCore/Sources/SynchronizeAppLogEventsOperation.swift index 99669da865..541fb91a73 100644 --- a/submodules/TelegramCore/Sources/SynchronizeAppLogEventsOperation.swift +++ b/submodules/TelegramCore/Sources/SynchronizeAppLogEventsOperation.swift @@ -5,7 +5,7 @@ import MtProtoKit import SyncCore -public func addAppLogEvent(postbox: Postbox, time: Double, type: String, peerId: PeerId?, data: JSON) { +public func addAppLogEvent(postbox: Postbox, time: Double = Date().timeIntervalSince1970, type: String, peerId: PeerId? = nil, data: JSON = .dictionary([:])) { let tag: PeerOperationLogTag = OperationLogTags.SynchronizeAppLogEvents let peerId = PeerId(namespace: 0, id: 0) let _ = (postbox.transaction { transaction in diff --git a/submodules/TelegramCore/Sources/SynchronizePeerReadState.swift b/submodules/TelegramCore/Sources/SynchronizePeerReadState.swift index 5f55a1bce3..9817e3c28b 100644 --- a/submodules/TelegramCore/Sources/SynchronizePeerReadState.swift +++ b/submodules/TelegramCore/Sources/SynchronizePeerReadState.swift @@ -34,12 +34,18 @@ private func inputSecretChat(postbox: Postbox, peerId: PeerId) -> Signal take(1) } -private func dialogTopMessage(network: Network, postbox: Postbox, peerId: PeerId) -> Signal<(Int32, Int32), PeerReadStateValidationError> { +private func dialogTopMessage(network: Network, postbox: Postbox, peerId: PeerId) -> Signal<(Int32, Int32)?, PeerReadStateValidationError> { return inputPeer(postbox: postbox, peerId: peerId) - |> mapToSignal { inputPeer -> Signal<(Int32, Int32), PeerReadStateValidationError> in + |> mapToSignal { inputPeer -> Signal<(Int32, Int32)?, PeerReadStateValidationError> in return network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: Int32.max, offsetDate: Int32.max, addOffset: 0, limit: 1, maxId: Int32.max, minId: 1, hash: 0)) - |> retryRequest - |> mapToSignalPromotingError { result -> Signal<(Int32, Int32), PeerReadStateValidationError> in + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignalPromotingError { result -> Signal<(Int32, Int32)?, PeerReadStateValidationError> in + guard let result = result else { + return .single(nil) + } let apiMessages: [Api.Message] switch result { case let .channelMessages(_, _, _, messages, _, _): @@ -60,14 +66,18 @@ private func dialogTopMessage(network: Network, postbox: Postbox, peerId: PeerId } } -private func dialogReadState(network: Network, postbox: Postbox, peerId: PeerId) -> Signal<(PeerReadState, PeerReadStateMarker), PeerReadStateValidationError> { +private func dialogReadState(network: Network, postbox: Postbox, peerId: PeerId) -> Signal<(PeerReadState, PeerReadStateMarker)?, PeerReadStateValidationError> { return dialogTopMessage(network: network, postbox: postbox, peerId: peerId) - |> mapToSignal { topMessage -> Signal<(PeerReadState, PeerReadStateMarker), PeerReadStateValidationError> in + |> mapToSignal { topMessage -> Signal<(PeerReadState, PeerReadStateMarker)?, PeerReadStateValidationError> in + guard let _ = topMessage else { + return .single(nil) + } + return inputPeer(postbox: postbox, peerId: peerId) - |> mapToSignal { inputPeer -> Signal<(PeerReadState, PeerReadStateMarker), PeerReadStateValidationError> in + |> mapToSignal { inputPeer -> Signal<(PeerReadState, PeerReadStateMarker)?, PeerReadStateValidationError> in return network.request(Api.functions.messages.getPeerDialogs(peers: [.inputDialogPeer(peer: inputPeer)])) |> retryRequest - |> mapToSignalPromotingError { result -> Signal<(PeerReadState, PeerReadStateMarker), PeerReadStateValidationError> in + |> mapToSignalPromotingError { result -> Signal<(PeerReadState, PeerReadStateMarker)?, PeerReadStateValidationError> in switch result { case let .peerDialogs(dialogs, _, _, _, state): if let dialog = dialogs.filter({ $0.peerId == peerId }).first { @@ -150,10 +160,16 @@ enum PeerReadStateValidationError { private func validatePeerReadState(network: Network, postbox: Postbox, stateManager: AccountStateManager, peerId: PeerId) -> Signal { let readStateWithInitialState = dialogReadState(network: network, postbox: postbox, peerId: peerId) - |> map { ($0.0, $0.1) } let maybeAppliedReadState = readStateWithInitialState - |> mapToSignal { (readState, finalMarker) -> Signal in + |> mapToSignal { data -> Signal in + guard let (readState, _) = data else { + return postbox.transaction { transaction -> Void in + transaction.confirmSynchronizedIncomingReadState(peerId) + } + |> castError(PeerReadStateValidationError.self) + |> ignoreValues + } return stateManager.addCustomOperation(postbox.transaction { transaction -> PeerReadStateValidationError? in if let currentReadState = transaction.getCombinedPeerReadState(peerId) { loop: for (namespace, currentState) in currentReadState.states { diff --git a/submodules/TelegramCore/Sources/TelegramChannel.swift b/submodules/TelegramCore/Sources/TelegramChannel.swift index e3c12f4686..3961526d83 100644 --- a/submodules/TelegramCore/Sources/TelegramChannel.swift +++ b/submodules/TelegramCore/Sources/TelegramChannel.swift @@ -12,11 +12,19 @@ public enum TelegramChannelPermission { case banMembers case addAdmins case changeInfo + case canBeAnonymous } public extension TelegramChannel { func hasPermission(_ permission: TelegramChannelPermission) -> Bool { if self.flags.contains(.isCreator) { + if case .canBeAnonymous = permission { + if let adminRights = self.adminRights { + return adminRights.flags.contains(.canBeAnonymous) + } else { + return false + } + } return true } switch permission { @@ -116,6 +124,11 @@ public extension TelegramChannel { return true } return false + case .canBeAnonymous: + if let adminRights = self.adminRights, adminRights.flags.contains(.canBeAnonymous) { + return true + } + return false } } diff --git a/submodules/TelegramCore/Sources/UnauthorizedAccountStateManager.swift b/submodules/TelegramCore/Sources/UnauthorizedAccountStateManager.swift index 19a87d91f3..b9993cf1f9 100644 --- a/submodules/TelegramCore/Sources/UnauthorizedAccountStateManager.swift +++ b/submodules/TelegramCore/Sources/UnauthorizedAccountStateManager.swift @@ -27,7 +27,7 @@ private final class UnauthorizedUpdateMessageService: NSObject, MTMessageService self.pipe.putNext(updates) } - func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!) { + func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!, authInfoSelector: MTDatacenterAuthInfoSelector) { if let updates = (message.body as? BoxedMessage)?.body as? Api.Updates { self.addUpdates(updates) } diff --git a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift index 493276fbae..e94467f96f 100644 --- a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift @@ -498,7 +498,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI .withUpdatedStickerPack(stickerPack) .withUpdatedMinAvailableMessageId(minAvailableMessageId) .withUpdatedMigrationReference(migrationReference) - .withUpdatedLinkedDiscussionPeerId(linkedDiscussionPeerId) + .withUpdatedLinkedDiscussionPeerId(.known(linkedDiscussionPeerId)) .withUpdatedPeerGeoLocation(peerGeoLocation) .withUpdatedSlowModeTimeout(slowmodeSeconds) .withUpdatedSlowModeValidUntilTimestamp(slowmodeNextSendDate) diff --git a/submodules/TelegramCore/Sources/UpdateMessageMedia.swift b/submodules/TelegramCore/Sources/UpdateMessageMedia.swift index 62e433f3f2..5e90e06f8f 100644 --- a/submodules/TelegramCore/Sources/UpdateMessageMedia.swift +++ b/submodules/TelegramCore/Sources/UpdateMessageMedia.swift @@ -24,7 +24,68 @@ func updateMessageMedia(transaction: Transaction, id: MediaId, media: Media?) { if let forwardInfo = currentMessage.forwardInfo { storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } } + +func updateMessageThreadStats(transaction: Transaction, threadMessageId: MessageId, difference: Int, addedMessagePeers: [PeerId]) { + updateMessageThreadStatsInternal(transaction: transaction, threadMessageId: threadMessageId, difference: difference, addedMessagePeers: addedMessagePeers, allowChannel: false) +} + +private func updateMessageThreadStatsInternal(transaction: Transaction, threadMessageId: MessageId, difference: Int, addedMessagePeers: [PeerId], allowChannel: Bool) { + guard let channel = transaction.getPeer(threadMessageId.peerId) as? TelegramChannel else { + return + } + var isGroup = true + if case .broadcast = channel.info { + isGroup = false + if !allowChannel { + return + } + } + + var channelThreadMessageId: MessageId? + + func mergeLatestUsers(current: [PeerId], added: [PeerId], isGroup: Bool, isEmpty: Bool) -> [PeerId] { + if isEmpty { + return [] + } + if isGroup { + return current + } + var current = current + for i in 0 ..< min(3, added.count) { + let peerId = added[added.count - 1 - i] + if let index = current.firstIndex(of: peerId) { + current.remove(at: index) + current.insert(peerId, at: 0) + } else { + if current.count >= 3 { + current.removeLast() + } + current.insert(peerId, at: 0) + } + } + return current + } + + transaction.updateMessage(threadMessageId, update: { currentMessage in + let countDifference = Int32(difference) + + var attributes = currentMessage.attributes + loop: for j in 0 ..< attributes.count { + if let attribute = attributes[j] as? ReplyThreadMessageAttribute { + let count = max(0, attribute.count + countDifference) + attributes[j] = ReplyThreadMessageAttribute(count: count, latestUsers: mergeLatestUsers(current: attribute.latestUsers, added: addedMessagePeers, isGroup: isGroup, isEmpty: count == 0), commentsPeerId: attribute.commentsPeerId) + } else if let attribute = attributes[j] as? SourceReferenceMessageAttribute { + channelThreadMessageId = attribute.messageId + } + } + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + + if let channelThreadMessageId = channelThreadMessageId { + updateMessageThreadStatsInternal(transaction: transaction, threadMessageId: channelThreadMessageId, difference: difference, addedMessagePeers: addedMessagePeers, allowChannel: true) + } +} diff --git a/submodules/TelegramCore/Sources/UpdateMessageService.swift b/submodules/TelegramCore/Sources/UpdateMessageService.swift index 34cd793682..bca8a5a76e 100644 --- a/submodules/TelegramCore/Sources/UpdateMessageService.swift +++ b/submodules/TelegramCore/Sources/UpdateMessageService.swift @@ -34,7 +34,7 @@ class UpdateMessageService: NSObject, MTMessageService { self.pipe.putNext(groups) } - func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!) { + func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!, authInfoSelector: MTDatacenterAuthInfoSelector) { if let updates = (message.body as? BoxedMessage)?.body as? Api.Updates { self.addUpdates(updates) } @@ -57,25 +57,24 @@ class UpdateMessageService: NSObject, MTMessageService { if groups.count != 0 { self.putNext(groups) } - case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyToMsgId, entities): - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: fromId, toId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) + case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities): + let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: .peerChat(chatId: fromId), peerId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { self.putNext(groups) } - case let .updateShortMessage(flags, id, userId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyToMsgId, entities): - let generatedFromId: Int32 - let generatedToId: Api.Peer - if (Int(flags) & 2) != 0 { - generatedFromId = self.peerId.id - generatedToId = Api.Peer.peerUser(userId: userId) + case let .updateShortMessage(flags, id, userId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities): + let generatedFromId: Api.Peer + if (Int(flags) & 1 << 1) != 0 { + generatedFromId = Api.Peer.peerUser(userId: self.peerId.id) } else { - generatedFromId = userId - generatedToId = Api.Peer.peerUser(userId: self.peerId.id) + generatedFromId = Api.Peer.peerUser(userId: userId) } - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, toId: generatedToId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) + let generatedPeerId = Api.Peer.peerUser(userId: userId) + + let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, peerId: generatedPeerId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { diff --git a/submodules/TelegramCore/Sources/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/UpdatesApiUtils.swift index e457e6aa1e..0b80c1de20 100644 --- a/submodules/TelegramCore/Sources/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/UpdatesApiUtils.swift @@ -112,36 +112,13 @@ extension Api.Message { let flags = message.flags let id = message.id let fromId = message.fromId - let toId = message.toId - let peerId: PeerId - switch toId { - case let .peerUser(userId): - let id: PeerId.Id - if namespace == Namespaces.Message.ScheduledCloud { - id = userId - } else { - id = (flags & Int32(2)) != 0 ? userId : (fromId ?? userId) - } - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: id) - case let .peerChat(chatId): - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) - case let .peerChannel(channelId): - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) - } + let peerId: PeerId = message.peerId.peerId return MessageId(peerId: peerId, namespace: namespace, id: id) case .messageEmpty: return nil - case let .messageService(flags, id, fromId, toId, _, _, _): - let peerId: PeerId - switch toId { - case let .peerUser(userId): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) - case let .peerChat(chatId): - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) - case let .peerChannel(channelId): - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) - } + case let .messageService(flags, id, fromId, chatPeerId, _, _, _): + let peerId: PeerId = chatPeerId.peerId return MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id) } } diff --git a/submodules/TelegramCore/Sources/WebpagePreview.swift b/submodules/TelegramCore/Sources/WebpagePreview.swift index 8fcf5fe9dc..5c9085b550 100644 --- a/submodules/TelegramCore/Sources/WebpagePreview.swift +++ b/submodules/TelegramCore/Sources/WebpagePreview.swift @@ -83,7 +83,6 @@ public func actualizedWebpage(postbox: Postbox, network: Network, webpage: Teleg return .single(nil) } |> mapToSignal { result -> Signal in - if let result = result, let updatedWebpage = telegramMediaWebpageFromApiWebpage(result, url: nil), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId == webpage.webpageId { return postbox.transaction { transaction -> TelegramMediaWebpage in updateMessageMedia(transaction: transaction, id: webpage.webpageId, media: updatedWebpage) diff --git a/submodules/TelegramPresentationData/Sources/ChatPresentationData.swift b/submodules/TelegramPresentationData/Sources/ChatPresentationData.swift new file mode 100644 index 0000000000..614ea5416e --- /dev/null +++ b/submodules/TelegramPresentationData/Sources/ChatPresentationData.swift @@ -0,0 +1,71 @@ +import Foundation +import UIKit +import Display +import TelegramCore +import SyncCore +import TelegramPresentationData +import TelegramUIPreferences + +public final class ChatPresentationThemeData: Equatable { + public let theme: PresentationTheme + public let wallpaper: TelegramWallpaper + + public init(theme: PresentationTheme, wallpaper: TelegramWallpaper) { + self.theme = theme + self.wallpaper = wallpaper + } + + public static func ==(lhs: ChatPresentationThemeData, rhs: ChatPresentationThemeData) -> Bool { + return lhs.theme === rhs.theme && lhs.wallpaper == rhs.wallpaper + } +} + +public final class ChatPresentationData { + public let theme: ChatPresentationThemeData + public let fontSize: PresentationFontSize + public let strings: PresentationStrings + public let dateTimeFormat: PresentationDateTimeFormat + public let nameDisplayOrder: PresentationPersonNameOrder + public let disableAnimations: Bool + public let largeEmoji: Bool + public let chatBubbleCorners: PresentationChatBubbleCorners + public let animatedEmojiScale: CGFloat + public let isPreview: Bool + + public let messageFont: UIFont + public let messageEmojiFont: UIFont + public let messageBoldFont: UIFont + public let messageItalicFont: UIFont + public let messageBoldItalicFont: UIFont + public let messageFixedFont: UIFont + public let messageBlockQuoteFont: UIFont + + public init(theme: ChatPresentationThemeData, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool, largeEmoji: Bool, chatBubbleCorners: PresentationChatBubbleCorners, animatedEmojiScale: CGFloat = 1.0, isPreview: Bool = false) { + self.theme = theme + self.fontSize = fontSize + self.strings = strings + self.dateTimeFormat = dateTimeFormat + self.nameDisplayOrder = nameDisplayOrder + self.disableAnimations = disableAnimations + self.chatBubbleCorners = chatBubbleCorners + self.largeEmoji = largeEmoji + self.isPreview = isPreview + + let baseFontSize = fontSize.baseDisplaySize + self.messageFont = Font.regular(baseFontSize) + self.messageEmojiFont = Font.regular(53.0) + self.messageBoldFont = Font.bold(baseFontSize) + self.messageItalicFont = Font.italic(baseFontSize) + self.messageBoldItalicFont = Font.semiboldItalic(baseFontSize) + self.messageFixedFont = Font.monospace(baseFontSize) + self.messageBlockQuoteFont = Font.regular(baseFontSize - 1.0) + + self.animatedEmojiScale = animatedEmojiScale + } +} + +extension ChatPresentationData { + public convenience init(presentationData: PresentationData) { + self.init(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper), fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations, largeEmoji: presentationData.largeEmoji, chatBubbleCorners: presentationData.chatBubbleCorners) + } +} diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index d3b258eaa4..c4b9484c1e 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -354,7 +354,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio controlColor: UIColor(rgb: 0x7e8791), accentTextColor: UIColor(rgb: 0x007ee5), backgroundColor: UIColor(rgb: 0xf7f7f7), - separatorColor: UIColor(rgb: 0xb1b1b1), + separatorColor: UIColor(rgb: 0xc8c7cc), badgeBackgroundColor: UIColor(rgb: 0xff3b30), badgeStrokeColor: UIColor(rgb: 0xff3b30), badgeTextColor: UIColor(rgb: 0xffffff), @@ -374,7 +374,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio inputPlaceholderTextColor: UIColor(rgb: 0x8e8e93), inputIconColor: UIColor(rgb: 0x8e8e93), inputClearButtonColor: UIColor(rgb: 0x7b7b81), - separatorColor: UIColor(rgb: 0xb1b1b1) + separatorColor: UIColor(rgb: 0xc8c7cc) ) let rootController = PresentationThemeRootController( @@ -685,7 +685,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio let historyNavigation = PresentationThemeChatHistoryNavigation( fillColor: UIColor(rgb: 0xf7f7f7), - strokeColor: UIColor(rgb: 0xb1b1b1), + strokeColor: UIColor(rgb: 0xc8c7cc), foregroundColor: UIColor(rgb: 0x88888d), badgeBackgroundColor: UIColor(rgb: 0x007ee5), badgeStrokeColor: UIColor(rgb: 0x007ee5), @@ -751,7 +751,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio backgroundColor: UIColor(rgb: 0xffffff), primaryTextColor: UIColor(rgb: 0x000000), controlColor: UIColor(rgb: 0x7e8791), - separatorColor: UIColor(rgb: 0xb1b1b1) + separatorColor: UIColor(rgb: 0xc8c7cc) ) ) ) diff --git a/submodules/TelegramPresentationData/Sources/PresentationStrings.swift b/submodules/TelegramPresentationData/Sources/PresentationStrings.swift index d820c37425..090d06bb8a 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationStrings.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationStrings.swift @@ -187,5507 +187,5528 @@ public final class PresentationStrings: Equatable { private let _s: [Int: String] private let _r: [Int: [(Int, NSRange)]] private let _ps: [Int: String] - public var CallFeedback_ReasonSilentLocal: String { return self._s[0]! } - public var StickerPack_ShowStickers: String { return self._s[1]! } - public var Map_PullUpForPlaces: String { return self._s[2]! } - public var Channel_Status: String { return self._s[4]! } - public var Wallet_Updated_JustNow: String { return self._s[5]! } - public var TwoStepAuth_ChangePassword: String { return self._s[7]! } - public var Map_LiveLocationFor1Hour: String { return self._s[8]! } - public var CheckoutInfo_ShippingInfoAddress2Placeholder: String { return self._s[9]! } - public var Settings_AppleWatch: String { return self._s[10]! } - public var Login_InvalidCountryCode: String { return self._s[11]! } - public var WebSearch_RecentSectionTitle: String { return self._s[12]! } - public var UserInfo_DeleteContact: String { return self._s[13]! } - public var ShareFileTip_CloseTip: String { return self._s[14]! } - public var UserInfo_Invite: String { return self._s[15]! } - public var Passport_Identity_MiddleName: String { return self._s[16]! } - public var Passport_Identity_FrontSideHelp: String { return self._s[17]! } - public var Month_GenDecember: String { return self._s[19]! } - public var Common_Yes: String { return self._s[20]! } - public func EncryptionKey_Description(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[21]!, self._r[21]!, [_1, _2]) + public var SocksProxySetup_Secret: String { return self._s[0]! } + public var Channel_AdminLog_EmptyTitle: String { return self._s[2]! } + public var Contacts_PermissionsText: String { return self._s[3]! } + public var SettingsSearch_Synonyms_Notifications_MessageNotificationsSound: String { return self._s[4]! } + public var Map_Work: String { return self._s[5]! } + public var Channel_AddBotAsAdmin: String { return self._s[6]! } + public func Wallet_Updated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[7]!, self._r[7]!, [_0]) } - public var Channel_AdminLogFilter_EventsLeaving: String { return self._s[22]! } - public var WallpaperPreview_PreviewBottomText: String { return self._s[23]! } - public func Notification_PinnedStickerMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[24]!, self._r[24]!, [_0]) + public var Call_CallInProgressTitle: String { return self._s[8]! } + public var TwoFactorSetup_Done_Action: String { return self._s[9]! } + public var Compose_NewChannel_Members: String { return self._s[10]! } + public var FastTwoStepSetup_PasswordPlaceholder: String { return self._s[11]! } + public func Channel_AdminLog_MessageInvitedName(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[12]!, self._r[12]!, [_1]) } - public var Passport_Address_ScansHelp: String { return self._s[25]! } - public var FastTwoStepSetup_PasswordHelp: String { return self._s[26]! } - public var SettingsSearch_Synonyms_Notifications_Title: String { return self._s[27]! } - public var StickerPacksSettings_AnimatedStickers: String { return self._s[28]! } - public var Wallet_WordCheck_IncorrectText: String { return self._s[29]! } - public func Items_NOfM(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[30]!, self._r[30]!, [_1, _2]) + public func Notification_MessageLifetimeRemoved(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[15]!, self._r[15]!, [_1]) } - public var AutoDownloadSettings_Files: String { return self._s[31]! } - public var TextFormat_AddLinkPlaceholder: String { return self._s[32]! } - public var Stats_MessagePublicForwardsTitle: String { return self._s[33]! } - public var ChatList_GenericPsaLabel: String { return self._s[38]! } - public var LastSeen_Lately: String { return self._s[39]! } - public func PUSH_CHANNEL_MESSAGE_VIDEOS(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[40]!, self._r[40]!, [_1, _2]) + public var Undo_DeletedGroup: String { return self._s[17]! } + public var ChatListFolder_CategoryNonContacts: String { return self._s[18]! } + public var Gif_NoGifsPlaceholder: String { return self._s[19]! } + public var Conversation_ShareInlineBotLocationConfirmation: String { return self._s[20]! } + public var AutoNightTheme_ScheduleSection: String { return self._s[22]! } + public var Map_LiveLocationTitle: String { return self._s[23]! } + public var Wallet_Receive_AddressHeader: String { return self._s[24]! } + public var Wallet_Navigation_Cancel: String { return self._s[25]! } + public var Passport_PasswordCreate: String { return self._s[26]! } + public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[27]! } + public var Settings_ProxyConnected: String { return self._s[28]! } + public func PUSH_PINNED_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[29]!, self._r[29]!, [_1, _2]) } - public var Camera_Discard: String { return self._s[41]! } - public var Channel_EditAdmin_PermissinAddAdminOff: String { return self._s[42]! } - public var Login_InvalidPhoneError: String { return self._s[44]! } - public var SettingsSearch_Synonyms_Privacy_AuthSessions: String { return self._s[45]! } - public var GroupInfo_LabelOwner: String { return self._s[46]! } - public var Conversation_Moderate_Delete: String { return self._s[47]! } - public var ClearCache_ClearCache: String { return self._s[48]! } - public var Conversation_DeleteMessagesForEveryone: String { return self._s[49]! } - public var WatchRemote_AlertOpen: String { return self._s[50]! } - public func MediaPicker_Nof(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[51]!, self._r[51]!, [_0]) + public var Channel_Management_LabelOwner: String { return self._s[30]! } + public var ApplyLanguage_ApplySuccess: String { return self._s[31]! } + public var Group_Setup_HistoryHidden: String { return self._s[32]! } + public var Month_ShortNovember: String { return self._s[33]! } + public var Call_ReportIncludeLog: String { return self._s[34]! } + public var ChatList_RemoveFolder: String { return self._s[35]! } + public var PrivacyPhoneNumberSettings_CustomHelp: String { return self._s[36]! } + public var Appearance_ThemePreview_ChatList_5_Text: String { return self._s[37]! } + public var Checkout_Receipt_Title: String { return self._s[38]! } + public func Conversation_ClearChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[39]!, self._r[39]!, [_0]) } - public var ChatState_ConnectingToProxy: String { return self._s[52]! } - public var EditTheme_Expand_Preview_IncomingReplyName: String { return self._s[53]! } - public var AutoDownloadSettings_MediaTypes: String { return self._s[55]! } - public var Watch_GroupInfo_Title: String { return self._s[56]! } - public var Passport_Identity_AddPersonalDetails: String { return self._s[57]! } - public var Channel_Info_Members: String { return self._s[58]! } - public var LoginPassword_InvalidPasswordError: String { return self._s[60]! } - public var Conversation_LiveLocation: String { return self._s[61]! } - public var Wallet_Month_ShortNovember: String { return self._s[62]! } - public var PrivacyLastSeenSettings_CustomShareSettingsHelp: String { return self._s[63]! } - public var NetworkUsageSettings_BytesReceived: String { return self._s[66]! } - public var Stickers_Search: String { return self._s[68]! } - public var NotificationsSound_Synth: String { return self._s[69]! } - public var LogoutOptions_LogOutInfo: String { return self._s[70]! } - public func VoiceOver_Chat_ForwardedFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[72]!, self._r[72]!, [_0]) + public var AuthSessions_LogOutApplicationsHelp: String { return self._s[40]! } + public var SearchImages_Title: String { return self._s[41]! } + public var Notification_PaymentSent: String { return self._s[42]! } + public var Appearance_TintAllColors: String { return self._s[43]! } + public var Group_Setup_TypePublicHelp: String { return self._s[44]! } + public var ChatSettings_Cache: String { return self._s[45]! } + public var Login_InvalidLastNameError: String { return self._s[46]! } + public var PeerInfo_PaneMedia: String { return self._s[47]! } + public var Wallet_Month_GenNovember: String { return self._s[48]! } + public var Wallet_Month_GenApril: String { return self._s[49]! } + public var Wallet_Weekday_Today: String { return self._s[50]! } + public var GroupPermission_PermissionGloballyDisabled: String { return self._s[51]! } + public var LiveLocationUpdated_JustNow: String { return self._s[52]! } + public func Map_LiveLocationPrivateDescription(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[53]!, self._r[53]!, [_0]) } - public var NetworkUsageSettings_MediaAudioDataSection: String { return self._s[73]! } - public var ChatListFolder_NameBots: String { return self._s[74]! } - public var ChatList_EmptyChatListFilterText: String { return self._s[76]! } - public var Call_IncomingVoiceCall: String { return self._s[78]! } - public var ChatList_Context_HideArchive: String { return self._s[79]! } - public var AutoNightTheme_UseSunsetSunrise: String { return self._s[80]! } - public var FastTwoStepSetup_Title: String { return self._s[81]! } - public var EditTheme_Create_Preview_IncomingReplyText: String { return self._s[82]! } - public var Channel_Info_BlackList: String { return self._s[83]! } - public var Channel_AdminLog_InfoPanelTitle: String { return self._s[84]! } - public var Conversation_OpenFile: String { return self._s[86]! } - public var SecretTimer_ImageDescription: String { return self._s[87]! } - public var PrivacySettings_AutoArchive: String { return self._s[88]! } - public var StickerSettings_ContextInfo: String { return self._s[89]! } - public var TwoStepAuth_GenericHelp: String { return self._s[91]! } - public var AutoDownloadSettings_Unlimited: String { return self._s[92]! } - public var PrivacyLastSeenSettings_NeverShareWith_Title: String { return self._s[93]! } - public var AutoDownloadSettings_DataUsageHigh: String { return self._s[94]! } - public func PUSH_CHAT_MESSAGE_VIDEO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[95]!, self._r[95]!, [_1, _2]) + public var Channel_Info_Members: String { return self._s[54]! } + public var Common_edit: String { return self._s[55]! } + public var ChatList_DeleteSavedMessagesConfirmationText: String { return self._s[57]! } + public var OldChannels_GroupEmptyFormat: String { return self._s[58]! } + public func PUSH_PINNED_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[59]!, self._r[59]!, [_1]) } - public var AuthSessions_AddDevice_ScanInfo: String { return self._s[96]! } - public var Notifications_AddExceptionTitle: String { return self._s[97]! } - public var Watch_MessageView_Reply: String { return self._s[98]! } - public var Tour_Text6: String { return self._s[99]! } - public var TwoStepAuth_SetupPasswordEnterPasswordChange: String { return self._s[100]! } - public func Notification_PinnedAnimationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[101]!, self._r[101]!, [_0]) + public var Passport_DiscardMessageAction: String { return self._s[60]! } + public var Passport_FieldOneOf_FinalDelimeter: String { return self._s[61]! } + public var Stickers_SuggestNone: String { return self._s[62]! } + public var Channel_AdminLog_CanPinMessages: String { return self._s[64]! } + public var Stickers_Search: String { return self._s[66]! } + public var Passport_Identity_EditPersonalDetails: String { return self._s[67]! } + public var NotificationSettings_ShowNotificationsAllAccounts: String { return self._s[68]! } + public var Login_ContinueWithLocalization: String { return self._s[69]! } + public var Privacy_ProfilePhoto_NeverShareWith_Title: String { return self._s[70]! } + public var TelegramWallet_Intro_TermsUrl: String { return self._s[72]! } + public var ChatList_Search_NoResultsFitlerLinks: String { return self._s[74]! } + public var TextFormat_Italic: String { return self._s[75]! } + public var Stickers_GroupChooseStickerPack: String { return self._s[76]! } + public var Notification_MessageLifetime1w: String { return self._s[77]! } + public var Channel_Management_AddModerator: String { return self._s[78]! } + public var Conversation_UnsupportedMediaPlaceholder: String { return self._s[79]! } + public var Gif_Search: String { return self._s[80]! } + public var Checkout_ErrorGeneric: String { return self._s[81]! } + public var Conversation_ContextMenuSendMessage: String { return self._s[82]! } + public var Map_SetThisLocation: String { return self._s[83]! } + public var Wallet_Info_ReceiveGrams: String { return self._s[84]! } + public var Notifications_ExceptionsDefaultSound: String { return self._s[85]! } + public var PrivacySettings_AutoArchiveInfo: String { return self._s[86]! } + public var Stats_NotificationsTitle: String { return self._s[87]! } + public var Conversation_ClearSecretHistory: String { return self._s[89]! } + public func Notification_CallFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[90]!, self._r[90]!, [_1, _2]) } - public func ShareFileTip_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[102]!, self._r[102]!, [_0]) + public var Wallet_TransactionInfo_Title: String { return self._s[91]! } + public var ChatListFolder_DiscardDiscard: String { return self._s[92]! } + public var PrivacyLastSeenSettings_AlwaysShareWith: String { return self._s[93]! } + public var Contacts_InviteFriends: String { return self._s[94]! } + public var Group_LinkedChannel: String { return self._s[95]! } + public var Notification_PassportValuePhone: String { return self._s[97]! } + public func InviteText_SingleContact(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[98]!, self._r[98]!, [_0]) } - public var Wallet_Configuration_BlockchainIdPlaceholder: String { return self._s[103]! } - public var AccessDenied_LocationDenied: String { return self._s[104]! } - public var CallSettings_RecentCalls: String { return self._s[105]! } - public var ConversationProfile_LeaveDeleteAndExit: String { return self._s[106]! } - public var Conversation_Dice_u1F3C0: String { return self._s[107]! } - public var Channel_Members_AddAdminErrorBlacklisted: String { return self._s[109]! } - public var Passport_Authorize: String { return self._s[110]! } - public var StickerPacksSettings_ArchivedMasks_Info: String { return self._s[111]! } - public var AutoDownloadSettings_Videos: String { return self._s[112]! } - public var TwoStepAuth_ReEnterPasswordTitle: String { return self._s[113]! } - public var Wallet_Info_Send: String { return self._s[114]! } - public var AuthSessions_AddDevice_UrlLoginHint: String { return self._s[115]! } - public var Wallet_TransactionInfo_SendGrams: String { return self._s[116]! } - public var Tour_StartButton: String { return self._s[117]! } - public var Watch_AppName: String { return self._s[119]! } - public var Settings_AddAnotherAccount: String { return self._s[120]! } - public var StickerPack_ErrorNotFound: String { return self._s[121]! } - public var Channel_Info_Subscribers: String { return self._s[122]! } - public func Channel_AdminLog_MessageGroupPreHistoryVisible(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[123]!, self._r[123]!, [_0]) + public var UserInfo_BotHelp: String { return self._s[100]! } + public var Passport_Identity_MainPage: String { return self._s[102]! } + public var LogoutOptions_ContactSupportText: String { return self._s[103]! } + public func VoiceOver_Chat_Title(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[104]!, self._r[104]!, [_0]) } - public func DialogList_PinLimitError(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[124]!, self._r[124]!, [_0]) + public var StickerPack_ShowStickers: String { return self._s[106]! } + public var AttachmentMenu_PhotoOrVideo: String { return self._s[107]! } + public var Map_Satellite: String { return self._s[108]! } + public var Passport_Identity_MainPageHelp: String { return self._s[109]! } + public var Profile_About: String { return self._s[111]! } + public var Group_Setup_TypePrivate: String { return self._s[112]! } + public var Notifications_ChannelNotifications: String { return self._s[113]! } + public func Login_WillCallYou(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[114]!, self._r[114]!, [_0]) } - public var Appearance_RemoveTheme: String { return self._s[125]! } - public func Wallet_Info_TransactionBlockchainFee(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[126]!, self._r[126]!, [_0]) + public var WallpaperPreview_Motion: String { return self._s[115]! } + public var Message_VideoMessage: String { return self._s[116]! } + public var SharedMedia_CategoryOther: String { return self._s[117]! } + public var Passport_FieldIdentityUploadHelp: String { return self._s[118]! } + public var PUSH_REMINDER_TITLE: String { return self._s[119]! } + public var Appearance_ThemePreview_Chat_3_Text: String { return self._s[121]! } + public var Login_ResetAccountProtected_Reset: String { return self._s[123]! } + public var Passport_Identity_TypeInternalPassportUploadScan: String { return self._s[124]! } + public var ChatList_PeerTypeContact: String { return self._s[125]! } + public var Stickers_SuggestAll: String { return self._s[127]! } + public var EmptyGroupInfo_Line3: String { return self._s[128]! } + public var Login_InvalidPhoneError: String { return self._s[129]! } + public var MediaPicker_GroupDescription: String { return self._s[130]! } + public var NetworkUsageSettings_MediaDocumentDataSection: String { return self._s[131]! } + public var Conversation_PrivateChannelTimeLimitedAlertText: String { return self._s[132]! } + public var PrivateDataSettings_Title: String { return self._s[133]! } + public var SecretChat_Title: String { return self._s[134]! } + public var Privacy_ChatsTitle: String { return self._s[135]! } + public var EditProfile_NameAndPhotoHelp: String { return self._s[136]! } + public var Watch_MessageView_Forward: String { return self._s[138]! } + public var ChannelMembers_WhoCanAddMembers_AllMembers: String { return self._s[139]! } + public func PUSH_PINNED_QUIZ(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[140]!, self._r[140]!, [_1, _2]) } - public var Conversation_StopLiveLocation: String { return self._s[129]! } - public var Channel_AdminLogFilter_EventsAll: String { return self._s[130]! } - public var GroupInfo_InviteLink_CopyAlert_Success: String { return self._s[132]! } - public var Username_LinkCopied: String { return self._s[134]! } - public var GroupRemoved_Title: String { return self._s[135]! } - public var SecretVideo_Title: String { return self._s[136]! } - public func PUSH_PINNED_VIDEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[137]!, self._r[137]!, [_1]) + public var PhotoEditor_DiscardChanges: String { return self._s[141]! } + public var SocksProxySetup_AdNoticeHelp: String { return self._s[142]! } + public var Date_DialogDateFormat: String { return self._s[143]! } + public var SettingsSearch_Synonyms_Proxy_Title: String { return self._s[144]! } + public var Notifications_AlertTones: String { return self._s[145]! } + public var Permissions_SiriAllow_v0: String { return self._s[146]! } + public var Tour_StartButton: String { return self._s[147]! } + public var Stats_InstantViewInteractionsTitle: String { return self._s[148]! } + public var UserInfo_ScamUserWarning: String { return self._s[150]! } + public var NotificationsSound_Chime: String { return self._s[151]! } + public var Update_Skip: String { return self._s[152]! } + public func ChannelInfo_ChannelForbidden(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[153]!, self._r[153]!, [_0]) } - public var AccessDenied_PhotosAndVideos: String { return self._s[138]! } - public var Appearance_ThemePreview_Chat_1_Text: String { return self._s[139]! } - public func PUSH_CHANNEL_MESSAGE_GEOLIVE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[141]!, self._r[141]!, [_1]) + public var SettingsSearch_Synonyms_EditProfile_PhoneNumber: String { return self._s[154]! } + public var Notifications_PermissionsTitle: String { return self._s[155]! } + public var Channel_AdminLog_BanSendMedia: String { return self._s[156]! } + public var Wallet_Receive_CommentHeader: String { return self._s[157]! } + public var Notifications_Badge_CountUnreadMessages: String { return self._s[158]! } + public var Appearance_AppIcon: String { return self._s[159]! } + public var Passport_Identity_FilesUploadNew: String { return self._s[160]! } + public func Passport_Email_UseTelegramEmail(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[161]!, self._r[161]!, [_0]) } - public var Map_OpenInGoogleMaps: String { return self._s[143]! } - public var Conversation_Dice_u26BD: String { return self._s[144]! } - public func Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[145]!, self._r[145]!, [_1, _2, _3]) + public var CreatePoll_QuizTitle: String { return self._s[162]! } + public var DialogList_DeleteConversationConfirmation: String { return self._s[163]! } + public var NotificationsSound_Calypso: String { return self._s[164]! } + public var ChannelMembers_GroupAdminsTitle: String { return self._s[165]! } + public var Checkout_NewCard_PaymentCard: String { return self._s[166]! } + public var Wallpaper_SetCustomBackground: String { return self._s[168]! } + public var Conversation_ContextMenuOpenProfile: String { return self._s[169]! } + public func PUSH_MESSAGE_VIDEO_SECRET(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[171]!, self._r[171]!, [_1]) } - public func Channel_AdminLog_MessageKickedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[146]!, self._r[146]!, [_1, _2]) + public var AuthSessions_Terminate: String { return self._s[172]! } + public var ShareFileTip_CloseTip: String { return self._s[173]! } + public var ChatSettings_DownloadInBackgroundInfo: String { return self._s[174]! } + public var Channel_Moderator_AccessLevelRevoke: String { return self._s[175]! } + public var Channel_AdminLogFilter_EventsDeletedMessages: String { return self._s[176]! } + public var Passport_Language_fr: String { return self._s[177]! } + public func Watch_Time_ShortTodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[179]!, self._r[179]!, [_0]) } - public var Call_StatusRinging: String { return self._s[147]! } - public var SettingsSearch_Synonyms_EditProfile_Username: String { return self._s[148]! } - public var Group_Username_InvalidStartsWithNumber: String { return self._s[149]! } - public var UserInfo_NotificationsEnabled: String { return self._s[150]! } - public var PeopleNearby_MakeVisibleDescription: String { return self._s[151]! } - public var Settings_RemoveConfirmation: String { return self._s[152]! } - public var ChatListFolder_CategoryRead: String { return self._s[153]! } - public var Map_Search: String { return self._s[154]! } - public var ClearCache_StorageFree: String { return self._s[156]! } - public var Login_TermsOfServiceHeader: String { return self._s[157]! } - public func Notification_PinnedVideoMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[158]!, self._r[158]!, [_0]) + public var Passport_Identity_TypeIdentityCard: String { return self._s[180]! } + public func Conversation_OpenBotLinkAllowMessages(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[181]!, self._r[181]!, [_0]) } - public func Channel_AdminLog_MessageToggleSignaturesOn(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[160]!, self._r[160]!, [_0]) + public var ReportPeer_ReasonCopyright: String { return self._s[182]! } + public var Permissions_PeopleNearbyText_v0: String { return self._s[184]! } + public var Channel_Stickers_NotFoundHelp: String { return self._s[185]! } + public var Passport_Identity_AddDriversLicense: String { return self._s[186]! } + public func Wallet_Sent_Text(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[187]!, self._r[187]!, [_0]) } - public var ChatList_GenericPsaAlert: String { return self._s[161]! } - public var Wallet_Sent_Title: String { return self._s[162]! } - public var TwoStepAuth_SetupPasswordConfirmPassword: String { return self._s[163]! } - public var Weekday_Today: String { return self._s[164]! } - public var Stats_InstantViewInteractionsTitle: String { return self._s[165]! } - public func InstantPage_AuthorAndDateTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[167]!, self._r[167]!, [_1, _2]) + public var Permissions_SiriAllowInSettings_v0: String { return self._s[188]! } + public var AutoDownloadSettings_AutodownloadFiles: String { return self._s[189]! } + public var ApplyLanguage_ChangeLanguageTitle: String { return self._s[190]! } + public var Map_LocatingError: String { return self._s[192]! } + public var ChatSettings_AutoDownloadSettings_TypePhoto: String { return self._s[193]! } + public func VoiceOver_Chat_MusicFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[195]!, self._r[195]!, [_0]) } - public func Conversation_MessageDialogRetryAll(_ _1: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[168]!, self._r[168]!, ["\(_1)"]) + public func Contacts_AccessDeniedHelpLandscape(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[196]!, self._r[196]!, [_0]) } - public var Notification_PassportValuePersonalDetails: String { return self._s[170]! } - public var ProfilePhoto_SearchWeb: String { return self._s[171]! } - public var Channel_AdminLog_MessagePreviousLink: String { return self._s[172]! } - public var ChangePhoneNumberNumber_NewNumber: String { return self._s[173]! } - public var ApplyLanguage_LanguageNotSupportedError: String { return self._s[174]! } - public var TwoStepAuth_ChangePasswordDescription: String { return self._s[175]! } - public var PhotoEditor_BlurToolLinear: String { return self._s[176]! } - public var Contacts_PermissionsAllowInSettings: String { return self._s[177]! } - public var Weekday_ShortMonday: String { return self._s[178]! } - public var Cache_KeepMedia: String { return self._s[179]! } - public var Passport_FieldIdentitySelfieHelp: String { return self._s[180]! } - public func PUSH_PINNED_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[181]!, self._r[181]!, [_1, _2]) + public var Channel_AdminLog_EmptyFilterText: String { return self._s[197]! } + public var Login_SmsRequestState2: String { return self._s[198]! } + public var Conversation_Unmute: String { return self._s[200]! } + public var TwoFactorSetup_Intro_Text: String { return self._s[201]! } + public var Channel_AdminLog_BanSendMessages: String { return self._s[202]! } + public func Channel_Management_RemovedBy(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[203]!, self._r[203]!, [_0]) } - public func Chat_SlowmodeTooltip(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[182]!, self._r[182]!, [_0]) + public var AccessDenied_LocationDenied: String { return self._s[204]! } + public var Share_AuthTitle: String { return self._s[205]! } + public var Month_ShortAugust: String { return self._s[206]! } + public func Notification_PinnedDeletedMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[207]!, self._r[207]!, [_0]) } - public var Wallet_Receive_ShareUrlInfo: String { return self._s[183]! } - public var Conversation_ClousStorageInfo_Description4: String { return self._s[184]! } - public var Wallet_RestoreFailed_Title: String { return self._s[185]! } - public var Passport_Language_ru: String { return self._s[186]! } - public func Notification_CreatedChatWithTitle(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[187]!, self._r[187]!, [_0, _1]) + public var SettingsSearch_Synonyms_Data_DownloadInBackground: String { return self._s[209]! } + public func PUSH_CONTACT_JOINED(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[210]!, self._r[210]!, [_1]) } - public var WallpaperPreview_PatternIntensity: String { return self._s[188]! } - public var ChatList_EditFolder: String { return self._s[191]! } - public var ChatList_AutoarchiveSuggestion_Title: String { return self._s[192]! } - public var WebBrowser_InAppSafari: String { return self._s[193]! } - public var TwoStepAuth_RecoveryUnavailable: String { return self._s[194]! } - public var EnterPasscode_TouchId: String { return self._s[195]! } - public var PhotoEditor_QualityVeryHigh: String { return self._s[198]! } - public var Checkout_NewCard_SaveInfo: String { return self._s[200]! } - public var Gif_NoGifsPlaceholder: String { return self._s[202]! } - public func Notification_InvitedMultiple(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[204]!, self._r[204]!, [_0, _1]) + public var Channel_BanUser_PermissionSendMedia: String { return self._s[211]! } + public var WallpaperSearch_ColorTitle: String { return self._s[213]! } + public var Wallpaper_Search: String { return self._s[214]! } + public var ClearCache_StorageUsage: String { return self._s[215]! } + public var CreatePoll_TextPlaceholder: String { return self._s[216]! } + public var Conversation_EditingMessagePanelTitle: String { return self._s[217]! } + public var Channel_EditAdmin_PermissionBanUsers: String { return self._s[218]! } + public var OldChannels_NoticeCreateText: String { return self._s[219]! } + public var ProfilePhoto_MainVideo: String { return self._s[220]! } + public var UserInfo_NotificationsDisabled: String { return self._s[221]! } + public var Wallet_Info_WalletCreated: String { return self._s[222]! } + public var Map_Unknown: String { return self._s[223]! } + public var Notifications_MessageNotificationsAlert: String { return self._s[224]! } + public var Conversation_StopQuiz: String { return self._s[225]! } + public var Checkout_LiabilityAlertTitle: String { return self._s[226]! } + public func Username_UsernameIsAvailable(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[227]!, self._r[227]!, [_0]) } - public var ChatSettings_AutoDownloadEnabled: String { return self._s[205]! } - public var NetworkUsageSettings_BytesSent: String { return self._s[206]! } - public var Checkout_PasswordEntry_Pay: String { return self._s[207]! } - public var AuthSessions_TerminateSession: String { return self._s[208]! } - public var Message_File: String { return self._s[209]! } - public var MediaPicker_VideoMuteDescription: String { return self._s[210]! } - public var SocksProxySetup_ProxyStatusConnected: String { return self._s[211]! } - public var TwoStepAuth_RecoveryCode: String { return self._s[212]! } - public var EnterPasscode_EnterCurrentPasscode: String { return self._s[213]! } - public func TwoStepAuth_EnterPasswordHint(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[214]!, self._r[214]!, [_0]) - } - public var Conversation_Moderate_Report: String { return self._s[216]! } - public var TwoStepAuth_EmailInvalid: String { return self._s[217]! } - public var Passport_Language_ms: String { return self._s[218]! } - public var Channel_Edit_AboutItem: String { return self._s[220]! } - public var DialogList_SearchSectionGlobal: String { return self._s[224]! } - public var AttachmentMenu_WebSearch: String { return self._s[225]! } - public var ChatState_WaitingForNetwork: String { return self._s[226]! } - public var Channel_BanUser_Title: String { return self._s[227]! } - public var PasscodeSettings_TurnPasscodeOn: String { return self._s[228]! } - public var WallpaperPreview_SwipeTopText: String { return self._s[229]! } - public var ChatList_DeleteSavedMessagesConfirmationText: String { return self._s[230]! } - public var ArchivedChats_IntroText2: String { return self._s[231]! } - public var ChatSearch_SearchPlaceholder: String { return self._s[233]! } - public var Conversation_OpenBotLinkTitle: String { return self._s[234]! } - public var Passport_FieldAddressTranslationHelp: String { return self._s[235]! } - public var NotificationsSound_Aurora: String { return self._s[236]! } - public var Notification_Exceptions_DeleteAll: String { return self._s[237]! } - public func FileSize_GB(_ _0: String) -> (String, [(Int, NSRange)]) { + public var CreatePoll_OptionPlaceholder: String { return self._s[228]! } + public var Wallet_Month_GenJanuary: String { return self._s[229]! } + public var Conversation_RestrictedStickers: String { return self._s[230]! } + public var MemberSearch_BotSection: String { return self._s[232]! } + public var Channel_Management_AddModeratorHelp: String { return self._s[233]! } + public var MaskStickerSettings_Title: String { return self._s[234]! } + public var ShareMenu_Comment: String { return self._s[235]! } + public var GroupInfo_Notifications: String { return self._s[236]! } + public var CheckoutInfo_ReceiverInfoTitle: String { return self._s[237]! } + public func DialogList_EncryptedChatStartedOutgoing(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[238]!, self._r[238]!, [_0]) } - public var AuthSessions_LoggedInWithTelegram: String { return self._s[241]! } - public func Privacy_GroupsAndChannels_InviteToGroupError(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[242]!, self._r[242]!, [_0, _1]) - } - public var Passport_PasswordNext: String { return self._s[243]! } - public var Bot_GroupStatusReadsHistory: String { return self._s[244]! } - public var EmptyGroupInfo_Line2: String { return self._s[245]! } - public func Channel_AdminLog_MessageTransferedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[246]!, self._r[246]!, [_1, _2]) - } - public var VoiceOver_Chat_SeenByRecipients: String { return self._s[247]! } - public var Settings_FAQ_Intro: String { return self._s[250]! } - public var PrivacySettings_PasscodeAndTouchId: String { return self._s[252]! } - public var FeaturedStickerPacks_Title: String { return self._s[253]! } - public var TwoStepAuth_PasswordRemoveConfirmation: String { return self._s[255]! } - public var Username_Title: String { return self._s[256]! } - public func Message_StickerText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[257]!, self._r[257]!, [_0]) - } - public var PeerInfo_PaneFiles: String { return self._s[258]! } - public var PasscodeSettings_AlphanumericCode: String { return self._s[259]! } - public var Localization_LanguageOther: String { return self._s[260]! } - public var Stickers_SuggestStickers: String { return self._s[261]! } - public func Channel_AdminLog_MessageRemovedGroupUsername(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[262]!, self._r[262]!, [_0]) - } - public var NotificationSettings_ShowNotificationsFromAccountsSection: String { return self._s[263]! } - public var Channel_AdminLogFilter_EventsAdmins: String { return self._s[264]! } - public var Conversation_DefaultRestrictedStickers: String { return self._s[265]! } - public func Notification_PinnedDeletedMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[266]!, self._r[266]!, [_0]) - } - public var Wallet_TransactionInfo_CopyAddress: String { return self._s[268]! } - public var Group_UpgradeConfirmation: String { return self._s[270]! } - public var DialogList_Unpin: String { return self._s[271]! } - public var Passport_Identity_DateOfBirth: String { return self._s[273]! } - public var Month_ShortOctober: String { return self._s[274]! } - public var SettingsSearch_Synonyms_Privacy_Data_ContactsSync: String { return self._s[275]! } - public var TwoFactorSetup_Done_Text: String { return self._s[276]! } - public var Notification_CallCanceledShort: String { return self._s[277]! } - public var Conversation_StopQuiz: String { return self._s[278]! } - public var Passport_Phone_Help: String { return self._s[279]! } - public var Passport_Language_az: String { return self._s[281]! } - public var CreatePoll_TextPlaceholder: String { return self._s[283]! } - public var VoiceOver_Chat_AnonymousPoll: String { return self._s[284]! } - public var Passport_Identity_DocumentNumber: String { return self._s[285]! } - public var PhotoEditor_CurvesRed: String { return self._s[287]! } - public var PhoneNumberHelp_Alert: String { return self._s[289]! } - public var Stats_GroupTopPostersTitle: String { return self._s[290]! } - public var SocksProxySetup_Port: String { return self._s[291]! } - public var Checkout_PayNone: String { return self._s[292]! } - public var AutoDownloadSettings_WiFi: String { return self._s[293]! } - public var GroupInfo_GroupType: String { return self._s[294]! } - public var StickerSettings_ContextHide: String { return self._s[295]! } - public var Passport_Address_OneOfTypeTemporaryRegistration: String { return self._s[296]! } - public var Group_Setup_HistoryTitle: String { return self._s[298]! } - public var Passport_Identity_FilesUploadNew: String { return self._s[299]! } - public var PasscodeSettings_AutoLock: String { return self._s[300]! } - public var Passport_Title: String { return self._s[301]! } - public var VoiceOver_Chat_ContactPhoneNumber: String { return self._s[302]! } - public var Channel_AdminLogFilter_EventsNewSubscribers: String { return self._s[303]! } - public var GroupPermission_NoSendGifs: String { return self._s[304]! } - public var PrivacySettings_PasscodeOn: String { return self._s[305]! } - public func Conversation_ScheduleMessage_SendTomorrow(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[306]!, self._r[306]!, [_0]) - } - public var ChatList_PeerTypeNonContact: String { return self._s[309]! } - public var State_WaitingForNetwork: String { return self._s[310]! } - public func Notification_Invited(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[311]!, self._r[311]!, [_0, _1]) - } - public var Calls_NotNow: String { return self._s[313]! } - public func Channel_DiscussionGroup_HeaderSet(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[314]!, self._r[314]!, [_0]) - } - public var UserInfo_SendMessage: String { return self._s[315]! } - public var PhotoEditor_SelectCoverFrame: String { return self._s[316]! } - public var ChatList_AutoarchiveSuggestion_Text: String { return self._s[317]! } - public var TwoStepAuth_PasswordSet: String { return self._s[318]! } - public var Passport_DeleteDocument: String { return self._s[319]! } - public var SocksProxySetup_AddProxyTitle: String { return self._s[320]! } - public func PUSH_MESSAGE_VIDEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[321]!, self._r[321]!, [_1]) - } - public var AuthSessions_AddedDeviceTitle: String { return self._s[322]! } - public var GroupRemoved_Remove: String { return self._s[323]! } - public var Passport_FieldIdentity: String { return self._s[324]! } - public var Group_Setup_TypePrivateHelp: String { return self._s[325]! } - public var Conversation_Processing: String { return self._s[328]! } - public var Wallet_Settings_BackupWallet: String { return self._s[330]! } - public var ChatListFolder_NameNonMuted: String { return self._s[331]! } - public var ChatSettings_AutoPlayAnimations: String { return self._s[332]! } - public var AuthSessions_LogOutApplicationsHelp: String { return self._s[335]! } - public var Forward_ErrorPublicQuizDisabledInChannels: String { return self._s[336]! } - public var Month_GenFebruary: String { return self._s[337]! } - public var ChatListFilter_AddChatsTitle: String { return self._s[338]! } - public var Wallet_Send_NetworkErrorTitle: String { return self._s[339]! } - public var Stats_GroupTopPoster_History: String { return self._s[341]! } - public func Login_InvalidPhoneEmailBody(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[342]!, self._r[342]!, [_1, _2, _3, _4, _5]) - } - public var Passport_Identity_TypeIdentityCard: String { return self._s[343]! } - public var Wallet_Month_ShortJune: String { return self._s[345]! } - public var AutoDownloadSettings_DataUsageMedium: String { return self._s[346]! } - public var GroupInfo_AddParticipant: String { return self._s[347]! } - public var KeyCommand_SendMessage: String { return self._s[348]! } - public var VoiceOver_Chat_YourContact: String { return self._s[350]! } - public var Map_LiveLocationShowAll: String { return self._s[351]! } - public var WallpaperSearch_ColorOrange: String { return self._s[353]! } - public var Appearance_AppIconDefaultX: String { return self._s[354]! } - public var Checkout_Receipt_Title: String { return self._s[355]! } - public var Group_OwnershipTransfer_ErrorPrivacyRestricted: String { return self._s[356]! } - public var WallpaperPreview_PreviewTopText: String { return self._s[357]! } - public var Message_Contact: String { return self._s[359]! } - public var Call_StatusIncoming: String { return self._s[360]! } - public var Wallet_TransactionInfo_StorageFeeInfo: String { return self._s[361]! } - public func Channel_AdminLog_MessageKickedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[362]!, self._r[362]!, [_1]) - } - public func PUSH_ENCRYPTED_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[364]!, self._r[364]!, [_1]) - } - public var VoiceOver_Media_PlaybackRate: String { return self._s[365]! } - public var Passport_FieldIdentityDetailsHelp: String { return self._s[366]! } - public var Conversation_ViewChannel: String { return self._s[367]! } - public func Time_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[368]!, self._r[368]!, [_0]) - } - public var Theme_Colors_Accent: String { return self._s[369]! } - public var Paint_Arrow: String { return self._s[370]! } - public var Passport_Language_nl: String { return self._s[372]! } - public var Camera_Retake: String { return self._s[373]! } - public func UserInfo_BlockActionTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[374]!, self._r[374]!, [_0]) - } - public var AuthSessions_LogOutApplications: String { return self._s[375]! } - public var ApplyLanguage_ApplySuccess: String { return self._s[376]! } - public var Tour_Title6: String { return self._s[377]! } - public var Map_ChooseAPlace: String { return self._s[378]! } - public var CallSettings_Never: String { return self._s[380]! } - public func Notification_ChangedGroupPhoto(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[381]!, self._r[381]!, [_0]) - } - public var ChannelRemoved_RemoveInfo: String { return self._s[382]! } - public func AutoDownloadSettings_PreloadVideoInfo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[383]!, self._r[383]!, [_0]) - } - public var SettingsSearch_Synonyms_Notifications_MessageNotificationsExceptions: String { return self._s[384]! } - public func Conversation_ClearChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[385]!, self._r[385]!, [_0]) - } - public var GroupInfo_InviteLink_Title: String { return self._s[386]! } - public func Channel_AdminLog_MessageUnkickedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[387]!, self._r[387]!, [_1, _2]) - } - public var KeyCommand_ScrollUp: String { return self._s[388]! } - public var ContactInfo_URLLabelHomepage: String { return self._s[389]! } - public var Channel_OwnershipTransfer_ChangeOwner: String { return self._s[390]! } - public func Channel_AdminLog_DisabledSlowmode(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[391]!, self._r[391]!, [_0]) - } - public var TwoFactorSetup_Done_Title: String { return self._s[392]! } - public func Conversation_EncryptedPlaceholderTitleOutgoing(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[393]!, self._r[393]!, [_0]) - } - public var CallFeedback_ReasonDistortedSpeech: String { return self._s[394]! } - public var Watch_LastSeen_WithinAWeek: String { return self._s[395]! } - public var ContactList_Context_SendMessage: String { return self._s[397]! } - public var Weekday_Tuesday: String { return self._s[398]! } - public var Wallet_Created_Title: String { return self._s[400]! } - public var ScheduledMessages_Delete: String { return self._s[401]! } - public var UserInfo_StartSecretChat: String { return self._s[402]! } - public var Passport_Identity_FilesTitle: String { return self._s[403]! } - public var Permissions_NotificationsAllow_v0: String { return self._s[404]! } - public var DialogList_DeleteConversationConfirmation: String { return self._s[406]! } - public var ChatList_UndoArchiveRevealedTitle: String { return self._s[407]! } - public func Wallet_Configuration_ApplyErrorTextURLUnreachable(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[408]!, self._r[408]!, [_0]) - } - public var AuthSessions_Sessions: String { return self._s[409]! } - public var Conversation_PeerNearbyText: String { return self._s[410]! } - public func Settings_KeepPhoneNumber(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[413]!, self._r[413]!, [_0]) - } - public var TwoStepAuth_RecoveryEmailChangeDescription: String { return self._s[414]! } - public var Call_StatusWaiting: String { return self._s[415]! } - public var CreateGroup_SoftUserLimitAlert: String { return self._s[416]! } - public var FastTwoStepSetup_HintHelp: String { return self._s[417]! } - public var WallpaperPreview_CustomColorBottomText: String { return self._s[418]! } - public var EditTheme_Expand_Preview_OutgoingText: String { return self._s[419]! } - public var LogoutOptions_AddAccountText: String { return self._s[420]! } - public var PasscodeSettings_6DigitCode: String { return self._s[421]! } - public var Settings_LogoutConfirmationText: String { return self._s[422]! } - public var ProfilePhoto_OpenGallery: String { return self._s[423]! } - public var Passport_Identity_TypePassport: String { return self._s[425]! } - public var Map_Work: String { return self._s[428]! } - public func PUSH_MESSAGE_VIDEOS(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[429]!, self._r[429]!, [_1, _2]) - } - public var SocksProxySetup_SaveProxy: String { return self._s[430]! } - public var AccessDenied_SaveMedia: String { return self._s[431]! } - public var Checkout_ErrorInvoiceAlreadyPaid: String { return self._s[433]! } - public var CreatePoll_MultipleChoice: String { return self._s[434]! } - public var Settings_Title: String { return self._s[436]! } - public var VoiceOver_Chat_RecordModeVideoMessageInfo: String { return self._s[437]! } - public var Contacts_InviteSearchLabel: String { return self._s[439]! } - public var PrivacySettings_WebSessions: String { return self._s[440]! } - public var ConvertToSupergroup_Title: String { return self._s[441]! } - public func Channel_AdminLog_CaptionEdited(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[442]!, self._r[442]!, [_0]) - } - public var TwoFactorSetup_Hint_Text: String { return self._s[443]! } - public var InfoPlist_NSSiriUsageDescription: String { return self._s[444]! } - public func PUSH_MESSAGE_CHANNEL_MESSAGE_GAME_SCORE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[445]!, self._r[445]!, [_1, _2, _3]) - } - public var ChatSettings_AutomaticPhotoDownload: String { return self._s[446]! } - public var UserInfo_BotHelp: String { return self._s[447]! } - public var PrivacySettings_LastSeenEverybody: String { return self._s[448]! } - public var Checkout_Name: String { return self._s[449]! } - public var AutoDownloadSettings_DataUsage: String { return self._s[450]! } - public var Channel_BanUser_BlockFor: String { return self._s[451]! } - public var Checkout_ShippingAddress: String { return self._s[452]! } - public var AutoDownloadSettings_MaxVideoSize: String { return self._s[453]! } - public var Privacy_PaymentsClearInfoDoneHelp: String { return self._s[454]! } - public var Privacy_Forwards: String { return self._s[455]! } - public var Channel_BanUser_PermissionSendPolls: String { return self._s[456]! } - public var Appearance_ThemeCarouselNewNight: String { return self._s[457]! } - public func SecretVideo_NotViewedYet(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[460]!, self._r[460]!, [_0]) - } - public var Contacts_SortedByName: String { return self._s[461]! } - public var Group_OwnershipTransfer_Title: String { return self._s[462]! } - public var PeerInfo_BioExpand: String { return self._s[464]! } - public var VoiceOver_Chat_OpenHint: String { return self._s[465]! } - public var Group_LeaveGroup: String { return self._s[466]! } - public var Settings_UsernameEmpty: String { return self._s[467]! } - public func Notification_PinnedPollMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[468]!, self._r[468]!, [_0]) - } - public func TwoStepAuth_ConfirmEmailDescription(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[469]!, self._r[469]!, [_1]) - } - public func Channel_OwnershipTransfer_DescriptionInfo(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[470]!, self._r[470]!, [_1, _2]) - } - public var Message_ImageExpired: String { return self._s[471]! } - public var TwoStepAuth_RecoveryFailed: String { return self._s[473]! } - public var EditTheme_Edit_Preview_OutgoingText: String { return self._s[474]! } - public var UserInfo_AddToExisting: String { return self._s[475]! } - public var TwoStepAuth_EnabledSuccess: String { return self._s[476]! } - public var Wallet_Send_SyncInProgress: String { return self._s[477]! } - public var ChatListFolderSettings_RecommendedFoldersSection: String { return self._s[478]! } - public var ChatListFolder_IncludeSectionInfo: String { return self._s[479]! } - public var SettingsSearch_Synonyms_Appearance_ChatBackground_SetColor: String { return self._s[480]! } - public func PUSH_CHANNEL_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[481]!, self._r[481]!, [_1]) - } - public var Notifications_GroupNotificationsAlert: String { return self._s[482]! } - public var Passport_Language_km: String { return self._s[483]! } - public var SocksProxySetup_AdNoticeHelp: String { return self._s[485]! } - public var VoiceOver_Media_PlaybackPlay: String { return self._s[486]! } - public var Notification_CallMissedShort: String { return self._s[487]! } - public var Wallet_Info_YourBalance: String { return self._s[488]! } - public var ReportPeer_ReasonOther_Send: String { return self._s[490]! } - public var Watch_Compose_Send: String { return self._s[491]! } - public var Passport_Identity_TypeInternalPassportUploadScan: String { return self._s[494]! } - public var TwoFactorSetup_Email_Action: String { return self._s[495]! } - public var Conversation_HoldForVideo: String { return self._s[496]! } - public var Wallet_Configuration_ApplyErrorTextURLInvalidData: String { return self._s[497]! } - public var AuthSessions_OtherDevices: String { return self._s[498]! } - public var Wallet_TransactionInfo_CommentHeader: String { return self._s[499]! } - public var CheckoutInfo_ErrorCityInvalid: String { return self._s[501]! } - public var Appearance_AutoNightThemeDisabled: String { return self._s[503]! } - public var Channel_LinkItem: String { return self._s[504]! } - public func PrivacySettings_LastSeenContactsMinusPlus(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[505]!, self._r[505]!, [_0, _1]) - } - public func Passport_Identity_NativeNameTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[508]!, self._r[508]!, [_0]) - } - public var VoiceOver_Recording_StopAndPreview: String { return self._s[509]! } - public var Passport_Language_dv: String { return self._s[510]! } - public var Undo_LeftChannel: String { return self._s[511]! } - public var Notifications_ExceptionsMuted: String { return self._s[512]! } - public var ChatList_UnhideAction: String { return self._s[513]! } - public var Conversation_ContextMenuShare: String { return self._s[515]! } - public var Conversation_ContextMenuStickerPackInfo: String { return self._s[516]! } - public var ShareFileTip_Title: String { return self._s[517]! } - public var NotificationsSound_Chord: String { return self._s[518]! } - public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[519]! } - public func PUSH_CHAT_RETURNED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[520]!, self._r[520]!, [_1, _2]) - } - public var PeerInfo_ButtonVideoCall: String { return self._s[521]! } - public var Passport_Address_EditTemporaryRegistration: String { return self._s[522]! } - public func Notification_Joined(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[523]!, self._r[523]!, [_0]) - } - public func Wallet_Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[524]!, self._r[524]!, [_1, _2, _3]) - } - public var Wallet_Settings_ConfigurationInfo: String { return self._s[525]! } - public var Wallpaper_ErrorNotFound: String { return self._s[526]! } - public var Notification_CallOutgoingShort: String { return self._s[528]! } - public var Wallet_WordImport_IncorrectText: String { return self._s[529]! } - public func Watch_Time_ShortFullAt(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[530]!, self._r[530]!, [_1, _2]) - } - public var Passport_Address_TypeUtilityBill: String { return self._s[531]! } - public var Privacy_Forwards_LinkIfAllowed: String { return self._s[532]! } - public var ReportPeer_Report: String { return self._s[533]! } - public var SettingsSearch_Synonyms_Proxy_Title: String { return self._s[534]! } - public var GroupInfo_DeactivatedStatus: String { return self._s[535]! } - public func VoiceOver_Chat_MusicTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[536]!, self._r[536]!, [_1, _2]) - } - public var StickerPack_Send: String { return self._s[537]! } - public var Login_CodeSentInternal: String { return self._s[538]! } - public var Wallet_Month_GenJanuary: String { return self._s[539]! } - public var GroupInfo_InviteLink_LinkSection: String { return self._s[541]! } - public func Channel_AdminLog_MessageDeleted(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[542]!, self._r[542]!, [_0]) - } - public func Conversation_EncryptionWaiting(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[544]!, self._r[544]!, [_0]) - } - public var Channel_BanUser_PermissionSendStickersAndGifs: String { return self._s[545]! } - public func PUSH_PINNED_GAME(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[546]!, self._r[546]!, [_1]) - } - public var ReportPeer_ReasonViolence: String { return self._s[548]! } - public var Appearance_ShareThemeColor: String { return self._s[549]! } - public var Map_Locating: String { return self._s[550]! } - public func VoiceOver_Chat_VideoFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[551]!, self._r[551]!, [_0]) - } - public func PUSH_ALBUM(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[552]!, self._r[552]!, [_1]) - } - public var ChatListFolderSettings_FoldersSection: String { return self._s[553]! } - public var AutoDownloadSettings_GroupChats: String { return self._s[555]! } - public var CheckoutInfo_SaveInfo: String { return self._s[556]! } - public var ChatList_ChatTypesSection: String { return self._s[557]! } - public var SharedMedia_EmptyLinksText: String { return self._s[559]! } - public var Passport_Address_CityPlaceholder: String { return self._s[560]! } - public var CheckoutInfo_ErrorStateInvalid: String { return self._s[561]! } - public var Privacy_ProfilePhoto_CustomHelp: String { return self._s[562]! } - public var Wallet_Send_OwnAddressAlertTitle: String { return self._s[564]! } - public var Channel_AdminLog_CanAddAdmins: String { return self._s[565]! } - public func PUSH_CHANNEL_MESSAGE_FWD(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[566]!, self._r[566]!, [_1]) - } - public func Time_MonthOfYear_m8(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[567]!, self._r[567]!, [_0]) - } - public var InfoPlist_NSLocationWhenInUseUsageDescription: String { return self._s[568]! } - public var GroupInfo_InviteLink_RevokeAlert_Success: String { return self._s[569]! } - public var ChangePhoneNumberCode_Code: String { return self._s[570]! } - public var Appearance_CreateTheme: String { return self._s[571]! } - public func UserInfo_NotificationsDefaultSound(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[572]!, self._r[572]!, [_0]) - } - public var TwoStepAuth_SetupEmail: String { return self._s[573]! } - public var HashtagSearch_AllChats: String { return self._s[574]! } - public var MediaPlayer_UnknownTrack: String { return self._s[575]! } - public var SettingsSearch_Synonyms_Data_AutoDownloadUsingCellular: String { return self._s[577]! } - public func ChatList_DeleteForEveryone(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[578]!, self._r[578]!, [_0]) - } - public var Chat_Gifs_SavedSectionHeader: String { return self._s[579]! } - public var PhotoEditor_QualityHigh: String { return self._s[581]! } - public func Passport_Phone_UseTelegramNumber(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[582]!, self._r[582]!, [_0]) - } - public var ApplyLanguage_ApplyLanguageAction: String { return self._s[583]! } - public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsPreview: String { return self._s[584]! } - public var Message_LiveLocation: String { return self._s[585]! } - public var Cache_LowDiskSpaceText: String { return self._s[586]! } - public var Wallet_Receive_ShareAddress: String { return self._s[587]! } - public var EditTheme_ErrorLinkTaken: String { return self._s[589]! } - public var Conversation_SendMessage: String { return self._s[590]! } - public var AuthSessions_EmptyTitle: String { return self._s[591]! } - public var Privacy_PhoneNumber: String { return self._s[592]! } - public var PeopleNearby_CreateGroup: String { return self._s[593]! } - public var Stats_SharesPerPost: String { return self._s[595]! } - public var CallSettings_UseLessData: String { return self._s[596]! } - public var NetworkUsageSettings_MediaDocumentDataSection: String { return self._s[597]! } - public var Stickers_AddToFavorites: String { return self._s[598]! } - public var Wallet_WordImport_Title: String { return self._s[599]! } - public var PhotoEditor_QualityLow: String { return self._s[600]! } - public var Watch_UserInfo_Unblock: String { return self._s[601]! } - public var Settings_Logout: String { return self._s[602]! } - public func PUSH_MESSAGE_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[603]!, self._r[603]!, [_1]) - } - public var ContactInfo_PhoneLabelWork: String { return self._s[604]! } - public var ChannelInfo_Stats: String { return self._s[605]! } - public var TextFormat_Link: String { return self._s[606]! } - public func Date_ChatDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[607]!, self._r[607]!, [_1, _2]) - } - public var Paint_Framed: String { return self._s[608]! } - public var Wallet_TransactionInfo_Title: String { return self._s[609]! } - public func Message_ForwardedMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[610]!, self._r[610]!, [_0]) - } - public var Watch_Notification_Joined: String { return self._s[611]! } - public var Group_Setup_TypePublicHelp: String { return self._s[612]! } - public var Passport_Scans_UploadNew: String { return self._s[613]! } - public var Checkout_LiabilityAlertTitle: String { return self._s[614]! } - public var DialogList_Title: String { return self._s[617]! } - public var NotificationSettings_ContactJoined: String { return self._s[618]! } - public var GroupInfo_LabelAdmin: String { return self._s[619]! } - public var KeyCommand_ChatInfo: String { return self._s[620]! } - public var Conversation_EditingCaptionPanelTitle: String { return self._s[621]! } - public var Call_ReportIncludeLog: String { return self._s[622]! } - public func Notifications_ExceptionsChangeSound(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[625]!, self._r[625]!, [_0]) - } - public var Stats_Followers: String { return self._s[626]! } - public var Stats_GroupLanguagesTitle: String { return self._s[627]! } - public var Cache_NoLimit: String { return self._s[628]! } - public var Channel_AdminLog_InfoPanelChannelAlertText: String { return self._s[629]! } - public var ChatAdmins_AllMembersAreAdmins: String { return self._s[630]! } - public var LocalGroup_IrrelevantWarning: String { return self._s[631]! } - public var Conversation_DefaultRestrictedInline: String { return self._s[632]! } - public var Message_Sticker: String { return self._s[633]! } - public var LastSeen_JustNow: String { return self._s[635]! } - public var Passport_Email_EmailPlaceholder: String { return self._s[637]! } - public var SettingsSearch_Synonyms_AppLanguage: String { return self._s[638]! } - public var Channel_AdminLogFilter_EventsEditedMessages: String { return self._s[640]! } - public var Channel_EditAdmin_PermissionsHeader: String { return self._s[641]! } - public var TwoStepAuth_Email: String { return self._s[642]! } - public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsSound: String { return self._s[643]! } - public var PhotoEditor_BlurToolOff: String { return self._s[644]! } - public var Message_PinnedStickerMessage: String { return self._s[645]! } - public var ContactInfo_PhoneLabelPager: String { return self._s[646]! } - public var SettingsSearch_Synonyms_Appearance_TextSize: String { return self._s[647]! } - public var Passport_DiscardMessageTitle: String { return self._s[648]! } - public var Privacy_PaymentsTitle: String { return self._s[649]! } - public var EditTheme_Edit_Preview_IncomingReplyName: String { return self._s[650]! } - public var ClearCache_StorageCache: String { return self._s[651]! } - public var Cache_KeepMediaHelp: String { return self._s[652]! } - public var Appearance_TextSizeSetting: String { return self._s[653]! } - public var Channel_DiscussionGroup_Header: String { return self._s[655]! } - public var VoiceOver_Chat_OptionSelected: String { return self._s[656]! } - public var Appearance_ColorTheme: String { return self._s[657]! } - public var UserInfo_ShareContact: String { return self._s[658]! } - public var Passport_Address_TypePassportRegistration: String { return self._s[659]! } - public var Common_More: String { return self._s[660]! } - public var Watch_Message_Call: String { return self._s[661]! } - public var Profile_EncryptionKey: String { return self._s[664]! } - public var Privacy_TopPeers: String { return self._s[665]! } - public var Conversation_StopPollConfirmation: String { return self._s[666]! } - public var Wallet_Words_NotDoneText: String { return self._s[668]! } - public var Privacy_TopPeersWarning: String { return self._s[670]! } - public var SettingsSearch_Synonyms_Data_DownloadInBackground: String { return self._s[671]! } - public var SettingsSearch_Synonyms_Data_Storage_KeepMedia: String { return self._s[672]! } - public var Media_SendWithTimer: String { return self._s[675]! } - public var Wallet_RestoreFailed_EnterWords: String { return self._s[676]! } - public var DialogList_SearchSectionMessages: String { return self._s[677]! } - public var ChatList_Context_AddToFolder: String { return self._s[678]! } - public var Notifications_ChannelNotifications: String { return self._s[679]! } - public var CheckoutInfo_ShippingInfoAddress1Placeholder: String { return self._s[680]! } - public var Notification_MessageLifetime1h: String { return self._s[681]! } - public var Passport_Language_sk: String { return self._s[682]! } - public var Wallpaper_ResetWallpapersInfo: String { return self._s[683]! } - public var Appearance_ThemePreview_Chat_5_Text: String { return self._s[684]! } - public var PeerInfo_PaneGifs: String { return self._s[685]! } - public var Call_ReportSkip: String { return self._s[687]! } - public var Cache_ServiceFiles: String { return self._s[688]! } - public var Group_ErrorAddTooMuchAdmins: String { return self._s[689]! } - public var VoiceOver_Chat_YourFile: String { return self._s[690]! } - public var Map_Hybrid: String { return self._s[691]! } - public var Contacts_SearchUsersAndGroupsLabel: String { return self._s[693]! } - public func PUSH_MESSAGE_QUIZ(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[694]!, self._r[694]!, [_1]) - } - public var ChatSettings_AutoDownloadVideos: String { return self._s[696]! } - public var Channel_BanUser_PermissionEmbedLinks: String { return self._s[697]! } - public var InfoPlist_NSLocationAlwaysAndWhenInUseUsageDescription: String { return self._s[698]! } - public var SocksProxySetup_ProxyTelegram: String { return self._s[701]! } - public func PUSH_MESSAGE_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[702]!, self._r[702]!, [_1]) - } - public var Channel_Username_CreatePrivateLinkHelp: String { return self._s[704]! } - public var ScheduledMessages_ScheduledToday: String { return self._s[705]! } - public func PUSH_CHAT_TITLE_EDITED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[706]!, self._r[706]!, [_1, _2]) - } - public var Conversation_LiveLocationYou: String { return self._s[707]! } - public var SettingsSearch_Synonyms_Privacy_Calls: String { return self._s[708]! } - public var SettingsSearch_Synonyms_Notifications_MessageNotificationsPreview: String { return self._s[709]! } - public var UserInfo_ShareBot: String { return self._s[712]! } - public func PUSH_AUTH_REGION(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[713]!, self._r[713]!, [_1, _2]) - } - public var Conversation_ClearCache: String { return self._s[714]! } - public var PhotoEditor_ShadowsTint: String { return self._s[715]! } - public var ChatListFolderSettings_EditFoldersInfo: String { return self._s[716]! } - public var Message_Audio: String { return self._s[717]! } - public var Passport_Language_lt: String { return self._s[718]! } - public func Message_PinnedTextMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[719]!, self._r[719]!, [_0]) - } - public var Permissions_SiriText_v0: String { return self._s[720]! } - public var Conversation_FileICloudDrive: String { return self._s[721]! } - public var ChatList_DeleteForEveryoneConfirmationTitle: String { return self._s[722]! } - public var Notifications_Badge_IncludeMutedChats: String { return self._s[723]! } - public func Notification_NewAuthDetected(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String, _ _6: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[724]!, self._r[724]!, [_1, _2, _3, _4, _5, _6]) - } - public var DialogList_ProxyConnectionIssuesTooltip: String { return self._s[725]! } - public func Time_MonthOfYear_m5(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[726]!, self._r[726]!, [_0]) - } - public var Channel_SignMessages: String { return self._s[727]! } - public func PUSH_MESSAGE_NOTEXT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[728]!, self._r[728]!, [_1]) - } - public var Compose_ChannelTokenListPlaceholder: String { return self._s[729]! } - public var Passport_ScanPassport: String { return self._s[730]! } - public var Watch_Suggestion_Thanks: String { return self._s[731]! } - public var BlockedUsers_AddNew: String { return self._s[732]! } - public func PUSH_CHAT_MESSAGE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[733]!, self._r[733]!, [_1, _2]) - } - public var Watch_Message_Invoice: String { return self._s[734]! } - public var SettingsSearch_Synonyms_Privacy_LastSeen: String { return self._s[735]! } - public var Month_GenJuly: String { return self._s[736]! } - public var CreatePoll_QuizInfo: String { return self._s[737]! } - public var UserInfo_StartSecretChatStart: String { return self._s[738]! } - public var SocksProxySetup_ProxySocks5: String { return self._s[739]! } - public var IntentsSettings_SuggestByShare: String { return self._s[741]! } - public var Notification_Exceptions_DeleteAllConfirmation: String { return self._s[742]! } - public var Notification_ChannelInviterSelf: String { return self._s[743]! } - public var CheckoutInfo_ReceiverInfoEmail: String { return self._s[744]! } - public func ApplyLanguage_ChangeLanguageUnofficialText(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[745]!, self._r[745]!, [_1, _2]) - } - public var Stats_FollowersTitle: String { return self._s[746]! } - public var CheckoutInfo_Title: String { return self._s[747]! } - public var Watch_Stickers_RecentPlaceholder: String { return self._s[748]! } - public func Map_DistanceAway(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[749]!, self._r[749]!, [_0]) - } - public var Passport_Identity_MainPage: String { return self._s[750]! } - public var TwoStepAuth_ConfirmEmailResendCode: String { return self._s[751]! } - public var Passport_Language_de: String { return self._s[752]! } - public var Update_Title: String { return self._s[753]! } - public var ContactInfo_PhoneLabelWorkFax: String { return self._s[754]! } - public var Channel_AdminLog_BanEmbedLinks: String { return self._s[755]! } - public var Passport_Email_UseTelegramEmailHelp: String { return self._s[756]! } - public var Notifications_ChannelNotificationsPreview: String { return self._s[757]! } - public var NotificationsSound_Telegraph: String { return self._s[758]! } - public var Watch_LastSeen_ALongTimeAgo: String { return self._s[759]! } - public var ChannelMembers_WhoCanAddMembers: String { return self._s[760]! } - public func AutoDownloadSettings_UpTo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[761]!, self._r[761]!, [_0]) - } - public var ClearCache_Description: String { return self._s[762]! } - public var Stickers_SuggestAll: String { return self._s[763]! } - public var Conversation_ForwardTitle: String { return self._s[764]! } - public var Appearance_ThemePreview_ChatList_7_Name: String { return self._s[765]! } - public func Notification_JoinedChannel(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[766]!, self._r[766]!, [_0]) - } - public var Calls_NewCall: String { return self._s[767]! } - public var Call_StatusEnded: String { return self._s[768]! } - public var AutoDownloadSettings_DataUsageLow: String { return self._s[770]! } - public var Settings_ProxyConnected: String { return self._s[771]! } - public var Channel_AdminLogFilter_EventsPinned: String { return self._s[772]! } - public var PhotoEditor_QualityVeryLow: String { return self._s[773]! } - public var Channel_AdminLogFilter_EventsDeletedMessages: String { return self._s[774]! } - public var Passport_PasswordPlaceholder: String { return self._s[775]! } - public var Message_PinnedInvoice: String { return self._s[776]! } - public var Passport_Identity_IssueDate: String { return self._s[777]! } - public var Stats_GroupTopHoursTitle: String { return self._s[778]! } - public func ChannelInfo_ChannelForbidden(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[779]!, self._r[779]!, [_0]) - } - public var Passport_Language_pl: String { return self._s[780]! } - public var Call_StatusConnecting: String { return self._s[781]! } - public var SocksProxySetup_PasteFromClipboard: String { return self._s[782]! } - public func Username_UsernameIsAvailable(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[783]!, self._r[783]!, [_0]) - } - public var ChatSettings_ConnectionType_UseProxy: String { return self._s[785]! } - public var Common_Edit: String { return self._s[786]! } - public var PrivacySettings_LastSeenNobody: String { return self._s[787]! } - public func Notification_LeftChat(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[788]!, self._r[788]!, [_0]) - } - public var GroupInfo_ChatAdmins: String { return self._s[789]! } - public var PrivateDataSettings_Title: String { return self._s[790]! } - public var Login_CancelPhoneVerificationStop: String { return self._s[791]! } - public var ChatList_Read: String { return self._s[792]! } - public var Wallet_WordImport_Text: String { return self._s[793]! } - public var Undo_ChatClearedForBothSides: String { return self._s[794]! } - public var ChatListFolder_AddChats: String { return self._s[795]! } - public var GroupPermission_SectionTitle: String { return self._s[796]! } - public var Settings_ViewVideo: String { return self._s[797]! } - public var TwoFactorSetup_Intro_Title: String { return self._s[799]! } - public func PUSH_CHAT_LEFT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[800]!, self._r[800]!, [_1, _2]) - } - public var Checkout_ErrorPaymentFailed: String { return self._s[801]! } - public var Update_UpdateApp: String { return self._s[803]! } - public var Group_Username_RevokeExistingUsernamesInfo: String { return self._s[804]! } - public var Settings_Appearance: String { return self._s[805]! } - public var SettingsSearch_Synonyms_Stickers_SuggestStickers: String { return self._s[809]! } - public var Watch_Location_Access: String { return self._s[810]! } - public var ShareMenu_CopyShareLink: String { return self._s[812]! } - public var TwoStepAuth_SetupHintTitle: String { return self._s[813]! } - public var Conversation_Theme: String { return self._s[815]! } - public func DialogList_SingleRecordingVideoMessageSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[816]!, self._r[816]!, [_0]) - } - public var Notifications_ClassicTones: String { return self._s[817]! } - public var Weekday_ShortWednesday: String { return self._s[818]! } - public var WallpaperPreview_SwipeColorsBottomText: String { return self._s[819]! } - public var Undo_LeftGroup: String { return self._s[822]! } - public var ChatListFolder_DiscardCancel: String { return self._s[823]! } - public var Wallet_RestoreFailed_Text: String { return self._s[824]! } - public var Conversation_LinkDialogCopy: String { return self._s[825]! } - public var Wallet_TransactionInfo_NoAddress: String { return self._s[827]! } - public var Wallet_Navigation_Back: String { return self._s[828]! } - public var KeyCommand_FocusOnInputField: String { return self._s[829]! } - public var Contacts_SelectAll: String { return self._s[830]! } - public var Preview_SaveToCameraRoll: String { return self._s[831]! } - public var PrivacySettings_PasscodeOff: String { return self._s[832]! } - public var Appearance_ThemePreview_ChatList_6_Name: String { return self._s[833]! } - public func PUSH_CHANNEL_MESSAGE_QUIZ(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[834]!, self._r[834]!, [_1]) - } - public var Wallpaper_Title: String { return self._s[835]! } - public var Conversation_FilePhotoOrVideo: String { return self._s[836]! } - public var AccessDenied_Camera: String { return self._s[837]! } - public var Watch_Compose_CurrentLocation: String { return self._s[838]! } - public var PeerInfo_ButtonMessage: String { return self._s[840]! } - public var Channel_DiscussionGroup_MakeHistoryPublicProceed: String { return self._s[841]! } - public func SecretImage_NotViewedYet(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[842]!, self._r[842]!, [_0]) - } - public var GroupInfo_InvitationLinkDoesNotExist: String { return self._s[843]! } - public var Passport_Language_ro: String { return self._s[844]! } - public var EditTheme_UploadNewTheme: String { return self._s[845]! } - public var CheckoutInfo_SaveInfoHelp: String { return self._s[846]! } - public var Wallet_Intro_Terms: String { return self._s[847]! } - public func Notification_SecretChatMessageScreenshot(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[848]!, self._r[848]!, [_0]) - } - public var Login_CancelPhoneVerification: String { return self._s[849]! } - public var State_ConnectingToProxy: String { return self._s[850]! } - public var Calls_RatingTitle: String { return self._s[851]! } - public var Generic_ErrorMoreInfo: String { return self._s[852]! } - public var ChatList_Search_ShowMore: String { return self._s[853]! } - public var Appearance_PreviewReplyText: String { return self._s[854]! } - public var CheckoutInfo_ShippingInfoPostcodePlaceholder: String { return self._s[855]! } - public func Wallet_Send_Balance(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[856]!, self._r[856]!, [_0]) - } - public var IntentsSettings_SuggestedChatsContacts: String { return self._s[857]! } - public var SharedMedia_CategoryLinks: String { return self._s[858]! } - public var Calls_Missed: String { return self._s[859]! } - public var Settings_AddAnotherAccount_Help: String { return self._s[863]! } - public var Cache_Photos: String { return self._s[864]! } - public var GroupPermission_NoAddMembers: String { return self._s[865]! } - public var ScheduledMessages_Title: String { return self._s[866]! } - public func Channel_AdminLog_MessageUnpinned(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[867]!, self._r[867]!, [_0]) - } - public var Conversation_ShareBotLocationConfirmationTitle: String { return self._s[868]! } - public var Settings_ProxyDisabled: String { return self._s[869]! } - public func Settings_ApplyProxyAlertCredentials(_ _1: String, _ _2: String, _ _3: String, _ _4: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[870]!, self._r[870]!, [_1, _2, _3, _4]) - } - public func Conversation_RestrictedMediaTimed(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[871]!, self._r[871]!, [_0]) - } - public var Stats_ViewsPerPost: String { return self._s[873]! } - public var ChatList_AutoarchiveSuggestion_OpenSettings: String { return self._s[874]! } - public var ChatList_Context_RemoveFromRecents: String { return self._s[875]! } - public var Appearance_Title: String { return self._s[876]! } - public func Time_MonthOfYear_m2(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[878]!, self._r[878]!, [_0]) - } - public func Call_CameraOff(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[879]!, self._r[879]!, [_0]) - } - public var Conversation_WalletRequiredText: String { return self._s[880]! } - public var StickerPacksSettings_ShowStickersButtonHelp: String { return self._s[881]! } - public var OldChannels_NoticeCreateText: String { return self._s[882]! } - public var Channel_EditMessageErrorGeneric: String { return self._s[883]! } - public var Privacy_Calls_IntegrationHelp: String { return self._s[884]! } - public var Preview_DeletePhoto: String { return self._s[885]! } - public var Appearance_AppIconFilledX: String { return self._s[886]! } - public var PrivacySettings_PrivacyTitle: String { return self._s[887]! } - public func Conversation_BotInteractiveUrlAlert(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[888]!, self._r[888]!, [_0]) - } - public var ChatListFolder_TitleEdit: String { return self._s[891]! } - public var MuteFor_Forever: String { return self._s[892]! } - public var Coub_TapForSound: String { return self._s[893]! } - public var Map_LocatingError: String { return self._s[894]! } - public var TwoStepAuth_EmailChangeSuccess: String { return self._s[896]! } - public var Conversation_SendMessage_SendSilently: String { return self._s[897]! } - public var VoiceOver_MessageContextOpenMessageMenu: String { return self._s[898]! } - public func Wallet_Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[899]!, self._r[899]!, [_1, _2, _3]) - } - public var Passport_ForgottenPassword: String { return self._s[900]! } - public var GroupInfo_InviteLink_RevokeLink: String { return self._s[901]! } - public var StickerPacksSettings_ArchivedPacks: String { return self._s[902]! } - public var Login_TermsOfServiceSignupDecline: String { return self._s[904]! } - public var Channel_Moderator_AccessLevelRevoke: String { return self._s[905]! } - public var Message_Location: String { return self._s[906]! } - public var Passport_Identity_NamePlaceholder: String { return self._s[907]! } - public var Channel_Management_Title: String { return self._s[908]! } - public var DialogList_SearchSectionDialogs: String { return self._s[910]! } - public var Compose_NewChannel_Members: String { return self._s[911]! } - public func DialogList_SingleUploadingFileSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[912]!, self._r[912]!, [_0]) - } - public var GroupInfo_Location: String { return self._s[913]! } - public var Appearance_ThemePreview_ChatList_5_Name: String { return self._s[914]! } - public var ClearCache_Clear: String { return self._s[915]! } - public var InstantPage_FeedbackButtonShort: String { return self._s[916]! } - public var AutoNightTheme_ScheduledFrom: String { return self._s[917]! } - public var PhotoEditor_WarmthTool: String { return self._s[918]! } - public func PUSH_MESSAGE_GAME_SCORE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[919]!, self._r[919]!, [_1, _2, _3]) - } - public var Passport_Language_tr: String { return self._s[920]! } - public var OldChannels_NoticeUpgradeText: String { return self._s[921]! } - public var Login_ResetAccountProtected_Reset: String { return self._s[923]! } - public var Watch_PhotoView_Title: String { return self._s[924]! } - public var Passport_Phone_Delete: String { return self._s[925]! } - public var Undo_ChatDeletedForBothSides: String { return self._s[926]! } - public var Conversation_EditingMessageMediaEditCurrentPhoto: String { return self._s[927]! } - public var GroupInfo_Permissions: String { return self._s[928]! } - public var PasscodeSettings_TurnPasscodeOff: String { return self._s[929]! } - public var Profile_ShareContactButton: String { return self._s[930]! } - public var ChatSettings_Other: String { return self._s[931]! } - public var UserInfo_NotificationsDisabled: String { return self._s[932]! } - public var CheckoutInfo_ShippingInfoCity: String { return self._s[933]! } - public var LastSeen_WithinAMonth: String { return self._s[934]! } - public var VoiceOver_Chat_PlayHint: String { return self._s[935]! } - public var Conversation_ReportGroupLocation: String { return self._s[936]! } - public var Conversation_EncryptionCanceled: String { return self._s[937]! } - public var MediaPicker_GroupDescription: String { return self._s[938]! } - public var WebSearch_Images: String { return self._s[939]! } - public func Channel_Management_PromotedBy(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[940]!, self._r[940]!, [_0]) - } - public var Message_Photo: String { return self._s[941]! } - public var PasscodeSettings_HelpBottom: String { return self._s[942]! } - public var AutoDownloadSettings_VideosTitle: String { return self._s[943]! } - public var Conversation_ContextMenuSendMessage: String { return self._s[944]! } - public var VoiceOver_Media_PlaybackRateChange: String { return self._s[945]! } - public var Passport_Identity_AddDriversLicense: String { return self._s[946]! } - public var TwoStepAuth_EnterPasswordPassword: String { return self._s[947]! } - public var NotificationsSound_Calypso: String { return self._s[948]! } - public var Map_Map: String { return self._s[949]! } - public func Conversation_LiveLocationYouAndOther(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[950]!, self._r[950]!, [_0]) - } - public var CheckoutInfo_ReceiverInfoTitle: String { return self._s[953]! } - public var ChatSettings_TextSizeUnits: String { return self._s[954]! } - public func VoiceOver_Chat_FileFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[955]!, self._r[955]!, [_0]) - } - public var Common_of: String { return self._s[956]! } - public var Conversation_ForwardContacts: String { return self._s[959]! } - public var IntentsSettings_SuggestByAll: String { return self._s[961]! } - public func Call_AnsweringWithAccount(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[962]!, self._r[962]!, [_0]) - } - public var Call_CameraConfirmationText: String { return self._s[963]! } - public var Passport_Language_hy: String { return self._s[964]! } - public var Notifications_MessageNotificationsHelp: String { return self._s[965]! } - public var AutoDownloadSettings_Reset: String { return self._s[966]! } - public var Wallet_TransactionInfo_AddressCopied: String { return self._s[967]! } - public var Paint_ClearConfirm: String { return self._s[968]! } - public var Camera_VideoMode: String { return self._s[969]! } - public func Conversation_RestrictedStickersTimed(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[970]!, self._r[970]!, [_0]) - } - public var Privacy_Calls_AlwaysAllow_Placeholder: String { return self._s[971]! } - public var Conversation_ViewBackground: String { return self._s[972]! } - public func Wallet_Info_TransactionDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[973]!, self._r[973]!, [_1, _2, _3]) - } - public var Passport_Language_el: String { return self._s[974]! } - public var PhotoEditor_Original: String { return self._s[975]! } - public var Settings_FAQ_Button: String { return self._s[978]! } - public var Channel_Setup_PublicNoLink: String { return self._s[980]! } - public var Conversation_UnsupportedMedia: String { return self._s[981]! } - public var Conversation_SlideToCancel: String { return self._s[982]! } - public var Appearance_ThemePreview_ChatList_4_Name: String { return self._s[983]! } - public var Passport_Identity_OneOfTypeInternalPassport: String { return self._s[984]! } - public var CheckoutInfo_ShippingInfoPostcode: String { return self._s[985]! } - public var Conversation_ReportSpamChannelConfirmation: String { return self._s[986]! } - public var Stats_GroupViewers: String { return self._s[987]! } - public var AutoNightTheme_NotAvailable: String { return self._s[988]! } - public var Conversation_Owner: String { return self._s[989]! } - public var Common_Create: String { return self._s[990]! } - public var Settings_ApplyProxyAlertEnable: String { return self._s[991]! } - public var ContactList_Context_Call: String { return self._s[992]! } - public var Localization_ChooseLanguage: String { return self._s[994]! } - public var ChatList_Context_AddToContacts: String { return self._s[996]! } - public var OldChannels_NoticeTitle: String { return self._s[997]! } - public var Settings_Proxy: String { return self._s[999]! } - public var Privacy_TopPeersHelp: String { return self._s[1000]! } - public var CheckoutInfo_ShippingInfoCountryPlaceholder: String { return self._s[1001]! } - public var Chat_UnsendMyMessages: String { return self._s[1002]! } + public var Conversation_ContextMenuCopyLink: String { return self._s[239]! } + public var Wallet_Send_NetworkErrorTitle: String { return self._s[241]! } + public var ChatListFolder_CategoryMuted: String { return self._s[242]! } + public var TwoStepAuth_AddHintDescription: String { return self._s[243]! } public func VoiceOver_Chat_Duration(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1003]!, self._r[1003]!, [_0]) - } - public var TwoStepAuth_ConfirmationAbort: String { return self._s[1004]! } - public func Contacts_AccessDeniedHelpPortrait(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1006]!, self._r[1006]!, [_0]) - } - public var Contacts_SortedByPresence: String { return self._s[1007]! } - public var Passport_Identity_SurnamePlaceholder: String { return self._s[1008]! } - public var Cache_Title: String { return self._s[1009]! } - public func Login_PhoneBannedEmailSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1010]!, self._r[1010]!, [_0]) - } - public var TwoStepAuth_EmailCodeExpired: String { return self._s[1011]! } - public var Channel_Moderator_Title: String { return self._s[1012]! } - public var InstantPage_AutoNightTheme: String { return self._s[1014]! } - public func PUSH_MESSAGE_POLL(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1017]!, self._r[1017]!, [_1]) - } - public var Passport_Scans_Upload: String { return self._s[1018]! } - public var Undo_Undo: String { return self._s[1020]! } - public var Contacts_AccessDeniedHelpON: String { return self._s[1021]! } - public var TwoStepAuth_RemovePassword: String { return self._s[1022]! } - public var Common_Delete: String { return self._s[1023]! } - public var Contacts_AddPeopleNearby: String { return self._s[1025]! } - public var Conversation_ContextMenuDelete: String { return self._s[1026]! } - public var SocksProxySetup_Credentials: String { return self._s[1027]! } - public var Appearance_EditTheme: String { return self._s[1029]! } - public var ClearCache_StorageOtherApps: String { return self._s[1030]! } - public func Conversation_PeerNearbyTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1032]!, self._r[1032]!, [_1, _2]) - } - public var Settings_EditPhoto: String { return self._s[1033]! } - public var PasscodeSettings_AutoLock_Disabled: String { return self._s[1034]! } - public var Wallet_Send_NetworkErrorText: String { return self._s[1035]! } - public var AuthSessions_DevicesTitle: String { return self._s[1037]! } - public var Passport_Address_OneOfTypeRentalAgreement: String { return self._s[1039]! } - public var Conversation_ShareBotContactConfirmationTitle: String { return self._s[1040]! } - public var Passport_Language_id: String { return self._s[1042]! } - public var Chat_Gifs_TrendingSectionHeader: String { return self._s[1043]! } - public var WallpaperSearch_ColorTeal: String { return self._s[1044]! } - public var ChannelIntro_Title: String { return self._s[1045]! } - public func Channel_AdminLog_MessageToggleSignaturesOff(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1046]!, self._r[1046]!, [_0]) - } - public var VoiceOver_Chat_OpenLinkHint: String { return self._s[1048]! } - public var VoiceOver_Chat_Reply: String { return self._s[1049]! } - public var ScheduledMessages_BotActionUnavailable: String { return self._s[1050]! } - public var Channel_Info_Description: String { return self._s[1051]! } - public var Stickers_FavoriteStickers: String { return self._s[1052]! } - public var Channel_BanUser_PermissionAddMembers: String { return self._s[1053]! } - public var Notifications_DisplayNamesOnLockScreen: String { return self._s[1054]! } - public var ChatSearch_ResultsTooltip: String { return self._s[1055]! } - public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[1056]! } - public var Calls_NoMissedCallsPlacehoder: String { return self._s[1057]! } - public var Group_PublicLink_Placeholder: String { return self._s[1058]! } - public var Notifications_ExceptionsDefaultSound: String { return self._s[1059]! } - public func PUSH_CHANNEL_MESSAGE_POLL(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1060]!, self._r[1060]!, [_1]) - } - public var TextFormat_Underline: String { return self._s[1061]! } - public func DialogList_SearchSubtitleFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1063]!, self._r[1063]!, [_1, _2]) - } - public func Channel_AdminLog_MessageRemovedGroupStickerPack(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1064]!, self._r[1064]!, [_0]) - } - public var Appearance_ThemePreview_ChatList_3_Name: String { return self._s[1065]! } - public func Channel_OwnershipTransfer_TransferCompleted(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1066]!, self._r[1066]!, [_1, _2]) - } - public var Wallet_Intro_ImportExisting: String { return self._s[1067]! } - public var GroupPermission_Delete: String { return self._s[1068]! } - public var Passport_Language_uk: String { return self._s[1069]! } - public var StickerPack_HideStickers: String { return self._s[1071]! } - public var ChangePhoneNumberNumber_NumberPlaceholder: String { return self._s[1072]! } - public func PUSH_CHAT_MESSAGE_PHOTO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1073]!, self._r[1073]!, [_1, _2]) - } - public var Activity_UploadingVideoMessage: String { return self._s[1074]! } - public func GroupPermission_ApplyAlertText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1075]!, self._r[1075]!, [_0]) - } - public var Channel_TitleInfo: String { return self._s[1076]! } - public var StickerPacksSettings_ArchivedPacks_Info: String { return self._s[1077]! } - public var Settings_CallSettings: String { return self._s[1078]! } - public var Camera_SquareMode: String { return self._s[1079]! } - public var Conversation_SendMessage_ScheduleMessage: String { return self._s[1080]! } - public var GroupInfo_SharedMediaNone: String { return self._s[1081]! } - public func PUSH_MESSAGE_VIDEO_SECRET(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1082]!, self._r[1082]!, [_1]) - } - public var Bot_GenericBotStatus: String { return self._s[1083]! } - public var Application_Update: String { return self._s[1085]! } - public var Month_ShortJanuary: String { return self._s[1086]! } - public var Contacts_PermissionsKeepDisabled: String { return self._s[1087]! } - public var Channel_AdminLog_BanReadMessages: String { return self._s[1088]! } - public var Settings_AppLanguage_Unofficial: String { return self._s[1089]! } - public var Passport_Address_Street2Placeholder: String { return self._s[1090]! } - public func Map_LiveLocationShortHour(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1091]!, self._r[1091]!, [_0]) - } - public var NetworkUsageSettings_Cellular: String { return self._s[1092]! } - public var Appearance_PreviewOutgoingText: String { return self._s[1093]! } - public func StickerPackActionInfo_RemovedText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1094]!, self._r[1094]!, [_0]) - } - public var Notifications_PermissionsAllowInSettings: String { return self._s[1095]! } - public var AutoDownloadSettings_OnForAll: String { return self._s[1098]! } - public var Map_Directions: String { return self._s[1099]! } - public var Passport_FieldIdentityTranslationHelp: String { return self._s[1101]! } - public var Appearance_ThemeDay: String { return self._s[1102]! } - public var LogoutOptions_LogOut: String { return self._s[1103]! } - public var Group_PublicLink_Title: String { return self._s[1105]! } - public var Channel_AddBotErrorNoRights: String { return self._s[1106]! } - public var ChatList_Search_ShowLess: String { return self._s[1109]! } - public var Passport_Identity_AddPassport: String { return self._s[1110]! } - public var LocalGroup_ButtonTitle: String { return self._s[1111]! } - public var Stats_InteractionsTitle: String { return self._s[1112]! } - public var Stats_GroupActionsTitle: String { return self._s[1113]! } - public var Call_Message: String { return self._s[1114]! } - public var PhotoEditor_ExposureTool: String { return self._s[1115]! } - public var Wallet_Receive_CommentInfo: String { return self._s[1117]! } - public var Passport_FieldOneOf_Delimeter: String { return self._s[1118]! } - public var Channel_AdminLog_CanBanUsers: String { return self._s[1120]! } - public var Appearance_ThemePreview_ChatList_2_Name: String { return self._s[1121]! } - public var Appearance_Preview: String { return self._s[1122]! } - public var Compose_ChannelMembers: String { return self._s[1123]! } - public var Conversation_DeleteManyMessages: String { return self._s[1124]! } - public var ReportPeer_ReasonOther_Title: String { return self._s[1125]! } - public var Checkout_ErrorProviderAccountTimeout: String { return self._s[1126]! } - public var TwoStepAuth_ResetAccountConfirmation: String { return self._s[1127]! } - public var Channel_Stickers_CreateYourOwn: String { return self._s[1130]! } - public var Conversation_UpdateTelegram: String { return self._s[1131]! } - public var EditTheme_Create_TopInfo: String { return self._s[1132]! } - public func Notification_PinnedPhotoMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1133]!, self._r[1133]!, [_0]) - } - public var Wallet_WordCheck_Continue: String { return self._s[1134]! } - public var TwoFactorSetup_Hint_Action: String { return self._s[1135]! } - public var IntentsSettings_ResetAll: String { return self._s[1136]! } - public func PUSH_PINNED_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1137]!, self._r[1137]!, [_1]) - } - public var ChatList_RemoveFolder: String { return self._s[1138]! } - public var GroupInfo_Administrators_Title: String { return self._s[1139]! } - public var Stats_GroupPosters: String { return self._s[1140]! } - public var Stats_MessageTitle: String { return self._s[1141]! } - public var Privacy_Forwards_PreviewMessageText: String { return self._s[1142]! } - public func PrivacySettings_LastSeenNobodyPlus(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1143]!, self._r[1143]!, [_0]) - } - public var Tour_Title3: String { return self._s[1144]! } - public var Channel_EditAdmin_PermissionInviteSubscribers: String { return self._s[1145]! } - public var Settings_RemoveVideo: String { return self._s[1148]! } - public var Clipboard_SendPhoto: String { return self._s[1150]! } - public var MediaPicker_Videos: String { return self._s[1151]! } - public var Passport_Email_Title: String { return self._s[1152]! } - public func PrivacySettings_LastSeenEverybodyMinus(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1153]!, self._r[1153]!, [_0]) - } - public var StickerPacksSettings_Title: String { return self._s[1154]! } - public var Conversation_MessageDialogDelete: String { return self._s[1155]! } - public var Privacy_Calls_CustomHelp: String { return self._s[1157]! } - public var Message_Wallpaper: String { return self._s[1158]! } - public var MemberSearch_BotSection: String { return self._s[1159]! } - public var GroupInfo_SetSound: String { return self._s[1160]! } - public var Wallet_Send_EncryptComment: String { return self._s[1161]! } - public func Time_TomorrowAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1162]!, self._r[1162]!, [_0]) - } - public var Core_ServiceUserStatus: String { return self._s[1163]! } - public var LiveLocationUpdated_JustNow: String { return self._s[1164]! } - public var Call_StatusFailed: String { return self._s[1165]! } - public var TwoFactorSetup_Email_Placeholder: String { return self._s[1166]! } - public var TwoStepAuth_SetupPasswordDescription: String { return self._s[1167]! } - public var TwoStepAuth_SetPassword: String { return self._s[1168]! } - public var Permissions_PeopleNearbyText_v0: String { return self._s[1169]! } - public func SocksProxySetup_ProxyStatusPing(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1171]!, self._r[1171]!, [_0]) - } - public var Calls_SubmitRating: String { return self._s[1172]! } - public var Map_NoPlacesNearby: String { return self._s[1173]! } - public var Profile_Username: String { return self._s[1174]! } - public var Bot_DescriptionTitle: String { return self._s[1175]! } - public var MaskStickerSettings_Title: String { return self._s[1176]! } - public var SharedMedia_CategoryOther: String { return self._s[1177]! } - public var GroupInfo_SetGroupPhoto: String { return self._s[1178]! } - public var Common_NotNow: String { return self._s[1179]! } - public var CallFeedback_IncludeLogsInfo: String { return self._s[1180]! } - public var Conversation_ShareMyPhoneNumber: String { return self._s[1181]! } - public var Map_Location: String { return self._s[1182]! } - public var Invitation_JoinGroup: String { return self._s[1183]! } - public var AutoDownloadSettings_Title: String { return self._s[1185]! } - public var Conversation_DiscardVoiceMessageDescription: String { return self._s[1186]! } - public var Channel_ErrorAddBlocked: String { return self._s[1187]! } - public var ChatList_AddChatsToFolder: String { return self._s[1188]! } - public var Conversation_UnblockUser: String { return self._s[1189]! } - public var EditTheme_Edit_TopInfo: String { return self._s[1190]! } - public var Watch_Bot_Restart: String { return self._s[1191]! } - public var TwoStepAuth_Title: String { return self._s[1192]! } - public var Channel_AdminLog_BanSendMessages: String { return self._s[1193]! } - public var Checkout_ShippingMethod: String { return self._s[1194]! } - public var Passport_Identity_OneOfTypeIdentityCard: String { return self._s[1195]! } - public func PUSH_CHAT_MESSAGE_STICKER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1196]!, self._r[1196]!, [_1, _2, _3]) - } - public var PeerInfo_ButtonDiscuss: String { return self._s[1197]! } - public var EditTheme_ChangeColors: String { return self._s[1199]! } - public func Chat_UnsendMyMessagesAlertTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1200]!, self._r[1200]!, [_0]) - } - public func Channel_Username_LinkHint(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1201]!, self._r[1201]!, [_0]) - } - public var Appearance_ThemePreview_ChatList_1_Name: String { return self._s[1202]! } - public var Notification_VideoCallMissed: String { return self._s[1204]! } - public var SettingsSearch_Synonyms_Data_AutoplayGifs: String { return self._s[1205]! } - public var AuthSessions_TerminateOtherSessions: String { return self._s[1207]! } - public var Contacts_FailedToSendInvitesMessage: String { return self._s[1208]! } - public var PrivacySettings_TwoStepAuth: String { return self._s[1209]! } - public var Notification_Exceptions_PreviewAlwaysOn: String { return self._s[1210]! } - public var SettingsSearch_Synonyms_Privacy_Passcode: String { return self._s[1211]! } - public var Conversation_EditingMessagePanelMedia: String { return self._s[1212]! } - public var Checkout_PaymentMethod_Title: String { return self._s[1213]! } - public var SocksProxySetup_Connection: String { return self._s[1214]! } - public var Group_MessagePhotoRemoved: String { return self._s[1215]! } - public var PeopleNearby_MakeInvisible: String { return self._s[1217]! } - public var Channel_Stickers_NotFound: String { return self._s[1219]! } - public var Group_About_Help: String { return self._s[1220]! } - public var Notification_PassportValueProofOfIdentity: String { return self._s[1221]! } - public var PeopleNearby_Title: String { return self._s[1223]! } - public func ApplyLanguage_ChangeLanguageOfficialText(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1224]!, self._r[1224]!, [_1]) - } - public var Map_Home: String { return self._s[1225]! } - public var Stats_ZoomOut: String { return self._s[1226]! } - public var CheckoutInfo_ShippingInfoStatePlaceholder: String { return self._s[1228]! } - public var Notifications_GroupNotificationsExceptionsHelp: String { return self._s[1229]! } - public var SocksProxySetup_Password: String { return self._s[1230]! } - public var Notifications_PermissionsEnable: String { return self._s[1231]! } - public var TwoStepAuth_ChangeEmail: String { return self._s[1233]! } - public func Channel_AdminLog_MessageInvitedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1234]!, self._r[1234]!, [_1]) - } - public func Time_MonthOfYear_m10(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1236]!, self._r[1236]!, [_0]) - } - public var Passport_Identity_TypeDriversLicense: String { return self._s[1237]! } - public var ArchivedPacksAlert_Title: String { return self._s[1238]! } - public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[1239]! } - public var Map_PlacesNearby: String { return self._s[1240]! } - public func Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1241]!, self._r[1241]!, [_1, _2, _3]) - } - public var PrivacyLastSeenSettings_GroupsAndChannelsHelp: String { return self._s[1242]! } - public var Privacy_Calls_NeverAllow_Placeholder: String { return self._s[1245]! } - public var Conversation_StatusTyping: String { return self._s[1246]! } - public var Widget_ApplicationStartRequired: String { return self._s[1247]! } - public var Broadcast_AdminLog_EmptyText: String { return self._s[1248]! } - public var Notification_PassportValueProofOfAddress: String { return self._s[1249]! } - public var UserInfo_CreateNewContact: String { return self._s[1250]! } - public var Passport_Identity_FrontSide: String { return self._s[1251]! } - public var Login_PhoneNumberAlreadyAuthorizedSwitch: String { return self._s[1252]! } - public var Calls_CallTabTitle: String { return self._s[1253]! } - public var Channel_AdminLog_ChannelEmptyText: String { return self._s[1254]! } - public func Login_BannedPhoneBody(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1256]!, self._r[1256]!, [_0]) - } - public var Watch_UserInfo_MuteTitle: String { return self._s[1257]! } - public var Group_EditAdmin_RankAdminPlaceholder: String { return self._s[1258]! } - public var SharedMedia_EmptyMusicText: String { return self._s[1259]! } - public var Wallet_Completed_Text: String { return self._s[1260]! } - public var PasscodeSettings_AutoLock_IfAwayFor_1minute: String { return self._s[1261]! } - public var Paint_Stickers: String { return self._s[1262]! } - public var Privacy_GroupsAndChannels: String { return self._s[1263]! } - public var ChatList_Context_Delete: String { return self._s[1265]! } - public var UserInfo_AddContact: String { return self._s[1266]! } - public func Conversation_MessageViaUser(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1267]!, self._r[1267]!, [_0]) - } - public var PhoneNumberHelp_ChangeNumber: String { return self._s[1269]! } - public func ChatList_ClearChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1271]!, self._r[1271]!, [_0]) - } - public var DialogList_NoMessagesTitle: String { return self._s[1272]! } - public var EditProfile_NameAndPhotoHelp: String { return self._s[1273]! } - public var BlockedUsers_BlockUser: String { return self._s[1274]! } - public var Notifications_PermissionsOpenSettings: String { return self._s[1275]! } - public var MediaPicker_UngroupDescription: String { return self._s[1278]! } - public var Watch_NoConnection: String { return self._s[1279]! } - public var Month_GenSeptember: String { return self._s[1280]! } - public var Conversation_ViewGroup: String { return self._s[1282]! } - public var Channel_AdminLogFilter_EventsLeavingSubscribers: String { return self._s[1285]! } - public var Privacy_Forwards_AlwaysLink: String { return self._s[1286]! } - public var Channel_OwnershipTransfer_ErrorAdminsTooMuch: String { return self._s[1287]! } - public var Passport_FieldOneOf_FinalDelimeter: String { return self._s[1288]! } - public var Wallet_WordCheck_IncorrectHeader: String { return self._s[1289]! } - public var MediaPicker_CameraRoll: String { return self._s[1291]! } - public var Month_GenAugust: String { return self._s[1292]! } - public var Wallet_Configuration_SourceHeader: String { return self._s[1293]! } - public var AccessDenied_VideoMessageMicrophone: String { return self._s[1294]! } - public var SharedMedia_EmptyText: String { return self._s[1295]! } - public var Map_ShareLiveLocation: String { return self._s[1296]! } - public var Calls_All: String { return self._s[1297]! } - public var Map_SendThisPlace: String { return self._s[1299]! } - public var Appearance_ThemeNight: String { return self._s[1301]! } - public var Conversation_HoldForAudio: String { return self._s[1302]! } - public var SettingsSearch_Synonyms_Support: String { return self._s[1305]! } - public var GroupInfo_GroupHistoryHidden: String { return self._s[1306]! } - public var SocksProxySetup_Secret: String { return self._s[1307]! } - public func Activity_RemindAboutChannel(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1308]!, self._r[1308]!, [_0]) - } - public var Channel_BanList_RestrictedTitle: String { return self._s[1310]! } - public var Conversation_Location: String { return self._s[1311]! } - public func AutoDownloadSettings_UpToFor(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1312]!, self._r[1312]!, [_1, _2]) - } - public var ChatSettings_AutoDownloadPhotos: String { return self._s[1314]! } - public var SettingsSearch_Synonyms_Privacy_Title: String { return self._s[1315]! } - public var Notifications_PermissionsText: String { return self._s[1316]! } - public var SettingsSearch_Synonyms_Data_SaveIncomingPhotos: String { return self._s[1317]! } - public var Call_Flip: String { return self._s[1318]! } - public var Channel_AdminLog_CanDeleteMessagesOfOthers: String { return self._s[1320]! } - public var SocksProxySetup_ProxyStatusConnecting: String { return self._s[1321]! } - public var Wallet_TransactionInfo_StorageFeeInfoUrl: String { return self._s[1322]! } - public var PrivacyPhoneNumberSettings_DiscoveryHeader: String { return self._s[1323]! } - public var Stats_GroupTopAdmin_Promote: String { return self._s[1325]! } - public var Channel_EditAdmin_PermissionPinMessages: String { return self._s[1326]! } - public var TwoStepAuth_ReEnterPasswordDescription: String { return self._s[1328]! } - public var ChatList_EditFolders: String { return self._s[1330]! } - public var Channel_TooMuchBots: String { return self._s[1331]! } - public var Passport_DeletePassportConfirmation: String { return self._s[1332]! } - public var Login_InvalidCodeError: String { return self._s[1333]! } - public var StickerPacksSettings_FeaturedPacks: String { return self._s[1334]! } - public func ChatList_DeleteSecretChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1335]!, self._r[1335]!, [_0]) - } - public func GroupInfo_InvitationLinkAcceptChannel(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1336]!, self._r[1336]!, [_0]) - } - public var VoiceOver_Navigation_ProxySettings: String { return self._s[1337]! } - public var Call_CallInProgressTitle: String { return self._s[1338]! } - public var Month_ShortSeptember: String { return self._s[1339]! } - public var Watch_ChannelInfo_Title: String { return self._s[1340]! } - public var ChatList_DeleteSavedMessagesConfirmation: String { return self._s[1343]! } - public var DialogList_PasscodeLockHelp: String { return self._s[1344]! } - public var Chat_MultipleTextMessagesDisabled: String { return self._s[1345]! } - public var Wallet_Receive_Title: String { return self._s[1346]! } - public var Notifications_Badge_IncludePublicGroups: String { return self._s[1347]! } - public var EditProfile_NameAndPhotoOrVideoHelp: String { return self._s[1348]! } - public var Channel_AdminLogFilter_EventsTitle: String { return self._s[1349]! } - public var PhotoEditor_CropReset: String { return self._s[1350]! } - public var Group_Username_CreatePrivateLinkHelp: String { return self._s[1352]! } - public var Channel_Management_LabelEditor: String { return self._s[1353]! } - public var Passport_Identity_LatinNameHelp: String { return self._s[1355]! } - public var PhotoEditor_HighlightsTool: String { return self._s[1356]! } - public var Wallet_Info_WalletCreated: String { return self._s[1357]! } - public var UserInfo_Title: String { return self._s[1358]! } - public var ChatList_HideAction: String { return self._s[1359]! } - public var AccessDenied_Title: String { return self._s[1360]! } - public var DialogList_SearchLabel: String { return self._s[1361]! } - public var Group_Setup_HistoryHidden: String { return self._s[1362]! } - public var TwoStepAuth_PasswordChangeSuccess: String { return self._s[1363]! } - public var State_Updating: String { return self._s[1365]! } - public var Contacts_TabTitle: String { return self._s[1366]! } - public var Notifications_Badge_CountUnreadMessages: String { return self._s[1368]! } - public var GroupInfo_GroupHistory: String { return self._s[1369]! } - public var Conversation_UnsupportedMediaPlaceholder: String { return self._s[1370]! } - public var Wallpaper_SetColor: String { return self._s[1371]! } - public var CheckoutInfo_ShippingInfoCountry: String { return self._s[1372]! } - public var SettingsSearch_Synonyms_SavedMessages: String { return self._s[1373]! } - public var ChatList_ReorderTabs: String { return self._s[1374]! } - public var ChatListFolder_IncludeChatsTitle: String { return self._s[1375]! } - public var Chat_AttachmentLimitReached: String { return self._s[1376]! } - public var Passport_Identity_OneOfTypeDriversLicense: String { return self._s[1377]! } - public var Contacts_NotRegisteredSection: String { return self._s[1378]! } - public func Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1379]!, self._r[1379]!, [_1, _2, _3]) - } - public var Paint_Clear: String { return self._s[1380]! } - public var Call_Audio: String { return self._s[1381]! } - public var StickerPacksSettings_ArchivedMasks: String { return self._s[1382]! } - public var SocksProxySetup_Connecting: String { return self._s[1383]! } - public var ExplicitContent_AlertChannel: String { return self._s[1384]! } - public var CreatePoll_AllOptionsAdded: String { return self._s[1385]! } - public var Conversation_Contact: String { return self._s[1386]! } - public var Login_CodeExpired: String { return self._s[1387]! } - public var Passport_DiscardMessageAction: String { return self._s[1388]! } - public var ChatList_Context_Unpin: String { return self._s[1389]! } - public var Channel_AdminLog_MessagePreviousDescription: String { return self._s[1390]! } - public func VoiceOver_Chat_MusicFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1391]!, self._r[1391]!, [_0]) - } - public var Channel_AdminLog_EmptyMessageText: String { return self._s[1392]! } - public var SettingsSearch_Synonyms_Data_NetworkUsage: String { return self._s[1393]! } - public func Group_EditAdmin_RankInfo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1394]!, self._r[1394]!, [_0]) - } - public var Month_ShortApril: String { return self._s[1395]! } - public var AuthSessions_CurrentSession: String { return self._s[1396]! } - public var Chat_AttachmentMultipleFilesDisabled: String { return self._s[1399]! } - public var Wallet_Navigation_Cancel: String { return self._s[1401]! } - public var WallpaperPreview_CropTopText: String { return self._s[1402]! } - public var PrivacySettings_DeleteAccountIfAwayFor: String { return self._s[1403]! } - public var CheckoutInfo_ShippingInfoTitle: String { return self._s[1405]! } - public func Conversation_ScheduleMessage_SendOn(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1406]!, self._r[1406]!, [_0, _1]) - } - public var Appearance_ThemePreview_Chat_2_Text: String { return self._s[1407]! } - public var Channel_Setup_TypePrivate: String { return self._s[1409]! } - public var Forward_ChannelReadOnly: String { return self._s[1412]! } - public var PhotoEditor_CurvesBlue: String { return self._s[1413]! } - public var AddContact_SharedContactException: String { return self._s[1414]! } - public var UserInfo_BotPrivacy: String { return self._s[1416]! } - public var Wallet_CreateInvoice_Title: String { return self._s[1417]! } - public var Notification_PassportValueEmail: String { return self._s[1418]! } - public var EmptyGroupInfo_Subtitle: String { return self._s[1419]! } - public var GroupPermission_NewTitle: String { return self._s[1420]! } - public var CallFeedback_ReasonDropped: String { return self._s[1421]! } - public var GroupInfo_Permissions_AddException: String { return self._s[1422]! } - public var Channel_SignMessages_Help: String { return self._s[1425]! } - public var Undo_ChatDeleted: String { return self._s[1427]! } - public var Conversation_ChatBackground: String { return self._s[1428]! } - public func Wallet_WordCheck_Text(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1429]!, self._r[1429]!, [_1, _2, _3]) - } - public func PUSH_CHAT_MESSAGE_QUIZ(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1432]!, self._r[1432]!, [_1, _2, _3]) - } - public var ChannelMembers_WhoCanAddMembers_Admins: String { return self._s[1433]! } - public var FastTwoStepSetup_EmailPlaceholder: String { return self._s[1434]! } - public var Passport_Language_pt: String { return self._s[1435]! } - public var VoiceOver_Chat_YourVoiceMessage: String { return self._s[1436]! } - public var NotificationsSound_Popcorn: String { return self._s[1439]! } - public var AutoNightTheme_Disabled: String { return self._s[1440]! } - public var BlockedUsers_LeavePrefix: String { return self._s[1441]! } - public var WallpaperPreview_CustomColorTopText: String { return self._s[1442]! } - public var Contacts_PermissionsSuppressWarningText: String { return self._s[1443]! } - public var WallpaperSearch_ColorBlue: String { return self._s[1444]! } - public func CancelResetAccount_TextSMS(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1445]!, self._r[1445]!, [_0]) - } - public var ChatListFolder_TitleCreate: String { return self._s[1446]! } - public var CheckoutInfo_ErrorNameInvalid: String { return self._s[1447]! } - public var SocksProxySetup_UseForCalls: String { return self._s[1448]! } - public var Passport_DeleteDocumentConfirmation: String { return self._s[1450]! } - public var PeerInfo_PaneGroups: String { return self._s[1451]! } - public func Conversation_Megabytes(_ _0: Float) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1452]!, self._r[1452]!, ["\(_0)"]) - } - public var SocksProxySetup_Hostname: String { return self._s[1455]! } - public var ChatSettings_AutoDownloadSettings_OffForAll: String { return self._s[1456]! } - public var Compose_NewEncryptedChat: String { return self._s[1457]! } - public var Login_CodeFloodError: String { return self._s[1458]! } - public var Calls_TabTitle: String { return self._s[1459]! } - public var Privacy_ProfilePhoto: String { return self._s[1460]! } - public var Passport_Language_he: String { return self._s[1461]! } - public func Conversation_SetReminder_RemindToday(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1462]!, self._r[1462]!, [_0]) - } - public var ChatList_TabIconFoldersTooltipNonEmptyFolders: String { return self._s[1463]! } - public var GroupPermission_Title: String { return self._s[1464]! } - public func Channel_AdminLog_MessageGroupPreHistoryHidden(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1465]!, self._r[1465]!, [_0]) - } - public var Wallet_TransactionInfo_SenderHeader: String { return self._s[1466]! } - public var GroupPermission_NoChangeInfo: String { return self._s[1467]! } - public var ChatList_DeleteForCurrentUser: String { return self._s[1468]! } - public var Tour_Text1: String { return self._s[1469]! } - public var Channel_EditAdmin_TransferOwnership: String { return self._s[1470]! } - public var Month_ShortFebruary: String { return self._s[1471]! } - public var Call_ExternalCallInProgressMessage: String { return self._s[1472]! } - public var TwoStepAuth_EmailSkip: String { return self._s[1473]! } - public var ContactList_Context_VideoCall: String { return self._s[1474]! } - public func Wallet_Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1475]!, self._r[1475]!, [_1, _2, _3]) - } - public var NotificationsSound_Glass: String { return self._s[1476]! } - public var Appearance_ThemeNightBlue: String { return self._s[1477]! } - public var CheckoutInfo_Pay: String { return self._s[1478]! } - public var Stats_LanguagesTitle: String { return self._s[1480]! } - public var PeerInfo_ButtonLeave: String { return self._s[1481]! } - public var SettingsSearch_Synonyms_ChatFolders: String { return self._s[1482]! } - public var Invite_LargeRecipientsCountWarning: String { return self._s[1483]! } - public var Call_CallAgain: String { return self._s[1485]! } - public var AttachmentMenu_SendAsFile: String { return self._s[1486]! } - public var AccessDenied_MicrophoneRestricted: String { return self._s[1487]! } - public var Passport_InvalidPasswordError: String { return self._s[1488]! } - public var Watch_Message_Game: String { return self._s[1489]! } - public var Stickers_Install: String { return self._s[1490]! } - public var VoiceOver_Chat_Message: String { return self._s[1491]! } - public var PrivacyLastSeenSettings_NeverShareWith: String { return self._s[1492]! } - public var Passport_Identity_ResidenceCountry: String { return self._s[1494]! } - public var Notifications_GroupNotificationsHelp: String { return self._s[1495]! } - public var AuthSessions_OtherSessions: String { return self._s[1496]! } - public var Channel_Username_Help: String { return self._s[1497]! } - public var Camera_Title: String { return self._s[1498]! } - public var IntentsSettings_Title: String { return self._s[1500]! } - public var GroupInfo_SetGroupPhotoDelete: String { return self._s[1502]! } - public var Privacy_ProfilePhoto_NeverShareWith_Title: String { return self._s[1503]! } - public var Channel_AdminLog_SendPolls: String { return self._s[1504]! } - public var Channel_AdminLog_TitleAllEvents: String { return self._s[1505]! } - public var Channel_EditAdmin_PermissionInviteMembers: String { return self._s[1506]! } - public var Contacts_MemberSearchSectionTitleGroup: String { return self._s[1507]! } - public var ScheduledMessages_DeleteMany: String { return self._s[1508]! } - public var Conversation_RestrictedStickers: String { return self._s[1509]! } - public var Notifications_ExceptionsResetToDefaults: String { return self._s[1511]! } - public var UserInfo_TelegramCall: String { return self._s[1513]! } - public var TwoStepAuth_SetupResendEmailCode: String { return self._s[1514]! } - public var CreatePoll_OptionsHeader: String { return self._s[1515]! } - public var SettingsSearch_Synonyms_Data_CallsUseLessData: String { return self._s[1516]! } - public var ArchivedChats_IntroTitle1: String { return self._s[1517]! } - public var Privacy_GroupsAndChannels_AlwaysAllow_Title: String { return self._s[1518]! } - public var Theme_Colors_Proceed: String { return self._s[1519]! } - public var Passport_Identity_EditPersonalDetails: String { return self._s[1520]! } - public func Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1521]!, self._r[1521]!, [_1, _2, _3]) - } - public var Wallet_Month_GenAugust: String { return self._s[1522]! } - public var Settings_SaveEditedPhotos: String { return self._s[1523]! } - public var Stats_FollowersBySourceTitle: String { return self._s[1524]! } - public var TwoStepAuth_ConfirmationTitle: String { return self._s[1525]! } - public var Privacy_GroupsAndChannels_NeverAllow_Title: String { return self._s[1526]! } - public var Conversation_MessageDialogRetry: String { return self._s[1527]! } - public var ChatList_Context_MarkAsUnread: String { return self._s[1528]! } - public var MessagePoll_SubmitVote: String { return self._s[1529]! } - public var Conversation_DiscardVoiceMessageAction: String { return self._s[1530]! } - public var Permissions_PeopleNearbyTitle_v0: String { return self._s[1531]! } - public var ChatList_Context_Back: String { return self._s[1532]! } - public var Group_Setup_TypeHeader: String { return self._s[1533]! } - public var Paint_RecentStickers: String { return self._s[1534]! } - public var PhotoEditor_GrainTool: String { return self._s[1535]! } - public var CheckoutInfo_ShippingInfoState: String { return self._s[1536]! } - public var EmptyGroupInfo_Line4: String { return self._s[1537]! } - public var Watch_AuthRequired: String { return self._s[1539]! } - public func Passport_Email_UseTelegramEmail(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1540]!, self._r[1540]!, [_0]) - } - public var Conversation_EncryptedDescriptionTitle: String { return self._s[1541]! } - public var ChannelIntro_Text: String { return self._s[1542]! } - public var DialogList_DeleteBotConfirmation: String { return self._s[1543]! } - public var GroupPermission_NoSendMedia: String { return self._s[1544]! } - public var Calls_AddTab: String { return self._s[1545]! } - public var Message_ReplyActionButtonShowReceipt: String { return self._s[1546]! } - public var Channel_AdminLog_EmptyFilterText: String { return self._s[1547]! } - public var Conversation_WalletRequiredSetup: String { return self._s[1548]! } - public var Notification_MessageLifetime1d: String { return self._s[1549]! } - public var Notifications_ChannelNotificationsExceptionsHelp: String { return self._s[1550]! } - public var Channel_BanUser_PermissionsHeader: String { return self._s[1551]! } - public var Passport_Identity_GenderFemale: String { return self._s[1552]! } - public var BlockedUsers_BlockTitle: String { return self._s[1553]! } - public func PUSH_CHANNEL_MESSAGE_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1554]!, self._r[1554]!, [_1]) - } - public var Weekday_Yesterday: String { return self._s[1555]! } - public var WallpaperSearch_ColorBlack: String { return self._s[1556]! } - public var Settings_Context_Logout: String { return self._s[1557]! } - public var Wallet_Info_UnknownTransaction: String { return self._s[1558]! } - public var ChatList_ArchiveAction: String { return self._s[1559]! } - public var AutoNightTheme_Scheduled: String { return self._s[1560]! } - public var TwoFactorSetup_Email_SkipAction: String { return self._s[1561]! } - public var Settings_Devices: String { return self._s[1562]! } - public var ContactInfo_Note: String { return self._s[1563]! } - public func Login_PhoneGenericEmailBody(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String, _ _6: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1564]!, self._r[1564]!, [_1, _2, _3, _4, _5, _6]) - } - public var EditTheme_ThemeTemplateAlertTitle: String { return self._s[1565]! } - public var Wallet_Receive_CreateInvoice: String { return self._s[1566]! } - public var PrivacyPolicy_DeclineDeleteNow: String { return self._s[1567]! } - public var Theme_Colors_ColorWallpaperWarningProceed: String { return self._s[1568]! } - public func PUSH_CHAT_JOINED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1569]!, self._r[1569]!, [_1, _2]) - } - public var CreatePoll_Create: String { return self._s[1570]! } - public var Channel_Members_AddBannedErrorAdmin: String { return self._s[1571]! } - public func Notification_CallFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1572]!, self._r[1572]!, [_1, _2]) - } - public var ScheduledMessages_ClearAllConfirmation: String { return self._s[1573]! } - public var Checkout_ErrorProviderAccountInvalid: String { return self._s[1574]! } - public var Notifications_InAppNotificationsSounds: String { return self._s[1576]! } - public func PUSH_PINNED_GAME_SCORE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1577]!, self._r[1577]!, [_1]) - } - public var Preview_OpenInInstagram: String { return self._s[1578]! } - public var Notification_MessageLifetimeRemovedOutgoing: String { return self._s[1579]! } - public func PUSH_CHAT_ADD_MEMBER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1580]!, self._r[1580]!, [_1, _2, _3]) - } - public func Passport_PrivacyPolicy(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1581]!, self._r[1581]!, [_1, _2]) - } - public var Channel_AdminLog_InfoPanelAlertTitle: String { return self._s[1582]! } - public var ArchivedChats_IntroText3: String { return self._s[1583]! } - public var ChatList_UndoArchiveHiddenText: String { return self._s[1584]! } - public var NetworkUsageSettings_TotalSection: String { return self._s[1585]! } - public var Wallet_Month_GenSeptember: String { return self._s[1586]! } - public var Channel_Setup_TypePrivateHelp: String { return self._s[1587]! } - public func PUSH_CHAT_MESSAGE_POLL(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1588]!, self._r[1588]!, [_1, _2, _3]) - } - public var Privacy_GroupsAndChannels_NeverAllow_Placeholder: String { return self._s[1590]! } - public var FastTwoStepSetup_HintSection: String { return self._s[1591]! } - public var Wallpaper_PhotoLibrary: String { return self._s[1592]! } - public var TwoStepAuth_SetupResendEmailCodeAlert: String { return self._s[1593]! } - public var Gif_NoGifsFound: String { return self._s[1594]! } - public var Watch_LastSeen_WithinAMonth: String { return self._s[1595]! } - public var VoiceOver_MessageContextDelete: String { return self._s[1596]! } - public var EditTheme_Preview: String { return self._s[1597]! } - public func ClearCache_StorageTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1598]!, self._r[1598]!, [_0]) - } - public var GroupInfo_ActionPromote: String { return self._s[1599]! } - public var PasscodeSettings_SimplePasscode: String { return self._s[1600]! } - public var GroupInfo_Permissions_Title: String { return self._s[1601]! } - public var Permissions_ContactsText_v0: String { return self._s[1602]! } - public var PrivacyPhoneNumberSettings_CustomDisabledHelp: String { return self._s[1603]! } - public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedPublicGroups: String { return self._s[1604]! } - public var PrivacySettings_DataSettingsHelp: String { return self._s[1607]! } - public var Passport_FieldEmailHelp: String { return self._s[1608]! } - public func Activity_RemindAboutUser(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1609]!, self._r[1609]!, [_0]) - } - public var Passport_Identity_GenderPlaceholder: String { return self._s[1610]! } - public var Weekday_ShortSaturday: String { return self._s[1611]! } - public var ContactInfo_PhoneLabelMain: String { return self._s[1612]! } - public var Watch_Conversation_UserInfo: String { return self._s[1613]! } - public var CheckoutInfo_ShippingInfoCityPlaceholder: String { return self._s[1614]! } - public var GroupPermission_PermissionDisabledByDefault: String { return self._s[1615]! } - public var PrivacyLastSeenSettings_Title: String { return self._s[1616]! } - public var Conversation_ShareBotLocationConfirmation: String { return self._s[1618]! } - public var PhotoEditor_VignetteTool: String { return self._s[1619]! } - public var Conversation_ContextMenuDiscuss: String { return self._s[1620]! } - public var Passport_Address_Street1Placeholder: String { return self._s[1621]! } - public var Passport_Language_et: String { return self._s[1622]! } - public var AppUpgrade_Running: String { return self._s[1623]! } - public var Channel_DiscussionGroup_Info: String { return self._s[1625]! } - public var EditTheme_Create_Preview_IncomingReplyName: String { return self._s[1626]! } - public var Passport_Language_bg: String { return self._s[1627]! } - public var Stickers_NoStickersFound: String { return self._s[1629]! } - public func PUSH_CHANNEL_MESSAGE_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1631]!, self._r[1631]!, [_1, _2]) - } - public func VoiceOver_Chat_ContactFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1632]!, self._r[1632]!, [_0]) - } - public var Wallet_Month_GenJuly: String { return self._s[1633]! } - public var Wallet_Receive_AddressHeader: String { return self._s[1635]! } - public var Wallet_Send_AmountText: String { return self._s[1636]! } - public var Settings_About: String { return self._s[1637]! } - public func Channel_AdminLog_MessageRestricted(_ _0: String, _ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1638]!, self._r[1638]!, [_0, _1, _2]) - } - public var ChatList_Context_MarkAsRead: String { return self._s[1640]! } - public var KeyCommand_NewMessage: String { return self._s[1641]! } - public var Group_ErrorAddBlocked: String { return self._s[1642]! } - public func Message_PaymentSent(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1643]!, self._r[1643]!, [_0]) - } - public var Map_LocationTitle: String { return self._s[1644]! } - public var ReportGroupLocation_Title: String { return self._s[1645]! } - public var CallSettings_UseLessDataLongDescription: String { return self._s[1646]! } - public var Cache_ClearProgress: String { return self._s[1647]! } - public func Channel_Management_ErrorNotMember(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1648]!, self._r[1648]!, [_0]) - } - public var GroupRemoved_AddToGroup: String { return self._s[1649]! } - public func External_OpenIn(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1650]!, self._r[1650]!, [_0]) - } - public var Passport_UpdateRequiredError: String { return self._s[1651]! } - public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[1652]! } - public func PUSH_MESSAGE_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1653]!, self._r[1653]!, [_1]) - } - public var Notifications_PermissionsSuppressWarningText: String { return self._s[1655]! } - public var Passport_Identity_MainPageHelp: String { return self._s[1656]! } - public var PeerInfo_ButtonSearch: String { return self._s[1657]! } - public var Conversation_StatusKickedFromGroup: String { return self._s[1658]! } - public var Passport_Language_ka: String { return self._s[1659]! } - public func Wallet_Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1660]!, self._r[1660]!, [_1, _2, _3]) - } - public var Call_Decline: String { return self._s[1661]! } - public var SocksProxySetup_ProxyEnabled: String { return self._s[1662]! } - public var TwoFactorSetup_Email_SkipConfirmationText: String { return self._s[1665]! } - public func AuthCode_Alert(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1666]!, self._r[1666]!, [_0]) - } - public var CallFeedback_Send: String { return self._s[1667]! } - public var EditTheme_EditTitle: String { return self._s[1668]! } - public func Channel_AdminLog_MessagePromotedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1669]!, self._r[1669]!, [_1, _2]) - } - public var Passport_Phone_UseTelegramNumberHelp: String { return self._s[1670]! } - public func Wallet_Updated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1672]!, self._r[1672]!, [_0]) - } - public var Media_SendingOptionsTooltip: String { return self._s[1673]! } - public var Call_YourMicrophoneOff: String { return self._s[1674]! } - public var SettingsSearch_Synonyms_Data_Title: String { return self._s[1675]! } - public var Passport_DeletePassport: String { return self._s[1676]! } - public var Appearance_AppIconFilled: String { return self._s[1677]! } - public var Privacy_Calls_P2PAlways: String { return self._s[1678]! } - public var Month_ShortDecember: String { return self._s[1679]! } - public var Channel_AdminLog_CanEditMessages: String { return self._s[1681]! } - public func Contacts_AccessDeniedHelpLandscape(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1682]!, self._r[1682]!, [_0]) - } - public var Channel_Stickers_Searching: String { return self._s[1683]! } - public var Conversation_EncryptedDescription1: String { return self._s[1684]! } - public var Conversation_EncryptedDescription2: String { return self._s[1685]! } - public var PasscodeSettings_PasscodeOptions: String { return self._s[1686]! } - public var ChatListFolder_NameUnread: String { return self._s[1688]! } - public var Conversation_EncryptedDescription3: String { return self._s[1689]! } - public var PhotoEditor_SharpenTool: String { return self._s[1690]! } - public var Wallet_Configuration_Title: String { return self._s[1691]! } - public func Conversation_AddNameToContacts(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1692]!, self._r[1692]!, [_0]) - } - public var Conversation_EncryptedDescription4: String { return self._s[1695]! } - public var Channel_Members_AddMembers: String { return self._s[1696]! } - public var Wallpaper_Search: String { return self._s[1697]! } - public func Message_GenericForwardedPsa(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1699]!, self._r[1699]!, [_0]) - } - public var Weekday_Friday: String { return self._s[1700]! } - public var Privacy_ContactsSync: String { return self._s[1701]! } - public var SettingsSearch_Synonyms_Privacy_Data_ContactsReset: String { return self._s[1702]! } - public var ApplyLanguage_ChangeLanguageAction: String { return self._s[1703]! } - public func Channel_Management_RestrictedBy(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1704]!, self._r[1704]!, [_0]) - } - public var Wallet_Configuration_BlockchainIdHeader: String { return self._s[1705]! } - public var GroupInfo_Permissions_Removed: String { return self._s[1706]! } - public var ScheduledMessages_ScheduledOnline: String { return self._s[1707]! } - public var Passport_Identity_GenderMale: String { return self._s[1708]! } - public func Call_StatusBar(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1709]!, self._r[1709]!, [_0]) - } - public var Notifications_PermissionsKeepDisabled: String { return self._s[1710]! } - public var Conversation_JumpToDate: String { return self._s[1711]! } - public var Contacts_GlobalSearch: String { return self._s[1712]! } - public var AutoDownloadSettings_ResetHelp: String { return self._s[1713]! } - public var SettingsSearch_Synonyms_FAQ: String { return self._s[1714]! } - public var ChatListFolderSettings_NewFolder: String { return self._s[1715]! } - public var Profile_MessageLifetime1d: String { return self._s[1716]! } - public func MESSAGE_INVOICE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1717]!, self._r[1717]!, [_1, _2]) - } - public var StickerPack_BuiltinPackName: String { return self._s[1720]! } - public func PUSH_CHAT_MESSAGE_AUDIO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1721]!, self._r[1721]!, [_1, _2]) - } - public var VoiceOver_Chat_RecordModeVoiceMessageInfo: String { return self._s[1722]! } - public var Passport_InfoTitle: String { return self._s[1724]! } - public var Notifications_PermissionsUnreachableText: String { return self._s[1725]! } - public func NetworkUsageSettings_CellularUsageSince(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1729]!, self._r[1729]!, [_0]) - } - public func PUSH_CHAT_MESSAGE_GEO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1730]!, self._r[1730]!, [_1, _2]) - } - public var Passport_Address_TypePassportRegistrationUploadScan: String { return self._s[1731]! } - public var Profile_BotInfo: String { return self._s[1732]! } - public var Watch_Compose_CreateMessage: String { return self._s[1733]! } - public var AutoDownloadSettings_VoiceMessagesInfo: String { return self._s[1734]! } - public var Month_ShortNovember: String { return self._s[1735]! } - public var Conversation_ScamWarning: String { return self._s[1736]! } - public var Wallpaper_SetCustomBackground: String { return self._s[1737]! } - public var Appearance_TextSize_Title: String { return self._s[1738]! } - public var Conversation_ContextMenuOpenProfile: String { return self._s[1739]! } - public var ChatList_EmptyChatListFilterTitle: String { return self._s[1740]! } - public func Call_BatteryLow(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1741]!, self._r[1741]!, [_0]) - } - public var Passport_Identity_TranslationsHelp: String { return self._s[1742]! } - public var NotificationsSound_Chime: String { return self._s[1743]! } - public var Passport_Language_ko: String { return self._s[1745]! } - public var InviteText_URL: String { return self._s[1746]! } - public var TextFormat_Monospace: String { return self._s[1747]! } - public func Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1748]!, self._r[1748]!, [_1, _2, _3]) - } - public var EditTheme_Edit_BottomInfo: String { return self._s[1749]! } - public func Login_WillSendSms(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1750]!, self._r[1750]!, [_0]) - } - public func Watch_Time_ShortWeekdayAt(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1751]!, self._r[1751]!, [_1, _2]) - } - public var Wallet_Words_Title: String { return self._s[1752]! } - public var Wallet_Month_ShortMay: String { return self._s[1753]! } - public var EditTheme_CreateTitle: String { return self._s[1755]! } - public var Passport_InfoLearnMore: String { return self._s[1756]! } - public var TwoStepAuth_EmailPlaceholder: String { return self._s[1757]! } - public var Passport_Identity_AddIdentityCard: String { return self._s[1758]! } - public var Your_card_has_expired: String { return self._s[1759]! } - public var StickerPacksSettings_StickerPacksSection: String { return self._s[1760]! } - public var Call_AudioRouteMute: String { return self._s[1761]! } - public var GroupInfo_InviteLink_Help: String { return self._s[1762]! } - public var TwoFactorSetup_EmailVerification_ResendAction: String { return self._s[1766]! } - public var Conversation_Report: String { return self._s[1768]! } - public var Notifications_MessageNotificationsSound: String { return self._s[1769]! } - public var Notification_MessageLifetime1m: String { return self._s[1770]! } - public var Privacy_ContactsTitle: String { return self._s[1771]! } - public var Conversation_ShareMyContactInfo: String { return self._s[1772]! } - public var Wallet_WordCheck_Title: String { return self._s[1773]! } - public var ChannelMembers_WhoCanAddMembersAdminsHelp: String { return self._s[1774]! } - public var Channel_Members_Title: String { return self._s[1775]! } - public var Map_OpenInWaze: String { return self._s[1776]! } - public var Appearance_RemoveThemeColorConfirmation: String { return self._s[1777]! } - public var Stats_GroupTopWeekdaysTitle: String { return self._s[1778]! } - public var Login_PhoneBannedError: String { return self._s[1779]! } - public var PeerInfo_GroupAboutItem: String { return self._s[1780]! } - public func LiveLocationUpdated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1781]!, self._r[1781]!, [_0]) - } - public var IntentsSettings_MainAccount: String { return self._s[1782]! } - public var Group_Management_AddModeratorHelp: String { return self._s[1783]! } - public var AutoDownloadSettings_WifiTitle: String { return self._s[1784]! } - public var Common_OK: String { return self._s[1785]! } - public var Passport_Address_TypeBankStatementUploadScan: String { return self._s[1786]! } - public var Wallet_Words_NotDoneResponse: String { return self._s[1787]! } - public var Cache_Music: String { return self._s[1788]! } - public var Wallet_Configuration_SourceURL: String { return self._s[1789]! } - public var SettingsSearch_Synonyms_EditProfile_PhoneNumber: String { return self._s[1790]! } - public var PasscodeSettings_UnlockWithTouchId: String { return self._s[1793]! } - public var ChatList_EmptyChatListEditFilter: String { return self._s[1794]! } - public var TwoStepAuth_HintPlaceholder: String { return self._s[1795]! } - public func PUSH_PINNED_INVOICE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1796]!, self._r[1796]!, [_1]) - } - public func Passport_RequestHeader(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1797]!, self._r[1797]!, [_0]) - } - public var TwoFactorSetup_Done_Action: String { return self._s[1798]! } - public func VoiceOver_Chat_ContactOrganization(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1799]!, self._r[1799]!, [_0]) - } - public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[1800]! } - public var Watch_MessageView_ViewOnPhone: String { return self._s[1802]! } - public var Privacy_Calls_CustomShareHelp: String { return self._s[1803]! } - public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[1805]! } - public var ChangePhoneNumberNumber_Title: String { return self._s[1806]! } - public var State_ConnectingToProxyInfo: String { return self._s[1807]! } - public var Conversation_SwipeToReplyHintTitle: String { return self._s[1808]! } - public var Message_VideoMessage: String { return self._s[1810]! } - public var ChannelInfo_DeleteChannel: String { return self._s[1811]! } - public var ContactInfo_PhoneLabelOther: String { return self._s[1812]! } - public var Channel_EditAdmin_CannotEdit: String { return self._s[1813]! } - public var Passport_DeleteAddressConfirmation: String { return self._s[1814]! } - public func Wallet_Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1815]!, self._r[1815]!, [_1, _2, _3]) - } - public var WallpaperPreview_SwipeBottomText: String { return self._s[1816]! } - public var Activity_RecordingAudio: String { return self._s[1817]! } - public var SettingsSearch_Synonyms_Watch: String { return self._s[1818]! } - public var PasscodeSettings_TryAgainIn1Minute: String { return self._s[1819]! } - public var Wallet_Info_Address: String { return self._s[1820]! } - public var Notification_VideoCallCanceled: String { return self._s[1821]! } - public func Notification_ChangedGroupName(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1823]!, self._r[1823]!, [_0, _1]) - } - public func EmptyGroupInfo_Line1(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1827]!, self._r[1827]!, [_0]) - } - public var ChatList_RemoveFolderConfirmation: String { return self._s[1828]! } - public var Conversation_ApplyLocalization: String { return self._s[1829]! } - public func Conversation_PeerNearbyDistance(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1830]!, self._r[1830]!, [_1, _2]) - } - public var TwoFactorSetup_Intro_Action: String { return self._s[1831]! } - public var UserInfo_AddPhone: String { return self._s[1833]! } - public var Map_ShareLiveLocationHelp: String { return self._s[1834]! } - public func Passport_Identity_NativeNameGenericHelp(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1835]!, self._r[1835]!, [_0]) - } - public var ChatListFolder_CategoryArchived: String { return self._s[1837]! } - public var Call_IncomingVideoCall: String { return self._s[1838]! } - public var Passport_Scans: String { return self._s[1839]! } - public var BlockedUsers_Unblock: String { return self._s[1840]! } - public func PUSH_ENCRYPTION_REQUEST(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1841]!, self._r[1841]!, [_1]) - } - public var Channel_Management_LabelCreator: String { return self._s[1842]! } - public var Conversation_ReportSpamAndLeave: String { return self._s[1843]! } - public var SettingsSearch_Synonyms_EditProfile_Bio: String { return self._s[1844]! } - public var ChatList_UndoArchiveMultipleTitle: String { return self._s[1845]! } - public var Passport_Identity_NativeNameGenericTitle: String { return self._s[1846]! } - public func Login_EmailPhoneBody(_ _0: String, _ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1847]!, self._r[1847]!, [_0, _1, _2]) - } - public var Login_PhoneNumberHelp: String { return self._s[1848]! } - public var LastSeen_ALongTimeAgo: String { return self._s[1849]! } - public var Channel_AdminLog_CanPinMessages: String { return self._s[1850]! } - public var ChannelIntro_CreateChannel: String { return self._s[1851]! } - public var Conversation_UnreadMessages: String { return self._s[1852]! } - public var SettingsSearch_Synonyms_Stickers_ArchivedPacks: String { return self._s[1853]! } - public var Channel_AdminLog_EmptyText: String { return self._s[1854]! } - public var Theme_Context_Apply: String { return self._s[1855]! } - public var Notification_GroupActivated: String { return self._s[1856]! } - public var NotificationSettings_ContactJoinedInfo: String { return self._s[1857]! } - public var Wallet_Intro_CreateWallet: String { return self._s[1858]! } - public func Call_MicrophoneOff(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1859]!, self._r[1859]!, [_0]) - } - public func Notification_PinnedContactMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1860]!, self._r[1860]!, [_0]) - } - public func DownloadingStatus(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1861]!, self._r[1861]!, [_0, _1]) - } - public var GroupInfo_ConvertToSupergroup: String { return self._s[1863]! } - public func PrivacyPolicy_AgeVerificationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1864]!, self._r[1864]!, [_0]) - } - public var Undo_DeletedChannel: String { return self._s[1865]! } - public var CallFeedback_AddComment: String { return self._s[1866]! } - public func Conversation_OpenBotLinkAllowMessages(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1867]!, self._r[1867]!, [_0]) - } - public var Document_TargetConfirmationFormat: String { return self._s[1868]! } - public func Call_StatusOngoing(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1869]!, self._r[1869]!, [_0]) - } - public var LogoutOptions_SetPasscodeTitle: String { return self._s[1870]! } - public func PUSH_CHAT_MESSAGE_GAME_SCORE(_ _1: String, _ _2: String, _ _3: String, _ _4: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1871]!, self._r[1871]!, [_1, _2, _3, _4]) - } - public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[1872]! } - public var Theme_ErrorNotFound: String { return self._s[1873]! } - public var Contacts_SortByName: String { return self._s[1874]! } - public var SettingsSearch_Synonyms_Privacy_Forwards: String { return self._s[1875]! } - public func CHAT_MESSAGE_INVOICE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1878]!, self._r[1878]!, [_1, _2, _3]) - } - public var Notification_Exceptions_RemoveFromExceptions: String { return self._s[1879]! } - public var ScheduledMessages_EditTime: String { return self._s[1880]! } - public var Conversation_ClearSelfHistory: String { return self._s[1881]! } - public var Checkout_NewCard_PostcodePlaceholder: String { return self._s[1882]! } - public var PasscodeSettings_DoNotMatch: String { return self._s[1883]! } - public var Stickers_SuggestNone: String { return self._s[1884]! } - public var ChatSettings_Cache: String { return self._s[1885]! } - public var Settings_SaveIncomingPhotos: String { return self._s[1886]! } - public var Media_ShareThisPhoto: String { return self._s[1887]! } - public var Chat_SlowmodeTooltipPending: String { return self._s[1888]! } - public var InfoPlist_NSContactsUsageDescription: String { return self._s[1889]! } - public var Conversation_ContextMenuCopyLink: String { return self._s[1890]! } - public var PrivacyPolicy_AgeVerificationTitle: String { return self._s[1891]! } - public var SettingsSearch_Synonyms_Stickers_Masks: String { return self._s[1892]! } - public var TwoStepAuth_SetupPasswordEnterPasswordNew: String { return self._s[1893]! } - public var Appearance_ThemePreview_Chat_6_Text: String { return self._s[1894]! } - public func Wallet_SecureStorageReset_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1895]!, self._r[1895]!, [_0]) - } - public var PhotoEditor_BlurToolPortrait: String { return self._s[1896]! } - public var Permissions_CellularDataTitle_v0: String { return self._s[1897]! } - public var WallpaperSearch_ColorWhite: String { return self._s[1899]! } - public var Channel_AdminLog_DefaultRestrictionsUpdated: String { return self._s[1900]! } - public var Conversation_ErrorInaccessibleMessage: String { return self._s[1901]! } - public var Map_OpenIn: String { return self._s[1902]! } - public var PeerInfo_ButtonCall: String { return self._s[1903]! } - public func PUSH_PHONE_CALL_MISSED(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1907]!, self._r[1907]!, [_1]) - } - public func ChannelInfo_AddParticipantConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1908]!, self._r[1908]!, [_0]) - } - public var GroupInfo_Permissions_SlowmodeHeader: String { return self._s[1909]! } - public var MessagePoll_LabelClosed: String { return self._s[1910]! } - public var GroupPermission_PermissionGloballyDisabled: String { return self._s[1912]! } - public var Wallet_Send_SendAnyway: String { return self._s[1913]! } - public var Passport_Identity_MiddleNamePlaceholder: String { return self._s[1914]! } - public var UserInfo_FirstNamePlaceholder: String { return self._s[1915]! } - public var PrivacyLastSeenSettings_WhoCanSeeMyTimestamp: String { return self._s[1916]! } - public var Map_SetThisPlace: String { return self._s[1917]! } - public var Stats_GroupTopAdmin_Actions: String { return self._s[1918]! } - public var Login_SelectCountry_Title: String { return self._s[1919]! } - public var Channel_EditAdmin_PermissionBanUsers: String { return self._s[1920]! } - public func Conversation_OpenBotLinkLogin(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1921]!, self._r[1921]!, [_1, _2]) - } - public var Channel_AdminLog_ChangeInfo: String { return self._s[1922]! } - public var Watch_Suggestion_BRB: String { return self._s[1923]! } - public var Passport_Identity_EditIdentityCard: String { return self._s[1924]! } - public var Contacts_PermissionsTitle: String { return self._s[1925]! } - public var Conversation_RestrictedInline: String { return self._s[1926]! } - public var Appearance_RemoveThemeColor: String { return self._s[1928]! } - public var StickerPack_ViewPack: String { return self._s[1929]! } - public var Wallet_UnknownError: String { return self._s[1930]! } - public func Update_AppVersion(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1931]!, self._r[1931]!, [_0]) - } - public var Compose_NewChannel: String { return self._s[1933]! } - public var ChatSettings_AutoDownloadSettings_TypePhoto: String { return self._s[1937]! } - public var MessagePoll_LabelQuiz: String { return self._s[1939]! } - public var Conversation_ReportSpamGroupConfirmation: String { return self._s[1940]! } - public var Channel_Info_Stickers: String { return self._s[1941]! } - public var AutoNightTheme_PreferredTheme: String { return self._s[1942]! } - public var PrivacyPolicy_AgeVerificationAgree: String { return self._s[1943]! } - public var Passport_DeletePersonalDetails: String { return self._s[1944]! } - public var LogoutOptions_AddAccountTitle: String { return self._s[1945]! } - public var Channel_DiscussionGroupInfo: String { return self._s[1946]! } - public var Group_EditAdmin_RankOwnerPlaceholder: String { return self._s[1947]! } - public var Stats_LoadingText: String { return self._s[1950]! } - public var Conversation_SearchNoResults: String { return self._s[1951]! } - public var ChatList_AddFolder: String { return self._s[1952]! } - public var Wallet_Configuration_ApplyErrorTextURLInvalid: String { return self._s[1953]! } - public var ChatListFolder_NameNonContacts: String { return self._s[1954]! } - public var MessagePoll_LabelAnonymous: String { return self._s[1955]! } - public var Channel_Members_AddAdminErrorNotAMember: String { return self._s[1956]! } - public var Login_Code: String { return self._s[1957]! } - public var EditTheme_Create_BottomInfo: String { return self._s[1958]! } - public var Watch_Suggestion_WhatsUp: String { return self._s[1959]! } - public var Weekday_ShortThursday: String { return self._s[1960]! } - public var Notification_VideoCallOutgoing: String { return self._s[1961]! } - public var Resolve_ErrorNotFound: String { return self._s[1962]! } - public var LastSeen_Offline: String { return self._s[1964]! } - public var PeopleNearby_NoMembers: String { return self._s[1965]! } - public var GroupPermission_AddMembersNotAvailable: String { return self._s[1966]! } - public var Privacy_Calls_AlwaysAllow_Title: String { return self._s[1967]! } - public var Conversation_Dice_u1F3AF: String { return self._s[1969]! } - public var GroupInfo_Title: String { return self._s[1970]! } - public var NotificationsSound_Note: String { return self._s[1971]! } - public var Conversation_EditingMessagePanelTitle: String { return self._s[1972]! } - public var Watch_Message_Poll: String { return self._s[1973]! } - public var Privacy_Calls: String { return self._s[1974]! } - public func Channel_AdminLog_MessageRankUsername(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1975]!, self._r[1975]!, [_1, _2, _3]) - } - public var Month_ShortAugust: String { return self._s[1976]! } - public var TwoStepAuth_SetPasswordHelp: String { return self._s[1977]! } - public var Notifications_Reset: String { return self._s[1978]! } - public var Conversation_Pin: String { return self._s[1979]! } - public var Passport_Language_lv: String { return self._s[1980]! } - public var Permissions_PeopleNearbyAllowInSettings_v0: String { return self._s[1981]! } - public var BlockedUsers_Info: String { return self._s[1982]! } - public var SettingsSearch_Synonyms_Data_AutoplayVideos: String { return self._s[1984]! } - public var Watch_Conversation_Unblock: String { return self._s[1986]! } - public func Time_MonthOfYear_m9(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1987]!, self._r[1987]!, [_0]) - } - public var CloudStorage_Title: String { return self._s[1988]! } - public var GroupInfo_DeleteAndExitConfirmation: String { return self._s[1989]! } - public func NetworkUsageSettings_WifiUsageSince(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1990]!, self._r[1990]!, [_0]) - } - public var Channel_AdminLogFilter_AdminsTitle: String { return self._s[1991]! } - public var Watch_Suggestion_OnMyWay: String { return self._s[1992]! } - public var TwoStepAuth_RecoveryEmailTitle: String { return self._s[1993]! } - public var Passport_Address_EditBankStatement: String { return self._s[1994]! } - public func Channel_AdminLog_MessageChangedUnlinkedGroup(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[1995]!, self._r[1995]!, [_1, _2]) - } - public var ChatSettings_DownloadInBackgroundInfo: String { return self._s[1996]! } - public var ShareMenu_Comment: String { return self._s[1997]! } - public var Permissions_ContactsTitle_v0: String { return self._s[1998]! } - public var Notifications_PermissionsTitle: String { return self._s[1999]! } - public var GroupPermission_NoSendLinks: String { return self._s[2000]! } - public var Privacy_Forwards_NeverAllow_Title: String { return self._s[2001]! } - public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[2002]! } - public var PeerInfo_PaneLinks: String { return self._s[2003]! } - public var Settings_Support: String { return self._s[2004]! } - public var Notifications_ChannelNotificationsSound: String { return self._s[2005]! } - public var SettingsSearch_Synonyms_Data_AutoDownloadReset: String { return self._s[2006]! } - public var Settings_SetNewProfilePhotoOrVideo: String { return self._s[2007]! } - public var Privacy_Forwards_Preview: String { return self._s[2008]! } - public var GroupPermission_ApplyAlertAction: String { return self._s[2009]! } - public var Watch_Stickers_StickerPacks: String { return self._s[2010]! } - public var Common_Select: String { return self._s[2012]! } - public var CheckoutInfo_ErrorEmailInvalid: String { return self._s[2013]! } - public var WallpaperSearch_ColorGray: String { return self._s[2016]! } - public var TwoFactorSetup_Password_PlaceholderPassword: String { return self._s[2017]! } - public var TwoFactorSetup_Hint_SkipAction: String { return self._s[2018]! } - public var ChatAdmins_AllMembersAreAdminsOffHelp: String { return self._s[2019]! } - public var PollResults_Title: String { return self._s[2020]! } - public var PasscodeSettings_AutoLock_IfAwayFor_5hours: String { return self._s[2021]! } - public var Appearance_PreviewReplyAuthor: String { return self._s[2022]! } - public var TwoStepAuth_RecoveryTitle: String { return self._s[2023]! } - public var Widget_AuthRequired: String { return self._s[2024]! } - public var ProfilePhoto_OpenInEditor: String { return self._s[2025]! } - public var Camera_FlashOn: String { return self._s[2026]! } - public var Conversation_ContextMenuLookUp: String { return self._s[2027]! } - public var Channel_Stickers_NotFoundHelp: String { return self._s[2028]! } - public var Watch_Suggestion_OK: String { return self._s[2029]! } - public func Username_LinkHint(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2031]!, self._r[2031]!, [_0]) - } - public func Notification_PinnedLiveLocationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2033]!, self._r[2033]!, [_0]) - } - public var TextFormat_Strikethrough: String { return self._s[2034]! } - public var DialogList_AdLabel: String { return self._s[2035]! } - public var WatchRemote_NotificationText: String { return self._s[2036]! } - public var IntentsSettings_SuggestedChatsSavedMessages: String { return self._s[2037]! } - public var SettingsSearch_Synonyms_Notifications_MessageNotificationsAlert: String { return self._s[2038]! } - public var Conversation_ReportSpam: String { return self._s[2039]! } - public var SettingsSearch_Synonyms_Privacy_Data_TopPeers: String { return self._s[2040]! } - public var Settings_LogoutConfirmationTitle: String { return self._s[2042]! } - public var PhoneLabel_Title: String { return self._s[2043]! } - public var Passport_Address_EditRentalAgreement: String { return self._s[2044]! } - public var Settings_ChangePhoneNumber: String { return self._s[2045]! } - public var Notifications_ExceptionsTitle: String { return self._s[2046]! } - public var Notifications_AlertTones: String { return self._s[2047]! } - public var Call_ReportIncludeLogDescription: String { return self._s[2048]! } - public var SettingsSearch_Synonyms_Notifications_ResetAllNotifications: String { return self._s[2049]! } - public var AutoDownloadSettings_PrivateChats: String { return self._s[2050]! } - public var VoiceOver_Chat_Photo: String { return self._s[2052]! } - public var TwoStepAuth_AddHintTitle: String { return self._s[2053]! } - public var Stats_PostsTitle: String { return self._s[2054]! } - public var ReportPeer_ReasonOther: String { return self._s[2055]! } - public var ChatList_Context_JoinChannel: String { return self._s[2056]! } - public var PhotoEditor_SkinTool: String { return self._s[2057]! } - public var KeyCommand_ScrollDown: String { return self._s[2059]! } - public var Conversation_ScheduleMessage_Title: String { return self._s[2060]! } - public func Login_BannedPhoneSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2061]!, self._r[2061]!, [_0]) - } - public var NetworkUsageSettings_MediaVideoDataSection: String { return self._s[2063]! } - public var ChannelInfo_DeleteGroupConfirmation: String { return self._s[2064]! } - public var AuthSessions_LogOut: String { return self._s[2065]! } - public func PUSH_VIDEO_CALL_MISSED(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2066]!, self._r[2066]!, [_1]) - } - public var Passport_Identity_TypeInternalPassport: String { return self._s[2067]! } - public var ChatSettings_AutoDownloadVoiceMessages: String { return self._s[2068]! } - public var Passport_Phone_Title: String { return self._s[2069]! } - public var ContactList_Context_StartSecretChat: String { return self._s[2070]! } - public var Settings_PhoneNumber: String { return self._s[2071]! } - public func Conversation_ScheduleMessage_SendToday(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2072]!, self._r[2072]!, [_0]) - } - public var NotificationsSound_Alert: String { return self._s[2074]! } - public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[2075]! } - public var WebSearch_SearchNoResults: String { return self._s[2076]! } - public var Privacy_ProfilePhoto_AlwaysShareWith_Title: String { return self._s[2078]! } - public var Wallet_Configuration_SourceInfo: String { return self._s[2079]! } - public var LogoutOptions_AlternativeOptionsSection: String { return self._s[2080]! } - public var SettingsSearch_Synonyms_Passport: String { return self._s[2081]! } - public var PhotoEditor_CurvesTool: String { return self._s[2082]! } - public var Checkout_PaymentMethod: String { return self._s[2084]! } - public func PUSH_CHAT_ADD_YOU(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2085]!, self._r[2085]!, [_1, _2]) - } - public var Contacts_AccessDeniedError: String { return self._s[2086]! } - public var Camera_PhotoMode: String { return self._s[2089]! } - public var EditTheme_Expand_Preview_IncomingText: String { return self._s[2090]! } - public var Appearance_TextSize_Apply: String { return self._s[2091]! } - public var Passport_Address_AddUtilityBill: String { return self._s[2093]! } - public var ChatListFolderSettings_RecommendedNewFolder: String { return self._s[2094]! } - public var CallSettings_OnMobile: String { return self._s[2095]! } - public var Tour_Text2: String { return self._s[2096]! } - public func PUSH_CHAT_MESSAGE_ROUND(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2097]!, self._r[2097]!, [_1, _2]) - } - public var DialogList_EncryptionProcessing: String { return self._s[2099]! } - public var Permissions_Skip: String { return self._s[2100]! } - public var Wallet_Words_NotDoneOk: String { return self._s[2101]! } - public var SecretImage_Title: String { return self._s[2102]! } - public var Watch_MessageView_Title: String { return self._s[2103]! } - public var Channel_DiscussionGroupAdd: String { return self._s[2104]! } - public var AttachmentMenu_Poll: String { return self._s[2105]! } - public func Notification_GroupInviter(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2106]!, self._r[2106]!, [_0]) - } - public func Channel_DiscussionGroup_PrivateChannelLink(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2107]!, self._r[2107]!, [_1, _2]) - } - public var Notification_CallCanceled: String { return self._s[2108]! } - public var WallpaperPreview_Title: String { return self._s[2109]! } - public var Privacy_PaymentsClear_PaymentInfo: String { return self._s[2110]! } - public var Settings_ProxyConnecting: String { return self._s[2111]! } - public var Settings_CheckPhoneNumberText: String { return self._s[2113]! } - public var VoiceOver_Chat_YourVideo: String { return self._s[2114]! } - public var Wallet_Intro_Title: String { return self._s[2115]! } - public var TwoFactorSetup_Password_Action: String { return self._s[2116]! } - public var Profile_MessageLifetime5s: String { return self._s[2117]! } - public var Username_InvalidCharacters: String { return self._s[2118]! } - public var VoiceOver_Media_PlaybackRateFast: String { return self._s[2119]! } - public var ScheduledMessages_ClearAll: String { return self._s[2120]! } - public var Group_MessageVideoUpdated: String { return self._s[2121]! } - public var WallpaperPreview_CropBottomText: String { return self._s[2122]! } - public var AutoDownloadSettings_LimitBySize: String { return self._s[2123]! } - public var Settings_AddAccount: String { return self._s[2124]! } - public var Notification_CreatedChannel: String { return self._s[2127]! } - public func PUSH_CHAT_DELETE_MEMBER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2128]!, self._r[2128]!, [_1, _2, _3]) - } - public var Passcode_AppLockedAlert: String { return self._s[2130]! } - public var StickerPacksSettings_AnimatedStickersInfo: String { return self._s[2131]! } - public var VoiceOver_Media_PlaybackStop: String { return self._s[2132]! } - public var Contacts_TopSection: String { return self._s[2133]! } - public var ChatList_DeleteForEveryoneConfirmationAction: String { return self._s[2134]! } - public func Conversation_SetReminder_RemindOn(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2135]!, self._r[2135]!, [_0, _1]) - } - public var Wallet_Info_Receive: String { return self._s[2136]! } - public var Wallet_Completed_ViewWallet: String { return self._s[2138]! } - public func Time_MonthOfYear_m6(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2139]!, self._r[2139]!, [_0]) - } - public var ReportPeer_ReasonSpam: String { return self._s[2140]! } - public var UserInfo_TapToCall: String { return self._s[2141]! } - public var Conversation_ForwardAuthorHiddenTooltip: String { return self._s[2143]! } - public var AutoDownloadSettings_DataUsageCustom: String { return self._s[2144]! } - public var Common_Search: String { return self._s[2145]! } - public var ScheduledMessages_EmptyPlaceholder: String { return self._s[2146]! } - public func Channel_AdminLog_MessageChangedGroupGeoLocation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2147]!, self._r[2147]!, [_0]) - } - public var Wallet_Month_ShortJuly: String { return self._s[2148]! } - public var AuthSessions_IncompleteAttemptsInfo: String { return self._s[2150]! } - public var Message_InvoiceLabel: String { return self._s[2151]! } - public var Conversation_InputTextPlaceholder: String { return self._s[2152]! } - public var NetworkUsageSettings_MediaImageDataSection: String { return self._s[2153]! } - public func Passport_Address_UploadOneOfScan(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2154]!, self._r[2154]!, [_0]) - } - public var IntentsSettings_Reset: String { return self._s[2155]! } - public var Conversation_Info: String { return self._s[2156]! } - public var Login_InfoDeletePhoto: String { return self._s[2157]! } - public var ChatListFolder_DiscardDiscard: String { return self._s[2159]! } - public var Passport_Language_vi: String { return self._s[2160]! } - public var UserInfo_ScamUserWarning: String { return self._s[2161]! } - public var Conversation_Search: String { return self._s[2162]! } - public var DialogList_DeleteBotConversationConfirmation: String { return self._s[2164]! } - public var ChatListFolder_NameGroups: String { return self._s[2165]! } - public var ReportPeer_ReasonPornography: String { return self._s[2166]! } - public var AutoDownloadSettings_PhotosTitle: String { return self._s[2167]! } - public var Conversation_SendMessageErrorGroupRestricted: String { return self._s[2168]! } - public var Map_LiveLocationGroupDescription: String { return self._s[2169]! } - public var Channel_Setup_TypeHeader: String { return self._s[2170]! } - public var AuthSessions_LoggedIn: String { return self._s[2171]! } - public var Privacy_Forwards_AlwaysAllow_Title: String { return self._s[2172]! } - public var Login_SmsRequestState3: String { return self._s[2173]! } - public var Passport_Address_EditUtilityBill: String { return self._s[2174]! } - public var Appearance_ReduceMotionInfo: String { return self._s[2175]! } - public var Join_ChannelsTooMuch: String { return self._s[2176]! } - public var Channel_Edit_LinkItem: String { return self._s[2177]! } - public var Privacy_Calls_P2PNever: String { return self._s[2178]! } - public var Conversation_AddToReadingList: String { return self._s[2180]! } - public var Share_MultipleMessagesDisabled: String { return self._s[2181]! } - public var Message_Animation: String { return self._s[2182]! } - public var Conversation_DefaultRestrictedMedia: String { return self._s[2183]! } - public var Map_Unknown: String { return self._s[2184]! } - public var AutoDownloadSettings_LastDelimeter: String { return self._s[2185]! } - public func PUSH_PINNED_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2186]!, self._r[2186]!, [_1, _2]) - } - public func Passport_FieldOneOf_Or(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2187]!, self._r[2187]!, [_1, _2]) - } - public var Call_StatusRequesting: String { return self._s[2188]! } - public var Conversation_SecretChatContextBotAlert: String { return self._s[2189]! } - public var SocksProxySetup_ProxyStatusChecking: String { return self._s[2190]! } - public var Stats_MessageInteractionsTitle: String { return self._s[2191]! } - public func PUSH_CHAT_MESSAGE_DOC(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2192]!, self._r[2192]!, [_1, _2]) - } - public func Notification_PinnedLocationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2193]!, self._r[2193]!, [_0]) - } - public var Update_Skip: String { return self._s[2194]! } - public var Group_Username_RemoveExistingUsernamesInfo: String { return self._s[2195]! } - public var BlockedUsers_Title: String { return self._s[2196]! } - public var Weekday_Monday: String { return self._s[2197]! } - public func PUSH_CHANNEL_MESSAGE_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2198]!, self._r[2198]!, [_1]) - } - public var Username_CheckingUsername: String { return self._s[2199]! } - public var NotificationsSound_Bell: String { return self._s[2200]! } - public var Conversation_SendMessageErrorFlood: String { return self._s[2201]! } - public var SettingsSearch_Synonyms_Notifications_DisplayNamesOnLockScreen: String { return self._s[2202]! } - public var ChannelMembers_ChannelAdminsTitle: String { return self._s[2203]! } - public func Notification_ChangedGroupVideo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2204]!, self._r[2204]!, [_0]) - } - public var ChatSettings_Groups: String { return self._s[2205]! } - public var WallpaperPreview_PatternPaternDiscard: String { return self._s[2206]! } - public var ChatList_PeerTypeContact: String { return self._s[2207]! } - public func Conversation_SetReminder_RemindTomorrow(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2208]!, self._r[2208]!, [_0]) - } - public var Your_card_was_declined: String { return self._s[2209]! } - public var TwoStepAuth_EnterPasswordHelp: String { return self._s[2211]! } - public var Wallet_Month_ShortApril: String { return self._s[2212]! } - public var ChatList_Unmute: String { return self._s[2213]! } - public var AuthSessions_AddDevice_ScanTitle: String { return self._s[2214]! } - public var PhotoEditor_CurvesAll: String { return self._s[2215]! } - public var Weekday_ShortTuesday: String { return self._s[2216]! } - public var DialogList_Read: String { return self._s[2217]! } - public var Appearance_AppIconClassic: String { return self._s[2218]! } - public var Conversation_Dice_u1F3B2: String { return self._s[2219]! } - public var ChannelMembers_WhoCanAddMembers_AllMembers: String { return self._s[2220]! } - public var Passport_Identity_Gender: String { return self._s[2221]! } - public func Target_ShareGameConfirmationPrivate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2222]!, self._r[2222]!, [_0]) - } - public var Target_SelectGroup: String { return self._s[2223]! } - public var Map_HomeAndWorkInfo: String { return self._s[2225]! } - public func DialogList_EncryptedChatStartedIncoming(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2226]!, self._r[2226]!, [_0]) - } - public var Passport_Language_en: String { return self._s[2227]! } - public var AutoDownloadSettings_AutodownloadPhotos: String { return self._s[2228]! } - public var Channel_Username_CreatePublicLinkHelp: String { return self._s[2229]! } - public var Login_CancelPhoneVerificationContinue: String { return self._s[2230]! } - public var ScheduledMessages_SendNow: String { return self._s[2231]! } - public var Checkout_NewCard_PaymentCard: String { return self._s[2233]! } - public var Login_InfoHelp: String { return self._s[2234]! } - public var Appearance_BubbleCorners_AdjustAdjacent: String { return self._s[2235]! } - public var ProfilePhoto_SetMainPhoto: String { return self._s[2236]! } - public var Contacts_PermissionsSuppressWarningTitle: String { return self._s[2237]! } - public var SettingsSearch_Synonyms_Stickers_FeaturedPacks: String { return self._s[2238]! } - public func Channel_AdminLog_MessageChangedLinkedChannel(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2239]!, self._r[2239]!, [_1, _2]) - } - public var SocksProxySetup_AddProxy: String { return self._s[2242]! } - public var CreatePoll_Title: String { return self._s[2243]! } - public var MessagePoll_QuizNoUsers: String { return self._s[2244]! } - public var Conversation_ViewTheme: String { return self._s[2245]! } - public var SettingsSearch_Synonyms_Privacy_Data_SecretChatLinkPreview: String { return self._s[2246]! } - public var PasscodeSettings_SimplePasscodeHelp: String { return self._s[2247]! } - public var TwoFactorSetup_Intro_Text: String { return self._s[2248]! } - public var UserInfo_GroupsInCommon: String { return self._s[2249]! } - public var TelegramWallet_Intro_TermsUrl: String { return self._s[2250]! } - public var Stats_ViewsByHoursTitle: String { return self._s[2251]! } - public var Conversation_PrivateChannelTimeLimitedAlertTitle: String { return self._s[2252]! } - public var Call_AudioRouteHide: String { return self._s[2253]! } - public func Wallet_Info_TransactionDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2255]!, self._r[2255]!, [_1, _2]) - } - public var ContactInfo_PhoneLabelMobile: String { return self._s[2256]! } - public var IntentsSettings_SuggestedChatsInfo: String { return self._s[2257]! } - public var CreatePoll_QuizOptionsHeader: String { return self._s[2258]! } - public func ChatList_LeaveGroupConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2259]!, self._r[2259]!, [_0]) - } - public var TextFormat_Bold: String { return self._s[2260]! } - public var CreatePoll_ExplanationInfo: String { return self._s[2261]! } - public var FastTwoStepSetup_EmailSection: String { return self._s[2262]! } - public var StickerPackActionInfo_AddedTitle: String { return self._s[2263]! } - public var Notifications_Title: String { return self._s[2264]! } - public var Group_Username_InvalidTooShort: String { return self._s[2265]! } - public var Channel_ErrorAddTooMuch: String { return self._s[2266]! } - public func DialogList_MultipleTypingSuffix(_ _0: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2267]!, self._r[2267]!, ["\(_0)"]) - } - public var VoiceOver_DiscardPreparedContent: String { return self._s[2269]! } - public var Stickers_SuggestAdded: String { return self._s[2270]! } - public var Login_CountryCode: String { return self._s[2271]! } - public var ChatSettings_AutoPlayVideos: String { return self._s[2272]! } - public var Map_GetDirections: String { return self._s[2273]! } - public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[2274]! } - public var Stats_GroupNewMembersBySourceTitle: String { return self._s[2275]! } - public var Login_PhoneFloodError: String { return self._s[2276]! } - public func Time_MonthOfYear_m3(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2277]!, self._r[2277]!, [_0]) - } - public func Wallet_Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2278]!, self._r[2278]!, [_1, _2, _3]) - } - public var IntentsSettings_SuggestedChatsPrivateChats: String { return self._s[2279]! } - public var Settings_SetUsername: String { return self._s[2281]! } - public var Group_Location_ChangeLocation: String { return self._s[2282]! } - public var Notification_GroupInviterSelf: String { return self._s[2283]! } - public var InstantPage_TapToOpenLink: String { return self._s[2284]! } - public func Notification_ChannelInviter(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2285]!, self._r[2285]!, [_0]) - } - public var PrivacySettings_AutoArchiveInfo: String { return self._s[2286]! } - public var Watch_Suggestion_TalkLater: String { return self._s[2287]! } - public var SecretChat_Title: String { return self._s[2288]! } - public var Group_UpgradeNoticeText1: String { return self._s[2289]! } - public var AuthSessions_Title: String { return self._s[2290]! } - public func TextFormat_AddLinkText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2291]!, self._r[2291]!, [_0]) - } - public var PhotoEditor_CropAuto: String { return self._s[2292]! } - public var Channel_About_Title: String { return self._s[2294]! } - public var Theme_ThemeChanged: String { return self._s[2295]! } - public var FastTwoStepSetup_EmailHelp: String { return self._s[2296]! } - public func Conversation_Bytes(_ _0: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2299]!, self._r[2299]!, ["\(_0)"]) - } - public var VoiceOver_MessageContextReport: String { return self._s[2300]! } - public var Conversation_PinMessageAlert_OnlyPin: String { return self._s[2302]! } - public var Group_Setup_HistoryVisibleHelp: String { return self._s[2303]! } - public func PUSH_MESSAGE_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2304]!, self._r[2304]!, [_1]) - } - public func SharedMedia_SearchNoResultsDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2306]!, self._r[2306]!, [_0]) - } - public func TwoStepAuth_RecoveryEmailUnavailable(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2307]!, self._r[2307]!, [_0]) - } - public var Privacy_PaymentsClearInfoHelp: String { return self._s[2308]! } - public var PeopleNearby_DiscoverDescription: String { return self._s[2310]! } - public var Presence_online: String { return self._s[2312]! } - public var PasscodeSettings_Title: String { return self._s[2313]! } - public var Passport_Identity_ExpiryDatePlaceholder: String { return self._s[2314]! } - public var Web_OpenExternal: String { return self._s[2315]! } - public var AutoDownloadSettings_AutoDownload: String { return self._s[2317]! } - public var Channel_OwnershipTransfer_EnterPasswordText: String { return self._s[2318]! } - public var LocalGroup_Title: String { return self._s[2319]! } - public func AutoNightTheme_AutomaticHelp(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2320]!, self._r[2320]!, [_0]) - } - public var FastTwoStepSetup_PasswordConfirmationPlaceholder: String { return self._s[2321]! } - public var Conversation_StopQuizConfirmation: String { return self._s[2322]! } - public var Map_YouAreHere: String { return self._s[2323]! } - public func AuthSessions_Message(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2324]!, self._r[2324]!, [_0]) - } - public func ChatList_DeleteChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2325]!, self._r[2325]!, [_0]) - } - public var Theme_Context_ChangeColors: String { return self._s[2326]! } - public var PrivacyLastSeenSettings_AlwaysShareWith: String { return self._s[2327]! } - public var Target_InviteToGroupErrorAlreadyInvited: String { return self._s[2328]! } - public func AuthSessions_AppUnofficial(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2329]!, self._r[2329]!, [_0]) - } - public func DialogList_LiveLocationSharingTo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2330]!, self._r[2330]!, [_0]) - } - public var SocksProxySetup_Username: String { return self._s[2331]! } - public var Bot_Start: String { return self._s[2332]! } - public func Channel_AdminLog_EmptyFilterQueryText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2333]!, self._r[2333]!, [_0]) - } - public func Channel_AdminLog_MessagePinned(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2334]!, self._r[2334]!, [_0]) - } - public var Contacts_SortByPresence: String { return self._s[2335]! } - public var AccentColor_Title: String { return self._s[2338]! } - public var Conversation_DiscardVoiceMessageTitle: String { return self._s[2339]! } - public func PUSH_CHAT_CREATED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2340]!, self._r[2340]!, [_1, _2]) - } + return formatWithArgumentRanges(self._s[244]!, self._r[244]!, [_0]) + } + public var Conversation_ClousStorageInfo_Description3: String { return self._s[245]! } + public var Contacts_SortByPresence: String { return self._s[246]! } + public var Watch_Location_Access: String { return self._s[247]! } + public var WallpaperPreview_CustomColorTopText: String { return self._s[248]! } + public var Passport_Address_TypeBankStatement: String { return self._s[249]! } + public var Group_Username_RevokeExistingUsernamesInfo: String { return self._s[250]! } + public var Conversation_ClearPrivateHistory: String { return self._s[251]! } + public var ChatList_Mute: String { return self._s[254]! } + public var Channel_AdminLog_CanDeleteMessagesOfOthers: String { return self._s[255]! } + public var Stats_PostsTitle: String { return self._s[256]! } + public var Paint_Masks: String { return self._s[258]! } + public var PasscodeSettings_TryAgainIn1Minute: String { return self._s[260]! } + public var Chat_AttachmentLimitReached: String { return self._s[261]! } + public var StickerPackActionInfo_ArchivedTitle: String { return self._s[262]! } + public var Watch_Stickers_StickerPacks: String { return self._s[263]! } + public var Channel_Setup_Title: String { return self._s[264]! } + public var GroupInfo_Administrators: String { return self._s[265]! } + public var NotificationSettings_ShowNotificationsAllAccountsInfoOff: String { return self._s[267]! } + public var Conversation_ContextMenuDiscuss: String { return self._s[268]! } + public var StickerPack_BuiltinPackName: String { return self._s[269]! } + public var TwoStepAuth_RecoveryEmailChangeDescription: String { return self._s[271]! } + public var Checkout_ShippingMethod: String { return self._s[273]! } + public var ClearCache_FreeSpace: String { return self._s[274]! } + public var EditTheme_Expand_Preview_IncomingReplyText: String { return self._s[275]! } + public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsSound: String { return self._s[277]! } + public func TwoStepAuth_ConfirmEmailDescription(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[278]!, self._r[278]!, [_1]) + } + public var Conversation_typing: String { return self._s[279]! } public func PrivacySettings_LastSeenContactsMinus(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2341]!, self._r[2341]!, [_0]) + return formatWithArgumentRanges(self._s[281]!, self._r[281]!, [_0]) } - public func Channel_AdminLog_MessageChangedLinkedGroup(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2342]!, self._r[2342]!, [_1, _2]) - } - public var Stats_GroupOverview: String { return self._s[2343]! } - public var Passport_Email_EnterOtherEmail: String { return self._s[2344]! } - public var Login_InfoAvatarPhoto: String { return self._s[2345]! } - public func ChatList_RemovedFromFolderTooltip(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2346]!, self._r[2346]!, [_1, _2]) - } - public var Privacy_PaymentsClear_ShippingInfo: String { return self._s[2347]! } - public var Tour_Title4: String { return self._s[2348]! } - public var Passport_Identity_Translation: String { return self._s[2349]! } - public var SettingsSearch_Synonyms_Notifications_ContactJoined: String { return self._s[2350]! } - public var Login_TermsOfServiceLabel: String { return self._s[2352]! } - public var CallFeedback_VideoReasonLowQuality: String { return self._s[2353]! } - public var Passport_Language_it: String { return self._s[2354]! } - public var KeyCommand_JumpToNextUnreadChat: String { return self._s[2355]! } - public var Passport_Identity_SelfieHelp: String { return self._s[2356]! } - public var Conversation_ClearAll: String { return self._s[2358]! } - public var Wallet_Send_UninitializedText: String { return self._s[2360]! } - public var Channel_OwnershipTransfer_Title: String { return self._s[2361]! } - public var TwoStepAuth_FloodError: String { return self._s[2362]! } - public func PUSH_CHANNEL_MESSAGE_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2363]!, self._r[2363]!, [_1]) - } - public var Paint_Delete: String { return self._s[2364]! } - public func Wallet_Sent_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2365]!, self._r[2365]!, [_0]) - } - public var Privacy_AddNewPeer: String { return self._s[2366]! } - public func Channel_AdminLog_MessageRank(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2367]!, self._r[2367]!, [_1]) - } - public var LogoutOptions_SetPasscodeText: String { return self._s[2368]! } - public func Passport_AcceptHelp(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2369]!, self._r[2369]!, [_1, _2]) - } - public var Message_PinnedAudioMessage: String { return self._s[2370]! } - public func Watch_Time_ShortTodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2371]!, self._r[2371]!, [_0]) - } - public var Notification_Mute1hMin: String { return self._s[2372]! } - public var Notifications_GroupNotificationsSound: String { return self._s[2373]! } - public var Wallet_Month_GenNovember: String { return self._s[2374]! } - public var SocksProxySetup_ShareProxyList: String { return self._s[2376]! } - public var Conversation_MessageEditedLabel: String { return self._s[2377]! } - public func ClearCache_Success(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2378]!, self._r[2378]!, [_0, _1]) - } - public var Notification_Exceptions_AlwaysOff: String { return self._s[2379]! } - public var Conversation_ContextMenuOpenChannelProfile: String { return self._s[2380]! } - public var Notification_Exceptions_NewException_MessagePreviewHeader: String { return self._s[2381]! } - public func Channel_AdminLog_MessageAdmin(_ _0: String, _ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2382]!, self._r[2382]!, [_0, _1, _2]) - } - public var NetworkUsageSettings_ResetStats: String { return self._s[2383]! } - public func PUSH_MESSAGE_GEOLIVE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2384]!, self._r[2384]!, [_1]) - } - public var AccessDenied_LocationTracking: String { return self._s[2385]! } - public var Month_GenOctober: String { return self._s[2386]! } - public var GroupInfo_InviteLink_RevokeAlert_Revoke: String { return self._s[2387]! } - public var EnterPasscode_EnterPasscode: String { return self._s[2388]! } - public var Call_CameraConfirmationConfirm: String { return self._s[2390]! } - public var MediaPicker_TimerTooltip: String { return self._s[2391]! } - public var SharedMedia_TitleAll: String { return self._s[2392]! } - public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsExceptions: String { return self._s[2395]! } - public var Conversation_RestrictedMedia: String { return self._s[2396]! } - public var AccessDenied_PhotosRestricted: String { return self._s[2397]! } - public var Privacy_Forwards_WhoCanForward: String { return self._s[2399]! } - public var ChangePhoneNumberCode_Called: String { return self._s[2400]! } - public func Notification_PinnedDocumentMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2401]!, self._r[2401]!, [_0]) - } - public var Conversation_SavedMessages: String { return self._s[2404]! } - public var Your_cards_expiration_month_is_invalid: String { return self._s[2406]! } - public var FastTwoStepSetup_PasswordPlaceholder: String { return self._s[2407]! } - public func Target_ShareGameConfirmationGroup(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2409]!, self._r[2409]!, [_0]) - } - public var VoiceOver_Chat_YourMessage: String { return self._s[2410]! } - public func VoiceOver_Chat_Title(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2411]!, self._r[2411]!, [_0]) - } - public var ReportPeer_AlertSuccess: String { return self._s[2412]! } - public var PhotoEditor_CropAspectRatioOriginal: String { return self._s[2413]! } - public func InstantPage_RelatedArticleAuthorAndDateTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2414]!, self._r[2414]!, [_1, _2]) - } - public var Checkout_PasswordEntry_Title: String { return self._s[2415]! } - public var PhotoEditor_FadeTool: String { return self._s[2416]! } - public var Privacy_ContactsReset: String { return self._s[2417]! } - public var Conversation_PrivateChannelTimeLimitedAlertText: String { return self._s[2418]! } - public func Channel_AdminLog_MessageRestrictedUntil(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2420]!, self._r[2420]!, [_0]) - } - public var Message_PinnedVideoMessage: String { return self._s[2421]! } - public var ChatList_Mute: String { return self._s[2422]! } - public func Wallet_Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2423]!, self._r[2423]!, [_1, _2, _3]) - } - public var Permissions_CellularDataText_v0: String { return self._s[2424]! } - public var Conversation_PinnedQuiz: String { return self._s[2426]! } - public var ShareMenu_SelectChats: String { return self._s[2428]! } - public var ChatList_Context_Unarchive: String { return self._s[2429]! } - public var MusicPlayer_VoiceNote: String { return self._s[2430]! } - public var Conversation_RestrictedText: String { return self._s[2431]! } - public var SettingsSearch_Synonyms_Privacy_Data_DeleteDrafts: String { return self._s[2432]! } - public var Wallet_Month_GenApril: String { return self._s[2433]! } - public var Wallet_Month_ShortMarch: String { return self._s[2434]! } - public var TwoStepAuth_DisableSuccess: String { return self._s[2435]! } - public var Chat_PsaTooltip_covid: String { return self._s[2436]! } - public var Cache_Videos: String { return self._s[2437]! } - public var PrivacySettings_PhoneNumber: String { return self._s[2438]! } - public var Wallet_Month_GenFebruary: String { return self._s[2439]! } - public var FeatureDisabled_Oops: String { return self._s[2441]! } - public var ChatList_RemoveFolderAction: String { return self._s[2442]! } - public var Passport_Address_PostcodePlaceholder: String { return self._s[2443]! } - public func AddContact_StatusSuccess(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2444]!, self._r[2444]!, [_0]) - } - public var Stickers_GroupStickersHelp: String { return self._s[2446]! } - public var GroupPermission_NoSendPolls: String { return self._s[2447]! } - public var Wallet_Qr_ScanCode: String { return self._s[2448]! } - public var Message_VideoExpired: String { return self._s[2450]! } - public var GroupInfo_GroupHistoryVisible: String { return self._s[2451]! } - public var Notifications_Badge: String { return self._s[2452]! } - public var Wallet_Receive_AddressCopied: String { return self._s[2453]! } - public var CreatePoll_OptionPlaceholder: String { return self._s[2454]! } - public var Username_InvalidTooShort: String { return self._s[2455]! } - public var EnterPasscode_EnterNewPasscodeChange: String { return self._s[2456]! } - public var Channel_AdminLog_PinMessages: String { return self._s[2457]! } - public var ArchivedChats_IntroTitle3: String { return self._s[2458]! } - public func Notification_MessageLifetimeRemoved(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2459]!, self._r[2459]!, [_1]) - } - public var Permissions_SiriAllowInSettings_v0: String { return self._s[2460]! } - public var Conversation_DefaultRestrictedText: String { return self._s[2461]! } - public var SharedMedia_CategoryDocs: String { return self._s[2464]! } - public func PUSH_MESSAGE_CONTACT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2465]!, self._r[2465]!, [_1]) - } - public var Wallet_Send_UninitializedTitle: String { return self._s[2466]! } - public var CallFeedback_VideoReasonDistorted: String { return self._s[2467]! } - public var StickerPackActionInfo_ArchivedTitle: String { return self._s[2468]! } - public var Privacy_Forwards_NeverLink: String { return self._s[2470]! } - public func Notification_MessageLifetimeChangedOutgoing(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2471]!, self._r[2471]!, [_1]) - } - public var CheckoutInfo_ErrorShippingNotAvailable: String { return self._s[2472]! } - public func Time_MonthOfYear_m12(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2473]!, self._r[2473]!, [_0]) - } - public var ChatSettings_PrivateChats: String { return self._s[2474]! } - public var SettingsSearch_Synonyms_EditProfile_Logout: String { return self._s[2475]! } - public var Conversation_PrivateMessageLinkCopied: String { return self._s[2476]! } - public var Channel_UpdatePhotoItem: String { return self._s[2477]! } - public var GroupInfo_LeftStatus: String { return self._s[2478]! } - public var Watch_MessageView_Forward: String { return self._s[2480]! } - public var ReportPeer_ReasonChildAbuse: String { return self._s[2481]! } - public var Cache_ClearEmpty: String { return self._s[2483]! } - public var Localization_LanguageName: String { return self._s[2485]! } - public var Wallet_AccessDenied_Title: String { return self._s[2486]! } - public var WebSearch_GIFs: String { return self._s[2487]! } - public var Notifications_DisplayNamesOnLockScreenInfoWithLink: String { return self._s[2488]! } - public var Wallet_AccessDenied_Settings: String { return self._s[2489]! } - public var AccessDenied_VideoCallCamera: String { return self._s[2490]! } - public var Username_InvalidStartsWithNumber: String { return self._s[2491]! } - public var Common_Back: String { return self._s[2492]! } - public var GroupInfo_Permissions_EditingDisabled: String { return self._s[2493]! } - public var Passport_Identity_DateOfBirthPlaceholder: String { return self._s[2494]! } - public var Wallet_Send_Send: String { return self._s[2495]! } - public func PUSH_CHANNEL_MESSAGE_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2497]!, self._r[2497]!, [_1, _2]) - } - public var Wallet_Info_RefreshErrorTitle: String { return self._s[2498]! } - public var ChatList_Tabs_All: String { return self._s[2499]! } - public var Wallet_Month_GenJune: String { return self._s[2500]! } - public var Passport_Email_Help: String { return self._s[2501]! } - public var Watch_Conversation_Reply: String { return self._s[2503]! } - public var Stats_GroupTopInvitersTitle: String { return self._s[2504]! } - public var Conversation_EditingMessageMediaChange: String { return self._s[2507]! } - public var Passport_Identity_IssueDatePlaceholder: String { return self._s[2508]! } - public var Channel_BanUser_Unban: String { return self._s[2510]! } - public var Channel_EditAdmin_PermissionPostMessages: String { return self._s[2511]! } - public var Group_Username_CreatePublicLinkHelp: String { return self._s[2512]! } - public var TwoStepAuth_ConfirmEmailCodePlaceholder: String { return self._s[2514]! } - public var Wallet_Send_AddressHeader: String { return self._s[2515]! } - public var Passport_Identity_Name: String { return self._s[2516]! } - public func Channel_DiscussionGroup_HeaderGroupSet(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2517]!, self._r[2517]!, [_0]) - } - public var GroupRemoved_ViewUserInfo: String { return self._s[2518]! } - public var Stats_MessageOverview: String { return self._s[2519]! } - public var Conversation_BlockUser: String { return self._s[2520]! } - public var Month_GenJanuary: String { return self._s[2521]! } - public var ChatSettings_TextSize: String { return self._s[2522]! } - public var Notification_PassportValuePhone: String { return self._s[2523]! } - public var MediaPlayer_UnknownArtist: String { return self._s[2524]! } - public var Passport_Language_ne: String { return self._s[2525]! } - public var Notification_CallBack: String { return self._s[2526]! } - public var Wallet_SecureStorageReset_BiometryTouchId: String { return self._s[2527]! } - public var TwoStepAuth_EmailHelp: String { return self._s[2528]! } - public func Time_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2529]!, self._r[2529]!, [_0]) - } - public var Channel_Info_Management: String { return self._s[2530]! } - public var Passport_FieldIdentityUploadHelp: String { return self._s[2531]! } - public var Stickers_FrequentlyUsed: String { return self._s[2533]! } - public var Channel_BanUser_PermissionSendMessages: String { return self._s[2534]! } - public var Passport_Address_OneOfTypeUtilityBill: String { return self._s[2536]! } - public func LOCAL_CHANNEL_MESSAGE_FWDS(_ _1: String, _ _2: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2537]!, self._r[2537]!, [_1, "\(_2)"]) - } - public var TwoFactorSetup_Password_Title: String { return self._s[2538]! } - public var Passport_Address_EditResidentialAddress: String { return self._s[2539]! } - public var PrivacyPolicy_DeclineTitle: String { return self._s[2540]! } - public var CreatePoll_TextHeader: String { return self._s[2541]! } - public func Checkout_SavePasswordTimeoutAndTouchId(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2542]!, self._r[2542]!, [_0]) - } - public var PhotoEditor_QualityMedium: String { return self._s[2543]! } - public var InfoPlist_NSMicrophoneUsageDescription: String { return self._s[2544]! } - public var Conversation_StatusKickedFromChannel: String { return self._s[2546]! } - public var CheckoutInfo_ReceiverInfoName: String { return self._s[2547]! } - public var Group_ErrorSendRestrictedStickers: String { return self._s[2548]! } - public func Conversation_RestrictedInlineTimed(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2549]!, self._r[2549]!, [_0]) - } - public func Channel_AdminLog_MessageTransferedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2550]!, self._r[2550]!, [_1]) - } - public var LogoutOptions_LogOutWalletInfo: String { return self._s[2551]! } - public var TwoFactorSetup_Email_SkipConfirmationTitle: String { return self._s[2552]! } - public var Conversation_LinkDialogOpen: String { return self._s[2554]! } - public var TwoFactorSetup_Hint_Title: String { return self._s[2555]! } - public var VoiceOver_Chat_PollNoVotes: String { return self._s[2556]! } - public var Settings_Username: String { return self._s[2558]! } - public var Conversation_Block: String { return self._s[2560]! } - public var Wallpaper_Wallpaper: String { return self._s[2561]! } - public var SocksProxySetup_UseProxy: String { return self._s[2563]! } - public var Wallet_Send_Confirmation: String { return self._s[2564]! } - public var EditTheme_UploadEditedTheme: String { return self._s[2565]! } - public var UserInfo_ShareMyContactInfo: String { return self._s[2566]! } - public var MessageTimer_Forever: String { return self._s[2567]! } - public var Privacy_Calls_WhoCanCallMe: String { return self._s[2568]! } - public var PhotoEditor_DiscardChanges: String { return self._s[2569]! } - public var AuthSessions_TerminateOtherSessionsHelp: String { return self._s[2570]! } - public var Passport_Language_da: String { return self._s[2571]! } - public var Conversation_PrivateChannelTimeLimitedAlertJoin: String { return self._s[2573]! } - public var SocksProxySetup_PortPlaceholder: String { return self._s[2574]! } - public func SecretGIF_NotViewedYet(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2575]!, self._r[2575]!, [_0]) - } - public var Passport_Address_EditPassportRegistration: String { return self._s[2576]! } - public func Channel_AdminLog_MessageChangedGroupAbout(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2578]!, self._r[2578]!, [_0]) - } - public var Settings_AddDevice: String { return self._s[2579]! } - public var Passport_Identity_ResidenceCountryPlaceholder: String { return self._s[2581]! } - public var AuthSessions_AddDeviceIntro_Text1: String { return self._s[2582]! } - public var Conversation_SearchByName_Prefix: String { return self._s[2583]! } - public var Conversation_PinnedPoll: String { return self._s[2584]! } - public var AuthSessions_AddDeviceIntro_Text2: String { return self._s[2585]! } - public var Conversation_EmptyGifPanelPlaceholder: String { return self._s[2586]! } - public var AuthSessions_AddDeviceIntro_Text3: String { return self._s[2587]! } - public func PUSH_ENCRYPTION_ACCEPT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2588]!, self._r[2588]!, [_1]) - } - public var WallpaperSearch_ColorPurple: String { return self._s[2589]! } - public var Cache_ByPeerHeader: String { return self._s[2590]! } - public func Conversation_EncryptedPlaceholderTitleIncoming(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2591]!, self._r[2591]!, [_0]) - } - public var ChatSettings_AutoDownloadDocuments: String { return self._s[2592]! } - public var Appearance_ThemePreview_Chat_3_Text: String { return self._s[2595]! } - public var Wallet_Completed_Title: String { return self._s[2596]! } - public var Notification_PinnedMessage: String { return self._s[2597]! } - public var TwoFactorSetup_EmailVerification_Placeholder: String { return self._s[2598]! } - public var VoiceOver_Chat_RecordModeVideoMessage: String { return self._s[2600]! } - public var Contacts_SortBy: String { return self._s[2601]! } + public var WebSearch_RecentSectionTitle: String { return self._s[282]! } + public var ChatList_UnhideAction: String { return self._s[283]! } + public var PasscodeSettings_6DigitCode: String { return self._s[284]! } + public var CallFeedback_AddComment: String { return self._s[285]! } + public var LoginPassword_PasswordHelp: String { return self._s[286]! } + public var Call_Flip: String { return self._s[287]! } + public var Weekday_ShortWednesday: String { return self._s[289]! } + public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[290]! } + public var VoiceOver_Chat_PollFinalResults: String { return self._s[291]! } + public var PeerInfo_ButtonAddMember: String { return self._s[292]! } + public var Call_Decline: String { return self._s[294]! } + public var Join_ChannelsTooMuch: String { return self._s[295]! } public func PUSH_CHANNEL_MESSAGE_NOTEXT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2602]!, self._r[2602]!, [_1]) - } - public var Appearance_ColorThemeNight: String { return self._s[2604]! } - public func PUSH_MESSAGE_GAME(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2605]!, self._r[2605]!, [_1, _2]) - } - public var Call_EncryptionKey_Title: String { return self._s[2606]! } - public var Watch_UserInfo_Service: String { return self._s[2607]! } - public var SettingsSearch_Synonyms_Data_SaveEditedPhotos: String { return self._s[2609]! } - public var Conversation_Unpin: String { return self._s[2611]! } - public var CancelResetAccount_Title: String { return self._s[2612]! } - public var Map_LiveLocationFor15Minutes: String { return self._s[2613]! } - public func Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2615]!, self._r[2615]!, [_1, _2, _3]) - } - public var Group_Members_AddMemberBotErrorNotAllowed: String { return self._s[2616]! } - public var Appearance_BubbleCorners_Title: String { return self._s[2617]! } - public var CallSettings_Title: String { return self._s[2618]! } - public var SettingsSearch_Synonyms_Appearance_ChatBackground: String { return self._s[2619]! } - public var PasscodeSettings_EncryptDataHelp: String { return self._s[2621]! } - public var AutoDownloadSettings_Contacts: String { return self._s[2622]! } - public func Channel_AdminLog_MessageRankName(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2623]!, self._r[2623]!, [_1, _2]) - } - public var ChatList_Tabs_AllChats: String { return self._s[2624]! } - public var Passport_Identity_DocumentDetails: String { return self._s[2625]! } - public var LoginPassword_PasswordHelp: String { return self._s[2626]! } - public var ChatListFolderSettings_Info: String { return self._s[2627]! } - public var SettingsSearch_Synonyms_Data_AutoDownloadUsingWifi: String { return self._s[2628]! } - public var PrivacyLastSeenSettings_CustomShareSettings_Delete: String { return self._s[2629]! } - public var ChatContextMenu_TextSelectionTip: String { return self._s[2630]! } - public var ChatListFolder_CategoryGroups: String { return self._s[2631]! } - public var Checkout_TotalPaidAmount: String { return self._s[2633]! } - public func FileSize_KB(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2634]!, self._r[2634]!, [_0]) - } - public var ChatState_Updating: String { return self._s[2635]! } - public var PasscodeSettings_ChangePasscode: String { return self._s[2636]! } - public var ChatListFolder_ExcludedSectionHeader: String { return self._s[2637]! } - public var Conversation_SecretLinkPreviewAlert: String { return self._s[2639]! } - public var Privacy_SecretChatsLinkPreviews: String { return self._s[2640]! } - public func PUSH_CHANNEL_MESSAGE_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2641]!, self._r[2641]!, [_1]) - } - public var VoiceOver_Chat_ReplyToYourMessage: String { return self._s[2642]! } - public var Contacts_InviteFriends: String { return self._s[2644]! } - public var Map_ChooseLocationTitle: String { return self._s[2645]! } - public var Conversation_StopPoll: String { return self._s[2647]! } - public func WebSearch_SearchNoResultsDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2648]!, self._r[2648]!, [_0]) - } - public var Call_Camera: String { return self._s[2649]! } - public var LogoutOptions_ChangePhoneNumberTitle: String { return self._s[2650]! } - public var AppWallet_Intro_Text: String { return self._s[2651]! } - public var Appearance_BubbleCornersSetting: String { return self._s[2652]! } - public var Calls_RatingFeedback: String { return self._s[2653]! } - public func Conversation_NoticeInvitedByInGroup(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2655]!, self._r[2655]!, [_0]) - } - public var GroupInfo_BroadcastListNamePlaceholder: String { return self._s[2656]! } - public var Wallet_Alert_OK: String { return self._s[2657]! } - public var NotificationsSound_Pulse: String { return self._s[2658]! } - public var Watch_LastSeen_Lately: String { return self._s[2659]! } - public var ReportGroupLocation_Report: String { return self._s[2662]! } - public var Widget_NoUsers: String { return self._s[2663]! } - public var Conversation_UnvotePoll: String { return self._s[2664]! } - public var SettingsSearch_Synonyms_Privacy_ProfilePhoto: String { return self._s[2666]! } - public var Privacy_ProfilePhoto_WhoCanSeeMyPhoto: String { return self._s[2667]! } - public var NotificationsSound_Circles: String { return self._s[2668]! } - public var PrivacyLastSeenSettings_AlwaysShareWith_Title: String { return self._s[2671]! } - public var Wallet_Settings_DeleteWallet: String { return self._s[2672]! } - public var ChatListFolder_CategoryBots: String { return self._s[2673]! } - public var TwoStepAuth_RecoveryCodeExpired: String { return self._s[2674]! } - public var Proxy_TooltipUnavailable: String { return self._s[2675]! } - public var Passport_Identity_CountryPlaceholder: String { return self._s[2677]! } - public var GroupInfo_Permissions_SlowmodeInfo: String { return self._s[2679]! } - public var Conversation_FileDropbox: String { return self._s[2680]! } - public var Notifications_ExceptionsUnmuted: String { return self._s[2681]! } - public var Tour_Text3: String { return self._s[2683]! } - public var Login_ResetAccountProtected_Title: String { return self._s[2686]! } - public var ChatListFolder_NamePlaceholder: String { return self._s[2687]! } - public var Settings_FrequentlyAskedQuestions: String { return self._s[2688]! } - public var GroupPermission_NoSendMessages: String { return self._s[2689]! } - public var WallpaperSearch_ColorTitle: String { return self._s[2690]! } - public var ChatAdmins_AllMembersAreAdminsOnHelp: String { return self._s[2691]! } - public func Conversation_LiveLocationYouAnd(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2693]!, self._r[2693]!, [_0]) - } - public var GroupInfo_AddParticipantTitle: String { return self._s[2694]! } - public var Checkout_ShippingOption_Title: String { return self._s[2695]! } - public var ChatSettings_AutoDownloadTitle: String { return self._s[2696]! } - public func DialogList_SingleTypingSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2697]!, self._r[2697]!, [_0]) - } - public func ChatSettings_AutoDownloadSettings_TypeVideo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2698]!, self._r[2698]!, [_0]) - } - public var Channel_Management_LabelAdministrator: String { return self._s[2699]! } - public var EditTheme_FileReadError: String { return self._s[2700]! } - public var OwnershipTransfer_ComeBackLater: String { return self._s[2701]! } - public var PrivacyLastSeenSettings_NeverShareWith_Placeholder: String { return self._s[2702]! } - public var AutoDownloadSettings_Photos: String { return self._s[2704]! } - public var Appearance_PreviewIncomingText: String { return self._s[2705]! } - public var ChatList_Context_MarkAllAsRead: String { return self._s[2706]! } - public var ChannelInfo_ConfirmLeave: String { return self._s[2707]! } - public var ChatListFolder_ExcludeSectionInfo: String { return self._s[2708]! } - public var MediaPicker_MomentsDateRangeSameMonthYearFormat: String { return self._s[2709]! } - public var Passport_Identity_DocumentNumberPlaceholder: String { return self._s[2710]! } - public var Channel_AdminLogFilter_EventsNewMembers: String { return self._s[2711]! } - public var PasscodeSettings_AutoLock_IfAwayFor_5minutes: String { return self._s[2712]! } - public var GroupInfo_SetGroupPhotoStop: String { return self._s[2713]! } - public var Notification_SecretChatScreenshot: String { return self._s[2714]! } - public var AccessDenied_Wallpapers: String { return self._s[2715]! } - public var ChatList_Context_Mute: String { return self._s[2717]! } - public var Passport_Address_City: String { return self._s[2718]! } - public var Settings_EditVideo: String { return self._s[2719]! } - public var InfoPlist_NSPhotoLibraryAddUsageDescription: String { return self._s[2720]! } - public var Appearance_ThemeCarouselClassic: String { return self._s[2721]! } - public var SocksProxySetup_SecretPlaceholder: String { return self._s[2722]! } - public var AccessDenied_LocationDisabled: String { return self._s[2723]! } - public var Group_Location_Title: String { return self._s[2724]! } - public var SocksProxySetup_HostnamePlaceholder: String { return self._s[2726]! } - public var GroupInfo_Sound: String { return self._s[2727]! } - public var SettingsSearch_Synonyms_ChatSettings_OpenLinksIn: String { return self._s[2728]! } - public var ChannelInfo_ScamChannelWarning: String { return self._s[2729]! } - public var Stickers_RemoveFromFavorites: String { return self._s[2730]! } - public var Contacts_Title: String { return self._s[2731]! } - public var EditTheme_ThemeTemplateAlertText: String { return self._s[2732]! } - public var Passport_Language_fr: String { return self._s[2733]! } - public var TwoFactorSetup_EmailVerification_Action: String { return self._s[2734]! } - public var Notifications_ResetAllNotifications: String { return self._s[2735]! } - public var IntentsSettings_SuggestedChats: String { return self._s[2737]! } - public var PrivacySettings_SecurityTitle: String { return self._s[2739]! } - public var Checkout_NewCard_Title: String { return self._s[2740]! } - public var Login_HaveNotReceivedCodeInternal: String { return self._s[2741]! } - public var Conversation_ForwardChats: String { return self._s[2742]! } - public var Wallet_SecureStorageReset_PasscodeText: String { return self._s[2744]! } - public var PasscodeSettings_4DigitCode: String { return self._s[2746]! } - public var Settings_FAQ: String { return self._s[2748]! } - public var AutoDownloadSettings_DocumentsTitle: String { return self._s[2749]! } - public var Conversation_ContextMenuForward: String { return self._s[2750]! } - public var VoiceOver_Chat_YourPhoto: String { return self._s[2753]! } - public var PrivacyPolicy_Title: String { return self._s[2756]! } - public var Notifications_TextTone: String { return self._s[2757]! } - public var Profile_CreateNewContact: String { return self._s[2758]! } - public var PrivacyPhoneNumberSettings_WhoCanSeeMyPhoneNumber: String { return self._s[2759]! } - public var TwoFactorSetup_EmailVerification_Title: String { return self._s[2761]! } - public var Call_Speaker: String { return self._s[2762]! } - public var AutoNightTheme_AutomaticSection: String { return self._s[2763]! } - public var Channel_OwnershipTransfer_EnterPassword: String { return self._s[2765]! } - public var Channel_Username_InvalidCharacters: String { return self._s[2766]! } - public func Channel_AdminLog_MessageChangedChannelUsername(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2767]!, self._r[2767]!, [_0]) - } - public var AutoDownloadSettings_AutodownloadFiles: String { return self._s[2768]! } - public var PrivacySettings_LastSeenTitle: String { return self._s[2769]! } - public var Channel_AdminLog_CanInviteUsers: String { return self._s[2770]! } - public var SettingsSearch_Synonyms_Privacy_Data_ClearPaymentsInfo: String { return self._s[2771]! } - public var OwnershipTransfer_SecurityCheck: String { return self._s[2772]! } - public var Conversation_MessageDeliveryFailed: String { return self._s[2773]! } - public var Watch_ChatList_NoConversationsText: String { return self._s[2774]! } - public var Bot_Unblock: String { return self._s[2775]! } - public var TextFormat_Italic: String { return self._s[2776]! } - public var WallpaperSearch_ColorPink: String { return self._s[2777]! } - public var Settings_About_Help: String { return self._s[2779]! } - public var SearchImages_Title: String { return self._s[2780]! } - public var Weekday_Wednesday: String { return self._s[2781]! } - public var Conversation_ClousStorageInfo_Description1: String { return self._s[2782]! } - public var ExplicitContent_AlertTitle: String { return self._s[2783]! } - public func Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2784]!, self._r[2784]!, [_1, _2, _3]) - } - public var Channel_DiscussionGroup_Create: String { return self._s[2785]! } - public var Weekday_Thursday: String { return self._s[2786]! } - public var Channel_BanUser_PermissionChangeGroupInfo: String { return self._s[2787]! } - public var Channel_Members_AddMembersHelp: String { return self._s[2788]! } - public func Checkout_SavePasswordTimeout(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2789]!, self._r[2789]!, [_0]) - } - public var Channel_DiscussionGroup_LinkGroup: String { return self._s[2790]! } - public var SettingsSearch_Synonyms_Notifications_InAppNotificationsVibrate: String { return self._s[2791]! } - public var Passport_RequestedInformation: String { return self._s[2792]! } - public var Login_PhoneAndCountryHelp: String { return self._s[2793]! } - public var Conversation_EncryptionProcessing: String { return self._s[2795]! } - public var Notifications_PermissionsSuppressWarningTitle: String { return self._s[2796]! } - public var PhotoEditor_EnhanceTool: String { return self._s[2798]! } - public var Channel_Setup_Title: String { return self._s[2799]! } - public var PeerInfo_PaneVoiceAndVideo: String { return self._s[2800]! } - public var Conversation_SearchPlaceholder: String { return self._s[2801]! } - public var OldChannels_GroupEmptyFormat: String { return self._s[2802]! } - public var AccessDenied_LocationAlwaysDenied: String { return self._s[2803]! } - public var Checkout_ErrorGeneric: String { return self._s[2804]! } - public var Passport_Language_hu: String { return self._s[2805]! } - public var GroupPermission_EditingDisabled: String { return self._s[2806]! } - public var Wallet_Month_ShortSeptember: String { return self._s[2808]! } - public func Passport_Identity_UploadOneOfScan(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2809]!, self._r[2809]!, [_0]) - } - public func PUSH_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2812]!, self._r[2812]!, [_1]) - } - public var ChatList_DeleteSavedMessagesConfirmationTitle: String { return self._s[2813]! } - public func UserInfo_BlockConfirmationTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2814]!, self._r[2814]!, [_0]) - } - public var Conversation_CloudStorageInfo_Title: String { return self._s[2815]! } - public var Group_Location_Info: String { return self._s[2816]! } - public var PhotoEditor_CropAspectRatioSquare: String { return self._s[2817]! } - public var Permissions_PeopleNearbyAllow_v0: String { return self._s[2818]! } - public func Notification_Exceptions_MutedUntil(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2820]!, self._r[2820]!, [_0]) - } - public var Conversation_ClearPrivateHistory: String { return self._s[2821]! } - public var ContactInfo_PhoneLabelHome: String { return self._s[2822]! } - public var Appearance_RemoveThemeConfirmation: String { return self._s[2823]! } - public var PrivacySettings_LastSeenContacts: String { return self._s[2824]! } - public func ChangePhone_ErrorOccupied(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2825]!, self._r[2825]!, [_0]) - } - public var Cache_MaximumCacheSizeHelp: String { return self._s[2826]! } - public func Notification_PinnedQuizMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2827]!, self._r[2827]!, [_0]) - } - public var Passport_Language_cs: String { return self._s[2828]! } - public var Message_PinnedAnimationMessage: String { return self._s[2830]! } - public var Passport_Identity_ReverseSideHelp: String { return self._s[2832]! } - public var SettingsSearch_Synonyms_Data_Storage_Title: String { return self._s[2833]! } - public var Wallet_Info_TransactionTo: String { return self._s[2835]! } - public var Stats_ViewsBySourceTitle: String { return self._s[2836]! } - public var ChatList_DeleteForEveryoneConfirmationText: String { return self._s[2837]! } - public var SettingsSearch_Synonyms_Privacy_PasscodeAndTouchId: String { return self._s[2838]! } - public var Embed_PlayingInPIP: String { return self._s[2839]! } - public var Appearance_ThemePreview_Chat_3_TextWithLink: String { return self._s[2840]! } - public var AutoNightTheme_ScheduleSection: String { return self._s[2841]! } - public var Stats_GroupMessages: String { return self._s[2842]! } - public func Call_EmojiDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2843]!, self._r[2843]!, [_0]) - } - public var MediaPicker_LivePhotoDescription: String { return self._s[2844]! } - public func Channel_AdminLog_MessageRestrictedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2845]!, self._r[2845]!, [_1]) - } - public var Notification_PaymentSent: String { return self._s[2846]! } - public var PhotoEditor_CurvesGreen: String { return self._s[2847]! } - public var Notification_Exceptions_PreviewAlwaysOff: String { return self._s[2848]! } - public var AutoNightTheme_System: String { return self._s[2849]! } - public var SaveIncomingPhotosSettings_Title: String { return self._s[2850]! } - public var CreatePoll_QuizTitle: String { return self._s[2851]! } - public var NotificationSettings_ShowNotificationsAllAccounts: String { return self._s[2852]! } - public var VoiceOver_Chat_PagePreview: String { return self._s[2853]! } - public func PUSH_MESSAGE_SCREENSHOT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2856]!, self._r[2856]!, [_1]) - } - public func PUSH_MESSAGE_PHOTO_SECRET(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2857]!, self._r[2857]!, [_1]) - } - public func ApplyLanguage_UnsufficientDataText(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2858]!, self._r[2858]!, [_1]) - } - public var NetworkUsageSettings_CallDataSection: String { return self._s[2860]! } - public var PasscodeSettings_HelpTop: String { return self._s[2861]! } - public var Conversation_WalletRequiredTitle: String { return self._s[2862]! } - public var PeerInfo_AddToContacts: String { return self._s[2863]! } - public var Group_OwnershipTransfer_ErrorAdminsTooMuch: String { return self._s[2864]! } - public var Passport_Address_TypeRentalAgreement: String { return self._s[2865]! } - public var FeaturedStickers_OtherSection: String { return self._s[2866]! } - public var EditTheme_ShortLink: String { return self._s[2868]! } - public var Theme_Colors_ColorWallpaperWarning: String { return self._s[2869]! } - public var ProxyServer_VoiceOver_Active: String { return self._s[2870]! } - public var ReportPeer_ReasonOther_Placeholder: String { return self._s[2871]! } - public var CheckoutInfo_ErrorPhoneInvalid: String { return self._s[2872]! } - public var Call_Accept: String { return self._s[2874]! } - public var GroupRemoved_RemoveInfo: String { return self._s[2875]! } - public var Month_GenMarch: String { return self._s[2877]! } - public var PhotoEditor_ShadowsTool: String { return self._s[2878]! } - public var LoginPassword_Title: String { return self._s[2879]! } - public var Call_End: String { return self._s[2880]! } - public var Watch_Conversation_GroupInfo: String { return self._s[2881]! } - public var VoiceOver_Chat_Contact: String { return self._s[2882]! } - public var EditTheme_Create_Preview_IncomingText: String { return self._s[2883]! } - public var CallSettings_Always: String { return self._s[2884]! } - public var CallFeedback_Success: String { return self._s[2885]! } - public var TwoStepAuth_SetupHint: String { return self._s[2886]! } - public func AddContact_ContactWillBeSharedAfterMutual(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2887]!, self._r[2887]!, [_1]) - } - public var ConversationProfile_UsersTooMuchError: String { return self._s[2888]! } - public var PeerInfo_ButtonAddMember: String { return self._s[2889]! } - public var Login_PhoneTitle: String { return self._s[2890]! } - public var Passport_FieldPhoneHelp: String { return self._s[2891]! } - public var Weekday_ShortSunday: String { return self._s[2892]! } - public var Passport_InfoFAQ_URL: String { return self._s[2893]! } - public var ContactInfo_Job: String { return self._s[2895]! } - public var UserInfo_InviteBotToGroup: String { return self._s[2896]! } - public var Appearance_ThemeCarouselNightBlue: String { return self._s[2897]! } - public var CreatePoll_QuizTip: String { return self._s[2898]! } - public var TwoFactorSetup_Email_Text: String { return self._s[2899]! } - public var TwoStepAuth_PasswordRemovePassportConfirmation: String { return self._s[2900]! } - public var Invite_ChannelsTooMuch: String { return self._s[2901]! } - public var Wallet_Send_ConfirmationConfirm: String { return self._s[2902]! } - public var Wallet_TransactionInfo_OtherFeeInfo: String { return self._s[2903]! } - public var SettingsSearch_Synonyms_Notifications_InAppNotificationsPreview: String { return self._s[2904]! } - public var Wallet_Receive_AmountText: String { return self._s[2905]! } - public var TwoStepAuth_Disable: String { return self._s[2906]! } - public var Passport_DeletePersonalDetailsConfirmation: String { return self._s[2907]! } - public var CallFeedback_ReasonNoise: String { return self._s[2908]! } - public var Appearance_AppIconDefault: String { return self._s[2910]! } - public var Passport_Identity_AddInternalPassport: String { return self._s[2911]! } - public var MediaPicker_AddCaption: String { return self._s[2912]! } - public var CallSettings_TabIconDescription: String { return self._s[2913]! } - public func VoiceOver_Chat_Caption(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2914]!, self._r[2914]!, [_0]) - } - public var IntentsSettings_SuggestedChatsGroups: String { return self._s[2915]! } - public func Map_SearchNoResultsDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2916]!, self._r[2916]!, [_0]) - } - public var CreatePoll_ExplanationHeader: String { return self._s[2918]! } - public var ChatList_UndoArchiveHiddenTitle: String { return self._s[2919]! } - public var Privacy_GroupsAndChannels_AlwaysAllow: String { return self._s[2920]! } - public var Passport_Identity_TypePersonalDetails: String { return self._s[2921]! } - public var DialogList_SearchSectionRecent: String { return self._s[2922]! } - public var PrivacyPolicy_DeclineMessage: String { return self._s[2923]! } - public var CreatePoll_Anonymous: String { return self._s[2924]! } - public var LogoutOptions_ClearCacheText: String { return self._s[2927]! } - public var Stats_GroupTopInviter_Promote: String { return self._s[2928]! } - public var LastSeen_WithinAWeek: String { return self._s[2929]! } - public var ChannelMembers_GroupAdminsTitle: String { return self._s[2930]! } - public var SettingsSearch_Synonyms_ChatSettings_IntentsSettings: String { return self._s[2932]! } - public var Conversation_CloudStorage_ChatStatus: String { return self._s[2933]! } - public var VoiceOver_Media_PlaybackRateNormal: String { return self._s[2935]! } - public func AddContact_SharedContactExceptionInfo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2936]!, self._r[2936]!, [_0]) - } - public var Passport_Address_TypeResidentialAddress: String { return self._s[2937]! } - public var Conversation_StatusLeftGroup: String { return self._s[2938]! } - public var SocksProxySetup_ProxyDetailsTitle: String { return self._s[2939]! } - public var OwnershipTransfer_Transfer: String { return self._s[2941]! } - public var SettingsSearch_Synonyms_Calls_Title: String { return self._s[2942]! } - public var ProfilePhoto_MainPhoto: String { return self._s[2943]! } - public var GroupPermission_AddSuccess: String { return self._s[2945]! } - public var PhotoEditor_BlurToolRadial: String { return self._s[2947]! } - public var Conversation_ContextMenuCopy: String { return self._s[2948]! } - public var AccessDenied_CallMicrophone: String { return self._s[2949]! } - public func Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2950]!, self._r[2950]!, [_1, _2, _3]) - } - public var Login_InvalidFirstNameError: String { return self._s[2951]! } - public var Notifications_Badge_CountUnreadMessages_InfoOn: String { return self._s[2952]! } - public var Checkout_PaymentMethod_New: String { return self._s[2953]! } - public var ShareMenu_CopyShareLinkGame: String { return self._s[2954]! } - public var PhotoEditor_QualityTool: String { return self._s[2955]! } - public var Login_SendCodeViaSms: String { return self._s[2956]! } - public var SettingsSearch_Synonyms_Privacy_DeleteAccountIfAwayFor: String { return self._s[2957]! } - public var Chat_SlowmodeAttachmentLimitReached: String { return self._s[2958]! } - public var Wallet_Receive_CopyAddress: String { return self._s[2959]! } - public var Login_EmailNotConfiguredError: String { return self._s[2960]! } - public var Stats_GroupTopAdminsTitle: String { return self._s[2961]! } - public var SocksProxySetup_Status: String { return self._s[2962]! } - public var Conversation_ScheduleMessage_SendWhenOnline: String { return self._s[2963]! } - public var PrivacyPolicy_Accept: String { return self._s[2964]! } - public var Notifications_ExceptionsMessagePlaceholder: String { return self._s[2965]! } - public var Appearance_AppIconClassicX: String { return self._s[2966]! } - public func PUSH_CHAT_MESSAGE_TEXT(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2967]!, self._r[2967]!, [_1, _2, _3]) - } - public var OwnershipTransfer_SecurityRequirements: String { return self._s[2968]! } - public var InfoPlist_NSLocationAlwaysUsageDescription: String { return self._s[2970]! } - public var AutoNightTheme_Automatic: String { return self._s[2971]! } - public var Channel_Username_InvalidStartsWithNumber: String { return self._s[2972]! } - public var Privacy_ContactsSyncHelp: String { return self._s[2973]! } - public var Cache_Help: String { return self._s[2974]! } - public var Group_ErrorAccessDenied: String { return self._s[2975]! } - public var Passport_Language_fa: String { return self._s[2976]! } - public var Wallet_Intro_Text: String { return self._s[2977]! } - public var ProfilePhoto_SetMainVideo: String { return self._s[2978]! } - public var Login_ResetAccountProtected_TimerTitle: String { return self._s[2979]! } - public var VoiceOver_Chat_YourVideoMessage: String { return self._s[2980]! } - public var PrivacySettings_LastSeen: String { return self._s[2981]! } - public func DialogList_MultipleTyping(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2982]!, self._r[2982]!, [_0, _1]) - } - public var Wallet_Configuration_Apply: String { return self._s[2986]! } - public var Preview_SaveGif: String { return self._s[2987]! } - public var SettingsSearch_Synonyms_Privacy_TwoStepAuth: String { return self._s[2988]! } - public var Profile_About: String { return self._s[2989]! } - public var Channel_About_Placeholder: String { return self._s[2991]! } - public var Login_InfoTitle: String { return self._s[2992]! } - public func TwoStepAuth_SetupPendingEmail(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[2993]!, self._r[2993]!, [_0]) - } - public var EditTheme_Expand_Preview_IncomingReplyText: String { return self._s[2994]! } - public var Watch_Suggestion_CantTalk: String { return self._s[2997]! } - public var ContactInfo_Title: String { return self._s[2998]! } - public var Media_ShareThisVideo: String { return self._s[2999]! } - public var Chat_GenericPsaTooltip: String { return self._s[3000]! } - public var Weekday_ShortFriday: String { return self._s[3001]! } - public var AccessDenied_Contacts: String { return self._s[3003]! } - public var Notification_CallIncomingShort: String { return self._s[3004]! } - public var Group_Setup_TypePublic: String { return self._s[3005]! } - public var Notifications_MessageNotificationsExceptions: String { return self._s[3006]! } - public var Notifications_Badge_IncludeChannels: String { return self._s[3007]! } - public var Settings_EditAccount: String { return self._s[3010]! } - public var Notifications_MessageNotificationsPreview: String { return self._s[3011]! } - public var ConversationProfile_ErrorCreatingConversation: String { return self._s[3012]! } - public var Group_ErrorAddTooMuchBots: String { return self._s[3013]! } - public var Privacy_GroupsAndChannels_CustomShareHelp: String { return self._s[3014]! } - public var Permissions_CellularDataAllowInSettings_v0: String { return self._s[3015]! } - public func Call_RemoteVideoPaused(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3016]!, self._r[3016]!, [_0]) - } - public func Wallet_SecureStorageChanged_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3017]!, self._r[3017]!, [_0]) - } - public var DialogList_Typing: String { return self._s[3018]! } - public var CallFeedback_IncludeLogs: String { return self._s[3020]! } - public var Checkout_Phone: String { return self._s[3022]! } - public var Login_InfoFirstNamePlaceholder: String { return self._s[3025]! } - public var Privacy_Calls_Integration: String { return self._s[3026]! } - public var Notifications_PermissionsAllow: String { return self._s[3027]! } - public var TwoStepAuth_AddHintDescription: String { return self._s[3033]! } - public var Settings_ChatSettings: String { return self._s[3034]! } - public var Conversation_SendingOptionsTooltip: String { return self._s[3035]! } - public func UserInfo_StartSecretChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3037]!, self._r[3037]!, [_0]) - } - public func Channel_AdminLog_MessageInvitedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3038]!, self._r[3038]!, [_1, _2]) - } - public var GroupRemoved_DeleteUser: String { return self._s[3040]! } - public func Channel_AdminLog_PollStopped(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3041]!, self._r[3041]!, [_0]) - } - public var ChatListFolder_CategoryMuted: String { return self._s[3042]! } - public func PUSH_MESSAGE_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3043]!, self._r[3043]!, [_1]) - } - public var Login_ContinueWithLocalization: String { return self._s[3044]! } - public var Watch_Message_ForwardedFrom: String { return self._s[3045]! } - public var TwoStepAuth_EnterEmailCode: String { return self._s[3047]! } - public var Notification_VideoCallIncoming: String { return self._s[3048]! } - public var Conversation_Unblock: String { return self._s[3049]! } - public var PrivacySettings_DataSettings: String { return self._s[3050]! } - public var WallpaperPreview_PatternPaternApply: String { return self._s[3051]! } - public var Group_PublicLink_Info: String { return self._s[3052]! } - public func Wallet_Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3053]!, self._r[3053]!, [_1, _2, _3]) - } - public var Notifications_InAppNotificationsVibrate: String { return self._s[3054]! } - public func Privacy_GroupsAndChannels_InviteToChannelError(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3055]!, self._r[3055]!, [_0, _1]) - } - public var ChatList_FolderAllChats: String { return self._s[3056]! } - public var OldChannels_ChannelsHeader: String { return self._s[3058]! } - public var Wallet_RestoreFailed_CreateWallet: String { return self._s[3059]! } - public var PrivacySettings_Passcode: String { return self._s[3061]! } - public var Call_Mute: String { return self._s[3062]! } - public var Call_CameraTooltip: String { return self._s[3063]! } - public var Wallet_Weekday_Yesterday: String { return self._s[3064]! } - public var Passport_Language_dz: String { return self._s[3065]! } - public var Wallet_Receive_AmountHeader: String { return self._s[3066]! } - public var Wallet_TransactionInfo_OtherFeeInfoUrl: String { return self._s[3067]! } - public var Passport_Language_tk: String { return self._s[3068]! } - public func Login_EmailCodeSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3069]!, self._r[3069]!, [_0]) - } - public var Settings_Search: String { return self._s[3070]! } - public var Wallet_Month_ShortFebruary: String { return self._s[3071]! } - public var InfoPlist_NSPhotoLibraryUsageDescription: String { return self._s[3072]! } - public var Wallet_Configuration_SourceJSON: String { return self._s[3073]! } - public var Conversation_ContextMenuReply: String { return self._s[3074]! } - public var WallpaperSearch_ColorBrown: String { return self._s[3075]! } - public var Chat_AttachmentMultipleForwardDisabled: String { return self._s[3076]! } - public var Tour_Title1: String { return self._s[3077]! } - public var Wallet_Alert_Cancel: String { return self._s[3078]! } - public var Stats_Total: String { return self._s[3080]! } - public var Conversation_ClearGroupHistory: String { return self._s[3081]! } - public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[3082]! } - public var WallpaperPreview_Motion: String { return self._s[3083]! } - public func Checkout_PasswordEntry_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3084]!, self._r[3084]!, [_0]) - } - public var Wallet_Configuration_ApplyErrorTextJSONInvalidData: String { return self._s[3085]! } - public var Call_RateCall: String { return self._s[3086]! } - public var Channel_AdminLog_BanSendStickersAndGifs: String { return self._s[3087]! } - public var Passport_PasswordCompleteSetup: String { return self._s[3088]! } - public var Conversation_InputTextSilentBroadcastPlaceholder: String { return self._s[3089]! } - public var UserInfo_LastNamePlaceholder: String { return self._s[3091]! } - public func Login_WillCallYou(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3093]!, self._r[3093]!, [_0]) - } - public var Compose_Create: String { return self._s[3094]! } - public var Contacts_InviteToTelegram: String { return self._s[3095]! } - public var GroupInfo_Notifications: String { return self._s[3096]! } - public var ChatList_DeleteSavedMessagesConfirmationAction: String { return self._s[3098]! } - public var Message_PinnedLiveLocationMessage: String { return self._s[3099]! } - public var Month_GenApril: String { return self._s[3100]! } - public var Appearance_AutoNightTheme: String { return self._s[3101]! } - public var ChatSettings_AutomaticAudioDownload: String { return self._s[3103]! } - public var Login_CodeSentSms: String { return self._s[3105]! } - public func UserInfo_UnblockConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3106]!, self._r[3106]!, [_0]) - } - public var EmptyGroupInfo_Line3: String { return self._s[3107]! } - public var LogoutOptions_ContactSupportText: String { return self._s[3108]! } - public var Passport_Language_hr: String { return self._s[3109]! } - public var Common_ActionNotAllowedError: String { return self._s[3110]! } - public func Channel_AdminLog_MessageRestrictedNewSetting(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3111]!, self._r[3111]!, [_0]) - } - public var GroupInfo_InviteLink_CopyLink: String { return self._s[3112]! } - public var Wallet_Info_TransactionFrom: String { return self._s[3113]! } - public var Wallet_Send_ErrorDecryptionFailed: String { return self._s[3114]! } - public var Conversation_InputTextBroadcastPlaceholder: String { return self._s[3115]! } - public var Privacy_SecretChatsTitle: String { return self._s[3116]! } - public var Notification_SecretChatMessageScreenshotSelf: String { return self._s[3118]! } - public var GroupInfo_AddUserLeftError: String { return self._s[3119]! } - public var AutoDownloadSettings_TypePrivateChats: String { return self._s[3120]! } - public var ChatListFolder_NameSectionHeader: String { return self._s[3121]! } - public var LogoutOptions_ContactSupportTitle: String { return self._s[3122]! } - public var Appearance_ThemePreview_Chat_7_Text: String { return self._s[3123]! } - public var Conversation_Unarchive: String { return self._s[3124]! } - public var Channel_AddBotErrorHaveRights: String { return self._s[3125]! } - public var Preview_DeleteGif: String { return self._s[3126]! } - public var GroupInfo_Permissions_Exceptions: String { return self._s[3127]! } - public var Group_ErrorNotMutualContact: String { return self._s[3128]! } - public var Notification_MessageLifetime5s: String { return self._s[3129]! } - public var Wallet_Send_OwnAddressAlertText: String { return self._s[3130]! } - public var OldChannels_ChannelFormat: String { return self._s[3131]! } - public func Watch_LastSeen_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3132]!, self._r[3132]!, [_0]) - } - public var VoiceOver_Chat_Video: String { return self._s[3133]! } - public var Channel_OwnershipTransfer_ErrorPublicChannelsTooMuch: String { return self._s[3135]! } - public var ReportSpam_DeleteThisChat: String { return self._s[3136]! } - public var Passport_Address_AddBankStatement: String { return self._s[3137]! } - public var Notification_CallIncoming: String { return self._s[3138]! } - public var Wallet_Words_NotDoneTitle: String { return self._s[3139]! } - public var Compose_NewGroupTitle: String { return self._s[3140]! } - public var TwoStepAuth_RecoveryCodeHelp: String { return self._s[3142]! } - public var Passport_Address_Postcode: String { return self._s[3144]! } - public func LastSeen_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3145]!, self._r[3145]!, [_0]) - } - public var Checkout_NewCard_SaveInfoHelp: String { return self._s[3146]! } - public var Wallet_Month_ShortOctober: String { return self._s[3147]! } - public var VoiceOver_Chat_YourMusic: String { return self._s[3148]! } - public var WallpaperColors_Title: String { return self._s[3149]! } - public var SocksProxySetup_ShareQRCodeInfo: String { return self._s[3150]! } - public var VoiceOver_MessageContextForward: String { return self._s[3151]! } - public var GroupPermission_Duration: String { return self._s[3152]! } - public func Cache_Clear(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3153]!, self._r[3153]!, [_0]) - } - public var Bot_GroupStatusDoesNotReadHistory: String { return self._s[3154]! } - public var Username_Placeholder: String { return self._s[3155]! } - public var CallFeedback_WhatWentWrong: String { return self._s[3156]! } - public var Passport_FieldAddressUploadHelp: String { return self._s[3157]! } - public var Permissions_NotificationsAllowInSettings_v0: String { return self._s[3158]! } - public func Channel_AdminLog_MessageChangedUnlinkedChannel(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3161]!, self._r[3161]!, [_1, _2]) - } - public var Passport_PasswordDescription: String { return self._s[3162]! } - public var Channel_MessagePhotoUpdated: String { return self._s[3163]! } - public var MediaPicker_TapToUngroupDescription: String { return self._s[3164]! } - public var SettingsSearch_Synonyms_Notifications_BadgeCountUnreadMessages: String { return self._s[3165]! } - public var AttachmentMenu_PhotoOrVideo: String { return self._s[3166]! } - public var Conversation_ContextMenuMore: String { return self._s[3167]! } - public var Privacy_PaymentsClearInfo: String { return self._s[3168]! } - public var CallSettings_TabIcon: String { return self._s[3169]! } - public var KeyCommand_Find: String { return self._s[3170]! } - public var ClearCache_FreeSpaceDescription: String { return self._s[3171]! } - public var Appearance_ThemePreview_ChatList_7_Text: String { return self._s[3172]! } - public var EditTheme_Edit_Preview_IncomingText: String { return self._s[3173]! } - public func Conversation_NoticeInvitedByInChannel(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3174]!, self._r[3174]!, [_0]) - } - public var Message_PinnedGame: String { return self._s[3175]! } - public var VoiceOver_Chat_ForwardedFromYou: String { return self._s[3176]! } - public var Notifications_Badge_CountUnreadMessages_InfoOff: String { return self._s[3178]! } - public var Login_CallRequestState2: String { return self._s[3180]! } - public var CheckoutInfo_ReceiverInfoNamePlaceholder: String { return self._s[3182]! } - public func VoiceOver_Chat_PhotoFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3183]!, self._r[3183]!, [_0]) - } - public func Checkout_PayPrice(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3185]!, self._r[3185]!, [_0]) - } - public var AuthSessions_AddDevice: String { return self._s[3186]! } - public var WallpaperPreview_Blurred: String { return self._s[3187]! } - public var Conversation_InstantPagePreview: String { return self._s[3188]! } - public var PeerInfo_ButtonUnmute: String { return self._s[3189]! } - public func DialogList_SingleUploadingVideoSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3190]!, self._r[3190]!, [_0]) - } - public var ChatList_PeerTypeChannel: String { return self._s[3191]! } - public var SecretTimer_VideoDescription: String { return self._s[3194]! } - public var WallpaperSearch_ColorRed: String { return self._s[3195]! } - public var GroupPermission_NoPinMessages: String { return self._s[3196]! } - public var Passport_Language_es: String { return self._s[3197]! } - public var Permissions_ContactsAllow_v0: String { return self._s[3199]! } - public var Conversation_EditingMessageMediaEditCurrentVideo: String { return self._s[3200]! } - public func PUSH_CHAT_MESSAGE_CONTACT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3201]!, self._r[3201]!, [_1, _2]) - } - public var Privacy_Forwards_CustomHelp: String { return self._s[3202]! } - public var WebPreview_GettingLinkInfo: String { return self._s[3204]! } - public var Watch_UserInfo_Unmute: String { return self._s[3205]! } - public var GroupInfo_ChannelListNamePlaceholder: String { return self._s[3206]! } - public var AccessDenied_CameraRestricted: String { return self._s[3208]! } - public func Conversation_Kilobytes(_ _0: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3209]!, self._r[3209]!, ["\(_0)"]) - } - public var ChatList_ReadAll: String { return self._s[3211]! } - public var Settings_CopyUsername: String { return self._s[3212]! } - public var Contacts_SearchLabel: String { return self._s[3213]! } - public var Map_OpenInYandexNavigator: String { return self._s[3215]! } - public var PasscodeSettings_EncryptData: String { return self._s[3216]! } - public var Settings_Wallet: String { return self._s[3217]! } - public var Group_ErrorSupergroupConversionNotPossible: String { return self._s[3218]! } - public var ChatList_PeerTypeBot: String { return self._s[3219]! } - public var WallpaperSearch_ColorPrefix: String { return self._s[3220]! } - public var Notifications_GroupNotificationsPreview: String { return self._s[3221]! } - public var DialogList_AdNoticeAlert: String { return self._s[3222]! } - public var Wallet_Month_GenMay: String { return self._s[3224]! } - public var CheckoutInfo_ShippingInfoAddress1: String { return self._s[3225]! } - public var CheckoutInfo_ShippingInfoAddress2: String { return self._s[3226]! } - public var Localization_LanguageCustom: String { return self._s[3227]! } - public var Passport_Identity_TypeDriversLicenseUploadScan: String { return self._s[3228]! } - public var CallFeedback_Title: String { return self._s[3229]! } - public var VoiceOver_Chat_RecordPreviewVoiceMessage: String { return self._s[3232]! } - public var Passport_Address_OneOfTypePassportRegistration: String { return self._s[3233]! } - public var Wallet_Intro_CreateErrorTitle: String { return self._s[3234]! } - public var Conversation_InfoGroup: String { return self._s[3235]! } - public var Compose_NewMessage: String { return self._s[3236]! } - public var FastTwoStepSetup_HintPlaceholder: String { return self._s[3237]! } - public var ChatSettings_AutoDownloadVideoMessages: String { return self._s[3238]! } - public var Wallet_SecureStorageReset_BiometryFaceId: String { return self._s[3239]! } - public var Channel_DiscussionGroup_UnlinkChannel: String { return self._s[3240]! } - public func Passport_Scans_ScanIndex(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3241]!, self._r[3241]!, [_0]) - } - public var Channel_AdminLog_CanDeleteMessages: String { return self._s[3242]! } - public var Login_CancelSignUpConfirmation: String { return self._s[3243]! } - public var ChangePhoneNumberCode_Help: String { return self._s[3244]! } - public var PrivacySettings_DeleteAccountHelp: String { return self._s[3245]! } - public var ChatList_Context_RemoveFromFolder: String { return self._s[3246]! } - public var Channel_BlackList_Title: String { return self._s[3247]! } - public var UserInfo_PhoneCall: String { return self._s[3248]! } - public var Passport_Address_OneOfTypeBankStatement: String { return self._s[3250]! } - public var Wallet_Month_ShortJanuary: String { return self._s[3251]! } - public var State_connecting: String { return self._s[3252]! } - public var Appearance_ThemePreview_ChatList_6_Text: String { return self._s[3253]! } - public var Wallet_Month_GenMarch: String { return self._s[3254]! } - public var EditTheme_Expand_BottomInfo: String { return self._s[3255]! } - public var AuthSessions_AddedDeviceTerminate: String { return self._s[3256]! } - public func LastSeen_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3257]!, self._r[3257]!, [_0]) - } - public func DialogList_SingleRecordingAudioSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3258]!, self._r[3258]!, [_0]) - } - public var Notifications_GroupNotifications: String { return self._s[3259]! } - public var Conversation_SendMessageErrorTooMuchScheduled: String { return self._s[3260]! } - public var Passport_Identity_EditPassport: String { return self._s[3261]! } - public var EnterPasscode_RepeatNewPasscode: String { return self._s[3263]! } - public var Localization_EnglishLanguageName: String { return self._s[3264]! } - public var Share_AuthDescription: String { return self._s[3265]! } - public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsAlert: String { return self._s[3266]! } - public var Passport_Identity_Surname: String { return self._s[3267]! } - public var Compose_TokenListPlaceholder: String { return self._s[3268]! } - public var Wallet_AccessDenied_Camera: String { return self._s[3269]! } - public var Passport_Identity_OneOfTypePassport: String { return self._s[3270]! } - public var Settings_AboutEmpty: String { return self._s[3271]! } - public var Conversation_Unmute: String { return self._s[3272]! } - public var CreateGroup_ChannelsTooMuch: String { return self._s[3274]! } - public var Wallet_Sending_Text: String { return self._s[3275]! } - public func PUSH_CONTACT_JOINED(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3276]!, self._r[3276]!, [_1]) - } - public var Login_CodeSentCall: String { return self._s[3277]! } - public var ContactInfo_PhoneLabelHomeFax: String { return self._s[3279]! } - public var ChatSettings_Appearance: String { return self._s[3280]! } - public var ClearCache_StorageUsage: String { return self._s[3281]! } - public var ChatListFolder_NameContacts: String { return self._s[3282]! } - public var Appearance_PickAccentColor: String { return self._s[3284]! } - public func PUSH_CHAT_MESSAGE_NOTEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3285]!, self._r[3285]!, [_1, _2]) - } - public func PUSH_MESSAGE_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3286]!, self._r[3286]!, [_1]) - } - public var Notification_CallMissed: String { return self._s[3287]! } - public var SettingsSearch_Synonyms_Appearance_ChatBackground_Custom: String { return self._s[3288]! } - public var Channel_AdminLogFilter_EventsInfo: String { return self._s[3289]! } - public var Wallet_Month_GenOctober: String { return self._s[3291]! } - public var ChatAdmins_AdminLabel: String { return self._s[3292]! } - public var KeyCommand_JumpToNextChat: String { return self._s[3293]! } - public var Conversation_StopPollConfirmationTitle: String { return self._s[3295]! } - public var ChangePhoneNumberCode_CodePlaceholder: String { return self._s[3296]! } - public var Month_GenJune: String { return self._s[3297]! } - public var IntentsSettings_MainAccountInfo: String { return self._s[3298]! } - public var Watch_Location_Current: String { return self._s[3299]! } - public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[3300]! } - public var Conversation_TitleMute: String { return self._s[3301]! } - public var Map_PlacesInThisArea: String { return self._s[3302]! } - public func PUSH_CHANNEL_MESSAGE_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3303]!, self._r[3303]!, [_1]) - } - public var GroupInfo_DeleteAndExit: String { return self._s[3304]! } - public func Conversation_Moderate_DeleteAllMessages(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3305]!, self._r[3305]!, [_0]) - } - public var Call_ReportPlaceholder: String { return self._s[3306]! } - public var Chat_SlowmodeSendError: String { return self._s[3307]! } - public var MaskStickerSettings_Info: String { return self._s[3308]! } - public var EditTheme_Expand_TopInfo: String { return self._s[3309]! } - public func GroupInfo_AddParticipantConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3310]!, self._r[3310]!, [_0]) - } - public var Checkout_NewCard_PostcodeTitle: String { return self._s[3311]! } - public var Passport_Address_RegionPlaceholder: String { return self._s[3313]! } - public var Contacts_ShareTelegram: String { return self._s[3314]! } - public var EnterPasscode_EnterNewPasscodeNew: String { return self._s[3315]! } - public var Map_AddressOnMap: String { return self._s[3316]! } - public var Channel_ErrorAccessDenied: String { return self._s[3317]! } - public var UserInfo_ScamBotWarning: String { return self._s[3319]! } - public var Stickers_GroupChooseStickerPack: String { return self._s[3320]! } - public var Call_ConnectionErrorTitle: String { return self._s[3321]! } - public var UserInfo_NotificationsEnable: String { return self._s[3322]! } - public var ArchivedChats_IntroText1: String { return self._s[3323]! } - public var Tour_Text4: String { return self._s[3326]! } - public var WallpaperSearch_Recent: String { return self._s[3327]! } - public var GroupInfo_ScamGroupWarning: String { return self._s[3328]! } - public var PeopleNearby_MakeVisibleTitle: String { return self._s[3329]! } - public var Profile_MessageLifetime2s: String { return self._s[3331]! } - public var Appearance_ThemePreview_ChatList_5_Text: String { return self._s[3332]! } - public var Notification_MessageLifetime2s: String { return self._s[3333]! } - public func Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3334]!, self._r[3334]!, [_1, _2, _3]) - } - public var Cache_ClearCache: String { return self._s[3335]! } - public var AutoNightTheme_UpdateLocation: String { return self._s[3336]! } - public var Permissions_NotificationsUnreachableText_v0: String { return self._s[3337]! } - public func Channel_AdminLog_MessageChangedGroupUsername(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3341]!, self._r[3341]!, [_0]) - } - public func Conversation_ShareMyPhoneNumber_StatusSuccess(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3343]!, self._r[3343]!, [_0]) - } - public var LocalGroup_Text: String { return self._s[3344]! } - public var PeerInfo_PaneMembers: String { return self._s[3345]! } - public var Channel_AdminLog_EmptyFilterTitle: String { return self._s[3346]! } - public var SocksProxySetup_TypeSocks: String { return self._s[3347]! } - public var ChatList_UnarchiveAction: String { return self._s[3348]! } - public var AutoNightTheme_Title: String { return self._s[3349]! } - public var InstantPage_FeedbackButton: String { return self._s[3350]! } - public var Passport_FieldAddress: String { return self._s[3351]! } - public func Channel_AdminLog_SetSlowmode(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3352]!, self._r[3352]!, [_1, _2]) - } - public var Month_ShortMarch: String { return self._s[3353]! } - public func PUSH_MESSAGE_INVOICE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3354]!, self._r[3354]!, [_1, _2]) - } - public var SocksProxySetup_UsernamePlaceholder: String { return self._s[3355]! } - public var Conversation_ShareInlineBotLocationConfirmation: String { return self._s[3356]! } - public var Passport_FloodError: String { return self._s[3357]! } - public var SecretGif_Title: String { return self._s[3358]! } - public var NotificationSettings_ShowNotificationsAllAccountsInfoOn: String { return self._s[3359]! } - public var ChatList_Context_UnhideArchive: String { return self._s[3360]! } - public var Passport_Language_th: String { return self._s[3362]! } - public var Passport_Address_Address: String { return self._s[3363]! } - public var Login_InvalidLastNameError: String { return self._s[3364]! } - public var Notifications_InAppNotificationsPreview: String { return self._s[3365]! } - public var Notifications_PermissionsUnreachableTitle: String { return self._s[3366]! } - public var ChatList_Context_Archive: String { return self._s[3367]! } - public var SettingsSearch_FAQ: String { return self._s[3368]! } - public var ShareMenu_Send: String { return self._s[3369]! } - public var ChatState_Connecting: String { return self._s[3370]! } - public var WallpaperSearch_ColorYellow: String { return self._s[3372]! } - public var Month_GenNovember: String { return self._s[3374]! } - public var SettingsSearch_Synonyms_Appearance_LargeEmoji: String { return self._s[3376]! } - public func Conversation_ShareMyPhoneNumberConfirmation(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3377]!, self._r[3377]!, [_1, _2]) - } - public var ChatListFolder_CategoryChannels: String { return self._s[3378]! } - public var Conversation_SwipeToReplyHintText: String { return self._s[3379]! } - public var Checkout_Email: String { return self._s[3380]! } - public var NotificationsSound_Tritone: String { return self._s[3381]! } - public var Paint_Marker: String { return self._s[3383]! } - public var StickerPacksSettings_ManagingHelp: String { return self._s[3385]! } - public var Wallet_ContextMenuCopy: String { return self._s[3387]! } - public func Wallet_Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3389]!, self._r[3389]!, [_1, _2, _3]) - } - public var Appearance_TextSize_Automatic: String { return self._s[3390]! } - public var Stickers_Installed: String { return self._s[3392]! } - public func PUSH_PINNED_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3393]!, self._r[3393]!, [_1]) - } - public func StickerPackActionInfo_AddedText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3394]!, self._r[3394]!, [_0]) - } - public var ChangePhoneNumberNumber_Help: String { return self._s[3395]! } - public func Checkout_LiabilityAlert(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3396]!, self._r[3396]!, [_1, _1, _1, _2]) - } - public var ChatList_UndoArchiveTitle: String { return self._s[3397]! } - public var Notification_Exceptions_Add: String { return self._s[3398]! } - public var DialogList_You: String { return self._s[3399]! } - public var ChatList_PsaLabel_covid: String { return self._s[3401]! } - public var MediaPicker_Send: String { return self._s[3403]! } - public var SettingsSearch_Synonyms_Stickers_Title: String { return self._s[3404]! } - public var Appearance_ThemePreview_ChatList_4_Text: String { return self._s[3405]! } - public var Call_AudioRouteSpeaker: String { return self._s[3406]! } - public var Watch_UserInfo_Title: String { return self._s[3407]! } - public var VoiceOver_Chat_PollFinalResults: String { return self._s[3408]! } - public var Appearance_AccentColor: String { return self._s[3410]! } - public func Login_EmailPhoneSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3411]!, self._r[3411]!, [_0]) - } - public var Permissions_ContactsAllowInSettings_v0: String { return self._s[3412]! } - public func PUSH_CHANNEL_MESSAGE_GAME(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3413]!, self._r[3413]!, [_1, _2]) - } - public var Conversation_ClousStorageInfo_Description2: String { return self._s[3414]! } - public var WebSearch_RecentClearConfirmation: String { return self._s[3415]! } - public var Notification_CallOutgoing: String { return self._s[3416]! } - public var PrivacySettings_PasscodeAndFaceId: String { return self._s[3417]! } - public var Channel_DiscussionGroup_MakeHistoryPublic: String { return self._s[3418]! } - public var Call_RecordingDisabledMessage: String { return self._s[3419]! } - public var Message_Game: String { return self._s[3420]! } - public var Conversation_PressVolumeButtonForSound: String { return self._s[3421]! } - public var PrivacyLastSeenSettings_CustomHelp: String { return self._s[3422]! } - public var Channel_DiscussionGroup_PrivateGroup: String { return self._s[3423]! } - public var Channel_EditAdmin_PermissionAddAdmins: String { return self._s[3424]! } - public var Date_DialogDateFormat: String { return self._s[3426]! } - public var WallpaperColors_SetCustomColor: String { return self._s[3427]! } - public var Notifications_InAppNotifications: String { return self._s[3428]! } - public func Channel_Management_RemovedBy(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3429]!, self._r[3429]!, [_0]) - } - public func Settings_ApplyProxyAlert(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3430]!, self._r[3430]!, [_1, _2]) - } - public var NewContact_Title: String { return self._s[3431]! } - public func AutoDownloadSettings_UpToForAll(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3432]!, self._r[3432]!, [_0]) - } - public var Stats_GroupTopPoster_Promote: String { return self._s[3433]! } - public var Conversation_ViewContactDetails: String { return self._s[3434]! } - public func PUSH_CHANNEL_MESSAGE_CONTACT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3436]!, self._r[3436]!, [_1]) - } - public var Checkout_NewCard_CardholderNameTitle: String { return self._s[3437]! } - public var Passport_Identity_ExpiryDateNone: String { return self._s[3438]! } - public var PrivacySettings_Title: String { return self._s[3439]! } - public var Conversation_SilentBroadcastTooltipOff: String { return self._s[3442]! } - public var GroupRemoved_UsersSectionTitle: String { return self._s[3443]! } - public var VoiceOver_Chat_ContactEmail: String { return self._s[3444]! } - public var Contacts_PhoneNumber: String { return self._s[3445]! } - public var PeerInfo_ButtonMute: String { return self._s[3446]! } - public var TwoFactorSetup_Password_PlaceholderConfirmPassword: String { return self._s[3448]! } - public var Map_ShowPlaces: String { return self._s[3449]! } - public var ChatAdmins_Title: String { return self._s[3450]! } - public var InstantPage_Reference: String { return self._s[3452]! } - public var Wallet_Info_Updating: String { return self._s[3453]! } - public var ReportGroupLocation_Text: String { return self._s[3454]! } - public func PUSH_CHAT_MESSAGE_FWD(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3455]!, self._r[3455]!, [_1, _2]) - } - public var Camera_FlashOff: String { return self._s[3456]! } - public var Watch_UserInfo_Block: String { return self._s[3457]! } - public var ChatSettings_Stickers: String { return self._s[3458]! } - public var ChatSettings_DownloadInBackground: String { return self._s[3459]! } - public var Appearance_ThemeCarouselTintedNight: String { return self._s[3460]! } - public func UserInfo_BlockConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3461]!, self._r[3461]!, [_0]) - } - public var Settings_ViewPhoto: String { return self._s[3462]! } - public var Login_CheckOtherSessionMessages: String { return self._s[3463]! } - public var AutoDownloadSettings_Cellular: String { return self._s[3464]! } - public var Wallet_Created_ExportErrorTitle: String { return self._s[3465]! } - public var SettingsSearch_Synonyms_Notifications_GroupNotificationsExceptions: String { return self._s[3466]! } - public var VoiceOver_MessageContextShare: String { return self._s[3467]! } - public func Target_InviteToGroupConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3469]!, self._r[3469]!, [_0]) - } - public var Privacy_DeleteDrafts: String { return self._s[3470]! } - public var Wallpaper_SetCustomBackgroundInfo: String { return self._s[3471]! } - public func LastSeen_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3472]!, self._r[3472]!, [_0]) - } - public var DialogList_SavedMessagesHelp: String { return self._s[3473]! } - public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[3474]! } - public var DialogList_SavedMessages: String { return self._s[3475]! } - public var GroupInfo_UpgradeButton: String { return self._s[3476]! } - public var Appearance_ThemePreview_ChatList_3_Text: String { return self._s[3478]! } - public var DialogList_Pin: String { return self._s[3479]! } - public func ForwardedAuthors2(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3480]!, self._r[3480]!, [_0, _1]) - } - public func Login_PhoneGenericEmailSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3481]!, self._r[3481]!, [_0]) - } - public var Notification_Exceptions_AlwaysOn: String { return self._s[3482]! } - public var UserInfo_NotificationsDisable: String { return self._s[3483]! } - public var Conversation_UnarchiveDone: String { return self._s[3484]! } - public var Conversation_ContextMenuCancelEditing: String { return self._s[3485]! } - public var Paint_Outlined: String { return self._s[3486]! } - public var Activity_PlayingGame: String { return self._s[3487]! } - public var SearchImages_NoImagesFound: String { return self._s[3488]! } - public var SocksProxySetup_ProxyType: String { return self._s[3489]! } - public var AppleWatch_ReplyPresetsHelp: String { return self._s[3491]! } - public var Conversation_ContextMenuCancelSending: String { return self._s[3492]! } - public var Settings_AppLanguage: String { return self._s[3493]! } - public var TwoStepAuth_ResetAccountHelp: String { return self._s[3494]! } - public var Common_ChoosePhoto: String { return self._s[3495]! } - public var AuthSessions_AddDevice_InvalidQRCode: String { return self._s[3496]! } - public var CallFeedback_ReasonEcho: String { return self._s[3497]! } - public func PUSH_PINNED_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3498]!, self._r[3498]!, [_1]) - } - public var Privacy_Calls_AlwaysAllow: String { return self._s[3499]! } - public var PollResults_Collapse: String { return self._s[3500]! } - public var Activity_UploadingVideo: String { return self._s[3501]! } - public var Conversation_WalletRequiredNotNow: String { return self._s[3502]! } - public var ChannelInfo_DeleteChannelConfirmation: String { return self._s[3503]! } - public var NetworkUsageSettings_Wifi: String { return self._s[3504]! } - public var VoiceOver_Editing_ClearText: String { return self._s[3505]! } - public var PUSH_SENDER_YOU: String { return self._s[3506]! } - public var Channel_BanUser_PermissionReadMessages: String { return self._s[3507]! } - public var Checkout_PayWithTouchId: String { return self._s[3508]! } - public var Wallpaper_ResetWallpapersConfirmation: String { return self._s[3509]! } - public func PUSH_LOCKED_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3511]!, self._r[3511]!, [_1]) - } - public var Notifications_ExceptionsNone: String { return self._s[3512]! } - public func Message_ForwardedMessageShort(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3513]!, self._r[3513]!, [_0]) - } - public func PUSH_PINNED_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3514]!, self._r[3514]!, [_1]) - } - public var AuthSessions_IncompleteAttempts: String { return self._s[3516]! } - public var Passport_Address_Region: String { return self._s[3519]! } - public var ChatList_DeleteChat: String { return self._s[3520]! } - public var LogoutOptions_ClearCacheTitle: String { return self._s[3521]! } - public var PhotoEditor_TiltShift: String { return self._s[3522]! } - public var Settings_FAQ_URL: String { return self._s[3523]! } - public var TwoFactorSetup_EmailVerification_ChangeAction: String { return self._s[3524]! } - public var SharedMedia_TitleLink: String { return self._s[3527]! } - public var Settings_PrivacySettings: String { return self._s[3528]! } - public var Passport_Identity_TypePassportUploadScan: String { return self._s[3529]! } - public var Passport_Language_sl: String { return self._s[3530]! } - public var Settings_SetProfilePhoto: String { return self._s[3531]! } - public var Channel_About_Help: String { return self._s[3532]! } - public var Contacts_PermissionsEnable: String { return self._s[3533]! } - public var Wallet_Sending_Title: String { return self._s[3534]! } - public var PeerInfo_PaneMedia: String { return self._s[3535]! } - public var SettingsSearch_Synonyms_Notifications_GroupNotificationsAlert: String { return self._s[3536]! } - public var AttachmentMenu_SendAsFiles: String { return self._s[3537]! } - public var CallFeedback_ReasonInterruption: String { return self._s[3539]! } - public var Passport_Address_AddTemporaryRegistration: String { return self._s[3540]! } - public var AutoDownloadSettings_AutodownloadVideos: String { return self._s[3541]! } - public var ChatSettings_AutoDownloadSettings_Delimeter: String { return self._s[3542]! } - public var OldChannels_Title: String { return self._s[3543]! } - public var PrivacySettings_DeleteAccountTitle: String { return self._s[3544]! } - public var AccessDenied_VideoMessageCamera: String { return self._s[3546]! } - public var Map_OpenInYandexMaps: String { return self._s[3548]! } - public var CreateGroup_ErrorLocatedGroupsTooMuch: String { return self._s[3549]! } - public var VoiceOver_MessageContextReply: String { return self._s[3550]! } - public var ChatListFolder_DiscardConfirmation: String { return self._s[3552]! } - public var PhotoEditor_SaturationTool: String { return self._s[3553]! } - public func PUSH_MESSAGE_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3554]!, self._r[3554]!, [_1, _2]) - } - public var PrivacyPhoneNumberSettings_CustomHelp: String { return self._s[3555]! } - public var Notification_Exceptions_NewException_NotificationHeader: String { return self._s[3556]! } - public var Group_OwnershipTransfer_ErrorLocatedGroupsTooMuch: String { return self._s[3557]! } - public func LOCAL_MESSAGE_FWDS(_ _1: String, _ _2: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3558]!, self._r[3558]!, [_1, "\(_2)"]) - } - public var Appearance_ThemePreview_ChatList_2_Text: String { return self._s[3559]! } - public var Channel_Username_InvalidTooShort: String { return self._s[3561]! } - public var SettingsSearch_Synonyms_Wallet: String { return self._s[3562]! } - public func Group_OwnershipTransfer_DescriptionInfo(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3563]!, self._r[3563]!, [_1, _2]) - } - public var Forward_ErrorPublicPollDisabledInChannels: String { return self._s[3564]! } - public func PUSH_CHAT_MESSAGE_GAME(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3565]!, self._r[3565]!, [_1, _2, _3]) - } - public var WallpaperPreview_PatternTitle: String { return self._s[3566]! } - public var GroupInfo_PublicLinkAdd: String { return self._s[3567]! } - public var Passport_PassportInformation: String { return self._s[3570]! } - public var Theme_Unsupported: String { return self._s[3571]! } - public var WatchRemote_AlertTitle: String { return self._s[3572]! } - public var Privacy_GroupsAndChannels_NeverAllow: String { return self._s[3573]! } - public var ConvertToSupergroup_HelpText: String { return self._s[3575]! } - public func Time_MonthOfYear_m7(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3576]!, self._r[3576]!, [_0]) - } - public func PUSH_PHONE_CALL_REQUEST(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3577]!, self._r[3577]!, [_1]) - } - public var Privacy_GroupsAndChannels_CustomHelp: String { return self._s[3578]! } - public var Wallet_Navigation_Done: String { return self._s[3580]! } - public var TwoStepAuth_RecoveryCodeInvalid: String { return self._s[3581]! } - public var AccessDenied_CameraDisabled: String { return self._s[3582]! } - public func Channel_Username_UsernameIsAvailable(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3583]!, self._r[3583]!, [_0]) - } - public var ClearCache_Forever: String { return self._s[3584]! } - public var AuthSessions_AddDeviceIntro_Title: String { return self._s[3585]! } - public var CreatePoll_Quiz: String { return self._s[3586]! } - public var PhotoEditor_ContrastTool: String { return self._s[3589]! } - public func PUSH_PINNED_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3590]!, self._r[3590]!, [_1]) - } - public var DialogList_Draft: String { return self._s[3591]! } - public var Wallet_Configuration_BlockchainIdInfo: String { return self._s[3592]! } - public func PeopleNearby_VisibleUntil(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3593]!, self._r[3593]!, [_0]) - } - public var ChatList_PsaAlert_covid: String { return self._s[3594]! } - public var Privacy_TopPeersDelete: String { return self._s[3596]! } - public var LoginPassword_PasswordPlaceholder: String { return self._s[3597]! } - public var Passport_Identity_TypeIdentityCardUploadScan: String { return self._s[3598]! } - public var WebSearch_RecentSectionClear: String { return self._s[3599]! } - public var EditTheme_ErrorInvalidCharacters: String { return self._s[3600]! } - public var Watch_ChatList_NoConversationsTitle: String { return self._s[3602]! } - public var PeerInfo_ButtonMore: String { return self._s[3604]! } - public var Common_Done: String { return self._s[3605]! } - public var Shortcut_SwitchAccount: String { return self._s[3606]! } - public var AuthSessions_EmptyText: String { return self._s[3607]! } - public var Wallet_Configuration_BlockchainNameChangedTitle: String { return self._s[3608]! } - public var Conversation_ShareBotContactConfirmation: String { return self._s[3609]! } - public var Tour_Title5: String { return self._s[3611]! } - public var Wallet_Settings_Title: String { return self._s[3612]! } - public func Map_DirectionsDriveEta(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3613]!, self._r[3613]!, [_0]) - } - public var ApplyLanguage_UnsufficientDataTitle: String { return self._s[3614]! } - public var Conversation_LinkDialogSave: String { return self._s[3615]! } - public var GroupInfo_ActionRestrict: String { return self._s[3616]! } - public var Checkout_Title: String { return self._s[3618]! } - public var Channel_DiscussionGroup_HeaderLabel: String { return self._s[3620]! } - public var Channel_AdminLog_CanChangeInfo: String { return self._s[3622]! } - public var Notification_RenamedGroup: String { return self._s[3623]! } - public var PeopleNearby_Groups: String { return self._s[3624]! } - public var Checkout_PayWithFaceId: String { return self._s[3625]! } - public var Channel_BanList_BlockedTitle: String { return self._s[3626]! } - public var SettingsSearch_Synonyms_Notifications_InAppNotificationsSound: String { return self._s[3628]! } - public var Checkout_WebConfirmation_Title: String { return self._s[3629]! } - public var Notifications_MessageNotificationsAlert: String { return self._s[3630]! } - public func Activity_RemindAboutGroup(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3631]!, self._r[3631]!, [_0]) - } - public var Stats_GroupGrowthTitle: String { return self._s[3632]! } - public var Profile_AddToExisting: String { return self._s[3634]! } - public func Profile_CreateEncryptedChatOutdatedError(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3635]!, self._r[3635]!, [_0, _1]) - } - public var Cache_Files: String { return self._s[3637]! } - public var Permissions_PrivacyPolicy: String { return self._s[3639]! } - public var SocksProxySetup_ConnectAndSave: String { return self._s[3640]! } - public var UserInfo_NotificationsDefaultDisabled: String { return self._s[3641]! } - public var AutoDownloadSettings_TypeContacts: String { return self._s[3643]! } - public var Appearance_ThemePreview_ChatList_1_Text: String { return self._s[3645]! } - public var Calls_NoCallsPlaceholder: String { return self._s[3646]! } - public func Wallet_Receive_ShareInvoiceUrlInfo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3647]!, self._r[3647]!, [_0]) - } - public var Channel_Username_RevokeExistingUsernamesInfo: String { return self._s[3648]! } - public var VoiceOver_AttachMedia: String { return self._s[3651]! } - public var Notifications_ExceptionsGroupPlaceholder: String { return self._s[3652]! } - public func PUSH_CHAT_MESSAGE_INVOICE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3653]!, self._r[3653]!, [_1, _2, _3]) - } - public var SettingsSearch_Synonyms_Notifications_GroupNotificationsSound: String { return self._s[3654]! } - public var Conversation_SetReminder_Title: String { return self._s[3655]! } - public var Passport_FieldAddressHelp: String { return self._s[3656]! } - public var Privacy_GroupsAndChannels_InviteToChannelMultipleError: String { return self._s[3657]! } - public var PUSH_REMINDER_TITLE: String { return self._s[3658]! } - public func Login_TermsOfService_ProceedBot(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3659]!, self._r[3659]!, [_0]) - } - public var Channel_AdminLog_EmptyTitle: String { return self._s[3660]! } - public var Privacy_Calls_NeverAllow_Title: String { return self._s[3661]! } - public var Login_UnknownError: String { return self._s[3662]! } - public var Group_UpgradeNoticeText2: String { return self._s[3665]! } - public var Watch_Compose_AddContact: String { return self._s[3666]! } - public var ClearCache_StorageServiceFiles: String { return self._s[3667]! } - public var Web_Error: String { return self._s[3668]! } - public var Paint_Neon: String { return self._s[3669]! } - public var Gif_Search: String { return self._s[3670]! } - public var Profile_MessageLifetime1h: String { return self._s[3671]! } - public var CheckoutInfo_ReceiverInfoEmailPlaceholder: String { return self._s[3672]! } - public var Channel_Username_CheckingUsername: String { return self._s[3673]! } - public var CallFeedback_ReasonSilentRemote: String { return self._s[3674]! } - public var AutoDownloadSettings_TypeChannels: String { return self._s[3675]! } - public var Channel_AboutItem: String { return self._s[3676]! } - public var Privacy_GroupsAndChannels_AlwaysAllow_Placeholder: String { return self._s[3679]! } - public var VoiceOver_Chat_VoiceMessage: String { return self._s[3680]! } - public var GroupInfo_SharedMedia: String { return self._s[3681]! } - public func Channel_AdminLog_MessagePromotedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3682]!, self._r[3682]!, [_1]) - } - public var Call_PhoneCallInProgressMessage: String { return self._s[3683]! } - public func PUSH_CHANNEL_ALBUM(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3684]!, self._r[3684]!, [_1]) - } - public var ChatList_UndoArchiveRevealedText: String { return self._s[3685]! } - public var GroupInfo_InviteLink_RevokeAlert_Text: String { return self._s[3686]! } - public var Conversation_SearchByName_Placeholder: String { return self._s[3687]! } - public var CreatePoll_AddOption: String { return self._s[3688]! } - public var GroupInfo_Permissions_SearchPlaceholder: String { return self._s[3689]! } - public var Group_UpgradeNoticeHeader: String { return self._s[3690]! } - public var Channel_Management_AddModerator: String { return self._s[3691]! } - public var AutoDownloadSettings_MaxFileSize: String { return self._s[3692]! } - public var StickerPacksSettings_ShowStickersButton: String { return self._s[3693]! } - public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[3694]! } - public var Theme_Colors_Background: String { return self._s[3695]! } - public var NotificationsSound_Hello: String { return self._s[3698]! } - public var SocksProxySetup_SavedProxies: String { return self._s[3700]! } - public var Channel_Stickers_Placeholder: String { return self._s[3702]! } - public func Login_EmailCodeBody(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3703]!, self._r[3703]!, [_0]) - } - public var PrivacyPolicy_DeclineDeclineAndDelete: String { return self._s[3704]! } - public var Channel_Management_AddModeratorHelp: String { return self._s[3705]! } - public var ContactInfo_BirthdayLabel: String { return self._s[3706]! } - public var ChangePhoneNumberCode_RequestingACall: String { return self._s[3707]! } - public var AutoDownloadSettings_Channels: String { return self._s[3708]! } - public var Passport_Language_mn: String { return self._s[3709]! } - public var Settings_ChatFolders: String { return self._s[3710]! } - public func ChatList_AddedToFolderTooltip(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3711]!, self._r[3711]!, [_1, _2]) - } - public var Notifications_ResetAllNotificationsHelp: String { return self._s[3714]! } - public var GroupInfo_Permissions_SlowmodeValue_Off: String { return self._s[3715]! } - public var Passport_Language_ja: String { return self._s[3717]! } - public var Settings_About_Title: String { return self._s[3718]! } - public var Settings_NotificationsAndSounds: String { return self._s[3719]! } - public var ChannelInfo_DeleteGroup: String { return self._s[3720]! } - public var Settings_BlockedUsers: String { return self._s[3721]! } - public func Time_MonthOfYear_m4(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3722]!, self._r[3722]!, [_0]) - } - public var EditTheme_Create_Preview_OutgoingText: String { return self._s[3723]! } - public var Wallet_Weekday_Today: String { return self._s[3724]! } - public var ChatListFolderSettings_AddRecommended: String { return self._s[3725]! } - public var AutoDownloadSettings_PreloadVideo: String { return self._s[3726]! } - public var Widget_ApplicationLocked: String { return self._s[3727]! } - public var Passport_Address_AddResidentialAddress: String { return self._s[3728]! } - public var Channel_Username_Title: String { return self._s[3729]! } - public func Notification_RemovedGroupPhoto(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3730]!, self._r[3730]!, [_0]) - } - public var AttachmentMenu_File: String { return self._s[3732]! } - public var AppleWatch_Title: String { return self._s[3733]! } - public var Activity_RecordingVideoMessage: String { return self._s[3734]! } - public func Channel_DiscussionGroup_PublicChannelLink(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3735]!, self._r[3735]!, [_1, _2]) - } - public var Theme_Colors_Messages: String { return self._s[3736]! } - public var Weekday_Saturday: String { return self._s[3737]! } - public var WallpaperPreview_SwipeColorsTopText: String { return self._s[3738]! } - public var Conversation_Timer_Send: String { return self._s[3739]! } - public var Settings_CancelUpload: String { return self._s[3740]! } - public var Profile_CreateEncryptedChatError: String { return self._s[3741]! } - public var Common_Next: String { return self._s[3743]! } - public var Channel_Stickers_YourStickers: String { return self._s[3745]! } - public var Message_Theme: String { return self._s[3746]! } - public var Call_AudioRouteHeadphones: String { return self._s[3747]! } - public var TwoStepAuth_EnterPasswordForgot: String { return self._s[3749]! } - public var Watch_Contacts_NoResults: String { return self._s[3751]! } - public var PhotoEditor_TintTool: String { return self._s[3754]! } - public var LoginPassword_ResetAccount: String { return self._s[3756]! } - public var Settings_SavedMessages: String { return self._s[3757]! } - public var SettingsSearch_Synonyms_Appearance_Animations: String { return self._s[3758]! } - public var Bot_GenericSupportStatus: String { return self._s[3759]! } - public var StickerPack_Add: String { return self._s[3760]! } - public var Checkout_TotalAmount: String { return self._s[3761]! } - public var Your_cards_number_is_invalid: String { return self._s[3762]! } - public var SettingsSearch_Synonyms_Appearance_AutoNightTheme: String { return self._s[3763]! } - public var VoiceOver_Chat_VideoMessage: String { return self._s[3764]! } - public func ChangePhoneNumberCode_CallTimer(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3765]!, self._r[3765]!, [_0]) - } - public func GroupPermission_AddedInfo(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3766]!, self._r[3766]!, [_1, _2]) - } - public var ChatSettings_ConnectionType_UseSocks5: String { return self._s[3767]! } - public func PUSH_CHAT_PHOTO_EDITED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3769]!, self._r[3769]!, [_1, _2]) - } - public func Conversation_RestrictedTextTimed(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3770]!, self._r[3770]!, [_0]) - } - public var GroupInfo_InviteLink_ShareLink: String { return self._s[3771]! } - public var StickerPack_Share: String { return self._s[3772]! } - public var Passport_DeleteAddress: String { return self._s[3773]! } - public var Settings_Passport: String { return self._s[3774]! } - public var SharedMedia_EmptyFilesText: String { return self._s[3775]! } - public var Conversation_DeleteMessagesForMe: String { return self._s[3776]! } - public var PasscodeSettings_AutoLock_IfAwayFor_1hour: String { return self._s[3777]! } - public var Contacts_PermissionsText: String { return self._s[3778]! } - public var Group_Setup_HistoryVisible: String { return self._s[3779]! } - public var Wallet_Month_ShortDecember: String { return self._s[3781]! } - public var PrivacySettings_AutoArchiveTitle: String { return self._s[3783]! } - public var Channel_EditAdmin_PermissionEnabledByDefault: String { return self._s[3784]! } - public var Passport_Address_AddRentalAgreement: String { return self._s[3785]! } - public var SocksProxySetup_Title: String { return self._s[3786]! } - public var Notification_Mute1h: String { return self._s[3787]! } - public func Passport_Email_CodeHelp(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3788]!, self._r[3788]!, [_0]) - } - public var NotificationSettings_ShowNotificationsAllAccountsInfoOff: String { return self._s[3789]! } - public func PUSH_PINNED_GEOLIVE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3790]!, self._r[3790]!, [_1]) - } - public var FastTwoStepSetup_PasswordSection: String { return self._s[3791]! } - public var NetworkUsageSettings_ResetStatsConfirmation: String { return self._s[3794]! } - public var InfoPlist_NSFaceIDUsageDescription: String { return self._s[3796]! } - public var DialogList_NoMessagesText: String { return self._s[3797]! } - public var Privacy_ContactsResetConfirmation: String { return self._s[3798]! } - public var Privacy_Calls_P2PHelp: String { return self._s[3799]! } - public var Channel_DiscussionGroup_SearchPlaceholder: String { return self._s[3801]! } - public var Your_cards_expiration_year_is_invalid: String { return self._s[3802]! } - public var Common_TakePhotoOrVideo: String { return self._s[3803]! } - public var Wallet_Words_Text: String { return self._s[3804]! } - public var Call_StatusBusy: String { return self._s[3805]! } - public var Conversation_PinnedMessage: String { return self._s[3806]! } - public var AutoDownloadSettings_VoiceMessagesTitle: String { return self._s[3807]! } - public var ChatList_EmptyChatListNewMessage: String { return self._s[3808]! } - public var Wallet_Configuration_BlockchainNameChangedProceed: String { return self._s[3809]! } - public var TwoStepAuth_SetupPasswordConfirmFailed: String { return self._s[3810]! } - public var Undo_ChatCleared: String { return self._s[3811]! } - public var CreatePoll_Explanation: String { return self._s[3812]! } - public var AppleWatch_ReplyPresets: String { return self._s[3813]! } - public var Passport_DiscardMessageDescription: String { return self._s[3815]! } - public var Login_NetworkError: String { return self._s[3816]! } - public func Notification_PinnedRoundMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3817]!, self._r[3817]!, [_0]) - } - public func Channel_AdminLog_MessageRemovedChannelUsername(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3818]!, self._r[3818]!, [_0]) - } - public var SocksProxySetup_PasswordPlaceholder: String { return self._s[3819]! } - public var Wallet_WordCheck_ViewWords: String { return self._s[3821]! } - public var Login_ResetAccountProtected_LimitExceeded: String { return self._s[3822]! } - public func Watch_LastSeen_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3824]!, self._r[3824]!, [_0]) - } - public var Call_ConnectionErrorMessage: String { return self._s[3825]! } - public var VoiceOver_Chat_Music: String { return self._s[3826]! } - public var ChatListFolder_CategoryContacts: String { return self._s[3827]! } - public var SettingsSearch_Synonyms_Notifications_MessageNotificationsSound: String { return self._s[3828]! } - public var Compose_GroupTokenListPlaceholder: String { return self._s[3830]! } - public var ConversationMedia_Title: String { return self._s[3831]! } - public var EncryptionKey_Title: String { return self._s[3833]! } - public var TwoStepAuth_EnterPasswordTitle: String { return self._s[3834]! } - public var Notification_Exceptions_AddException: String { return self._s[3835]! } - public var PrivacySettings_BlockedPeersEmpty: String { return self._s[3836]! } - public var Profile_MessageLifetime1m: String { return self._s[3837]! } - public func Channel_AdminLog_MessageUnkickedName(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3838]!, self._r[3838]!, [_1]) - } - public var Month_GenMay: String { return self._s[3839]! } - public func LiveLocationUpdated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3840]!, self._r[3840]!, [_0]) - } - public var PeopleNearby_Users: String { return self._s[3841]! } - public var Wallet_Send_AddressInfo: String { return self._s[3842]! } - public var ChannelMembers_WhoCanAddMembersAllHelp: String { return self._s[3843]! } - public var AutoDownloadSettings_ResetSettings: String { return self._s[3844]! } - public func Wallet_Updated_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3846]!, self._r[3846]!, [_0]) - } - public var Stats_LoadingTitle: String { return self._s[3847]! } - public var Conversation_EmptyPlaceholder: String { return self._s[3848]! } - public var Passport_Address_AddPassportRegistration: String { return self._s[3849]! } - public var Notifications_ChannelNotificationsAlert: String { return self._s[3850]! } - public var ChatSettings_AutoDownloadUsingCellular: String { return self._s[3851]! } - public var Camera_TapAndHoldForVideo: String { return self._s[3852]! } - public var Channel_JoinChannel: String { return self._s[3855]! } - public var Appearance_Animations: String { return self._s[3858]! } - public func Notification_MessageLifetimeChanged(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3859]!, self._r[3859]!, [_1, _2]) - } - public var Stickers_GroupStickers: String { return self._s[3861]! } - public var Appearance_ShareTheme: String { return self._s[3862]! } - public var TwoFactorSetup_Hint_Placeholder: String { return self._s[3863]! } - public var ConvertToSupergroup_HelpTitle: String { return self._s[3867]! } - public var StickerPackActionInfo_RemovedTitle: String { return self._s[3868]! } - public var Passport_Address_Street: String { return self._s[3869]! } - public var Conversation_AddContact: String { return self._s[3870]! } - public var Login_PhonePlaceholder: String { return self._s[3871]! } - public var Channel_Members_InviteLink: String { return self._s[3873]! } - public var Bot_Stop: String { return self._s[3874]! } - public var SettingsSearch_Synonyms_Proxy_UseForCalls: String { return self._s[3876]! } - public var Notification_PassportValueAddress: String { return self._s[3877]! } - public var Month_ShortJuly: String { return self._s[3878]! } - public var Passport_Address_TypeTemporaryRegistrationUploadScan: String { return self._s[3879]! } - public var Channel_AdminLog_BanSendMedia: String { return self._s[3880]! } - public var Passport_Identity_ReverseSide: String { return self._s[3881]! } - public func Call_ParticipantVideoVersionOutdatedError(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3883]!, self._r[3883]!, [_0]) - } - public var Watch_Stickers_Recents: String { return self._s[3886]! } - public var PrivacyLastSeenSettings_EmpryUsersPlaceholder: String { return self._s[3888]! } - public var Map_SendThisLocation: String { return self._s[3889]! } - public func Time_MonthOfYear_m1(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3890]!, self._r[3890]!, [_0]) - } - public func InviteText_SingleContact(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3891]!, self._r[3891]!, [_0]) - } - public var ConvertToSupergroup_Note: String { return self._s[3892]! } - public var Wallet_Intro_NotNow: String { return self._s[3893]! } - public var Stats_GroupMembers: String { return self._s[3894]! } - public func FileSize_MB(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3895]!, self._r[3895]!, [_0]) - } - public var NetworkUsageSettings_GeneralDataSection: String { return self._s[3896]! } - public func Compatibility_SecretMediaVersionTooLow(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3897]!, self._r[3897]!, [_0, _1]) - } - public var Login_CallRequestState3: String { return self._s[3899]! } - public var Wallpaper_SearchShort: String { return self._s[3900]! } - public var SettingsSearch_Synonyms_Appearance_ColorTheme: String { return self._s[3902]! } - public var PasscodeSettings_UnlockWithFaceId: String { return self._s[3903]! } - public var Channel_BotDoesntSupportGroups: String { return self._s[3904]! } - public func PUSH_CHAT_MESSAGE_GEOLIVE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3905]!, self._r[3905]!, [_1, _2]) - } - public var Channel_AdminLogFilter_Title: String { return self._s[3906]! } - public var Appearance_ThemePreview_Chat_4_Text: String { return self._s[3908]! } - public var Notifications_GroupNotificationsExceptions: String { return self._s[3911]! } - public func FileSize_B(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3912]!, self._r[3912]!, [_0]) - } - public var Passport_CorrectErrors: String { return self._s[3913]! } - public var VoiceOver_Chat_YourAnonymousPoll: String { return self._s[3914]! } - public func Channel_MessageTitleUpdated(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3915]!, self._r[3915]!, [_0]) - } - public var Map_SendMyCurrentLocation: String { return self._s[3916]! } - public var Channel_DiscussionGroup: String { return self._s[3918]! } - public var TwoFactorSetup_Email_SkipConfirmationSkip: String { return self._s[3919]! } - public func PUSH_PINNED_CONTACT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3920]!, self._r[3920]!, [_1, _2]) - } - public var SharedMedia_SearchNoResults: String { return self._s[3921]! } - public var Permissions_NotificationsText_v0: String { return self._s[3922]! } - public var Channel_EditAdmin_PermissionDeleteMessagesOfOthers: String { return self._s[3923]! } - public var Appearance_AppIcon: String { return self._s[3924]! } - public var Appearance_ThemePreview_ChatList_3_AuthorName: String { return self._s[3925]! } - public var LoginPassword_FloodError: String { return self._s[3926]! } - public var Wallet_Send_OwnAddressAlertProceed: String { return self._s[3928]! } - public var Group_Setup_HistoryHiddenHelp: String { return self._s[3929]! } - public func TwoStepAuth_PendingEmailHelp(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3930]!, self._r[3930]!, [_0]) - } - public var Passport_Language_bn: String { return self._s[3931]! } - public func DialogList_SingleUploadingPhotoSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3932]!, self._r[3932]!, [_0]) - } - public var ChatList_Context_Pin: String { return self._s[3933]! } - public func Notification_PinnedAudioMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3934]!, self._r[3934]!, [_0]) - } - public func Channel_AdminLog_MessageChangedGroupStickerPack(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3935]!, self._r[3935]!, [_0]) - } - public var Wallet_Navigation_Close: String { return self._s[3936]! } - public var GroupInfo_InvitationLinkGroupFull: String { return self._s[3940]! } - public var Group_EditAdmin_PermissionChangeInfo: String { return self._s[3942]! } - public var Wallet_Month_GenDecember: String { return self._s[3943]! } - public var Contacts_PermissionsAllow: String { return self._s[3944]! } - public var ReportPeer_ReasonCopyright: String { return self._s[3945]! } - public var Channel_EditAdmin_PermissinAddAdminOn: String { return self._s[3946]! } - public var WallpaperPreview_Pattern: String { return self._s[3947]! } - public var Paint_Duplicate: String { return self._s[3948]! } - public var Passport_Address_Country: String { return self._s[3949]! } - public var Notification_RenamedChannel: String { return self._s[3951]! } - public var DialogList_UnknownPinLimitError: String { return self._s[3952]! } - public var CheckoutInfo_ErrorPostcodeInvalid: String { return self._s[3953]! } - public var ChatList_Context_Unmute: String { return self._s[3954]! } - public var KeyCommand_SearchInChat: String { return self._s[3955]! } - public var Group_MessagePhotoUpdated: String { return self._s[3956]! } - public var Channel_BanUser_PermissionSendMedia: String { return self._s[3957]! } - public var Conversation_ContextMenuBan: String { return self._s[3958]! } - public var TwoStepAuth_EmailSent: String { return self._s[3959]! } - public var Settings_SetProfilePhotoOrVideo: String { return self._s[3960]! } - public var MessagePoll_NoVotes: String { return self._s[3961]! } - public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[3962]! } - public var Passport_Language_is: String { return self._s[3964]! } - public var PeopleNearby_UsersEmpty: String { return self._s[3966]! } - public var Tour_Text5: String { return self._s[3967]! } - public func Call_GroupFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3970]!, self._r[3970]!, [_1, _2]) - } - public var Undo_SecretChatDeleted: String { return self._s[3971]! } - public var SocksProxySetup_ShareQRCode: String { return self._s[3972]! } - public func VoiceOver_Chat_Size(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3973]!, self._r[3973]!, [_0]) - } - public var Forward_ErrorDisabledForChat: String { return self._s[3974]! } - public var LogoutOptions_ChangePhoneNumberText: String { return self._s[3976]! } - public var Paint_Edit: String { return self._s[3978]! } - public var ScheduledMessages_ReminderNotification: String { return self._s[3980]! } - public var Undo_DeletedGroup: String { return self._s[3982]! } - public var LoginPassword_ForgotPassword: String { return self._s[3983]! } - public var Wallet_WordImport_IncorrectTitle: String { return self._s[3984]! } - public var GroupInfo_GroupNamePlaceholder: String { return self._s[3985]! } - public func Notification_Kicked(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[3986]!, self._r[3986]!, [_0, _1]) - } - public var AppWallet_TransactionInfo_FeeInfoURL: String { return self._s[3987]! } - public var Conversation_InputTextCaptionPlaceholder: String { return self._s[3988]! } - public var Conversation_ContextMenuMention: String { return self._s[3989]! } - public var AutoDownloadSettings_VideoMessagesTitle: String { return self._s[3990]! } - public var Conversation_PinMessageAlertGroup: String { return self._s[3991]! } - public var Passport_Language_uz: String { return self._s[3992]! } - public var SettingsSearch_Synonyms_Privacy_GroupsAndChannels: String { return self._s[3993]! } - public var Channel_MessageVideoUpdated: String { return self._s[3995]! } - public var Map_StopLiveLocation: String { return self._s[3996]! } - public var VoiceOver_MessageContextSend: String { return self._s[3998]! } - public var PasscodeSettings_Help: String { return self._s[3999]! } - public var NotificationsSound_Input: String { return self._s[4000]! } - public var ProfilePhoto_MainVideo: String { return self._s[4002]! } - public var Share_Title: String { return self._s[4004]! } - public var LogoutOptions_Title: String { return self._s[4005]! } - public var Wallet_Send_AddressText: String { return self._s[4006]! } - public var Login_TermsOfServiceAgree: String { return self._s[4007]! } - public var Compose_NewEncryptedChatTitle: String { return self._s[4008]! } - public var Channel_AdminLog_TitleSelectedEvents: String { return self._s[4009]! } - public var Channel_EditAdmin_PermissionEditMessages: String { return self._s[4010]! } - public var EnterPasscode_EnterTitle: String { return self._s[4011]! } - public func Call_PrivacyErrorMessage(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4012]!, self._r[4012]!, [_0]) - } - public var Settings_CopyPhoneNumber: String { return self._s[4013]! } - public var Conversation_AddToContacts: String { return self._s[4014]! } - public func VoiceOver_Chat_ReplyFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4015]!, self._r[4015]!, [_0]) - } - public var NotificationsSound_Keys: String { return self._s[4016]! } - public func Call_ParticipantVersionOutdatedError(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4017]!, self._r[4017]!, [_0]) - } - public var Notification_MessageLifetime1w: String { return self._s[4018]! } - public var Message_Video: String { return self._s[4019]! } - public var AutoDownloadSettings_CellularTitle: String { return self._s[4020]! } - public func PUSH_CHANNEL_MESSAGE_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4021]!, self._r[4021]!, [_1]) - } - public var Wallet_Receive_AmountInfo: String { return self._s[4024]! } - public var Stats_Overview: String { return self._s[4025]! } - public func Notification_JoinedChat(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4026]!, self._r[4026]!, [_0]) - } - public func PrivacySettings_LastSeenContactsPlus(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4027]!, self._r[4027]!, [_0]) - } - public var ChatListFolder_ExcludeChatsTitle: String { return self._s[4028]! } - public var Passport_Language_mk: String { return self._s[4029]! } - public var ChatListFolder_CategoryNonContacts: String { return self._s[4030]! } - public func Wallet_Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4031]!, self._r[4031]!, [_1, _2, _3]) - } - public var CreatePoll_CancelConfirmation: String { return self._s[4032]! } - public var MessagePoll_LabelAnonymousQuiz: String { return self._s[4033]! } - public var Conversation_SilentBroadcastTooltipOn: String { return self._s[4035]! } - public var PrivacyPolicy_Decline: String { return self._s[4036]! } - public var Passport_Identity_DoesNotExpire: String { return self._s[4037]! } - public var Channel_AdminLogFilter_EventsRestrictions: String { return self._s[4038]! } - public var AuthSessions_AddDeviceIntro_Action: String { return self._s[4039]! } - public var Permissions_SiriAllow_v0: String { return self._s[4041]! } - public var Wallet_Month_ShortAugust: String { return self._s[4042]! } - public var Appearance_ThemeCarouselNight: String { return self._s[4043]! } - public func LOCAL_CHAT_MESSAGE_FWDS(_ _1: String, _ _2: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4044]!, self._r[4044]!, [_1, "\(_2)"]) - } - public func Notification_RenamedChat(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4045]!, self._r[4045]!, [_0]) - } - public var Paint_Regular: String { return self._s[4046]! } - public var ChatSettings_AutoDownloadReset: String { return self._s[4047]! } - public var SocksProxySetup_ShareLink: String { return self._s[4048]! } - public var Wallet_Qr_Title: String { return self._s[4049]! } - public var BlockedUsers_SelectUserTitle: String { return self._s[4050]! } - public var VoiceOver_Chat_RecordModeVoiceMessage: String { return self._s[4052]! } - public var Wallet_Settings_Configuration: String { return self._s[4053]! } - public var GroupInfo_InviteByLink: String { return self._s[4054]! } - public var MessageTimer_Custom: String { return self._s[4055]! } - public var UserInfo_NotificationsDefaultEnabled: String { return self._s[4056]! } - public var Conversation_StopQuizConfirmationTitle: String { return self._s[4057]! } - public var Passport_Address_TypeTemporaryRegistration: String { return self._s[4059]! } - public var Conversation_SendMessage_SetReminder: String { return self._s[4060]! } - public var VoiceOver_Chat_Selected: String { return self._s[4061]! } - public var Paint_Pen: String { return self._s[4062]! } - public var ChatSettings_AutoDownloadUsingWiFi: String { return self._s[4063]! } - public var Channel_Username_InvalidTaken: String { return self._s[4064]! } - public var Conversation_ClousStorageInfo_Description3: String { return self._s[4065]! } - public var Wallet_WordCheck_TryAgain: String { return self._s[4066]! } - public var Wallet_Info_TransactionPendingHeader: String { return self._s[4067]! } - public var Settings_ChatBackground: String { return self._s[4068]! } - public var Channel_Subscribers_Title: String { return self._s[4069]! } - public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[4070]! } - public var ApplyLanguage_ChangeLanguageTitle: String { return self._s[4071]! } - public var Watch_ConnectionDescription: String { return self._s[4072]! } - public var OldChannels_NoticeText: String { return self._s[4075]! } - public var Wallet_Configuration_ApplyErrorTitle: String { return self._s[4076]! } - public var IntentsSettings_SuggestBy: String { return self._s[4078]! } - public var Theme_ThemeChangedText: String { return self._s[4079]! } - public var ChatList_ArchivedChatsTitle: String { return self._s[4080]! } - public var Wallpaper_ResetWallpapers: String { return self._s[4081]! } - public var Wallet_Send_TransactionInProgress: String { return self._s[4082]! } - public var Conversation_SendDice: String { return self._s[4083]! } - public var EditProfile_Title: String { return self._s[4084]! } - public var NotificationsSound_Bamboo: String { return self._s[4086]! } - public var Channel_AdminLog_MessagePreviousMessage: String { return self._s[4088]! } - public var Login_SmsRequestState2: String { return self._s[4089]! } - public var Passport_Language_ar: String { return self._s[4090]! } - public func Message_AuthorPinnedGame(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4091]!, self._r[4091]!, [_0]) - } - public var SettingsSearch_Synonyms_EditProfile_Title: String { return self._s[4092]! } - public var Wallet_Created_Text: String { return self._s[4093]! } - public var Conversation_MessageDialogEdit: String { return self._s[4095]! } - public var Wallet_Created_Proceed: String { return self._s[4096]! } - public var Wallet_Words_Done: String { return self._s[4097]! } - public var VoiceOver_Media_PlaybackPause: String { return self._s[4098]! } - public var ChatListFolder_NameChannels: String { return self._s[4099]! } - public func PUSH_AUTH_UNKNOWN(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4100]!, self._r[4100]!, [_1]) - } - public var Common_Close: String { return self._s[4102]! } - public var GroupInfo_PublicLink: String { return self._s[4103]! } - public var Channel_OwnershipTransfer_ErrorPrivacyRestricted: String { return self._s[4104]! } - public var SettingsSearch_Synonyms_Notifications_GroupNotificationsPreview: String { return self._s[4105]! } - public var Conversation_ContextMenuOpenChannel: String { return self._s[4109]! } - public func Channel_AdminLog_MessageToggleInvitesOff(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4110]!, self._r[4110]!, [_0]) - } - public var UserInfo_About_Placeholder: String { return self._s[4111]! } - public func Conversation_FileHowToText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4112]!, self._r[4112]!, [_0]) - } - public var GroupInfo_Permissions_SectionTitle: String { return self._s[4113]! } - public var Channel_Info_Banned: String { return self._s[4115]! } - public func Time_MonthOfYear_m11(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4116]!, self._r[4116]!, [_0]) - } - public var Appearance_Other: String { return self._s[4117]! } - public var Passport_Language_my: String { return self._s[4118]! } - public var Group_Setup_BasicHistoryHiddenHelp: String { return self._s[4119]! } - public func Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4120]!, self._r[4120]!, [_1, _2, _3]) - } - public var SettingsSearch_Synonyms_Privacy_PasscodeAndFaceId: String { return self._s[4121]! } - public var IntentsSettings_SuggestedAndSpotlightChatsInfo: String { return self._s[4122]! } - public var Preview_CopyAddress: String { return self._s[4123]! } - public func DialogList_SinglePlayingGameSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4124]!, self._r[4124]!, [_0]) - } - public var KeyCommand_JumpToPreviousChat: String { return self._s[4125]! } - public var UserInfo_BotSettings: String { return self._s[4126]! } - public var LiveLocation_MenuStopAll: String { return self._s[4128]! } - public var Passport_PasswordCreate: String { return self._s[4129]! } - public var StickerSettings_MaskContextInfo: String { return self._s[4130]! } - public var Message_PinnedLocationMessage: String { return self._s[4131]! } - public var Map_Satellite: String { return self._s[4132]! } - public var Watch_Message_Unsupported: String { return self._s[4133]! } - public var Username_TooManyPublicUsernamesError: String { return self._s[4134]! } - public var TwoStepAuth_EnterPasswordInvalid: String { return self._s[4135]! } + return formatWithArgumentRanges(self._s[297]!, self._r[297]!, [_1]) + } + public var Passport_Identity_Selfie: String { return self._s[298]! } + public var Privacy_ContactsTitle: String { return self._s[299]! } + public var GroupInfo_InviteLink_Title: String { return self._s[301]! } + public var TwoFactorSetup_Password_PlaceholderPassword: String { return self._s[302]! } + public var Conversation_OpenFile: String { return self._s[303]! } + public var Map_SetThisPlace: String { return self._s[304]! } + public var Channel_Info_Management: String { return self._s[305]! } + public var Passport_Language_hr: String { return self._s[306]! } + public var EditTheme_Edit_Preview_IncomingText: String { return self._s[308]! } + public var Conversation_SecretChatContextBotAlert: String { return self._s[310]! } + public var GroupInfo_Permissions_SlowmodeValue_Off: String { return self._s[311]! } + public var Privacy_Calls_P2PContacts: String { return self._s[312]! } + public var Appearance_PickAccentColor: String { return self._s[313]! } + public var MediaPicker_TapToUngroupDescription: String { return self._s[314]! } + public var Localization_EnglishLanguageName: String { return self._s[315]! } + public var Stickers_SuggestStickers: String { return self._s[316]! } + public var Passport_Language_ko: String { return self._s[317]! } + public var Settings_ProxyDisabled: String { return self._s[318]! } + public var PrivacySettings_PasscodeOff: String { return self._s[319]! } + public var Undo_LeftChannel: String { return self._s[320]! } + public var Appearance_AutoNightThemeDisabled: String { return self._s[321]! } + public var TextFormat_Bold: String { return self._s[322]! } + public var Login_InfoTitle: String { return self._s[323]! } + public var Channel_BanUser_PermissionSendPolls: String { return self._s[324]! } + public var Settings_AddAnotherAccount: String { return self._s[325]! } + public var GroupPermission_NewTitle: String { return self._s[326]! } + public var Login_SelectCountry_Title: String { return self._s[327]! } + public var Cache_ServiceFiles: String { return self._s[328]! } + public var Passport_Language_nl: String { return self._s[329]! } + public var Contacts_TopSection: String { return self._s[330]! } + public var Passport_Identity_DateOfBirthPlaceholder: String { return self._s[331]! } + public var Conversation_ContextMenuReport: String { return self._s[333]! } + public func Login_BannedPhoneBody(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[334]!, self._r[334]!, [_0]) + } + public var Conversation_Search: String { return self._s[335]! } + public var Group_Setup_HistoryVisibleHelp: String { return self._s[337]! } + public var ReportPeer_AlertSuccess: String { return self._s[339]! } + public var AutoNightTheme_Title: String { return self._s[341]! } public func Notification_PinnedTextMessage(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4136]!, self._r[4136]!, [_0, _1]) + return formatWithArgumentRanges(self._s[343]!, self._r[343]!, [_0, _1]) } public func Conversation_OpenBotLinkText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4137]!, self._r[4137]!, [_0]) + return formatWithArgumentRanges(self._s[344]!, self._r[344]!, [_0]) + } + public var Conversation_ShareBotContactConfirmation: String { return self._s[345]! } + public var TwoStepAuth_RecoveryCode: String { return self._s[346]! } + public var SocksProxySetup_ConnectAndSave: String { return self._s[347]! } + public func MESSAGE_INVOICE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[348]!, self._r[348]!, [_1, _2]) + } + public func Channel_AdminLog_MessageChangedGroupUsername(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[349]!, self._r[349]!, [_0]) + } + public func Notification_GroupInviter(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[350]!, self._r[350]!, [_0]) + } + public var Conversation_InfoGroup: String { return self._s[351]! } + public func Map_AccurateTo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[353]!, self._r[353]!, [_0]) + } + public var Conversation_ChatBackground: String { return self._s[354]! } + public var PhotoEditor_Set: String { return self._s[355]! } + public func Channel_Management_PromotedBy(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[357]!, self._r[357]!, [_0]) + } + public var IntentsSettings_SuggestedChatsContacts: String { return self._s[358]! } + public var Passport_Phone_Title: String { return self._s[360]! } + public var Conversation_EditingMessageMediaChange: String { return self._s[361]! } + public var Channel_LinkItem: String { return self._s[362]! } + public func PUSH_CHAT_DELETE_MEMBER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[363]!, self._r[363]!, [_1, _2, _3]) + } + public var Conversation_DeleteManyMessages: String { return self._s[364]! } + public var Notifications_Badge_IncludeMutedChats: String { return self._s[365]! } + public var AuthSessions_AddedDeviceTitle: String { return self._s[368]! } + public var Privacy_Calls_NeverAllow_Placeholder: String { return self._s[369]! } + public var Settings_ProxyConnecting: String { return self._s[370]! } + public var Theme_Colors_Accent: String { return self._s[371]! } + public var Theme_Colors_ColorWallpaperWarning: String { return self._s[372]! } + public func PUSH_PHONE_CALL_MISSED(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[373]!, self._r[373]!, [_1]) + } + public var Passport_Language_lo: String { return self._s[374]! } + public var Wallet_WordCheck_Continue: String { return self._s[375]! } + public func Watch_Time_ShortWeekdayAt(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[377]!, self._r[377]!, [_1, _2]) + } + public var Permissions_NotificationsText_v0: String { return self._s[378]! } + public var ChatList_Context_RemoveFromRecents: String { return self._s[379]! } + public var Watch_GroupInfo_Title: String { return self._s[380]! } + public var Settings_AddDevice: String { return self._s[382]! } + public var WallpaperPreview_SwipeColorsTopText: String { return self._s[383]! } + public func PUSH_CHANNEL_ALBUM(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[384]!, self._r[384]!, [_1]) + } + public var TwoStepAuth_Disable: String { return self._s[386]! } + public func Conversation_AddNameToContacts(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[387]!, self._r[387]!, [_0]) + } + public func Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[388]!, self._r[388]!, [_1, _2, _3]) + } + public func Login_WillSendSms(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[389]!, self._r[389]!, [_0]) + } + public var Channel_AdminLog_BanReadMessages: String { return self._s[390]! } + public var Undo_ChatDeleted: String { return self._s[391]! } + public var ContactInfo_URLLabelHomepage: String { return self._s[392]! } + public func PUSH_CHAT_MESSAGE_STICKER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[393]!, self._r[393]!, [_1, _2, _3]) + } + public var FastTwoStepSetup_EmailHelp: String { return self._s[394]! } + public var Contacts_SelectAll: String { return self._s[395]! } + public var Privacy_ContactsReset: String { return self._s[396]! } + public var AttachmentMenu_File: String { return self._s[398]! } + public var PasscodeSettings_EncryptData: String { return self._s[399]! } + public var EditTheme_ThemeTemplateAlertText: String { return self._s[400]! } + public func Wallet_Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[402]!, self._r[402]!, [_1, _2, _3]) + } + public func Privacy_GroupsAndChannels_InviteToChannelError(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[403]!, self._r[403]!, [_0, _1]) + } + public func Profile_CreateEncryptedChatOutdatedError(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[404]!, self._r[404]!, [_0, _1]) + } + public var PhotoEditor_ShadowsTint: String { return self._s[406]! } + public var GroupInfo_ChatAdmins: String { return self._s[407]! } + public var ArchivedChats_IntroTitle2: String { return self._s[408]! } + public var Cache_LowDiskSpaceText: String { return self._s[409]! } + public var CreatePoll_Anonymous: String { return self._s[410]! } + public var Wallet_Created_ExportErrorText: String { return self._s[411]! } + public var Checkout_PaymentMethod_New: String { return self._s[412]! } + public var Wallet_Info_RefreshErrorText: String { return self._s[413]! } + public var Invitation_JoinGroup: String { return self._s[414]! } + public func Time_MonthOfYear_m4(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[417]!, self._r[417]!, [_0]) + } + public var CheckoutInfo_SaveInfoHelp: String { return self._s[418]! } + public var Notification_Reply: String { return self._s[420]! } + public var Wallet_Month_GenSeptember: String { return self._s[421]! } + public func Login_PhoneBannedEmailSubject(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[422]!, self._r[422]!, [_0]) + } + public var Login_PhoneTitle: String { return self._s[423]! } + public var VoiceOver_Media_PlaybackRateNormal: String { return self._s[424]! } + public func PUSH_CHAT_MESSAGE_INVOICE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[425]!, self._r[425]!, [_1, _2, _3]) + } + public var Appearance_TextSize_Title: String { return self._s[426]! } + public var NetworkUsageSettings_MediaImageDataSection: String { return self._s[428]! } + public var VoiceOver_Navigation_Compose: String { return self._s[429]! } + public var Passport_InfoText: String { return self._s[430]! } + public var ApplyLanguage_ApplyLanguageAction: String { return self._s[431]! } + public var MessagePoll_LabelClosed: String { return self._s[433]! } + public var AttachmentMenu_SendAsFiles: String { return self._s[434]! } + public var KeyCommand_FocusOnInputField: String { return self._s[435]! } + public var Privacy_SecretChatsLinkPreviews: String { return self._s[437]! } + public var Permissions_PeopleNearbyAllow_v0: String { return self._s[438]! } + public var Conversation_ContextMenuMention: String { return self._s[440]! } + public var CreatePoll_QuizInfo: String { return self._s[441]! } + public var Appearance_ThemePreview_ChatList_2_Name: String { return self._s[442]! } + public var Username_LinkCopied: String { return self._s[443]! } + public var IntentsSettings_SuggestedAndSpotlightChatsInfo: String { return self._s[444]! } + public var TwoStepAuth_ChangePassword: String { return self._s[445]! } + public var Watch_Suggestion_Thanks: String { return self._s[446]! } + public var Channel_TitleInfo: String { return self._s[447]! } + public var ChatList_ChatTypesSection: String { return self._s[448]! } + public func Watch_LastSeen_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[449]!, self._r[449]!, [_0]) + } + public func Channel_AdminLog_PollStopped(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[450]!, self._r[450]!, [_0]) + } + public var AuthSessions_AddDevice_InvalidQRCode: String { return self._s[451]! } + public func Call_MicrophoneOff(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[452]!, self._r[452]!, [_0]) + } + public var Channel_AdminLogFilter_ChannelEventsInfo: String { return self._s[453]! } + public var Profile_MessageLifetimeForever: String { return self._s[454]! } + public var ArchivedChats_IntroText1: String { return self._s[455]! } + public var Notifications_ChannelNotificationsPreview: String { return self._s[456]! } + public var Map_PullUpForPlaces: String { return self._s[458]! } + public var UserInfo_TelegramCall: String { return self._s[459]! } + public var Conversation_ShareMyContactInfo: String { return self._s[460]! } + public var ChatList_Tabs_All: String { return self._s[461]! } + public var Notification_PassportValueEmail: String { return self._s[462]! } + public var Notification_VideoCallIncoming: String { return self._s[463]! } + public var SettingsSearch_Synonyms_Appearance_AutoNightTheme: String { return self._s[464]! } + public var Channel_Username_InvalidTaken: String { return self._s[465]! } + public var GroupPermission_EditingDisabled: String { return self._s[466]! } + public var ChatContextMenu_TextSelectionTip: String { return self._s[467]! } + public var Passport_Language_pl: String { return self._s[469]! } + public var Call_Accept: String { return self._s[470]! } + public var ChatListFolder_NameSectionHeader: String { return self._s[471]! } + public func Passport_Identity_NativeNameTitle(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[472]!, self._r[472]!, [_0]) + } + public var ClearCache_Forever: String { return self._s[473]! } + public func ChannelInfo_AddParticipantConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[475]!, self._r[475]!, [_0]) + } + public var Group_EditAdmin_RankAdminPlaceholder: String { return self._s[476]! } + public var Calls_SubmitRating: String { return self._s[477]! } + public func ChatList_AddedToFolderTooltip(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[478]!, self._r[478]!, [_1, _2]) + } + public var IntentsSettings_MainAccountInfo: String { return self._s[479]! } + public var Map_Hybrid: String { return self._s[481]! } + public var ChatList_Context_Archive: String { return self._s[482]! } + public var Message_PinnedDocumentMessage: String { return self._s[483]! } + public var State_ConnectingToProxyInfo: String { return self._s[484]! } + public var Wallet_Month_GenDecember: String { return self._s[485]! } + public var Passport_Identity_NativeNameGenericTitle: String { return self._s[487]! } + public var Settings_AppLanguage: String { return self._s[488]! } + public func Checkout_SavePasswordTimeoutAndFaceId(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[489]!, self._r[489]!, [_0]) + } + public var Notifications_PermissionsEnable: String { return self._s[491]! } + public var CheckoutInfo_ShippingInfoAddress1Placeholder: String { return self._s[492]! } + public func UserInfo_BlockActionTitle(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[493]!, self._r[493]!, [_0]) + } + public func AuthSessions_Message(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[494]!, self._r[494]!, [_0]) + } + public var NotificationsSound_Aurora: String { return self._s[497]! } + public var ScheduledMessages_ClearAll: String { return self._s[500]! } + public func CancelResetAccount_TextSMS(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[501]!, self._r[501]!, [_0]) + } + public var Settings_BlockedUsers: String { return self._s[503]! } + public func UserInfo_StartSecretChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[505]!, self._r[505]!, [_0]) + } + public var Passport_Language_hu: String { return self._s[506]! } + public func Conversation_ScheduleMessage_SendTomorrow(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[507]!, self._r[507]!, [_0]) + } + public var StickerPack_Share: String { return self._s[508]! } + public var Checkout_NewCard_SaveInfoEnableHelp: String { return self._s[509]! } + public func ForwardedAuthors2(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[510]!, self._r[510]!, [_0, _1]) + } + public var Privacy_ContactsResetConfirmation: String { return self._s[511]! } + public var AppleWatch_ReplyPresets: String { return self._s[512]! } + public var Bot_GenericBotStatus: String { return self._s[513]! } + public var Appearance_ShareThemeColor: String { return self._s[514]! } + public var AuthSessions_AddDevice_UrlLoginHint: String { return self._s[515]! } + public var ReportGroupLocation_Title: String { return self._s[516]! } + public func Activity_RemindAboutUser(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[517]!, self._r[517]!, [_0]) + } + public var Profile_CreateEncryptedChatError: String { return self._s[518]! } + public var Channel_EditAdmin_TransferOwnership: String { return self._s[519]! } + public var Wallpaper_ErrorNotFound: String { return self._s[520]! } + public var Bot_GenericSupportStatus: String { return self._s[521]! } + public var Activity_UploadingPhoto: String { return self._s[523]! } + public var Watch_UserInfo_Title: String { return self._s[525]! } + public var SocksProxySetup_ProxyTelegram: String { return self._s[526]! } + public var Appearance_ThemeDay: String { return self._s[527]! } + public func ApplyLanguage_ChangeLanguageOfficialText(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[528]!, self._r[528]!, [_1]) + } + public func FileSize_B(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[529]!, self._r[529]!, [_0]) + } + public var Passport_Title: String { return self._s[532]! } + public func Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[534]!, self._r[534]!, [_1, _2, _3]) + } + public var Wallet_Sent_Title: String { return self._s[535]! } + public var CheckoutInfo_ShippingInfoCountryPlaceholder: String { return self._s[536]! } + public var SocksProxySetup_ShareLink: String { return self._s[539]! } + public var AuthSessions_OtherDevices: String { return self._s[540]! } + public var IntentsSettings_SuggestedChatsGroups: String { return self._s[541]! } + public var Watch_MessageView_Reply: String { return self._s[542]! } + public var Camera_FlashOn: String { return self._s[544]! } + public func PUSH_MESSAGE_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[545]!, self._r[545]!, [_1, _2]) + } + public var Channel_EditAdmin_PermissionEditMessages: String { return self._s[547]! } + public var Privacy_Calls_NeverAllow: String { return self._s[548]! } + public var SharedMedia_CategoryLinks: String { return self._s[549]! } + public var Conversation_PinMessageAlertGroup: String { return self._s[552]! } + public var Passport_Identity_ScansHelp: String { return self._s[553]! } + public var ShareMenu_CopyShareLink: String { return self._s[554]! } + public var StickerSettings_MaskContextInfo: String { return self._s[555]! } + public var SocksProxySetup_ProxyStatusChecking: String { return self._s[556]! } + public var Conversation_WalletRequiredText: String { return self._s[557]! } + public var AutoDownloadSettings_AutodownloadPhotos: String { return self._s[559]! } + public var Checkout_ErrorPrecheckoutFailed: String { return self._s[561]! } + public var NotificationsSound_Popcorn: String { return self._s[562]! } + public var FeatureDisabled_Oops: String { return self._s[563]! } + public func Channel_AdminLog_MessageChangedChannelAbout(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[564]!, self._r[564]!, [_0]) + } + public var Notification_PinnedMessage: String { return self._s[565]! } + public var Tour_Title4: String { return self._s[566]! } + public var Watch_Suggestion_OK: String { return self._s[567]! } + public var Compose_TokenListPlaceholder: String { return self._s[568]! } + public var EditTheme_Edit_TopInfo: String { return self._s[569]! } + public var Gif_NoGifsFound: String { return self._s[570]! } + public var Login_InvalidCountryCode: String { return self._s[571]! } + public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsExceptions: String { return self._s[572]! } + public func PUSH_LOCKED_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[574]!, self._r[574]!, [_1]) + } + public var Profile_CreateNewContact: String { return self._s[575]! } + public var AutoDownloadSettings_DataUsageLow: String { return self._s[576]! } + public var SettingsSearch_Synonyms_Notifications_InAppNotificationsPreview: String { return self._s[577]! } + public var Group_Setup_TypePublic: String { return self._s[578]! } + public var Weekday_ShortSaturday: String { return self._s[579]! } + public func Time_MonthOfYear_m12(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[580]!, self._r[580]!, [_0]) + } + public var LiveLocation_MenuStopAll: String { return self._s[581]! } + public func DialogList_EncryptedChatStartedIncoming(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[582]!, self._r[582]!, [_0]) + } + public var ChatListFolder_NamePlaceholder: String { return self._s[583]! } + public var Channel_OwnershipTransfer_ErrorPublicChannelsTooMuch: String { return self._s[584]! } + public func PUSH_CHAT_MESSAGE_GAME(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[585]!, self._r[585]!, [_1, _2, _3]) + } + public func Wallet_SecureStorageChanged_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[587]!, self._r[587]!, [_0]) + } + public var Chat_GenericPsaTooltip: String { return self._s[588]! } + public func Message_ForwardedMessageShort(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[589]!, self._r[589]!, [_0]) + } + public var PrivacyLastSeenSettings_AlwaysShareWith_Placeholder: String { return self._s[590]! } + public var Login_PhoneAndCountryHelp: String { return self._s[591]! } + public var SaveIncomingPhotosSettings_From: String { return self._s[592]! } + public var Conversation_JumpToDate: String { return self._s[593]! } + public var AuthSessions_AddDevice: String { return self._s[594]! } + public var Settings_FAQ: String { return self._s[596]! } + public var Username_Title: String { return self._s[597]! } + public var DialogList_Read: String { return self._s[598]! } + public var Conversation_InstantPagePreview: String { return self._s[599]! } + public var Login_ResetAccountProtected_Title: String { return self._s[601]! } + public var CallFeedback_ReasonDistortedSpeech: String { return self._s[602]! } + public var Channel_EditAdmin_PermissionChangeInfo: String { return self._s[603]! } + public func Channel_AdminLog_MessageRankUsername(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[604]!, self._r[604]!, [_1, _2, _3]) + } + public var WallpaperPreview_PreviewBottomText: String { return self._s[606]! } + public var Privacy_SecretChatsTitle: String { return self._s[609]! } + public func Notification_PassportValuesSentMessage(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[610]!, self._r[610]!, [_1, _2]) + } + public var Checkout_NewCard_SaveInfoHelp: String { return self._s[611]! } + public var Conversation_ClousStorageInfo_Description4: String { return self._s[612]! } + public var PasscodeSettings_TurnPasscodeOn: String { return self._s[613]! } + public var Message_ReplyActionButtonShowReceipt: String { return self._s[614]! } + public func PrivacyPolicy_AgeVerificationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[615]!, self._r[615]!, [_0]) + } + public var GroupInfo_DeleteAndExitConfirmation: String { return self._s[617]! } + public var TwoStepAuth_ConfirmationAbort: String { return self._s[618]! } + public var PrivacySettings_LastSeenEverybody: String { return self._s[619]! } + public var CallFeedback_ReasonDropped: String { return self._s[620]! } + public func ScheduledMessages_ScheduledDate(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[621]!, self._r[621]!, [_0]) + } + public var WebSearch_Images: String { return self._s[622]! } + public var Passport_Identity_Surname: String { return self._s[623]! } + public var Channel_Stickers_CreateYourOwn: String { return self._s[624]! } + public var TwoFactorSetup_Email_Title: String { return self._s[625]! } + public var Cache_ClearEmpty: String { return self._s[626]! } + public var AuthSessions_AddDeviceIntro_Action: String { return self._s[627]! } + public var Theme_Context_Apply: String { return self._s[628]! } + public var GroupInfo_Permissions_SearchPlaceholder: String { return self._s[629]! } + public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[630]! } + public var AutoDownloadSettings_DocumentsTitle: String { return self._s[631]! } + public func NetworkUsageSettings_CellularUsageSince(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[632]!, self._r[632]!, [_0]) + } + public var Call_StatusRinging: String { return self._s[633]! } + public func Map_DistanceAway(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[634]!, self._r[634]!, [_0]) + } + public func DialogList_SingleTypingSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[635]!, self._r[635]!, [_0]) + } + public var Cache_ClearNone: String { return self._s[636]! } + public var Wallet_Receive_CopyAddress: String { return self._s[637]! } + public var PrivacyPolicy_Accept: String { return self._s[638]! } + public var Wallet_TransactionInfo_SenderHeader: String { return self._s[639]! } + public var Contacts_PhoneNumber: String { return self._s[640]! } + public var Passport_Identity_OneOfTypePassport: String { return self._s[641]! } + public var PhotoEditor_HighlightsTint: String { return self._s[643]! } + public var AutoDownloadSettings_AutodownloadVideos: String { return self._s[644]! } + public var Checkout_PaymentMethod_Title: String { return self._s[647]! } + public var Month_GenAugust: String { return self._s[649]! } + public var DialogList_Draft: String { return self._s[650]! } + public var ChatList_EmptyChatListFilterText: String { return self._s[651]! } + public var PeopleNearby_Description: String { return self._s[652]! } + public var WallpaperPreview_SwipeColorsBottomText: String { return self._s[653]! } + public var AppWallet_TransactionInfo_FeeInfoURL: String { return self._s[654]! } + public var SettingsSearch_Synonyms_Privacy_Data_TopPeers: String { return self._s[656]! } + public var Watch_Message_ForwardedFrom: String { return self._s[657]! } + public var Wallet_Words_NotDoneOk: String { return self._s[658]! } + public var Notification_Mute1h: String { return self._s[659]! } + public var Appearance_ThemePreview_Chat_3_TextWithLink: String { return self._s[660]! } + public var SettingsSearch_Synonyms_Privacy_AuthSessions: String { return self._s[662]! } + public var Channel_Edit_LinkItem: String { return self._s[663]! } + public var Presence_online: String { return self._s[664]! } + public var AutoDownloadSettings_Title: String { return self._s[665]! } + public var Conversation_MessageDialogRetry: String { return self._s[666]! } + public var SettingsSearch_Synonyms_ChatSettings_OpenLinksIn: String { return self._s[668]! } + public var Channel_About_Placeholder: String { return self._s[669]! } + public var Passport_Language_sl: String { return self._s[670]! } + public var AppleWatch_Title: String { return self._s[672]! } + public var Settings_ViewPhoto: String { return self._s[674]! } + public var ChatList_DeleteSavedMessagesConfirmation: String { return self._s[675]! } + public var Cache_ClearProgress: String { return self._s[676]! } + public var Cache_Music: String { return self._s[677]! } + public var Conversation_ContextMenuShare: String { return self._s[679]! } + public var AutoDownloadSettings_Unlimited: String { return self._s[680]! } + public var Channel_OwnershipTransfer_ErrorPrivacyRestricted: String { return self._s[681]! } + public var Contacts_PermissionsAllow: String { return self._s[682]! } + public var Passport_Language_vi: String { return self._s[684]! } + public func PUSH_MESSAGE_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[687]!, self._r[687]!, [_1, _2]) + } + public var Passport_Language_de: String { return self._s[688]! } + public var Notifications_PermissionsText: String { return self._s[690]! } + public var GroupRemoved_AddToGroup: String { return self._s[691]! } + public var Appearance_ThemePreview_ChatList_4_Text: String { return self._s[692]! } + public var ChangePhoneNumberCode_RequestingACall: String { return self._s[693]! } + public var Login_TermsOfServiceAgree: String { return self._s[694]! } + public var VoiceOver_Navigation_ProxySettings: String { return self._s[695]! } + public func PUSH_CHAT_JOINED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[696]!, self._r[696]!, [_1, _2]) + } + public var SettingsSearch_Synonyms_Data_CallsUseLessData: String { return self._s[698]! } + public var ChatListFolder_NameGroups: String { return self._s[699]! } + public var SocksProxySetup_ProxyDetailsTitle: String { return self._s[700]! } + public func Channel_AdminLog_MessageChangedLinkedGroup(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[701]!, self._r[701]!, [_1, _2]) + } + public var Watch_Suggestion_TalkLater: String { return self._s[702]! } + public var Checkout_ShippingOption_Title: String { return self._s[703]! } + public var CreatePoll_TextHeader: String { return self._s[704]! } + public var VoiceOver_Chat_Message: String { return self._s[706]! } + public var InfoPlist_NSLocationWhenInUseUsageDescription: String { return self._s[707]! } + public var ContactInfo_Note: String { return self._s[709]! } + public var Channel_AdminLog_InfoPanelAlertText: String { return self._s[710]! } + public var Wallet_Receive_AmountHeader: String { return self._s[711]! } + public var Checkout_NewCard_CardholderNameTitle: String { return self._s[712]! } + public var AutoDownloadSettings_Photos: String { return self._s[713]! } + public var UserInfo_NotificationsDefaultDisabled: String { return self._s[714]! } + public var Channel_Info_Subscribers: String { return self._s[715]! } + public var ChatList_DeleteForCurrentUser: String { return self._s[716]! } + public var ChatListFolderSettings_FoldersSection: String { return self._s[717]! } + public func Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[721]!, self._r[721]!, [_1, _2, _3]) + } + public var AutoNightTheme_System: String { return self._s[722]! } + public var Call_StatusWaiting: String { return self._s[723]! } + public var GroupInfo_GroupHistoryHidden: String { return self._s[724]! } + public func CHAT_MESSAGE_INVOICE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[725]!, self._r[725]!, [_1, _2, _3]) + } + public var Conversation_ContextMenuCopy: String { return self._s[727]! } + public var Notifications_MessageNotificationsPreview: String { return self._s[728]! } + public var Notifications_InAppNotificationsVibrate: String { return self._s[729]! } + public func Conversation_RestrictedTextTimed(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[730]!, self._r[730]!, [_0]) + } + public var Group_Status: String { return self._s[732]! } + public var Group_Setup_HistoryVisible: String { return self._s[733]! } + public var Conversation_DiscardVoiceMessageAction: String { return self._s[734]! } + public var Paint_Edit: String { return self._s[735]! } + public var Channel_EditAdmin_CannotEdit: String { return self._s[737]! } + public var Username_InvalidTooShort: String { return self._s[738]! } + public var ClearCache_StorageOtherApps: String { return self._s[739]! } + public var Wallet_Configuration_SourceJSON: String { return self._s[740]! } + public var Conversation_ViewMessage: String { return self._s[741]! } + public var GroupInfo_PublicLinkAdd: String { return self._s[743]! } + public func Notification_RemovedGroupPhoto(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[744]!, self._r[744]!, [_0]) + } + public var CallSettings_Title: String { return self._s[745]! } + public func Conversation_BotInteractiveUrlAlert(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[746]!, self._r[746]!, [_0]) + } + public func VoiceOver_Chat_ContactFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[749]!, self._r[749]!, [_0]) + } + public var PUSH_SENDER_YOU: String { return self._s[752]! } + public var Profile_ShareContactButton: String { return self._s[753]! } + public var GroupInfo_Permissions_SectionTitle: String { return self._s[754]! } + public var Map_ShareLiveLocation: String { return self._s[755]! } + public var ChatListFolder_TitleEdit: String { return self._s[756]! } + public var Wallet_Send_ErrorInvalidAddress: String { return self._s[757]! } + public var Passport_Address_Address: String { return self._s[759]! } + public var LastSeen_JustNow: String { return self._s[761]! } + public func SecretImage_NotViewedYet(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[762]!, self._r[762]!, [_0]) + } + public var ContactInfo_PhoneLabelOther: String { return self._s[763]! } + public var PasscodeSettings_DoNotMatch: String { return self._s[764]! } + public var Weekday_Today: String { return self._s[767]! } + public var DialogList_Title: String { return self._s[768]! } + public var SettingsSearch_Synonyms_Notifications_MessageNotificationsPreview: String { return self._s[769]! } + public var Cache_ClearCache: String { return self._s[770]! } + public var CreatePoll_ExplanationInfo: String { return self._s[771]! } + public var Notifications_ResetAllNotificationsHelp: String { return self._s[773]! } + public var Stats_MessageTitle: String { return self._s[774]! } + public var Passport_Address_Street: String { return self._s[776]! } + public var Wallet_Receive_ShareUrlInfo: String { return self._s[777]! } + public func Channel_AdminLog_MessageRemovedGroupUsername(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[778]!, self._r[778]!, [_0]) + } + public var Channel_AdminLog_ChannelEmptyText: String { return self._s[779]! } + public func Login_PhoneGenericEmailSubject(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[780]!, self._r[780]!, [_0]) + } + public var TwoStepAuth_Email: String { return self._s[782]! } + public var Wallet_Words_Text: String { return self._s[783]! } + public var Conversation_SecretLinkPreviewAlert: String { return self._s[784]! } + public var PrivacySettings_PasscodeOn: String { return self._s[785]! } + public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[787]! } + public var Wallet_Send_AddressInfo: String { return self._s[788]! } + public var Camera_SquareMode: String { return self._s[789]! } + public var Wallet_Month_ShortJuly: String { return self._s[790]! } + public var SocksProxySetup_Port: String { return self._s[791]! } + public var Watch_LastSeen_JustNow: String { return self._s[793]! } + public func PUSH_MESSAGE_GAME(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[794]!, self._r[794]!, [_1, _2]) + } + public func Watch_LastSeen_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[795]!, self._r[795]!, [_0]) + } + public var EditTheme_Expand_Preview_OutgoingText: String { return self._s[796]! } + public var Channel_AdminLogFilter_EventsTitle: String { return self._s[797]! } + public var Wallet_AccessDenied_Settings: String { return self._s[799]! } + public var Watch_Suggestion_HoldOn: String { return self._s[801]! } + public func PUSH_CHANNEL_MESSAGE_GEOLIVE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[802]!, self._r[802]!, [_1]) + } + public var CallSettings_TabIcon: String { return self._s[803]! } + public var ScheduledMessages_SendNow: String { return self._s[804]! } + public var Stats_GroupTopWeekdaysTitle: String { return self._s[805]! } + public var Wallet_WordImport_IncorrectTitle: String { return self._s[806]! } + public var UserInfo_PhoneCall: String { return self._s[807]! } + public var Month_GenMarch: String { return self._s[808]! } + public var Camera_Discard: String { return self._s[809]! } + public var InfoPlist_NSFaceIDUsageDescription: String { return self._s[810]! } + public var Passport_RequestedInformation: String { return self._s[811]! } + public var Passport_Language_ro: String { return self._s[813]! } + public func PUSH_CHAT_MESSAGE_DOC(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[814]!, self._r[814]!, [_1, _2]) + } + public var AutoDownloadSettings_ResetHelp: String { return self._s[815]! } + public var Passport_Identity_DocumentDetails: String { return self._s[817]! } + public var Passport_Address_ScansHelp: String { return self._s[818]! } + public var ClearCache_StorageCache: String { return self._s[819]! } + public var Theme_Colors_ColorWallpaperWarningProceed: String { return self._s[820]! } + public var Conversation_RestrictedText: String { return self._s[821]! } + public var Notifications_MessageNotifications: String { return self._s[823]! } + public var Passport_Scans: String { return self._s[824]! } + public var TwoStepAuth_SetupHintTitle: String { return self._s[826]! } + public var LogoutOptions_ContactSupportTitle: String { return self._s[827]! } + public var Passport_Identity_SelfieHelp: String { return self._s[828]! } + public var Permissions_NotificationsUnreachableText_v0: String { return self._s[829]! } + public var Privacy_PaymentsClear_PaymentInfo: String { return self._s[830]! } + public var ShareMenu_CopyShareLinkGame: String { return self._s[831]! } + public var PeerInfo_ButtonSearch: String { return self._s[832]! } + public var SettingsSearch_Synonyms_Privacy_Data_ClearPaymentsInfo: String { return self._s[835]! } + public var Passport_FieldIdentityTranslationHelp: String { return self._s[837]! } + public var Conversation_InputTextSilentBroadcastPlaceholder: String { return self._s[838]! } + public var Month_GenSeptember: String { return self._s[839]! } + public func Call_GroupFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[841]!, self._r[841]!, [_1, _2]) + } + public var StickerPacksSettings_ArchivedPacks: String { return self._s[842]! } + public func Channel_Username_LinkHint(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[844]!, self._r[844]!, [_0]) + } + public func PUSH_PINNED_CONTACT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[846]!, self._r[846]!, [_1, _2]) + } + public var LogoutOptions_LogOutWalletInfo: String { return self._s[847]! } + public func PUSH_MESSAGE_VIDEOS(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[848]!, self._r[848]!, [_1, _2]) + } + public var Calls_NotNow: String { return self._s[850]! } + public var Wallet_Completed_Text: String { return self._s[853]! } + public var Settings_ChatFolders: String { return self._s[855]! } + public var Login_PadPhoneHelpTitle: String { return self._s[856]! } + public var TwoStepAuth_EnterPasswordInvalid: String { return self._s[857]! } + public var Settings_ChatBackground: String { return self._s[858]! } + public func PUSH_CHAT_MESSAGE_CONTACT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[860]!, self._r[860]!, [_1, _2]) + } + public var ProxyServer_VoiceOver_Active: String { return self._s[861]! } + public var Call_StatusBusy: String { return self._s[862]! } + public var Conversation_MessageDeliveryFailed: String { return self._s[863]! } + public var Login_NetworkError: String { return self._s[865]! } + public var TwoStepAuth_SetupPasswordDescription: String { return self._s[866]! } + public var Privacy_Calls_Integration: String { return self._s[867]! } + public var DialogList_SearchSectionMessages: String { return self._s[868]! } + public var AutoDownloadSettings_VideosTitle: String { return self._s[869]! } + public var Preview_DeletePhoto: String { return self._s[870]! } + public var PrivacySettings_PhoneNumber: String { return self._s[872]! } + public var Forward_ErrorDisabledForChat: String { return self._s[873]! } + public var Watch_Compose_CurrentLocation: String { return self._s[874]! } + public var Wallet_Info_TransactionFrom: String { return self._s[875]! } + public var Settings_CallSettings: String { return self._s[876]! } + public var AutoDownloadSettings_TypePrivateChats: String { return self._s[877]! } + public var ChatList_Context_MarkAllAsRead: String { return self._s[878]! } + public var ChatSettings_AutoPlayAnimations: String { return self._s[879]! } + public var SaveIncomingPhotosSettings_Title: String { return self._s[880]! } + public var OwnershipTransfer_SecurityRequirements: String { return self._s[881]! } + public var Map_LiveLocationFor1Hour: String { return self._s[882]! } + public func Privacy_GroupsAndChannels_InviteToGroupError(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[883]!, self._r[883]!, [_0, _1]) + } + public func Notification_PinnedLiveLocationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[884]!, self._r[884]!, [_0]) + } + public var Conversation_UnvotePoll: String { return self._s[885]! } + public var TwoStepAuth_EnterEmailCode: String { return self._s[886]! } + public func LOCAL_MESSAGE_FWDS(_ _1: String, _ _2: Int) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[887]!, self._r[887]!, [_1, "\(_2)"]) + } + public var Passport_InfoTitle: String { return self._s[888]! } + public func Conversation_Bytes(_ _0: Int) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[889]!, self._r[889]!, ["\(_0)"]) + } + public var AccentColor_Title: String { return self._s[890]! } + public func PUSH_MESSAGE_INVOICE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[891]!, self._r[891]!, [_1, _2]) + } + public func Notification_JoinedChannel(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[894]!, self._r[894]!, [_0]) + } + public var AutoDownloadSettings_DataUsageCustom: String { return self._s[895]! } + public var Conversation_ShareBotLocationConfirmation: String { return self._s[896]! } + public var PrivacyPhoneNumberSettings_WhoCanSeeMyPhoneNumber: String { return self._s[897]! } + public var VoiceOver_Editing_ClearText: String { return self._s[898]! } + public var Conversation_Unarchive: String { return self._s[899]! } + public var Notification_CallOutgoing: String { return self._s[900]! } + public var Channel_Setup_PublicNoLink: String { return self._s[901]! } + public var Passport_Identity_GenderPlaceholder: String { return self._s[902]! } + public var Message_Animation: String { return self._s[903]! } + public var SettingsSearch_Synonyms_Appearance_Animations: String { return self._s[904]! } + public var ChatSettings_ConnectionType_Title: String { return self._s[905]! } + public func Watch_Time_ShortFullAt(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[906]!, self._r[906]!, [_1, _2]) + } + public var Notification_CallBack: String { return self._s[908]! } + public var Appearance_Title: String { return self._s[910]! } + public var NotificationsSound_Glass: String { return self._s[912]! } + public var AutoDownloadSettings_CellularTitle: String { return self._s[914]! } + public var Notifications_PermissionsSuppressWarningTitle: String { return self._s[916]! } + public var ChatSearch_SearchPlaceholder: String { return self._s[917]! } + public var Passport_Identity_AddPassport: String { return self._s[918]! } + public func Wallet_Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[920]!, self._r[920]!, [_1, _2, _3]) + } + public var GroupPermission_NoAddMembers: String { return self._s[921]! } + public var ContactList_Context_SendMessage: String { return self._s[922]! } + public var PhotoEditor_GrainTool: String { return self._s[923]! } + public var Settings_CopyPhoneNumber: String { return self._s[924]! } + public var Passport_Address_City: String { return self._s[925]! } + public var ChannelRemoved_RemoveInfo: String { return self._s[926]! } + public var SocksProxySetup_Password: String { return self._s[928]! } + public var Wallet_Configuration_ApplyErrorTextJSONInvalidData: String { return self._s[929]! } + public var Settings_Passport: String { return self._s[930]! } + public var Channel_MessagePhotoUpdated: String { return self._s[932]! } + public var Stats_LanguagesTitle: String { return self._s[933]! } + public var ChatList_PeerTypeGroup: String { return self._s[934]! } + public var Privacy_Calls_P2PHelp: String { return self._s[935]! } + public var VoiceOver_Chat_PollNoVotes: String { return self._s[936]! } + public var Embed_PlayingInPIP: String { return self._s[937]! } + public var BlockedUsers_BlockUser: String { return self._s[939]! } + public var Login_CancelPhoneVerificationContinue: String { return self._s[940]! } + public func PUSH_CHANNEL_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[941]!, self._r[941]!, [_1]) + } + public var AuthSessions_LoggedIn: String { return self._s[942]! } + public var Channel_AdminLog_MessagePreviousCaption: String { return self._s[943]! } + public var Wallet_SecureStorageReset_PasscodeText: String { return self._s[944]! } + public var Activity_UploadingDocument: String { return self._s[945]! } + public var PeopleNearby_NoMembers: String { return self._s[946]! } + public var SettingsSearch_Synonyms_Stickers_Masks: String { return self._s[949]! } + public var ChatSettings_AutoPlayVideos: String { return self._s[950]! } + public var VoiceOver_Chat_OpenLinkHint: String { return self._s[951]! } + public var Settings_ViewVideo: String { return self._s[953]! } + public var Map_ShowPlaces: String { return self._s[955]! } + public var Passport_Phone_UseTelegramNumberHelp: String { return self._s[956]! } + public var SettingsSearch_Synonyms_Appearance_ChatBackground_Custom: String { return self._s[957]! } + public func PrivacySettings_LastSeenContactsPlus(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[958]!, self._r[958]!, [_0]) + } + public var Wallet_Month_ShortNovember: String { return self._s[959]! } + public var Conversation_StatusLeftGroup: String { return self._s[960]! } + public var Theme_Colors_Messages: String { return self._s[961]! } + public var AuthSessions_EmptyText: String { return self._s[962]! } + public func PUSH_MESSAGE_CONTACT(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[963]!, self._r[963]!, [_1]) + } + public var UserInfo_StartSecretChat: String { return self._s[964]! } + public var ChatListFolderSettings_EditFoldersInfo: String { return self._s[965]! } + public var Channel_Edit_PrivatePublicLinkAlert: String { return self._s[966]! } + public var Conversation_ReportSpamGroupConfirmation: String { return self._s[967]! } + public var Conversation_PrivateMessageLinkCopied: String { return self._s[969]! } + public var PeerInfo_PaneFiles: String { return self._s[970]! } + public var PrivacySettings_AutoArchive: String { return self._s[971]! } + public var Camera_VideoMode: String { return self._s[972]! } + public var NotificationsSound_Alert: String { return self._s[973]! } + public var Privacy_Forwards_NeverAllow_Title: String { return self._s[974]! } + public var Appearance_AutoNightTheme: String { return self._s[975]! } + public var Passport_Language_he: String { return self._s[976]! } + public var Wallet_Configuration_BlockchainNameChangedText: String { return self._s[977]! } + public var Passport_InvalidPasswordError: String { return self._s[978]! } + public var Conversation_PinMessageAlert_OnlyPin: String { return self._s[979]! } + public var UserInfo_InviteBotToGroup: String { return self._s[980]! } + public var Conversation_SilentBroadcastTooltipOff: String { return self._s[981]! } + public var Common_TakePhoto: String { return self._s[982]! } + public var Passport_Email_UseTelegramEmailHelp: String { return self._s[983]! } + public var Wallet_Configuration_ApplyErrorTextURLInvalid: String { return self._s[984]! } + public var ChatList_Context_JoinChannel: String { return self._s[985]! } + public var MediaPlayer_UnknownArtist: String { return self._s[986]! } + public var KeyCommand_JumpToPreviousUnreadChat: String { return self._s[989]! } + public var Channel_OwnershipTransfer_Title: String { return self._s[990]! } + public var EditTheme_UploadEditedTheme: String { return self._s[991]! } + public var Settings_SetProfilePhotoOrVideo: String { return self._s[993]! } + public var Passport_FieldOneOf_Delimeter: String { return self._s[994]! } + public var MessagePoll_ViewResults: String { return self._s[995]! } + public var Group_Setup_TypePrivateHelp: String { return self._s[996]! } + public var Passport_Address_OneOfTypeUtilityBill: String { return self._s[997]! } + public var ChatList_Search_ShowLess: String { return self._s[998]! } + public var UserInfo_ShareBot: String { return self._s[999]! } + public var Privacy_Calls_P2P: String { return self._s[1001]! } + public var WebBrowser_InAppSafari: String { return self._s[1002]! } + public var SharedMedia_EmptyFilesText: String { return self._s[1003]! } + public var Channel_AdminLog_MessagePreviousMessage: String { return self._s[1005]! } + public var GroupInfo_SetSound: String { return self._s[1006]! } + public var Permissions_PeopleNearbyAllowInSettings_v0: String { return self._s[1007]! } + public var Channel_AdminLog_MessagePreviousDescription: String { return self._s[1008]! } + public var Channel_AdminLogFilter_EventsAll: String { return self._s[1009]! } + public var CallSettings_UseLessData: String { return self._s[1010]! } + public var InfoPlist_NSCameraUsageDescription: String { return self._s[1011]! } + public var NotificationsSound_Chord: String { return self._s[1012]! } + public var PhotoEditor_CurvesTool: String { return self._s[1013]! } + public var Appearance_ThemePreview_Chat_2_Text: String { return self._s[1014]! } + public var Resolve_ErrorNotFound: String { return self._s[1015]! } + public var Activity_PlayingGame: String { return self._s[1016]! } + public var Wallet_Send_UninitializedText: String { return self._s[1019]! } + public var StickerPacksSettings_AnimatedStickersInfo: String { return self._s[1020]! } + public func PUSH_CHANNEL_MESSAGE_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1021]!, self._r[1021]!, [_1]) + } + public var Conversation_ShareBotContactConfirmationTitle: String { return self._s[1022]! } + public var Notification_CallIncoming: String { return self._s[1023]! } + public var Stats_EnabledNotifications: String { return self._s[1024]! } + public var Notifications_PermissionsOpenSettings: String { return self._s[1025]! } + public var Checkout_ErrorProviderAccountTimeout: String { return self._s[1026]! } + public func Activity_RemindAboutChannel(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1027]!, self._r[1027]!, [_0]) + } + public var VoiceOver_Chat_ReplyToYourMessage: String { return self._s[1028]! } + public var Channel_DiscussionGroup_MakeHistoryPublic: String { return self._s[1029]! } + public var StickerPacksSettings_Title: String { return self._s[1030]! } + public func Channel_AdminLog_MessageGroupPreHistoryVisible(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1031]!, self._r[1031]!, [_0]) + } + public var Watch_NoConnection: String { return self._s[1032]! } + public var EncryptionKey_Title: String { return self._s[1033]! } + public var Widget_AuthRequired: String { return self._s[1034]! } + public func PUSH_MESSAGE_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1035]!, self._r[1035]!, [_1]) + } + public var Notifications_ExceptionsTitle: String { return self._s[1036]! } + public var EditTheme_Expand_TopInfo: String { return self._s[1037]! } + public func Contacts_AddPhoneNumber(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1038]!, self._r[1038]!, [_0]) + } + public var Channel_AdminLogFilter_EventsRestrictions: String { return self._s[1040]! } + public var Notifications_GroupNotificationsSound: String { return self._s[1041]! } + public var Passport_Email_EnterOtherEmail: String { return self._s[1042]! } + public var Conversation_AddToContacts: String { return self._s[1045]! } + public var AutoDownloadSettings_DataUsageMedium: String { return self._s[1046]! } + public var AuthSessions_LogOutApplications: String { return self._s[1048]! } + public var ChatList_Context_Unpin: String { return self._s[1049]! } + public var PeopleNearby_DiscoverDescription: String { return self._s[1050]! } + public var Notification_MessageLifetime1d: String { return self._s[1051]! } + public var PrivacyLastSeenSettings_NeverShareWith_Title: String { return self._s[1052]! } + public var ChatListFolder_CategoryChannels: String { return self._s[1053]! } + public var VoiceOver_Chat_SeenByRecipient: String { return self._s[1054]! } + public var Notifications_PermissionsAllow: String { return self._s[1055]! } + public var Undo_ScheduledMessagesCleared: String { return self._s[1056]! } + public var AutoDownloadSettings_PrivateChats: String { return self._s[1058]! } + public var ApplyLanguage_ChangeLanguageAction: String { return self._s[1059]! } + public func PrivacySettings_LastSeenNobodyPlus(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1060]!, self._r[1060]!, [_0]) + } + public var Wallet_WordImport_Title: String { return self._s[1061]! } + public var Notifications_MessageNotificationsHelp: String { return self._s[1064]! } + public var WallpaperSearch_ColorPink: String { return self._s[1065]! } + public var ContactInfo_PhoneNumberHidden: String { return self._s[1066]! } + public var Passport_Identity_IssueDate: String { return self._s[1068]! } + public func PUSH_CHAT_MESSAGE_GIF(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1069]!, self._r[1069]!, [_1, _2]) + } + public var PrivacySettings_DeleteAccountIfAwayFor: String { return self._s[1070]! } + public var Channel_Info_Description: String { return self._s[1071]! } + public var Common_Back: String { return self._s[1072]! } + public var Weekday_ShortTuesday: String { return self._s[1073]! } + public var ChatListFolder_AddChats: String { return self._s[1075]! } + public var Common_Close: String { return self._s[1077]! } + public var Map_OpenIn: String { return self._s[1078]! } + public var Group_Setup_HistoryTitle: String { return self._s[1079]! } + public func Wallet_Info_TransactionDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1080]!, self._r[1080]!, [_1, _2, _3]) + } + public var SettingsSearch_Synonyms_Data_AutoDownloadUsingWifi: String { return self._s[1081]! } + public var Notification_MessageLifetime1h: String { return self._s[1082]! } + public func CancelResetAccount_Success(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1083]!, self._r[1083]!, [_0]) + } + public var Watch_Contacts_NoResults: String { return self._s[1085]! } + public var TwoStepAuth_SetupResendEmailCode: String { return self._s[1086]! } + public var Checkout_Phone: String { return self._s[1087]! } + public var OwnershipTransfer_ComeBackLater: String { return self._s[1088]! } + public func DialogList_MultipleTypingSuffix(_ _0: Int) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1089]!, self._r[1089]!, ["\(_0)"]) + } + public var ChatAdmins_Title: String { return self._s[1090]! } + public var Appearance_ThemePreview_Chat_7_Text: String { return self._s[1091]! } + public func PUSH_CHANNEL_MESSAGE_POLL(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1092]!, self._r[1092]!, [_1]) + } + public var Common_Done: String { return self._s[1093]! } + public var Wallet_Send_AddressHeader: String { return self._s[1096]! } + public func PUSH_PINNED_VIDEO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1098]!, self._r[1098]!, [_1]) + } + public var Appearance_ThemeCarouselNight: String { return self._s[1099]! } + public var Preview_OpenInInstagram: String { return self._s[1101]! } + public var Wallpaper_SetColor: String { return self._s[1105]! } + public var VoiceOver_Media_PlaybackRate: String { return self._s[1106]! } + public var ChatSettings_Groups: String { return self._s[1107]! } + public func VoiceOver_Chat_VoiceMessageFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1108]!, self._r[1108]!, [_0]) + } + public var Contacts_SortedByName: String { return self._s[1109]! } + public var SettingsSearch_Synonyms_Notifications_ContactJoined: String { return self._s[1110]! } + public var Wallet_Send_Title: String { return self._s[1111]! } + public var Channel_Management_LabelCreator: String { return self._s[1112]! } + public var Contacts_PermissionsSuppressWarningTitle: String { return self._s[1113]! } + public func PrivacySettings_LastSeenContactsMinusPlus(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1114]!, self._r[1114]!, [_0, _1]) + } + public var Group_PublicLink_Title: String { return self._s[1115]! } + public var Channel_OwnershipTransfer_ErrorAdminsTooMuch: String { return self._s[1116]! } + public var VoiceOver_Chat_Photo: String { return self._s[1117]! } + public var TwoFactorSetup_EmailVerification_Placeholder: String { return self._s[1118]! } + public var IntentsSettings_SuggestBy: String { return self._s[1119]! } + public var Privacy_Calls_AlwaysAllow_Placeholder: String { return self._s[1120]! } + public var Appearance_ThemePreview_ChatList_1_Name: String { return self._s[1121]! } + public var PhoneNumberHelp_ChangeNumber: String { return self._s[1122]! } + public var LogoutOptions_SetPasscodeText: String { return self._s[1123]! } + public var Map_OpenInMaps: String { return self._s[1124]! } + public var ContactInfo_PhoneLabelWorkFax: String { return self._s[1125]! } + public var BlockedUsers_Unblock: String { return self._s[1126]! } + public func Settings_ApplyProxyAlert(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1127]!, self._r[1127]!, [_1, _2]) + } + public func Channel_AdminLog_MessageRestrictedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1128]!, self._r[1128]!, [_1, _2]) + } + public var Conversation_Block: String { return self._s[1130]! } + public var Passport_Scans_UploadNew: String { return self._s[1131]! } + public var Share_Title: String { return self._s[1132]! } + public var Wallet_Send_SendAnyway: String { return self._s[1133]! } + public func Wallet_Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1134]!, self._r[1134]!, [_1, _2, _3]) + } + public var Conversation_ApplyLocalization: String { return self._s[1135]! } + public var SharedMedia_EmptyLinksText: String { return self._s[1136]! } + public var Settings_NotificationsAndSounds: String { return self._s[1137]! } + public var Stats_ViewsByHoursTitle: String { return self._s[1138]! } + public var PhotoEditor_QualityMedium: String { return self._s[1139]! } + public var Conversation_ContextMenuCancelSending: String { return self._s[1140]! } + public func PUSH_CHANNEL_MESSAGE_GAME(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1141]!, self._r[1141]!, [_1, _2]) + } + public var Conversation_RestrictedInline: String { return self._s[1142]! } + public var Passport_Language_tr: String { return self._s[1143]! } + public var Call_Mute: String { return self._s[1144]! } + public func Conversation_NoticeInvitedByInGroup(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1145]!, self._r[1145]!, [_0]) + } + public var Passport_Language_bn: String { return self._s[1146]! } + public var AccessDenied_LocationTracking: String { return self._s[1148]! } + public var Month_ShortOctober: String { return self._s[1149]! } + public var AutoDownloadSettings_WiFi: String { return self._s[1150]! } + public var ProfilePhoto_SetMainPhoto: String { return self._s[1152]! } + public var ChangePhoneNumberNumber_NewNumber: String { return self._s[1153]! } + public func Time_MonthOfYear_m3(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1154]!, self._r[1154]!, [_0]) + } + public var Watch_ChannelInfo_Title: String { return self._s[1155]! } + public var State_Updating: String { return self._s[1156]! } + public var Conversation_UnblockUser: String { return self._s[1157]! } + public var Notifications_ChannelNotificationsSound: String { return self._s[1158]! } + public var Map_GetDirections: String { return self._s[1159]! } + public var Watch_Compose_AddContact: String { return self._s[1161]! } + public var Conversation_Dice_u26BD: String { return self._s[1162]! } + public var AccessDenied_PhotosRestricted: String { return self._s[1163]! } + public func Channel_AdminLog_MessageRank(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1164]!, self._r[1164]!, [_1]) + } + public var Wallet_UnknownError: String { return self._s[1166]! } + public var Map_LoadError: String { return self._s[1167]! } + public var SettingsSearch_Synonyms_Privacy_Calls: String { return self._s[1168]! } + public var PhotoEditor_CropAuto: String { return self._s[1169]! } + public var Wallet_Month_ShortApril: String { return self._s[1172]! } + public func Target_ShareGameConfirmationPrivate(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1173]!, self._r[1173]!, [_0]) + } + public func PUSH_PINNED_GAME(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1175]!, self._r[1175]!, [_1]) + } + public var Username_TooManyPublicUsernamesError: String { return self._s[1176]! } + public var Settings_PhoneNumber: String { return self._s[1177]! } + public func Channel_AdminLog_MessageTransferedName(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1178]!, self._r[1178]!, [_1]) + } + public var Month_GenJune: String { return self._s[1180]! } + public var Notifications_ExceptionsGroupPlaceholder: String { return self._s[1181]! } + public var ChatListFolder_CategoryRead: String { return self._s[1182]! } + public var LoginPassword_ResetAccount: String { return self._s[1183]! } + public func DialogList_SingleUploadingFileSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1184]!, self._r[1184]!, [_0]) + } + public var Call_CameraConfirmationConfirm: String { return self._s[1185]! } + public var Notification_RenamedChannel: String { return self._s[1186]! } + public func Channel_AdminLog_MessageUnpinned(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1187]!, self._r[1187]!, [_0]) + } + public var Channel_AdminLogFilter_EventsAdmins: String { return self._s[1188]! } + public var IntentsSettings_Title: String { return self._s[1190]! } + public var Settings_AppleWatch: String { return self._s[1191]! } + public var DialogList_NoMessagesText: String { return self._s[1192]! } + public var GroupPermission_NoChangeInfo: String { return self._s[1193]! } + public var Channel_ErrorAccessDenied: String { return self._s[1195]! } + public var ScheduledMessages_EmptyPlaceholder: String { return self._s[1196]! } + public func Message_StickerText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1197]!, self._r[1197]!, [_0]) + } + public var AuthSessions_TerminateOtherSessionsHelp: String { return self._s[1198]! } + public var StickerPacksSettings_AnimatedStickers: String { return self._s[1199]! } + public var Month_ShortJanuary: String { return self._s[1200]! } + public var Conversation_UnreadMessages: String { return self._s[1201]! } + public var Conversation_PrivateChannelTooltip: String { return self._s[1203]! } + public var PrivacySettings_DeleteAccountTitle: String { return self._s[1205]! } + public var Channel_Members_AddBannedErrorAdmin: String { return self._s[1206]! } + public func Conversation_ShareMyPhoneNumberConfirmation(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1209]!, self._r[1209]!, [_1, _2]) + } + public var Widget_ApplicationLocked: String { return self._s[1210]! } + public func TextFormat_AddLinkText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1211]!, self._r[1211]!, [_0]) + } + public var Common_TakePhotoOrVideo: String { return self._s[1212]! } + public var Passport_Language_ru: String { return self._s[1213]! } + public var MediaPicker_VideoMuteDescription: String { return self._s[1214]! } + public var EditTheme_ErrorLinkTaken: String { return self._s[1215]! } + public func Group_EditAdmin_RankInfo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1217]!, self._r[1217]!, [_0]) + } + public var Channel_Members_AddAdminErrorBlacklisted: String { return self._s[1218]! } + public var Conversation_Owner: String { return self._s[1220]! } + public var Settings_FAQ_Intro: String { return self._s[1221]! } + public var PhotoEditor_QualityLow: String { return self._s[1223]! } + public var Call_End: String { return self._s[1224]! } + public var StickerPacksSettings_FeaturedPacks: String { return self._s[1226]! } + public var Privacy_ContactsSyncHelp: String { return self._s[1227]! } + public var OldChannels_NoticeUpgradeText: String { return self._s[1231]! } + public var Conversation_Call: String { return self._s[1232]! } + public var Watch_MessageView_Title: String { return self._s[1233]! } + public func Notification_RenamedChat(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1234]!, self._r[1234]!, [_0]) + } + public var Passport_PasswordCompleteSetup: String { return self._s[1235]! } + public func Notification_ChangedGroupVideo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1236]!, self._r[1236]!, [_0]) } - public var Wallet_WordImport_Continue: String { return self._s[4138]! } public func TwoFactorSetup_EmailVerification_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4139]!, self._r[4139]!, [_0]) + return formatWithArgumentRanges(self._s[1238]!, self._r[1238]!, [_0]) } - public var Notifications_ChannelNotificationsHelp: String { return self._s[4140]! } - public var Privacy_Calls_P2PContacts: String { return self._s[4141]! } - public var NotificationsSound_None: String { return self._s[4142]! } - public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[4143]! } - public var Channel_DiscussionGroup_UnlinkGroup: String { return self._s[4145]! } - public var AccessDenied_VoiceMicrophone: String { return self._s[4146]! } - public func ApplyLanguage_ChangeLanguageAlreadyActive(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4147]!, self._r[4147]!, [_1]) + public var Map_Location: String { return self._s[1239]! } + public var Watch_MessageView_ViewOnPhone: String { return self._s[1240]! } + public var Login_CountryCode: String { return self._s[1241]! } + public var Wallet_Settings_ConfigurationInfo: String { return self._s[1242]! } + public var Channel_DiscussionGroup_PrivateGroup: String { return self._s[1243]! } + public var ChatState_ConnectingToProxy: String { return self._s[1244]! } + public var Login_CallRequestState3: String { return self._s[1245]! } + public var NetworkUsageSettings_MediaAudioDataSection: String { return self._s[1247]! } + public var SocksProxySetup_ProxyStatusConnecting: String { return self._s[1248]! } + public var PrivacyLastSeenSettings_NeverShareWith_Placeholder: String { return self._s[1251]! } + public var Call_StatusEnded: String { return self._s[1252]! } + public var MusicPlayer_VoiceNote: String { return self._s[1255]! } + public func PUSH_CHANNEL_MESSAGE_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1256]!, self._r[1256]!, [_1, _2]) } - public var Cache_Indexing: String { return self._s[4148]! } - public var DialogList_RecentTitlePeople: String { return self._s[4150]! } - public var DialogList_EncryptionRejected: String { return self._s[4151]! } - public var GroupInfo_Administrators: String { return self._s[4152]! } - public var Passport_ScanPassportHelp: String { return self._s[4153]! } - public var Application_Name: String { return self._s[4154]! } - public var Channel_AdminLogFilter_ChannelEventsInfo: String { return self._s[4155]! } - public var Conversation_Timer_Title: String { return self._s[4156]! } - public var ChatList_PeerTypeGroup: String { return self._s[4157]! } - public var PeopleNearby_MakeVisible: String { return self._s[4159]! } - public var Appearance_ThemeCarouselDay: String { return self._s[4160]! } - public var Stats_GrowthTitle: String { return self._s[4161]! } - public var Passport_Identity_TranslationHelp: String { return self._s[4162]! } - public func VoiceOver_Chat_VideoMessageFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4163]!, self._r[4163]!, [_0]) + public var VoiceOver_MessageContextShare: String { return self._s[1257]! } + public var ProfilePhoto_SearchWeb: String { return self._s[1258]! } + public var EditProfile_Title: String { return self._s[1259]! } + public func Notification_PinnedQuizMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1260]!, self._r[1260]!, [_0]) } - public func Notification_JoinedGroupByLink(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4164]!, self._r[4164]!, [_0]) + public var ChangePhoneNumberCode_CodePlaceholder: String { return self._s[1261]! } + public var NetworkUsageSettings_ResetStats: String { return self._s[1263]! } + public var Wallet_Qr_ScanCode: String { return self._s[1264]! } + public var NetworkUsageSettings_GeneralDataSection: String { return self._s[1265]! } + public var StickerPackActionInfo_AddedTitle: String { return self._s[1266]! } + public var Channel_BanUser_PermissionSendStickersAndGifs: String { return self._s[1267]! } + public func Call_ParticipantVideoVersionOutdatedError(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1268]!, self._r[1268]!, [_0]) } - public func DialogList_EncryptedChatStartedOutgoing(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4165]!, self._r[4165]!, [_0]) + public var AuthSessions_AddDeviceIntro_Text1: String { return self._s[1270]! } + public var Passport_Identity_LatinNameHelp: String { return self._s[1272]! } + public var AuthSessions_AddDeviceIntro_Text2: String { return self._s[1273]! } + public var Stats_GroupMembersTitle: String { return self._s[1274]! } + public var AuthSessions_AddDeviceIntro_Text3: String { return self._s[1275]! } + public var Contacts_PermissionsSuppressWarningText: String { return self._s[1276]! } + public var Wallet_Info_Address: String { return self._s[1277]! } + public var Settings_SetUsername: String { return self._s[1278]! } + public var GroupInfo_ActionRestrict: String { return self._s[1279]! } + public func Wallet_Configuration_ApplyErrorTextURLUnreachable(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1280]!, self._r[1280]!, [_0]) } - public var Channel_EditAdmin_PermissionDeleteMessages: String { return self._s[4166]! } - public var Privacy_ChatsTitle: String { return self._s[4167]! } - public var DialogList_ClearHistoryConfirmation: String { return self._s[4168]! } - public var SettingsSearch_Synonyms_Data_Storage_ClearCache: String { return self._s[4169]! } - public var Watch_Suggestion_HoldOn: String { return self._s[4170]! } - public var Group_EditAdmin_TransferOwnership: String { return self._s[4171]! } - public var WebBrowser_Title: String { return self._s[4172]! } - public var Group_LinkedChannel: String { return self._s[4173]! } - public var VoiceOver_Chat_SeenByRecipient: String { return self._s[4174]! } - public var SocksProxySetup_RequiredCredentials: String { return self._s[4175]! } - public var Passport_Address_TypeRentalAgreementUploadScan: String { return self._s[4176]! } - public var Appearance_TextSize_UseSystem: String { return self._s[4177]! } - public var TwoStepAuth_EmailSkipAlert: String { return self._s[4178]! } - public var ScheduledMessages_RemindersTitle: String { return self._s[4180]! } - public var Channel_Setup_TypePublic: String { return self._s[4182]! } - public func Channel_AdminLog_MessageToggleInvitesOn(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4183]!, self._r[4183]!, [_0]) + public var SettingsSearch_Synonyms_SavedMessages: String { return self._s[1281]! } + public func Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1282]!, self._r[1282]!, [_1, _2, _3]) } - public var Channel_TypeSetup_Title: String { return self._s[4185]! } - public var MessagePoll_ViewResults: String { return self._s[4186]! } - public var Map_OpenInMaps: String { return self._s[4188]! } - public func PUSH_PINNED_NOTEXT(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4189]!, self._r[4189]!, [_1]) + public var Notifications_DisplayNamesOnLockScreenInfoWithLink: String { return self._s[1283]! } + public var Notification_Exceptions_AlwaysOff: String { return self._s[1284]! } + public var Conversation_ContextMenuDelete: String { return self._s[1285]! } + public var Privacy_Calls_WhoCanCallMe: String { return self._s[1286]! } + public var ChatList_PsaAlert_covid: String { return self._s[1289]! } + public var DialogList_Pin: String { return self._s[1290]! } + public var PrivacySettings_SecurityTitle: String { return self._s[1291]! } + public var GroupPermission_NotAvailableInPublicGroups: String { return self._s[1292]! } + public var PeopleNearby_Groups: String { return self._s[1293]! } + public var Message_File: String { return self._s[1294]! } + public var Calls_NoCallsPlaceholder: String { return self._s[1295]! } + public var ChatList_GenericPsaLabel: String { return self._s[1297]! } + public var UserInfo_LastNamePlaceholder: String { return self._s[1298]! } + public var IntentsSettings_Reset: String { return self._s[1300]! } + public var Call_ConnectionErrorTitle: String { return self._s[1301]! } + public var PhotoEditor_SaturationTool: String { return self._s[1302]! } + public var ChatSettings_AutomaticVideoMessageDownload: String { return self._s[1303]! } + public var SettingsSearch_Synonyms_Stickers_ArchivedPacks: String { return self._s[1304]! } + public var Conversation_SearchNoResults: String { return self._s[1305]! } + public var Map_OpenInWaze: String { return self._s[1306]! } + public var WallpaperPreview_Title: String { return self._s[1307]! } + public func Passport_AcceptHelp(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1309]!, self._r[1309]!, [_1, _2]) } - public var NotificationsSound_Tremolo: String { return self._s[4191]! } - public func Date_ChatDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4192]!, self._r[4192]!, [_1, _2, _3]) + public var AuthSessions_AddDeviceIntro_Title: String { return self._s[1310]! } + public var VoiceOver_Chat_RecordModeVideoMessageInfo: String { return self._s[1311]! } + public var Wallet_Month_ShortMay: String { return self._s[1312]! } + public var Wallet_Send_OwnAddressAlertTitle: String { return self._s[1313]! } + public var Passport_Identity_OneOfTypeInternalPassport: String { return self._s[1314]! } + public var Notifications_PermissionsUnreachableTitle: String { return self._s[1316]! } + public var Stats_Total: String { return self._s[1319]! } + public var Stats_GroupMessages: String { return self._s[1320]! } + public var TwoFactorSetup_Email_SkipAction: String { return self._s[1321]! } + public var CheckoutInfo_ErrorPhoneInvalid: String { return self._s[1322]! } + public var Wallet_TransactionInfo_OtherFeeInfoUrl: String { return self._s[1323]! } + public var Passport_Identity_Translation: String { return self._s[1324]! } + public var Notifications_TextTone: String { return self._s[1326]! } + public var Settings_RemoveConfirmation: String { return self._s[1328]! } + public var ScheduledMessages_Delete: String { return self._s[1329]! } + public var Channel_AdminLog_BanEmbedLinks: String { return self._s[1330]! } + public var Passport_PasswordNext: String { return self._s[1331]! } + public func PUSH_ENCRYPTED_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1332]!, self._r[1332]!, [_1]) } - public var ConversationProfile_UnknownAddMemberError: String { return self._s[4193]! } - public var Channel_OwnershipTransfer_PasswordPlaceholder: String { return self._s[4194]! } - public var Passport_PasswordHelp: String { return self._s[4196]! } - public var Login_CodeExpiredError: String { return self._s[4197]! } - public var Channel_EditAdmin_PermissionChangeInfo: String { return self._s[4198]! } - public var Conversation_TitleUnmute: String { return self._s[4199]! } - public var Passport_Identity_ScansHelp: String { return self._s[4200]! } - public var Passport_Language_lo: String { return self._s[4201]! } - public var Camera_FlashAuto: String { return self._s[4202]! } - public var Conversation_OpenBotLinkOpen: String { return self._s[4203]! } - public var Common_Cancel: String { return self._s[4204]! } - public var DialogList_SavedMessagesTooltip: String { return self._s[4205]! } - public var TwoStepAuth_SetupPasswordTitle: String { return self._s[4206]! } - public var Appearance_TintAllColors: String { return self._s[4207]! } - public func PUSH_MESSAGE_FWD(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4208]!, self._r[4208]!, [_1]) + public var Passport_Address_EditBankStatement: String { return self._s[1333]! } + public var PhotoEditor_ShadowsTool: String { return self._s[1334]! } + public var Notification_VideoCallMissed: String { return self._s[1335]! } + public var Wallet_WordCheck_IncorrectText: String { return self._s[1336]! } + public var AccessDenied_CameraDisabled: String { return self._s[1337]! } + public var AuthSessions_AddDevice_ScanInfo: String { return self._s[1338]! } + public var Notifications_ExceptionsMuted: String { return self._s[1339]! } + public var Conversation_ScheduleMessage_SendWhenOnline: String { return self._s[1340]! } + public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[1341]! } + public var Channel_BlackList_Title: String { return self._s[1342]! } + public var PasscodeSettings_4DigitCode: String { return self._s[1343]! } + public var NotificationsSound_Bamboo: String { return self._s[1344]! } + public var PrivacySettings_LastSeenContacts: String { return self._s[1345]! } + public var Passport_Address_TypeUtilityBill: String { return self._s[1346]! } + public var Passport_Address_CountryPlaceholder: String { return self._s[1347]! } + public var GroupPermission_SectionTitle: String { return self._s[1348]! } + public func Notification_InvitedMultiple(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1349]!, self._r[1349]!, [_0, _1]) } - public var Conversation_ReportSpamConfirmation: String { return self._s[4209]! } - public var ChatSettings_Title: String { return self._s[4211]! } - public var Passport_PasswordReset: String { return self._s[4212]! } - public var SocksProxySetup_TypeNone: String { return self._s[4213]! } - public var EditTheme_Title: String { return self._s[4216]! } - public var PhoneNumberHelp_Help: String { return self._s[4217]! } - public var Checkout_EnterPassword: String { return self._s[4218]! } - public var Activity_UploadingDocument: String { return self._s[4220]! } - public var Share_AuthTitle: String { return self._s[4221]! } - public var State_Connecting: String { return self._s[4222]! } - public var Profile_MessageLifetime1w: String { return self._s[4223]! } - public var Conversation_ContextMenuReport: String { return self._s[4224]! } - public var CheckoutInfo_ReceiverInfoPhone: String { return self._s[4225]! } - public var AutoNightTheme_ScheduledTo: String { return self._s[4226]! } - public func VoiceOver_Chat_AnonymousPollFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4227]!, self._r[4227]!, [_0]) + public var CheckoutInfo_ShippingInfoStatePlaceholder: String { return self._s[1350]! } + public var Channel_LeaveChannel: String { return self._s[1351]! } + public var Watch_Notification_Joined: String { return self._s[1352]! } + public var PeerInfo_ButtonMore: String { return self._s[1353]! } + public var Passport_FieldEmailHelp: String { return self._s[1354]! } + public var ChatList_Context_Pin: String { return self._s[1355]! } + public func Time_MonthOfYear_m9(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1356]!, self._r[1356]!, [_0]) } - public var AuthSessions_Terminate: String { return self._s[4228]! } - public var Wallet_WordImport_CanNotRemember: String { return self._s[4229]! } - public var PeerInfo_PaneAudio: String { return self._s[4230]! } - public func Message_ForwardedPsa_covid(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4231]!, self._r[4231]!, [_0]) + public var Group_Location_CreateInThisPlace: String { return self._s[1357]! } + public var PhotoEditor_QualityVeryHigh: String { return self._s[1358]! } + public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[1359]! } + public var Wallet_TransactionInfo_StorageFeeInfoUrl: String { return self._s[1360]! } + public func PUSH_CHAT_MESSAGE_FWD(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1361]!, self._r[1361]!, [_1, _2]) } - public var Checkout_NewCard_CardholderNamePlaceholder: String { return self._s[4233]! } - public var KeyCommand_JumpToPreviousUnreadChat: String { return self._s[4234]! } - public var PhotoEditor_Set: String { return self._s[4235]! } - public var EmptyGroupInfo_Title: String { return self._s[4236]! } - public var Login_PadPhoneHelp: String { return self._s[4238]! } - public var AutoDownloadSettings_TypeGroupChats: String { return self._s[4240]! } - public var PrivacyPolicy_DeclineLastWarning: String { return self._s[4242]! } - public var NotificationsSound_Complete: String { return self._s[4243]! } - public var SettingsSearch_Synonyms_Privacy_Data_Title: String { return self._s[4244]! } - public var Group_Info_AdminLog: String { return self._s[4245]! } - public var GroupPermission_NotAvailableInPublicGroups: String { return self._s[4246]! } - public func Wallet_Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4247]!, self._r[4247]!, [_1, _2, _3]) + public var Tour_Title5: String { return self._s[1362]! } + public var Wallet_Navigation_Back: String { return self._s[1363]! } + public var Passport_Language_en: String { return self._s[1364]! } + public var Checkout_Name: String { return self._s[1365]! } + public func NetworkUsageSettings_WifiUsageSince(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1366]!, self._r[1366]!, [_0]) } - public var Channel_AdminLog_InfoPanelAlertText: String { return self._s[4248]! } - public var Group_Location_CreateInThisPlace: String { return self._s[4250]! } - public var Conversation_Admin: String { return self._s[4251]! } - public var Conversation_GifTooltip: String { return self._s[4252]! } - public var Passport_NotLoggedInMessage: String { return self._s[4253]! } - public func AutoDownloadSettings_OnFor(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4255]!, self._r[4255]!, [_0]) + public var Wallet_Send_Confirmation: String { return self._s[1367]! } + public var PhotoEditor_EnhanceTool: String { return self._s[1368]! } + public func PUSH_CHAT_DELETE_YOU(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1369]!, self._r[1369]!, [_1, _2]) } - public var Profile_MessageLifetimeForever: String { return self._s[4256]! } - public var SharedMedia_EmptyTitle: String { return self._s[4258]! } - public var Channel_Edit_PrivatePublicLinkAlert: String { return self._s[4260]! } - public var Username_Help: String { return self._s[4261]! } - public var DialogList_LanguageTooltip: String { return self._s[4263]! } - public var Map_LoadError: String { return self._s[4264]! } - public var Login_PhoneNumberAlreadyAuthorized: String { return self._s[4265]! } - public var Channel_AdminLog_AddMembers: String { return self._s[4266]! } - public var ArchivedChats_IntroTitle2: String { return self._s[4267]! } - public var Notification_Exceptions_NewException: String { return self._s[4268]! } - public var TwoStepAuth_EmailTitle: String { return self._s[4269]! } - public var WatchRemote_AlertText: String { return self._s[4270]! } - public func Wallet_Send_ConfirmationText(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4271]!, self._r[4271]!, [_1, _2, _3]) + public func Login_TermsOfService_ProceedBot(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1370]!, self._r[1370]!, [_0]) } - public var ChatSettings_ConnectionType_Title: String { return self._s[4275]! } - public func PUSH_PINNED_QUIZ(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4276]!, self._r[4276]!, [_1, _2]) + public var Group_ErrorSendRestrictedMedia: String { return self._s[1371]! } + public func UserInfo_NotificationsDefaultSound(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1372]!, self._r[1372]!, [_0]) } - public func Settings_CheckPhoneNumberTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4277]!, self._r[4277]!, [_0]) + public var Login_UnknownError: String { return self._s[1373]! } + public var Passport_Identity_TypeDriversLicense: String { return self._s[1376]! } + public var ChatList_AutoarchiveSuggestion_Title: String { return self._s[1377]! } + public var Watch_PhotoView_Title: String { return self._s[1378]! } + public var Appearance_ThemePreview_ChatList_3_Text: String { return self._s[1379]! } + public var Checkout_TotalAmount: String { return self._s[1380]! } + public var ChatList_RemoveFolderAction: String { return self._s[1381]! } + public var GroupInfo_SetGroupPhoto: String { return self._s[1382]! } + public var Watch_AppName: String { return self._s[1383]! } + public func PUSH_PINNED_GAME_SCORE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1384]!, self._r[1384]!, [_1]) } - public var SettingsSearch_Synonyms_Calls_CallTab: String { return self._s[4278]! } - public var WebBrowser_DefaultBrowser: String { return self._s[4279]! } - public var Passport_Address_CountryPlaceholder: String { return self._s[4280]! } - public func DialogList_AwaitingEncryption(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4281]!, self._r[4281]!, [_0]) + public var Channel_Username_CheckingUsername: String { return self._s[1385]! } + public var ContactList_Context_Call: String { return self._s[1386]! } + public var ChatList_ReorderTabs: String { return self._s[1387]! } + public var Watch_ChatList_Compose: String { return self._s[1388]! } + public func Conversation_LiveLocationYouAnd(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1389]!, self._r[1389]!, [_0]) } - public func Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4282]!, self._r[4282]!, [_1, _2, _3]) + public var Channel_AdminLog_EmptyFilterTitle: String { return self._s[1390]! } + public var ArchivedChats_IntroTitle1: String { return self._s[1391]! } + public func PUSH_ENCRYPTION_ACCEPT(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1392]!, self._r[1392]!, [_1]) } - public var Group_AdminLog_EmptyText: String { return self._s[4283]! } - public var SettingsSearch_Synonyms_Appearance_Title: String { return self._s[4284]! } - public var Conversation_PrivateChannelTooltip: String { return self._s[4286]! } - public var Wallet_Created_ExportErrorText: String { return self._s[4287]! } - public var ChatList_UndoArchiveText1: String { return self._s[4288]! } - public var ChatListFolder_IncludedSectionHeader: String { return self._s[4289]! } - public var AccessDenied_VideoMicrophone: String { return self._s[4290]! } - public var Conversation_ContextMenuStickerPackAdd: String { return self._s[4291]! } - public var Stats_GroupTopInviter_History: String { return self._s[4292]! } - public var Cache_ClearNone: String { return self._s[4293]! } - public var SocksProxySetup_FailedToConnect: String { return self._s[4294]! } - public var Call_ShareStats: String { return self._s[4295]! } - public func Channel_AdminLog_MessageEdited(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4296]!, self._r[4296]!, [_0]) + public var Call_StatusRequesting: String { return self._s[1394]! } + public var Checkout_TotalPaidAmount: String { return self._s[1395]! } + public var Weekday_Friday: String { return self._s[1397]! } + public var CreateGroup_ChannelsTooMuch: String { return self._s[1398]! } + public var Watch_ChatList_NoConversationsText: String { return self._s[1399]! } + public func Channel_AdminLog_MessageChangedGroupStickerPack(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1400]!, self._r[1400]!, [_0]) } - public var Permissions_NotificationsTitle_v0: String { return self._s[4297]! } - public var Passport_Identity_Country: String { return self._s[4298]! } - public func ChatSettings_AutoDownloadSettings_TypeFile(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4299]!, self._r[4299]!, [_0]) + public var SecretVideo_Title: String { return self._s[1401]! } + public func Notification_PinnedStickerMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1404]!, self._r[1404]!, [_0]) } + public var Undo_Undo: String { return self._s[1405]! } + public var Watch_Microphone_Access: String { return self._s[1406]! } + public func PUSH_CHAT_MESSAGE_PHOTO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1407]!, self._r[1407]!, [_1, _2]) + } + public func ChatList_Search_NoResultsQueryDescription(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1408]!, self._r[1408]!, [_0]) + } + public var Wallet_Configuration_SourceURL: String { return self._s[1409]! } + public var Wallet_Intro_CreateErrorTitle: String { return self._s[1410]! } + public var Checkout_NewCard_PostcodeTitle: String { return self._s[1411]! } + public var TwoFactorSetup_Intro_Action: String { return self._s[1412]! } + public var Passport_Language_ne: String { return self._s[1414]! } + public var TwoStepAuth_EmailHelp: String { return self._s[1416]! } + public var Profile_MessageLifetime2s: String { return self._s[1417]! } + public func Conversation_MessageDialogRetryAll(_ _1: Int) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1418]!, self._r[1418]!, ["\(_1)"]) + } + public func Items_NOfM(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1419]!, self._r[1419]!, [_1, _2]) + } + public var GroupPermission_NoPinMessages: String { return self._s[1420]! } + public func PUSH_CHAT_TITLE_EDITED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1421]!, self._r[1421]!, [_1, _2]) + } + public var Wallet_Month_GenJuly: String { return self._s[1422]! } public func Notification_CreatedChat(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4300]!, self._r[4300]!, [_0]) + return formatWithArgumentRanges(self._s[1423]!, self._r[1423]!, [_0]) } - public var Exceptions_AddToExceptions: String { return self._s[4301]! } - public var AccessDenied_Settings: String { return self._s[4302]! } - public var Passport_Address_TypeUtilityBillUploadScan: String { return self._s[4303]! } - public var Month_ShortMay: String { return self._s[4305]! } - public var Compose_NewGroup: String { return self._s[4307]! } - public var Group_Setup_TypePrivate: String { return self._s[4309]! } - public var Login_PadPhoneHelpTitle: String { return self._s[4311]! } - public var Appearance_ThemeDayClassic: String { return self._s[4312]! } - public var Channel_AdminLog_MessagePreviousCaption: String { return self._s[4313]! } - public var AutoDownloadSettings_OffForAll: String { return self._s[4314]! } - public var Privacy_GroupsAndChannels_WhoCanAddMe: String { return self._s[4315]! } - public var Conversation_typing: String { return self._s[4317]! } - public var Undo_ScheduledMessagesCleared: String { return self._s[4318]! } - public var Paint_Masks: String { return self._s[4319]! } - public var Contacts_DeselectAll: String { return self._s[4320]! } - public func Wallet_Updated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4321]!, self._r[4321]!, [_0]) + public var FastTwoStepSetup_HintHelp: String { return self._s[1424]! } + public var WallpaperSearch_ColorRed: String { return self._s[1425]! } + public var Watch_ConnectionDescription: String { return self._s[1426]! } + public var Notification_Exceptions_AddException: String { return self._s[1427]! } + public var LocalGroup_IrrelevantWarning: String { return self._s[1428]! } + public var VoiceOver_MessageContextDelete: String { return self._s[1429]! } + public var LogoutOptions_AlternativeOptionsSection: String { return self._s[1430]! } + public var Passport_PasswordPlaceholder: String { return self._s[1431]! } + public var TwoStepAuth_RecoveryEmailAddDescription: String { return self._s[1432]! } + public var Stats_MessageInteractionsTitle: String { return self._s[1433]! } + public var Appearance_ThemeCarouselClassic: String { return self._s[1434]! } + public var TwoFactorSetup_Email_SkipConfirmationText: String { return self._s[1436]! } + public var Channel_AdminLog_PinMessages: String { return self._s[1437]! } + public var Passport_Address_AddRentalAgreement: String { return self._s[1438]! } + public var Watch_Message_Game: String { return self._s[1439]! } + public var PrivacyLastSeenSettings_NeverShareWith: String { return self._s[1440]! } + public var PrivacyPolicy_DeclineLastWarning: String { return self._s[1441]! } + public var EditTheme_FileReadError: String { return self._s[1442]! } + public var Group_ErrorAddBlocked: String { return self._s[1443]! } + public var CallSettings_UseLessDataLongDescription: String { return self._s[1444]! } + public func PUSH_MESSAGE_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1446]!, self._r[1446]!, [_1]) } - public var CreatePoll_MultipleChoiceQuizAlert: String { return self._s[4322]! } - public var Stats_GroupMembersTitle: String { return self._s[4323]! } - public var Username_InvalidTaken: String { return self._s[4324]! } - public var Call_StatusNoAnswer: String { return self._s[4325]! } - public var TwoStepAuth_EmailAddSuccess: String { return self._s[4326]! } - public var SettingsSearch_Synonyms_Privacy_BlockedUsers: String { return self._s[4327]! } - public var Passport_Identity_Selfie: String { return self._s[4328]! } - public var Login_InfoLastNamePlaceholder: String { return self._s[4329]! } - public var Privacy_SecretChatsLinkPreviewsHelp: String { return self._s[4330]! } - public var Conversation_ClearSecretHistory: String { return self._s[4331]! } - public var PeopleNearby_Description: String { return self._s[4333]! } - public var NetworkUsageSettings_Title: String { return self._s[4334]! } - public var Your_cards_security_code_is_invalid: String { return self._s[4336]! } - public var Stats_EnabledNotifications: String { return self._s[4337]! } - public func Notification_LeftChannel(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4340]!, self._r[4340]!, [_0]) + public func UserInfo_BlockConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1447]!, self._r[1447]!, [_0]) + } + public var CheckoutInfo_ShippingInfoAddress2Placeholder: String { return self._s[1448]! } + public var TwoFactorSetup_EmailVerification_Action: String { return self._s[1449]! } + public func Username_LinkHint(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1450]!, self._r[1450]!, [_0]) + } + public var ConversationProfile_ErrorCreatingConversation: String { return self._s[1451]! } + public var Bot_GroupStatusReadsHistory: String { return self._s[1452]! } + public var PhotoEditor_CurvesRed: String { return self._s[1453]! } + public var InstantPage_TapToOpenLink: String { return self._s[1454]! } + public var FastTwoStepSetup_PasswordHelp: String { return self._s[1455]! } + public var Notification_CallMissedShort: String { return self._s[1456]! } + public func Notification_JoinedGroupByLink(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1457]!, self._r[1457]!, [_0]) + } + public var Conversation_DeleteMessagesForEveryone: String { return self._s[1458]! } + public var Wallet_Words_NotDoneTitle: String { return self._s[1459]! } + public var Permissions_SiriTitle_v0: String { return self._s[1460]! } + public var GroupInfo_AddUserLeftError: String { return self._s[1461]! } + public var Conversation_SendMessage_SendSilently: String { return self._s[1462]! } + public var Paint_Duplicate: String { return self._s[1463]! } + public var AttachmentMenu_WebSearch: String { return self._s[1464]! } + public var Bot_Stop: String { return self._s[1466]! } + public var Conversation_PrivateChannelTimeLimitedAlertTitle: String { return self._s[1467]! } + public var Wallet_TransactionInfo_SendGrams: String { return self._s[1468]! } + public var ReportGroupLocation_Report: String { return self._s[1469]! } + public var Compose_Create: String { return self._s[1470]! } + public var Stats_GroupViewers: String { return self._s[1471]! } + public var AutoDownloadSettings_Channels: String { return self._s[1472]! } + public var PhotoEditor_QualityHigh: String { return self._s[1473]! } + public var Call_Speaker: String { return self._s[1474]! } + public func ChatList_LeaveGroupConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1475]!, self._r[1475]!, [_0]) + } + public var Conversation_CloudStorage_ChatStatus: String { return self._s[1476]! } + public var Chat_AttachmentMultipleFilesDisabled: String { return self._s[1477]! } + public var ChatList_Context_AddToFolder: String { return self._s[1478]! } + public func Wallet_SecureStorageReset_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1479]!, self._r[1479]!, [_0]) + } + public var Conversation_Unblock: String { return self._s[1480]! } + public var SettingsSearch_Synonyms_Proxy_UseForCalls: String { return self._s[1481]! } + public func Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1482]!, self._r[1482]!, [_1, _2, _3]) + } + public var Conversation_ContextMenuReply: String { return self._s[1483]! } + public var Contacts_SearchLabel: String { return self._s[1484]! } + public var Forward_ErrorPublicQuizDisabledInChannels: String { return self._s[1485]! } + public var Stats_GroupMessagesTitle: String { return self._s[1487]! } + public var Wallet_Send_UninitializedTitle: String { return self._s[1488]! } + public var Notification_CallCanceled: String { return self._s[1489]! } + public var VoiceOver_Chat_Selected: String { return self._s[1490]! } + public var NotificationsSound_Tremolo: String { return self._s[1492]! } + public var ChatList_Search_NoResultsDescription: String { return self._s[1493]! } + public var AccessDenied_PhotosAndVideos: String { return self._s[1494]! } + public var AppWallet_Intro_Text: String { return self._s[1495]! } + public var LogoutOptions_ClearCacheText: String { return self._s[1497]! } + public var ChatListFolder_NameUnread: String { return self._s[1498]! } + public var PeerInfo_ButtonMessage: String { return self._s[1500]! } + public var InfoPlist_NSPhotoLibraryAddUsageDescription: String { return self._s[1501]! } + public var BlockedUsers_SelectUserTitle: String { return self._s[1502]! } + public var ChatSettings_Other: String { return self._s[1503]! } + public var UserInfo_NotificationsEnabled: String { return self._s[1504]! } + public var CreatePoll_OptionsHeader: String { return self._s[1505]! } + public var Wallet_Created_Title: String { return self._s[1508]! } + public var Appearance_RemoveThemeColorConfirmation: String { return self._s[1509]! } + public var Channel_Moderator_Title: String { return self._s[1510]! } + public var Channel_AdminLog_MessageRestrictedForever: String { return self._s[1511]! } + public var WallpaperColors_Title: String { return self._s[1512]! } + public var PrivacyPolicy_DeclineMessage: String { return self._s[1514]! } + public var AutoDownloadSettings_VoiceMessagesTitle: String { return self._s[1515]! } + public var Your_card_was_declined: String { return self._s[1516]! } + public var SettingsSearch_FAQ: String { return self._s[1518]! } + public var EditTheme_Expand_Preview_IncomingReplyName: String { return self._s[1519]! } + public var Conversation_ReportSpamConfirmation: String { return self._s[1520]! } + public var OwnershipTransfer_SecurityCheck: String { return self._s[1522]! } + public var PrivacySettings_DataSettingsHelp: String { return self._s[1523]! } + public var Settings_About_Help: String { return self._s[1524]! } + public func Channel_DiscussionGroup_HeaderGroupSet(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1525]!, self._r[1525]!, [_0]) + } + public var Wallet_Settings_Title: String { return self._s[1526]! } + public var Settings_Proxy: String { return self._s[1527]! } + public var TwoStepAuth_ResetAccountConfirmation: String { return self._s[1528]! } + public var Passport_Identity_TypePassportUploadScan: String { return self._s[1530]! } + public var NotificationsSound_Bell: String { return self._s[1531]! } + public var PrivacySettings_Title: String { return self._s[1532]! } + public var PrivacySettings_DataSettings: String { return self._s[1533]! } + public var ConversationMedia_Title: String { return self._s[1534]! } + public func Conversation_EncryptedPlaceholderTitleIncoming(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1535]!, self._r[1535]!, [_0]) + } + public var PrivacySettings_BlockedPeersEmpty: String { return self._s[1536]! } + public var ReportPeer_ReasonPornography: String { return self._s[1538]! } + public var Privacy_Calls: String { return self._s[1539]! } + public var TwoFactorSetup_Email_Text: String { return self._s[1540]! } + public var Conversation_EncryptedDescriptionTitle: String { return self._s[1541]! } + public func VoiceOver_Chat_MusicTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1542]!, self._r[1542]!, [_1, _2]) + } + public var Passport_Identity_FrontSideHelp: String { return self._s[1543]! } + public var GroupInfo_Permissions_SlowmodeHeader: String { return self._s[1545]! } + public var ContactList_Context_VideoCall: String { return self._s[1546]! } + public var Settings_SaveIncomingPhotos: String { return self._s[1547]! } + public var Passport_Identity_MiddleName: String { return self._s[1548]! } + public var MessagePoll_QuizNoUsers: String { return self._s[1549]! } + public var OldChannels_ChannelFormat: String { return self._s[1550]! } + public var Watch_Message_Call: String { return self._s[1551]! } + public var Wallpaper_Title: String { return self._s[1552]! } + public var PasscodeSettings_TurnPasscodeOff: String { return self._s[1553]! } + public var IntentsSettings_SuggestedChatsSavedMessages: String { return self._s[1554]! } + public var ReportGroupLocation_Text: String { return self._s[1555]! } + public var InviteText_URL: String { return self._s[1556]! } + public var ClearCache_StorageServiceFiles: String { return self._s[1557]! } + public var MessageTimer_Custom: String { return self._s[1558]! } + public var Message_PinnedLocationMessage: String { return self._s[1559]! } + public func VoiceOver_Chat_ContactOrganization(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1560]!, self._r[1560]!, [_0]) + } + public var EditTheme_UploadNewTheme: String { return self._s[1561]! } + public func AutoDownloadSettings_UpToForAll(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1563]!, self._r[1563]!, [_0]) + } + public var Login_CodeSentCall: String { return self._s[1565]! } + public var Conversation_Report: String { return self._s[1566]! } + public var NotificationSettings_ContactJoined: String { return self._s[1567]! } + public func PUSH_MESSAGE_SCREENSHOT(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1568]!, self._r[1568]!, [_1]) + } + public var StickerPacksSettings_ShowStickersButtonHelp: String { return self._s[1569]! } + public var IntentsSettings_SuggestByAll: String { return self._s[1570]! } + public var StickerPacksSettings_ShowStickersButton: String { return self._s[1571]! } + public var AuthSessions_Title: String { return self._s[1572]! } + public var Channel_AdminLog_TitleAllEvents: String { return self._s[1573]! } + public var Wallet_Completed_ViewWallet: String { return self._s[1574]! } + public var KeyCommand_JumpToNextUnreadChat: String { return self._s[1575]! } + public var Passport_Address_AddPassportRegistration: String { return self._s[1579]! } + public var AutoDownloadSettings_MaxVideoSize: String { return self._s[1580]! } + public var ExplicitContent_AlertTitle: String { return self._s[1581]! } + public var Channel_UpdatePhotoItem: String { return self._s[1582]! } + public var ChatList_AutoarchiveSuggestion_Text: String { return self._s[1584]! } + public var Channel_DiscussionGroup_LinkGroup: String { return self._s[1585]! } + public func Call_BatteryLow(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1586]!, self._r[1586]!, [_0]) + } + public var Login_HaveNotReceivedCodeInternal: String { return self._s[1587]! } + public var WallpaperPreview_PatternPaternApply: String { return self._s[1588]! } + public var Notifications_MessageNotificationsSound: String { return self._s[1589]! } + public var Appearance_AccentColor: String { return self._s[1591]! } + public var GroupInfo_SharedMedia: String { return self._s[1592]! } + public var Login_PhonePlaceholder: String { return self._s[1593]! } + public var Appearance_TextSize_Automatic: String { return self._s[1594]! } + public var EmptyGroupInfo_Line2: String { return self._s[1595]! } + public func PUSH_CHAT_CREATED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1596]!, self._r[1596]!, [_1, _2]) + } + public var Conversation_WalletRequiredNotNow: String { return self._s[1598]! } + public var Appearance_AppIconDefaultX: String { return self._s[1599]! } + public var EditProfile_NameAndPhotoOrVideoHelp: String { return self._s[1600]! } + public var CheckoutInfo_ShippingInfoPostcodePlaceholder: String { return self._s[1601]! } + public var Notifications_GroupNotificationsHelp: String { return self._s[1602]! } + public func PUSH_CHAT_MESSAGE_NOTEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1603]!, self._r[1603]!, [_1, _2]) + } + public var ChatList_EmptyChatListEditFilter: String { return self._s[1604]! } + public var ChatSettings_ConnectionType_UseProxy: String { return self._s[1607]! } + public var UserInfo_NotificationsEnable: String { return self._s[1608]! } + public var Checkout_PayWithTouchId: String { return self._s[1609]! } + public var SharedMedia_ViewInChat: String { return self._s[1610]! } + public func Notification_CreatedChatWithTitle(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1611]!, self._r[1611]!, [_0, _1]) + } + public var ChatSettings_AutoDownloadSettings_OffForAll: String { return self._s[1612]! } + public func Channel_DiscussionGroup_PublicChannelLink(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1613]!, self._r[1613]!, [_1, _2]) + } + public func Cache_Clear(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1615]!, self._r[1615]!, [_0]) + } + public var Conversation_PeerNearbyText: String { return self._s[1617]! } + public var Conversation_StopPollConfirmationTitle: String { return self._s[1618]! } + public var PhotoEditor_Skip: String { return self._s[1619]! } + public var SettingsSearch_Synonyms_Appearance_ChatBackground_SetColor: String { return self._s[1620]! } + public var ChatList_EmptyChatList: String { return self._s[1621]! } + public var Channel_BanUser_Unban: String { return self._s[1622]! } + public func Message_GenericForwardedPsa(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1623]!, self._r[1623]!, [_0]) + } + public var Appearance_TextSize_Apply: String { return self._s[1624]! } + public var Wallet_Send_SyncInProgress: String { return self._s[1625]! } + public var Login_InfoFirstNamePlaceholder: String { return self._s[1626]! } + public var Wallet_Configuration_SourceHeader: String { return self._s[1627]! } + public var TwoStepAuth_HintPlaceholder: String { return self._s[1628]! } + public var TwoStepAuth_EmailSkip: String { return self._s[1630]! } + public var ChatList_UndoArchiveMultipleTitle: String { return self._s[1631]! } + public var TwoFactorSetup_Email_SkipConfirmationTitle: String { return self._s[1632]! } + public func PUSH_MESSAGE_QUIZ(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1633]!, self._r[1633]!, [_1]) + } + public var State_WaitingForNetwork: String { return self._s[1635]! } + public var AccessDenied_CameraRestricted: String { return self._s[1636]! } + public var ChatSettings_Appearance: String { return self._s[1637]! } + public var ScheduledMessages_BotActionUnavailable: String { return self._s[1638]! } + public func Wallet_Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1639]!, self._r[1639]!, [_1, _2, _3]) + } + public var GroupInfo_InviteLink_CopyAlert_Success: String { return self._s[1640]! } + public var Channel_DiscussionGroupAdd: String { return self._s[1641]! } + public var Map_NoPlacesNearby: String { return self._s[1643]! } + public var AuthSessions_IncompleteAttemptsInfo: String { return self._s[1644]! } + public var GroupRemoved_Title: String { return self._s[1645]! } + public var TwoStepAuth_EnterPasswordHelp: String { return self._s[1647]! } + public var Paint_Marker: String { return self._s[1648]! } + public func AddContact_ContactWillBeSharedAfterMutual(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1649]!, self._r[1649]!, [_1]) + } + public var SocksProxySetup_ShareProxyList: String { return self._s[1650]! } + public var GroupInfo_InvitationLinkDoesNotExist: String { return self._s[1651]! } + public func VoiceOver_Chat_Size(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1652]!, self._r[1652]!, [_0]) + } + public var EditTheme_ErrorInvalidCharacters: String { return self._s[1653]! } + public var Appearance_ThemePreview_ChatList_7_Name: String { return self._s[1654]! } + public var Notifications_GroupNotificationsAlert: String { return self._s[1655]! } + public var SocksProxySetup_ShareQRCode: String { return self._s[1656]! } + public var Compose_NewGroup: String { return self._s[1657]! } + public func Passport_Address_UploadOneOfScan(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1658]!, self._r[1658]!, [_0]) + } + public var Conversation_ClearGroupHistory: String { return self._s[1660]! } + public var GroupInfo_InviteLink_Help: String { return self._s[1663]! } + public var Channel_BanUser_BlockFor: String { return self._s[1664]! } + public var Bot_Start: String { return self._s[1665]! } + public var Your_card_has_expired: String { return self._s[1666]! } + public var Channel_About_Title: String { return self._s[1667]! } + public var Passport_Identity_ExpiryDatePlaceholder: String { return self._s[1668]! } + public var SettingsSearch_Synonyms_Notifications_MessageNotificationsExceptions: String { return self._s[1670]! } + public var Wallet_Info_Updating: String { return self._s[1671]! } + public var Conversation_FileDropbox: String { return self._s[1672]! } + public var Conversation_WalletRequiredTitle: String { return self._s[1673]! } + public var ChatList_Search_NoResultsFitlerMusic: String { return self._s[1674]! } + public var Month_GenNovember: String { return self._s[1675]! } + public var IntentsSettings_SuggestByShare: String { return self._s[1676]! } + public func Call_PrivacyErrorMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1677]!, self._r[1677]!, [_0]) + } + public var StickerPack_Add: String { return self._s[1678]! } + public var Theme_ErrorNotFound: String { return self._s[1679]! } + public var Wallpaper_SearchShort: String { return self._s[1681]! } + public var Channel_BanUser_PermissionsHeader: String { return self._s[1682]! } + public var ConversationProfile_UsersTooMuchError: String { return self._s[1683]! } + public var ChatList_FolderAllChats: String { return self._s[1684]! } + public var Passport_Authorize: String { return self._s[1685]! } + public func Channel_AdminLog_MessageChangedLinkedChannel(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1686]!, self._r[1686]!, [_1, _2]) + } + public var GroupInfo_GroupHistoryVisible: String { return self._s[1687]! } + public func PUSH_MESSAGE_VIDEO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1688]!, self._r[1688]!, [_1]) + } + public var LocalGroup_ButtonTitle: String { return self._s[1689]! } + public var UserInfo_GroupsInCommon: String { return self._s[1690]! } + public var Wallpaper_Set: String { return self._s[1691]! } + public var LoginPassword_Title: String { return self._s[1692]! } + public var Stats_InteractionsTitle: String { return self._s[1693]! } + public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[1695]! } + public func SecretGIF_NotViewedYet(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1696]!, self._r[1696]!, [_0]) + } + public var Conversation_MessageDialogEdit: String { return self._s[1697]! } + public var Paint_Outlined: String { return self._s[1698]! } + public func Login_ResetAccountProtected_Text(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1699]!, self._r[1699]!, [_0]) + } + public func Conversation_SetReminder_RemindTomorrow(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1700]!, self._r[1700]!, [_0]) + } + public var Invite_LargeRecipientsCountWarning: String { return self._s[1701]! } + public var Passport_Address_Street1Placeholder: String { return self._s[1702]! } + public var Appearance_ColorThemeNight: String { return self._s[1703]! } + public var ChannelInfo_Stats: String { return self._s[1704]! } + public var TwoStepAuth_RecoveryTitle: String { return self._s[1705]! } + public var MediaPicker_TimerTooltip: String { return self._s[1706]! } + public var Common_ChoosePhoto: String { return self._s[1707]! } + public var ChatSettings_AutoDownloadVideos: String { return self._s[1708]! } + public var PeerInfo_PaneGroups: String { return self._s[1709]! } + public var Wallet_Month_ShortMarch: String { return self._s[1711]! } + public var ChangePhoneNumberNumber_Title: String { return self._s[1712]! } + public var SocksProxySetup_UsernamePlaceholder: String { return self._s[1713]! } + public var ContactInfo_PhoneLabelMobile: String { return self._s[1714]! } + public var OldChannels_ChannelsHeader: String { return self._s[1715]! } + public var MuteFor_Forever: String { return self._s[1716]! } + public var Passport_Address_PostcodePlaceholder: String { return self._s[1717]! } + public var SettingsSearch_Synonyms_Appearance_ChatBackground: String { return self._s[1718]! } + public var MessagePoll_LabelAnonymous: String { return self._s[1719]! } + public var ContactInfo_Job: String { return self._s[1720]! } + public var Passport_Language_mk: String { return self._s[1721]! } + public var EditTheme_ShortLink: String { return self._s[1722]! } + public var AutoDownloadSettings_PhotosTitle: String { return self._s[1724]! } + public var Wallet_Send_Send: String { return self._s[1725]! } + public var Month_GenApril: String { return self._s[1727]! } + public var Channel_DiscussionGroup_HeaderLabel: String { return self._s[1729]! } + public var NetworkUsageSettings_TotalSection: String { return self._s[1730]! } + public var EditTheme_Create_Preview_OutgoingText: String { return self._s[1731]! } + public var EditTheme_Title: String { return self._s[1732]! } + public var Conversation_LinkDialogCopy: String { return self._s[1733]! } + public func Channel_AdminLog_MessageInvitedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1734]!, self._r[1734]!, [_1, _2]) + } + public var Passport_ForgottenPassword: String { return self._s[1735]! } + public var WallpaperSearch_Recent: String { return self._s[1736]! } + public var ChatSettings_Title: String { return self._s[1741]! } + public var Appearance_ReduceMotionInfo: String { return self._s[1742]! } + public func StickerPackActionInfo_AddedText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1743]!, self._r[1743]!, [_0]) + } + public var SocksProxySetup_UseForCallsHelp: String { return self._s[1744]! } + public var LastSeen_WithinAMonth: String { return self._s[1745]! } + public var PeerInfo_ButtonCall: String { return self._s[1746]! } + public var SettingsSearch_Synonyms_Appearance_Title: String { return self._s[1747]! } + public var Group_Username_InvalidStartsWithNumber: String { return self._s[1748]! } + public var Call_AudioRouteHide: String { return self._s[1749]! } + public var DialogList_SavedMessages: String { return self._s[1750]! } + public var ChatList_Context_Mute: String { return self._s[1751]! } + public var Conversation_StatusKickedFromChannel: String { return self._s[1752]! } + public func Notification_Exceptions_MutedUntil(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1753]!, self._r[1753]!, [_0]) + } + public var Passport_Language_et: String { return self._s[1754]! } + public var PhotoEditor_CropReset: String { return self._s[1755]! } + public var Wallet_Send_TransactionInProgress: String { return self._s[1756]! } + public var Privacy_GroupsAndChannels_AlwaysAllow: String { return self._s[1757]! } + public var SocksProxySetup_HostnamePlaceholder: String { return self._s[1758]! } + public var CreateGroup_ErrorLocatedGroupsTooMuch: String { return self._s[1759]! } + public var WallpaperSearch_ColorWhite: String { return self._s[1762]! } + public var Channel_AdminLog_CanEditMessages: String { return self._s[1764]! } + public var Privacy_PaymentsClearInfoDoneHelp: String { return self._s[1765]! } + public var Channel_Username_InvalidStartsWithNumber: String { return self._s[1767]! } + public var CheckoutInfo_ReceiverInfoName: String { return self._s[1769]! } + public var Map_YouAreHere: String { return self._s[1771]! } + public var Core_ServiceUserStatus: String { return self._s[1772]! } + public var Channel_Setup_TypePrivateHelp: String { return self._s[1775]! } + public var SettingsSearch_Synonyms_Notifications_BadgeCountUnreadMessages: String { return self._s[1776]! } + public var MediaPicker_Videos: String { return self._s[1778]! } + public var Map_LiveLocationFor15Minutes: String { return self._s[1780]! } + public var Passport_Identity_TranslationsHelp: String { return self._s[1781]! } + public var SharedMedia_CategoryMedia: String { return self._s[1782]! } + public var Wallet_Month_ShortJanuary: String { return self._s[1783]! } + public func MediaPicker_Nof(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1784]!, self._r[1784]!, [_0]) + } + public var ChatSettings_AutoPlayGifs: String { return self._s[1785]! } + public var Passport_Identity_CountryPlaceholder: String { return self._s[1786]! } + public var Bot_GroupStatusDoesNotReadHistory: String { return self._s[1787]! } + public var Notification_Exceptions_RemoveFromExceptions: String { return self._s[1788]! } + public func Chat_SlowmodeTooltip(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1789]!, self._r[1789]!, [_0]) + } + public var Web_Error: String { return self._s[1790]! } + public var PhotoEditor_SkinTool: String { return self._s[1791]! } + public var ApplyLanguage_UnsufficientDataTitle: String { return self._s[1792]! } + public var ChatSettings_ConnectionType_UseSocks5: String { return self._s[1793]! } + public var PasscodeSettings_Help: String { return self._s[1794]! } + public var Appearance_ColorTheme: String { return self._s[1795]! } + public func Channel_AdminLog_MessageRestrictedNewSetting(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1796]!, self._r[1796]!, [_0]) + } + public func PUSH_PINNED_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1797]!, self._r[1797]!, [_1]) + } + public var GroupInfo_LeftStatus: String { return self._s[1798]! } + public var EditTheme_Preview: String { return self._s[1799]! } + public var Watch_Suggestion_WhatsUp: String { return self._s[1800]! } + public func AutoDownloadSettings_PreloadVideoInfo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1801]!, self._r[1801]!, [_0]) + } + public var NotificationsSound_Keys: String { return self._s[1802]! } + public var PasscodeSettings_UnlockWithTouchId: String { return self._s[1803]! } + public var ChatList_Context_MarkAsUnread: String { return self._s[1804]! } + public var DialogList_AdNoticeAlert: String { return self._s[1805]! } + public var UserInfo_Invite: String { return self._s[1806]! } + public var Checkout_Email: String { return self._s[1807]! } + public var Stats_GroupActionsTitle: String { return self._s[1808]! } + public var Wallet_Navigation_Done: String { return self._s[1809]! } + public var Coub_TapForSound: String { return self._s[1810]! } + public var Theme_ThemeChangedText: String { return self._s[1811]! } + public var Call_ExternalCallInProgressMessage: String { return self._s[1812]! } + public var Settings_ApplyProxyAlertEnable: String { return self._s[1813]! } + public var ScheduledMessages_ScheduledToday: String { return self._s[1814]! } + public var Channel_AdminLog_DefaultRestrictionsUpdated: String { return self._s[1815]! } + public var Call_ReportIncludeLogDescription: String { return self._s[1816]! } + public var Settings_FrequentlyAskedQuestions: String { return self._s[1818]! } + public var Wallet_Words_NotDoneText: String { return self._s[1819]! } + public var Channel_MessagePhotoRemoved: String { return self._s[1820]! } + public var Passport_Email_Delete: String { return self._s[1821]! } + public func PUSH_PINNED_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1822]!, self._r[1822]!, [_1]) + } + public var NotificationSettings_ShowNotificationsAllAccountsInfoOn: String { return self._s[1823]! } + public var Channel_AdminLog_CanAddAdmins: String { return self._s[1824]! } + public var SocksProxySetup_FailedToConnect: String { return self._s[1826]! } + public var SettingsSearch_Synonyms_Data_NetworkUsage: String { return self._s[1827]! } + public var Wallet_Month_GenMay: String { return self._s[1828]! } + public var Common_of: String { return self._s[1829]! } + public var PeerInfo_ButtonUnmute: String { return self._s[1831]! } + public func ChatSettings_AutoDownloadSettings_TypeFile(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1832]!, self._r[1832]!, [_0]) + } + public var ChatList_AddChatsToFolder: String { return self._s[1833]! } + public var Login_ResetAccountProtected_LimitExceeded: String { return self._s[1834]! } + public var Settings_Title: String { return self._s[1836]! } + public var AutoDownloadSettings_Contacts: String { return self._s[1838]! } + public var Appearance_BubbleCornersSetting: String { return self._s[1839]! } + public var Privacy_Calls_AlwaysAllow: String { return self._s[1840]! } + public var Privacy_Forwards_AlwaysAllow_Title: String { return self._s[1842]! } + public var WallpaperPreview_CropBottomText: String { return self._s[1843]! } + public var SecretTimer_VideoDescription: String { return self._s[1844]! } + public var WallpaperPreview_Blurred: String { return self._s[1845]! } + public var SettingsSearch_Synonyms_Notifications_GroupNotificationsExceptions: String { return self._s[1846]! } + public var ChatListFolder_ExcludedSectionHeader: String { return self._s[1848]! } + public var DialogList_PasscodeLockHelp: String { return self._s[1849]! } + public var SocksProxySetup_SecretPlaceholder: String { return self._s[1850]! } + public var NetworkUsageSettings_CallDataSection: String { return self._s[1851]! } + public var SettingsSearch_Synonyms_Wallet: String { return self._s[1852]! } + public var TwoStepAuth_PasswordRemovePassportConfirmation: String { return self._s[1853]! } + public var Passport_FieldAddressTranslationHelp: String { return self._s[1854]! } + public var SocksProxySetup_Connection: String { return self._s[1855]! } + public var Passport_Address_TypePassportRegistration: String { return self._s[1856]! } + public var Contacts_PermissionsAllowInSettings: String { return self._s[1857]! } + public var Conversation_Unpin: String { return self._s[1858]! } + public var Notifications_MessageNotificationsExceptionsHelp: String { return self._s[1859]! } + public var TwoFactorSetup_Hint_Placeholder: String { return self._s[1860]! } + public var Call_ReportSkip: String { return self._s[1861]! } + public func VoiceOver_Chat_PhotoFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1862]!, self._r[1862]!, [_0]) + } + public func VoiceOver_Chat_Caption(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1864]!, self._r[1864]!, [_0]) + } + public var AutoNightTheme_Automatic: String { return self._s[1865]! } + public var Wallet_TransactionInfo_AddressCopied: String { return self._s[1866]! } + public var Wallet_Month_GenMarch: String { return self._s[1867]! } + public var Passport_Language_az: String { return self._s[1868]! } + public var SettingsSearch_Synonyms_Data_Storage_ClearCache: String { return self._s[1869]! } + public var Watch_UserInfo_Unmute: String { return self._s[1870]! } + public var Channel_Stickers_YourStickers: String { return self._s[1871]! } + public var Channel_DiscussionGroup_UnlinkChannel: String { return self._s[1872]! } + public var Tour_Text1: String { return self._s[1873]! } + public var Common_Delete: String { return self._s[1874]! } + public var Settings_EditPhoto: String { return self._s[1875]! } + public var Common_Edit: String { return self._s[1876]! } + public var ShareMenu_ShareTo: String { return self._s[1878]! } + public var Passport_Identity_ExpiryDate: String { return self._s[1879]! } + public var Preview_DeleteGif: String { return self._s[1880]! } + public var WallpaperPreview_PatternPaternDiscard: String { return self._s[1881]! } + public var ChatSettings_AutoDownloadUsingCellular: String { return self._s[1882]! } + public var Stats_LoadingText: String { return self._s[1883]! } + public var Channel_EditAdmin_PermissinAddAdminOn: String { return self._s[1884]! } + public var CheckoutInfo_ReceiverInfoEmailPlaceholder: String { return self._s[1885]! } + public var Channel_AdminLog_CanChangeInfo: String { return self._s[1886]! } + public func Passport_Phone_UseTelegramNumber(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1887]!, self._r[1887]!, [_0]) + } + public func Time_MonthOfYear_m2(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1888]!, self._r[1888]!, [_0]) + } + public func VoiceOver_Chat_VideoMessageFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1890]!, self._r[1890]!, [_0]) + } + public var Passport_Address_OneOfTypeRentalAgreement: String { return self._s[1891]! } + public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[1894]! } + public var IntentsSettings_MainAccount: String { return self._s[1895]! } + public var Group_MessagePhotoRemoved: String { return self._s[1898]! } + public var GroupInfo_Permissions_Exceptions: String { return self._s[1900]! } + public var GroupRemoved_UsersSectionTitle: String { return self._s[1901]! } + public var Contacts_PermissionsEnable: String { return self._s[1902]! } + public var Channel_EditAdmin_PermissionDeleteMessagesOfOthers: String { return self._s[1903]! } + public var Common_NotNow: String { return self._s[1904]! } + public var Notification_CreatedChannel: String { return self._s[1905]! } + public var Stats_ViewsBySourceTitle: String { return self._s[1907]! } + public var Appearance_AppIconClassic: String { return self._s[1908]! } + public var PhotoEditor_QualityTool: String { return self._s[1909]! } + public var ClearCache_ClearCache: String { return self._s[1910]! } + public var TwoFactorSetup_Password_PlaceholderConfirmPassword: String { return self._s[1911]! } + public var AutoDownloadSettings_Videos: String { return self._s[1912]! } + public var GroupPermission_Duration: String { return self._s[1913]! } + public var ChatList_Read: String { return self._s[1914]! } + public func Group_OwnershipTransfer_DescriptionInfo(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1915]!, self._r[1915]!, [_1, _2]) + } + public var CallFeedback_Send: String { return self._s[1916]! } + public var Channel_Stickers_Searching: String { return self._s[1917]! } + public var ScheduledMessages_ReminderNotification: String { return self._s[1918]! } + public var FastTwoStepSetup_HintSection: String { return self._s[1919]! } + public var ChatSettings_AutoDownloadVideoMessages: String { return self._s[1920]! } + public var EditTheme_CreateTitle: String { return self._s[1921]! } + public var Application_Name: String { return self._s[1922]! } + public var Paint_Stickers: String { return self._s[1923]! } + public var Appearance_ThemePreview_Chat_1_Text: String { return self._s[1924]! } + public var Call_StatusFailed: String { return self._s[1925]! } + public var Stickers_FavoriteStickers: String { return self._s[1926]! } + public var ClearCache_Clear: String { return self._s[1927]! } + public var Passport_Language_mn: String { return self._s[1928]! } + public var WallpaperPreview_PreviewTopText: String { return self._s[1929]! } + public var LogoutOptions_ClearCacheTitle: String { return self._s[1930]! } + public var TwoFactorSetup_Hint_Text: String { return self._s[1933]! } + public var WallpaperPreview_PatternIntensity: String { return self._s[1934]! } + public var CheckoutInfo_ErrorShippingNotAvailable: String { return self._s[1935]! } + public var Wallet_RestoreFailed_CreateWallet: String { return self._s[1936]! } + public var Passport_Address_AddBankStatement: String { return self._s[1937]! } + public var ChatListFolderSettings_RecommendedNewFolder: String { return self._s[1939]! } + public var UserInfo_ShareContact: String { return self._s[1940]! } + public var Passport_Identity_NamePlaceholder: String { return self._s[1941]! } + public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[1943]! } + public var Call_RateCall: String { return self._s[1944]! } + public var Contacts_AccessDeniedError: String { return self._s[1945]! } + public var Invite_ChannelsTooMuch: String { return self._s[1946]! } + public var CheckoutInfo_ShippingInfoPostcode: String { return self._s[1947]! } + public var Channel_BanUser_PermissionReadMessages: String { return self._s[1948]! } + public var Cache_NoLimit: String { return self._s[1950]! } + public var Conversation_EmptyPlaceholder: String { return self._s[1951]! } + public var Privacy_GroupsAndChannels_AlwaysAllow_Placeholder: String { return self._s[1955]! } + public var GroupRemoved_RemoveInfo: String { return self._s[1956]! } + public var Privacy_Calls_IntegrationHelp: String { return self._s[1957]! } + public func PUSH_VIDEO_CALL_MISSED(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1958]!, self._r[1958]!, [_1]) + } + public var VoiceOver_Media_PlaybackRateFast: String { return self._s[1959]! } + public var Theme_ThemeChanged: String { return self._s[1960]! } + public var Privacy_GroupsAndChannels_NeverAllow: String { return self._s[1962]! } + public var AutoDownloadSettings_MediaTypes: String { return self._s[1963]! } + public func Notification_PinnedDocumentMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1964]!, self._r[1964]!, [_0]) + } + public var Channel_AdminLog_InfoPanelTitle: String { return self._s[1965]! } + public var Passport_Language_da: String { return self._s[1967]! } + public var Wallet_Receive_AmountText: String { return self._s[1968]! } + public var Chat_SlowmodeSendError: String { return self._s[1969]! } + public var Application_Update: String { return self._s[1971]! } + public var SocksProxySetup_SaveProxy: String { return self._s[1972]! } + public func PUSH_AUTH_REGION(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1973]!, self._r[1973]!, [_1, _2]) + } + public var Wallet_Receive_ShareAddress: String { return self._s[1975]! } + public var Privacy_AddNewPeer: String { return self._s[1976]! } + public var Channel_DiscussionGroup_MakeHistoryPublicProceed: String { return self._s[1978]! } + public var Wallet_Receive_CommentInfo: String { return self._s[1979]! } + public var Channel_Members_Title: String { return self._s[1980]! } + public var Settings_LogoutConfirmationText: String { return self._s[1981]! } + public var Chat_UnsendMyMessages: String { return self._s[1982]! } + public var Conversation_EditingMessageMediaEditCurrentVideo: String { return self._s[1983]! } + public var ChatListFilter_AddChatsTitle: String { return self._s[1984]! } + public var Passport_FloodError: String { return self._s[1985]! } + public var NotificationSettings_ContactJoinedInfo: String { return self._s[1986]! } + public var SettingsSearch_Synonyms_Privacy_Data_SecretChatLinkPreview: String { return self._s[1987]! } + public var CallSettings_TabIconDescription: String { return self._s[1988]! } + public var Wallet_Intro_Text: String { return self._s[1989]! } + public var Group_Setup_HistoryHeader: String { return self._s[1991]! } + public var TwoStepAuth_EmailTitle: String { return self._s[1992]! } + public var GroupInfo_Permissions_Removed: String { return self._s[1993]! } + public var DialogList_ClearHistoryConfirmation: String { return self._s[1994]! } + public var Contacts_Title: String { return self._s[1996]! } + public func Notification_Invited(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[1997]!, self._r[1997]!, [_0, _1]) + } + public var ChatList_PeerTypeBot: String { return self._s[2000]! } + public func Channel_AdminLog_SetSlowmode(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2001]!, self._r[2001]!, [_1, _2]) + } + public var Appearance_ThemePreview_Chat_6_Text: String { return self._s[2002]! } + public func Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2003]!, self._r[2003]!, [_1, _2, _3]) + } + public var Camera_PhotoMode: String { return self._s[2005]! } + public func PUSH_MESSAGE_GAME_SCORE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2006]!, self._r[2006]!, [_1, _2, _3]) + } + public var ContactInfo_PhoneLabelPager: String { return self._s[2007]! } + public var SettingsSearch_Synonyms_FAQ: String { return self._s[2008]! } + public var Call_CallAgain: String { return self._s[2009]! } + public var TwoStepAuth_PasswordSet: String { return self._s[2010]! } + public func Channel_Management_RestrictedBy(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2011]!, self._r[2011]!, [_0]) + } + public var GroupInfo_InviteLink_RevokeAlert_Success: String { return self._s[2012]! } + public var ClearCache_FreeSpaceDescription: String { return self._s[2013]! } + public var Permissions_ContactsAllowInSettings_v0: String { return self._s[2014]! } + public var Group_LeaveGroup: String { return self._s[2015]! } + public var Wallet_WordImport_IncorrectText: String { return self._s[2018]! } + public var GroupInfo_LabelAdmin: String { return self._s[2019]! } + public var CheckoutInfo_ErrorStateInvalid: String { return self._s[2021]! } + public var Notification_PassportValuePersonalDetails: String { return self._s[2022]! } + public func WebSearch_SearchNoResultsDescription(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2023]!, self._r[2023]!, [_0]) + } + public var Stats_GroupNewMembersBySourceTitle: String { return self._s[2024]! } + public var Appearance_Preview: String { return self._s[2025]! } + public var VoiceOver_Chat_Contact: String { return self._s[2026]! } + public var Passport_Language_th: String { return self._s[2027]! } + public var PhotoEditor_CropAspectRatioOriginal: String { return self._s[2029]! } + public var LastSeen_Offline: String { return self._s[2032]! } + public var Map_OpenInHereMaps: String { return self._s[2033]! } + public var SettingsSearch_Synonyms_Data_AutoplayVideos: String { return self._s[2034]! } + public var AutoDownloadSettings_Reset: String { return self._s[2036]! } + public var Wallet_Month_GenFebruary: String { return self._s[2037]! } + public var Conversation_SendMessage_SetReminder: String { return self._s[2038]! } + public var Channel_AdminLog_EmptyMessageText: String { return self._s[2039]! } + public func AddContact_StatusSuccess(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2040]!, self._r[2040]!, [_0]) + } + public func AuthCode_Alert(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2041]!, self._r[2041]!, [_0]) + } + public var Passport_Identity_EditDriversLicense: String { return self._s[2042]! } + public var ChatListFolder_NameNonMuted: String { return self._s[2043]! } + public var Username_Placeholder: String { return self._s[2044]! } + public func PUSH_ALBUM(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2045]!, self._r[2045]!, [_1]) + } + public var Wallet_Send_NetworkErrorText: String { return self._s[2046]! } + public var Checkout_NewCard_SaveInfo: String { return self._s[2047]! } + public var Passport_Language_it: String { return self._s[2048]! } + public func Channel_OwnershipTransfer_DescriptionInfo(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2049]!, self._r[2049]!, [_1, _2]) + } + public var NotificationsSound_Pulse: String { return self._s[2050]! } + public var MessagePoll_NoVotes: String { return self._s[2054]! } + public var Message_Wallpaper: String { return self._s[2055]! } + public var Wallet_Created_Proceed: String { return self._s[2056]! } + public var Appearance_Other: String { return self._s[2057]! } + public var Passport_Identity_NativeNameHelp: String { return self._s[2059]! } + public var Group_PublicLink_Placeholder: String { return self._s[2062]! } + public var Appearance_ThemePreview_ChatList_2_Text: String { return self._s[2063]! } + public var VoiceOver_Recording_StopAndPreview: String { return self._s[2064]! } + public var ChatListFolder_NameBots: String { return self._s[2065]! } + public var Conversation_StopPollConfirmation: String { return self._s[2066]! } + public var UserInfo_DeleteContact: String { return self._s[2067]! } + public func Time_MonthOfYear_m11(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2068]!, self._r[2068]!, [_0]) + } + public var Wallpaper_Wallpaper: String { return self._s[2070]! } + public func PUSH_MESSAGE_NOTEXT(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2071]!, self._r[2071]!, [_1]) + } + public var LoginPassword_ForgotPassword: String { return self._s[2072]! } + public var FeaturedStickerPacks_Title: String { return self._s[2073]! } + public var Paint_Pen: String { return self._s[2074]! } + public var Channel_AdminLogFilter_EventsInfo: String { return self._s[2075]! } + public var ChatListFolderSettings_Info: String { return self._s[2076]! } + public var FastTwoStepSetup_HintPlaceholder: String { return self._s[2077]! } + public var PhotoEditor_CurvesAll: String { return self._s[2079]! } + public var Wallet_Info_UnknownTransaction: String { return self._s[2080]! } + public func Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2082]!, self._r[2082]!, [_1, _2, _3]) + } + public var Passport_Address_TypeRentalAgreement: String { return self._s[2084]! } + public var Message_ImageExpired: String { return self._s[2085]! } + public var Call_ConnectionErrorMessage: String { return self._s[2086]! } + public var SearchImages_NoImagesFound: String { return self._s[2088]! } + public var PeerInfo_PaneGifs: String { return self._s[2089]! } + public var Passport_DeletePersonalDetailsConfirmation: String { return self._s[2090]! } + public var EnterPasscode_RepeatNewPasscode: String { return self._s[2091]! } + public var PhotoEditor_VignetteTool: String { return self._s[2092]! } + public var Passport_Language_dz: String { return self._s[2093]! } + public var Notifications_ChannelNotificationsHelp: String { return self._s[2094]! } + public var Conversation_BlockUser: String { return self._s[2095]! } + public func Wallet_Send_Balance(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2097]!, self._r[2097]!, [_0]) + } + public var GroupPermission_PermissionDisabledByDefault: String { return self._s[2098]! } + public var Group_OwnershipTransfer_ErrorAdminsTooMuch: String { return self._s[2099]! } + public func Time_MonthOfYear_m8(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2100]!, self._r[2100]!, [_0]) + } + public var KeyCommand_NewMessage: String { return self._s[2101]! } + public var EditTheme_Edit_Preview_IncomingReplyText: String { return self._s[2103]! } + public func PUSH_CHAT_MESSAGE_GEO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2105]!, self._r[2105]!, [_1, _2]) + } + public var ContactList_Context_StartSecretChat: String { return self._s[2106]! } + public var VoiceOver_Chat_File: String { return self._s[2107]! } + public var ChatList_EditFolder: String { return self._s[2109]! } + public var Appearance_BubbleCorners_Title: String { return self._s[2110]! } + public var PeerInfo_PaneAudio: String { return self._s[2111]! } + public var Wallet_SecureStorageReset_Title: String { return self._s[2112]! } + public var ChatListFolder_CategoryContacts: String { return self._s[2114]! } + public func Login_InvalidPhoneEmailBody(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2115]!, self._r[2115]!, [_1, _2, _3, _4, _5]) + } + public var ChatList_PeerTypeChannel: String { return self._s[2116]! } + public var VoiceOver_Navigation_Search: String { return self._s[2117]! } + public var Settings_Search: String { return self._s[2118]! } + public var WallpaperSearch_ColorYellow: String { return self._s[2119]! } + public var Login_PhoneBannedError: String { return self._s[2120]! } + public var KeyCommand_JumpToNextChat: String { return self._s[2121]! } + public var Passport_Language_fa: String { return self._s[2122]! } + public var Settings_About: String { return self._s[2123]! } + public var Wallet_Configuration_Title: String { return self._s[2124]! } + public var AutoDownloadSettings_MaxFileSize: String { return self._s[2125]! } + public var Channel_AdminLog_InfoPanelChannelAlertText: String { return self._s[2126]! } + public var AutoDownloadSettings_DataUsageHigh: String { return self._s[2127]! } + public func PUSH_CHAT_MESSAGE_TEXT(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2128]!, self._r[2128]!, [_1, _2, _3]) + } + public var Common_OK: String { return self._s[2129]! } + public var Contacts_SortBy: String { return self._s[2130]! } + public var AutoNightTheme_PreferredTheme: String { return self._s[2131]! } + public func AutoDownloadSettings_OnFor(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2133]!, self._r[2133]!, [_0]) + } + public var CallFeedback_IncludeLogs: String { return self._s[2136]! } + public func External_OpenIn(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2137]!, self._r[2137]!, [_0]) + } + public var Passcode_AppLockedAlert: String { return self._s[2138]! } + public var TwoStepAuth_SetupPasswordTitle: String { return self._s[2139]! } + public var Wallet_Send_ErrorDecryptionFailed: String { return self._s[2140]! } + public var Channel_NotificationLoading: String { return self._s[2142]! } + public var Passport_Identity_DocumentNumber: String { return self._s[2143]! } + public var VoiceOver_Chat_PagePreview: String { return self._s[2144]! } + public var VoiceOver_Chat_OpenHint: String { return self._s[2145]! } + public var Weekday_ShortFriday: String { return self._s[2146]! } + public var Wallet_CreateInvoice_Title: String { return self._s[2147]! } + public var Conversation_TitleMute: String { return self._s[2148]! } + public var SettingsSearch_Synonyms_Notifications_GroupNotificationsSound: String { return self._s[2149]! } + public var ScheduledMessages_PollUnavailable: String { return self._s[2150]! } + public var DialogList_LanguageTooltip: String { return self._s[2151]! } + public var Channel_AdminLogFilter_EventsPinned: String { return self._s[2152]! } + public func DialogList_SingleUploadingVideoSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2153]!, self._r[2153]!, [_0]) + } + public var TwoStepAuth_SetupResendEmailCodeAlert: String { return self._s[2155]! } + public var Privacy_Calls_AlwaysAllow_Title: String { return self._s[2156]! } + public var Settings_EditVideo: String { return self._s[2157]! } + public var Stickers_FrequentlyUsed: String { return self._s[2158]! } + public var GroupPermission_Title: String { return self._s[2159]! } + public var AccessDenied_VideoMessageCamera: String { return self._s[2160]! } + public var Appearance_ThemeCarouselDay: String { return self._s[2161]! } + public func PUSH_CHAT_MESSAGE_AUDIO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2162]!, self._r[2162]!, [_1, _2]) + } + public var Passport_Identity_DocumentNumberPlaceholder: String { return self._s[2163]! } + public var Tour_Title6: String { return self._s[2164]! } + public var EmptyGroupInfo_Title: String { return self._s[2165]! } + public func Channel_AdminLog_MessageToggleSignaturesOn(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2166]!, self._r[2166]!, [_0]) + } + public var Passport_Language_sk: String { return self._s[2167]! } + public var VoiceOver_Chat_YourAnonymousPoll: String { return self._s[2168]! } + public var Preview_SaveToCameraRoll: String { return self._s[2169]! } + public var LogoutOptions_SetPasscodeTitle: String { return self._s[2170]! } + public var Passport_Address_TypeUtilityBillUploadScan: String { return self._s[2171]! } + public var Conversation_ContextMenuMore: String { return self._s[2172]! } + public var Conversation_ForwardAuthorHiddenTooltip: String { return self._s[2173]! } + public var Channel_AdminLog_CanBeAnonymous: String { return self._s[2174]! } + public var CallFeedback_ReasonSilentLocal: String { return self._s[2176]! } + public var UserInfo_NotificationsDisable: String { return self._s[2177]! } + public func Channel_AdminLog_EmptyFilterQueryText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2179]!, self._r[2179]!, [_0]) + } + public var SettingsSearch_Synonyms_EditProfile_Bio: String { return self._s[2180]! } + public func Date_ChatDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2182]!, self._r[2182]!, [_1, _2]) + } + public var WallpaperSearch_ColorPrefix: String { return self._s[2183]! } + public func Message_ForwardedPsa_covid(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2184]!, self._r[2184]!, [_0]) + } + public var Conversation_RestrictedMedia: String { return self._s[2186]! } + public var Group_MessageVideoUpdated: String { return self._s[2187]! } + public var NetworkUsageSettings_ResetStatsConfirmation: String { return self._s[2188]! } + public var GroupInfo_DeleteAndExit: String { return self._s[2189]! } + public var TwoFactorSetup_Email_Action: String { return self._s[2190]! } + public var Media_ShareThisVideo: String { return self._s[2192]! } + public var DialogList_Replies: String { return self._s[2193]! } + public func Conversation_Moderate_DeleteAllMessages(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2194]!, self._r[2194]!, [_0]) + } + public var CheckoutInfo_ShippingInfoAddress1: String { return self._s[2195]! } + public var Watch_Suggestion_OnMyWay: String { return self._s[2196]! } + public var CheckoutInfo_ShippingInfoAddress2: String { return self._s[2197]! } + public func PUSH_PINNED_POLL(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2198]!, self._r[2198]!, [_1, _2]) + } + public func GroupInfo_InvitationLinkAcceptChannel(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2199]!, self._r[2199]!, [_0]) + } + public var Channel_EditAdmin_PermissinAddAdminOff: String { return self._s[2200]! } + public var Conversation_WalletRequiredSetup: String { return self._s[2201]! } + public var ChatAdmins_AllMembersAreAdminsOnHelp: String { return self._s[2202]! } + public var ChatList_Search_NoResultsFitlerMedia: String { return self._s[2203]! } + public var Channel_Members_InviteLink: String { return self._s[2204]! } + public var Conversation_TapAndHoldToRecord: String { return self._s[2205]! } + public var Wallet_Info_Receive: String { return self._s[2206]! } + public var WatchRemote_AlertText: String { return self._s[2207]! } + public func Channel_DiscussionGroup_PrivateChannelLink(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2208]!, self._r[2208]!, [_1, _2]) + } + public var Conversation_Pin: String { return self._s[2209]! } + public var InfoPlist_NSMicrophoneUsageDescription: String { return self._s[2210]! } + public var Stickers_RemoveFromFavorites: String { return self._s[2211]! } + public func Notification_PinnedPollMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2212]!, self._r[2212]!, [_0]) + } + public var Appearance_AppIconFilled: String { return self._s[2213]! } + public var StickerPack_ErrorNotFound: String { return self._s[2214]! } + public func Channel_AdminLog_MessageRestrictedName(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2215]!, self._r[2215]!, [_1]) + } + public var Passport_Identity_AddIdentityCard: String { return self._s[2216]! } + public func PUSH_CHANNEL_MESSAGE_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2217]!, self._r[2217]!, [_1]) + } + public var Call_Camera: String { return self._s[2218]! } + public var GroupInfo_InviteLink_RevokeAlert_Text: String { return self._s[2219]! } + public var Group_Location_Info: String { return self._s[2220]! } + public var Watch_LastSeen_WithinAMonth: String { return self._s[2221]! } + public var UserInfo_NotificationsDefaultEnabled: String { return self._s[2222]! } + public func DialogList_PinLimitError(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2223]!, self._r[2223]!, [_0]) + } + public var Weekday_Yesterday: String { return self._s[2224]! } + public var TwoStepAuth_SetupPasswordEnterPasswordNew: String { return self._s[2225]! } + public var ArchivedPacksAlert_Title: String { return self._s[2226]! } + public var PeerInfo_PaneMembers: String { return self._s[2227]! } + public var PhotoEditor_SelectCoverFrame: String { return self._s[2228]! } + public var ContactInfo_PhoneLabelMain: String { return self._s[2229]! } + public func Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2230]!, self._r[2230]!, [_1, _2, _3]) + } + public var TwoFactorSetup_EmailVerification_ChangeAction: String { return self._s[2231]! } + public var Channel_DiscussionGroup: String { return self._s[2232]! } + public var EditTheme_Edit_Preview_IncomingReplyName: String { return self._s[2233]! } + public var Channel_EditAdmin_PermissionsHeader: String { return self._s[2235]! } + public var VoiceOver_MessageContextForward: String { return self._s[2236]! } + public var SocksProxySetup_TypeNone: String { return self._s[2237]! } + public var CreatePoll_MultipleChoiceQuizAlert: String { return self._s[2239]! } + public var ProfilePhoto_OpenInEditor: String { return self._s[2241]! } + public var WallpaperSearch_ColorPurple: String { return self._s[2242]! } + public var ChatListFolder_IncludeChatsTitle: String { return self._s[2243]! } + public var Group_Username_InvalidTooShort: String { return self._s[2244]! } + public func Login_EmailPhoneBody(_ _0: String, _ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2245]!, self._r[2245]!, [_0, _1, _2]) + } + public var Passport_Language_tk: String { return self._s[2246]! } + public var ConvertToSupergroup_Title: String { return self._s[2247]! } + public var Channel_BanUser_PermissionEmbedLinks: String { return self._s[2248]! } + public var Cache_KeepMediaHelp: String { return self._s[2249]! } + public var Channel_Management_Title: String { return self._s[2250]! } + public func PUSH_MESSAGE_PHOTO_SECRET(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2251]!, self._r[2251]!, [_1]) + } + public var Conversation_ForwardChats: String { return self._s[2252]! } + public var Passport_Language_bg: String { return self._s[2253]! } + public var SocksProxySetup_TypeSocks: String { return self._s[2254]! } + public var Permissions_PrivacyPolicy: String { return self._s[2255]! } + public var VoiceOver_Chat_YourMusic: String { return self._s[2256]! } + public var SettingsSearch_Synonyms_Notifications_ResetAllNotifications: String { return self._s[2257]! } + public var Conversation_EmptyGifPanelPlaceholder: String { return self._s[2258]! } + public var Conversation_ContextMenuOpenChannel: String { return self._s[2259]! } + public var Activity_UploadingVideo: String { return self._s[2260]! } + public var PrivacyPolicy_AgeVerificationAgree: String { return self._s[2262]! } + public var Wallet_Sending_Text: String { return self._s[2263]! } + public var SocksProxySetup_Credentials: String { return self._s[2265]! } + public var Preview_SaveGif: String { return self._s[2266]! } + public var Cache_Photos: String { return self._s[2267]! } + public var Conversation_ContextMenuCancelEditing: String { return self._s[2268]! } + public var Wallet_Intro_NotNow: String { return self._s[2269]! } + public var Contacts_FailedToSendInvitesMessage: String { return self._s[2270]! } + public var Passport_Language_lt: String { return self._s[2271]! } + public var Passport_DeleteDocument: String { return self._s[2272]! } + public var GroupInfo_SetGroupPhotoStop: String { return self._s[2273]! } + public var AccessDenied_VideoMessageMicrophone: String { return self._s[2274]! } + public func PeopleNearby_VisibleUntil(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2275]!, self._r[2275]!, [_0]) + } + public var AccessDenied_VideoCallCamera: String { return self._s[2276]! } + public func Channel_AdminLog_MessageDeleted(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2277]!, self._r[2277]!, [_0]) + } + public var PhotoEditor_SharpenTool: String { return self._s[2278]! } + public func PUSH_CHANNEL_MESSAGE_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2279]!, self._r[2279]!, [_1]) + } + public var DialogList_Unpin: String { return self._s[2280]! } + public var Stickers_NoStickersFound: String { return self._s[2281]! } + public var UserInfo_AddContact: String { return self._s[2283]! } + public func AddContact_SharedContactExceptionInfo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2285]!, self._r[2285]!, [_0]) + } + public func Notification_PinnedLocationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2286]!, self._r[2286]!, [_0]) + } + public var CallFeedback_VideoReasonDistorted: String { return self._s[2287]! } + public var Tour_Text2: String { return self._s[2288]! } + public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[2290]! } + public var Paint_Delete: String { return self._s[2292]! } + public var SettingsSearch_Synonyms_Notifications_InAppNotificationsVibrate: String { return self._s[2293]! } + public func PrivacySettings_LastSeenEverybodyMinus(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2295]!, self._r[2295]!, [_0]) + } + public var Privacy_Calls_NeverAllow_Title: String { return self._s[2296]! } + public var Notification_CallOutgoingShort: String { return self._s[2297]! } + public var Checkout_PasswordEntry_Title: String { return self._s[2298]! } + public var Channel_AdminLogFilter_AdminsAll: String { return self._s[2299]! } + public var Notification_MessageLifetime1m: String { return self._s[2300]! } + public var Wallet_TransactionInfo_CommentHeader: String { return self._s[2302]! } + public var BlockedUsers_AddNew: String { return self._s[2303]! } + public var Wallet_Intro_CreateErrorText: String { return self._s[2304]! } + public var FastTwoStepSetup_EmailSection: String { return self._s[2305]! } + public var Settings_SaveEditedPhotos: String { return self._s[2306]! } + public var GroupInfo_GroupNamePlaceholder: String { return self._s[2307]! } + public var Channel_AboutItem: String { return self._s[2308]! } + public var GroupInfo_InviteLink_RevokeLink: String { return self._s[2309]! } + public var Privacy_Calls_P2PNever: String { return self._s[2311]! } + public var Wallet_Weekday_Yesterday: String { return self._s[2312]! } + public var Passport_Language_uk: String { return self._s[2313]! } + public var NetworkUsageSettings_Wifi: String { return self._s[2314]! } + public var Conversation_Moderate_Report: String { return self._s[2315]! } + public var Wallpaper_ResetWallpapersConfirmation: String { return self._s[2316]! } + public var VoiceOver_Chat_SeenByRecipients: String { return self._s[2317]! } + public var Permissions_SiriText_v0: String { return self._s[2318]! } + public var Theme_Colors_Background: String { return self._s[2319]! } + public var Notification_CallMissed: String { return self._s[2320]! } + public var Stats_ZoomOut: String { return self._s[2321]! } + public var Profile_AddToExisting: String { return self._s[2322]! } + public var Passport_FieldAddressUploadHelp: String { return self._s[2325]! } + public var Undo_DeletedChannel: String { return self._s[2326]! } + public func Channel_AdminLog_MessagePinned(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2327]!, self._r[2327]!, [_0]) + } + public var Login_ResetAccountProtected_TimerTitle: String { return self._s[2328]! } + public var Map_LiveLocationGroupDescription: String { return self._s[2329]! } + public var Passport_InfoFAQ_URL: String { return self._s[2330]! } + public var IntentsSettings_SuggestedChats: String { return self._s[2332]! } + public func PUSH_MESSAGE_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2333]!, self._r[2333]!, [_1]) + } + public var State_connecting: String { return self._s[2334]! } + public var Passport_Identity_Country: String { return self._s[2335]! } + public var Passport_PasswordDescription: String { return self._s[2336]! } + public var ChatList_PsaLabel_covid: String { return self._s[2337]! } + public func PUSH_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2338]!, self._r[2338]!, [_1]) + } + public var Contacts_AddPeopleNearby: String { return self._s[2339]! } + public var OwnershipTransfer_SetupTwoStepAuth: String { return self._s[2340]! } + public var ClearCache_Description: String { return self._s[2341]! } + public var Localization_LanguageName: String { return self._s[2342]! } + public func UserInfo_UnblockConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2343]!, self._r[2343]!, [_0]) + } + public var ChatList_TabIconFoldersTooltipEmptyFolders: String { return self._s[2344]! } + public var UserInfo_CreateNewContact: String { return self._s[2345]! } + public var Channel_Stickers_NotFound: String { return self._s[2346]! } + public var Watch_Message_Poll: String { return self._s[2347]! } + public var Privacy_Forwards_WhoCanForward: String { return self._s[2348]! } + public func Notification_Kicked(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2349]!, self._r[2349]!, [_0, _1]) + } + public var Login_InfoDeletePhoto: String { return self._s[2350]! } + public var Appearance_ThemePreview_ChatList_6_Name: String { return self._s[2351]! } + public var InstantPage_FeedbackButton: String { return self._s[2352]! } + public var Appearance_PreviewReplyText: String { return self._s[2353]! } + public var Passport_FieldPhoneHelp: String { return self._s[2354]! } + public var Group_ErrorAddTooMuchBots: String { return self._s[2355]! } + public var Media_SendingOptionsTooltip: String { return self._s[2356]! } + public var ScheduledMessages_ScheduledOnline: String { return self._s[2357]! } + public var Notifications_Badge: String { return self._s[2358]! } + public var VoiceOver_Chat_VideoMessage: String { return self._s[2359]! } + public var TwoStepAuth_RecoveryCodeExpired: String { return self._s[2360]! } + public var Wallet_Configuration_ApplyErrorTitle: String { return self._s[2361]! } + public func Notification_PinnedPhotoMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2363]!, self._r[2363]!, [_0]) + } + public var Wallet_Info_Send: String { return self._s[2364]! } + public var Passport_InfoLearnMore: String { return self._s[2365]! } + public var EnterPasscode_EnterTitle: String { return self._s[2366]! } + public var Appearance_EditTheme: String { return self._s[2367]! } + public var EditTheme_Expand_BottomInfo: String { return self._s[2368]! } + public var Stats_FollowersTitle: String { return self._s[2369]! } + public var Passport_Identity_SurnamePlaceholder: String { return self._s[2370]! } + public var Channel_Subscribers_Title: String { return self._s[2371]! } + public var Group_ErrorSupergroupConversionNotPossible: String { return self._s[2372]! } + public func Wallet_Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2373]!, self._r[2373]!, [_1, _2, _3]) + } + public var EditTheme_ThemeTemplateAlertTitle: String { return self._s[2374]! } + public var Wallet_Intro_CreateWallet: String { return self._s[2375]! } + public var Conversation_AddToReadingList: String { return self._s[2376]! } + public var EditTheme_Create_Preview_IncomingText: String { return self._s[2377]! } + public func Notifications_ExceptionsChangeSound(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2378]!, self._r[2378]!, [_0]) + } + public var Group_AdminLog_EmptyText: String { return self._s[2379]! } + public var Passport_Identity_EditInternalPassport: String { return self._s[2380]! } + public var Wallet_Sending_Title: String { return self._s[2381]! } + public var Watch_Location_Current: String { return self._s[2382]! } + public var PrivacyPolicy_Title: String { return self._s[2383]! } + public var Privacy_GroupsAndChannels_CustomHelp: String { return self._s[2390]! } + public var Channel_TypeSetup_Title: String { return self._s[2393]! } + public var Appearance_PreviewReplyAuthor: String { return self._s[2394]! } + public var Passport_Language_ja: String { return self._s[2395]! } + public var ReportPeer_ReasonSpam: String { return self._s[2396]! } + public var Privacy_PaymentsClearInfoHelp: String { return self._s[2397]! } + public var Conversation_EditingMessageMediaEditCurrentPhoto: String { return self._s[2399]! } + public var Channel_AdminLog_ChangeInfo: String { return self._s[2400]! } + public var ChatListFolder_NameNonContacts: String { return self._s[2401]! } + public var Call_Audio: String { return self._s[2402]! } + public var PhotoEditor_CurvesGreen: String { return self._s[2403]! } + public var Wallet_Updated_JustNow: String { return self._s[2404]! } + public var ChatList_Search_NoResultsFitlerFiles: String { return self._s[2405]! } + public var Settings_PrivacySettings: String { return self._s[2406]! } + public var Stats_Followers: String { return self._s[2407]! } + public var Notifications_AddExceptionTitle: String { return self._s[2408]! } + public var TwoFactorSetup_Password_Title: String { return self._s[2409]! } + public var ChannelMembers_WhoCanAddMembersAllHelp: String { return self._s[2410]! } + public var OldChannels_NoticeText: String { return self._s[2411]! } + public var Conversation_SavedMessages: String { return self._s[2412]! } + public func Conversation_PeerNearbyTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2414]!, self._r[2414]!, [_1, _2]) + } + public var Passport_Address_TypeResidentialAddress: String { return self._s[2415]! } + public func Wallet_Info_TransactionBlockchainFee(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2416]!, self._r[2416]!, [_0]) + } + public var Appearance_ThemeNightBlue: String { return self._s[2417]! } + public var Notification_ChannelInviterSelf: String { return self._s[2418]! } + public var Watch_UserInfo_Service: String { return self._s[2420]! } + public var ChatList_Context_Back: String { return self._s[2421]! } + public var Passport_Email_Title: String { return self._s[2422]! } + public var Wallet_Month_ShortDecember: String { return self._s[2423]! } + public var Stats_GroupTopAdmin_Promote: String { return self._s[2424]! } + public func PUSH_PINNED_INVOICE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2425]!, self._r[2425]!, [_1]) + } + public var Conversation_UnsupportedMedia: String { return self._s[2426]! } + public var Passport_Address_OneOfTypePassportRegistration: String { return self._s[2427]! } + public var Privacy_TopPeersHelp: String { return self._s[2429]! } + public var Privacy_Forwards_AlwaysLink: String { return self._s[2430]! } + public var Notifications_Badge_CountUnreadMessages_InfoOn: String { return self._s[2431]! } + public var Permissions_NotificationsTitle_v0: String { return self._s[2432]! } + public var Notification_PassportValueProofOfAddress: String { return self._s[2433]! } + public var Map_Map: String { return self._s[2434]! } + public var WallpaperSearch_ColorBlue: String { return self._s[2435]! } + public var Privacy_Calls_CustomShareHelp: String { return self._s[2436]! } + public var PhotoEditor_BlurToolRadial: String { return self._s[2437]! } + public var ChatList_Search_FilterMusic: String { return self._s[2438]! } + public var SettingsSearch_Synonyms_Data_AutoplayGifs: String { return self._s[2439]! } + public var Privacy_PaymentsClear_ShippingInfo: String { return self._s[2440]! } + public var Settings_LogoutConfirmationTitle: String { return self._s[2442]! } + public func PUSH_CHANNEL_MESSAGE_VIDEOS(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2443]!, self._r[2443]!, [_1, _2]) + } + public func Notification_ChangedGroupPhoto(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2444]!, self._r[2444]!, [_0]) + } + public var Channel_Username_RevokeExistingUsernamesInfo: String { return self._s[2445]! } + public var Group_Username_CreatePublicLinkHelp: String { return self._s[2446]! } + public var GroupInfo_Location: String { return self._s[2448]! } + public var Passport_Language_ka: String { return self._s[2449]! } + public func TwoStepAuth_SetupPendingEmail(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2450]!, self._r[2450]!, [_0]) + } + public var Conversation_ContextMenuOpenChannelProfile: String { return self._s[2451]! } + public var ScheduledMessages_ClearAllConfirmation: String { return self._s[2454]! } + public var DialogList_SearchSectionRecent: String { return self._s[2455]! } + public var Passport_Address_OneOfTypeTemporaryRegistration: String { return self._s[2456]! } + public var Conversation_Timer_Send: String { return self._s[2457]! } + public var ChatState_Updating: String { return self._s[2459]! } + public var ChannelMembers_WhoCanAddMembers: String { return self._s[2460]! } + public var ChannelInfo_DeleteGroup: String { return self._s[2461]! } + public var TwoStepAuth_RecoveryFailed: String { return self._s[2462]! } + public var Channel_OwnershipTransfer_EnterPassword: String { return self._s[2463]! } + public var ChatList_Search_NoResults: String { return self._s[2464]! } + public var ChatListFolderSettings_AddRecommended: String { return self._s[2466]! } + public var ChangePhoneNumberCode_Called: String { return self._s[2467]! } + public var PeerInfo_GroupAboutItem: String { return self._s[2468]! } + public var Wallet_Info_YourBalance: String { return self._s[2470]! } + public func LiveLocationUpdated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2471]!, self._r[2471]!, [_0]) + } + public var PrivacySettings_AuthSessions: String { return self._s[2472]! } + public var Passport_Address_Postcode: String { return self._s[2473]! } + public var VoiceOver_Chat_YourVideoMessage: String { return self._s[2474]! } + public var Passport_Address_Street2Placeholder: String { return self._s[2475]! } + public var Group_Location_Title: String { return self._s[2476]! } + public var SettingsSearch_Synonyms_Data_AutoDownloadReset: String { return self._s[2477]! } + public var PeopleNearby_UsersEmpty: String { return self._s[2478]! } + public var SettingsSearch_Synonyms_Data_Title: String { return self._s[2480]! } + public func Checkout_PasswordEntry_Text(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2482]!, self._r[2482]!, [_0]) + } + public var Proxy_TooltipUnavailable: String { return self._s[2483]! } + public var Map_Search: String { return self._s[2484]! } + public var AutoDownloadSettings_TypeContacts: String { return self._s[2485]! } + public var Conversation_SearchByName_Prefix: String { return self._s[2486]! } + public func Channel_AdminLog_MessageToggleSignaturesOff(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2487]!, self._r[2487]!, [_0]) + } + public var TwoStepAuth_EmailAddSuccess: String { return self._s[2488]! } + public var ProfilePhoto_MainPhoto: String { return self._s[2489]! } + public var SettingsSearch_Synonyms_Notifications_InAppNotificationsSound: String { return self._s[2490]! } + public var SharedMedia_EmptyMusicText: String { return self._s[2491]! } + public var ChatSettings_AutoDownloadPhotos: String { return self._s[2492]! } + public var NetworkUsageSettings_BytesReceived: String { return self._s[2493]! } + public var Channel_AdminLog_EmptyText: String { return self._s[2494]! } + public var Channel_BanUser_PermissionSendMessages: String { return self._s[2495]! } + public var Undo_ChatDeletedForBothSides: String { return self._s[2496]! } + public var Notifications_GroupNotifications: String { return self._s[2497]! } + public var Wallet_Configuration_BlockchainNameChangedTitle: String { return self._s[2498]! } + public var Wallet_AccessDenied_Title: String { return self._s[2499]! } + public var AccessDenied_SaveMedia: String { return self._s[2500]! } + public var GroupInfo_LabelOwner: String { return self._s[2501]! } + public var Passport_Language_id: String { return self._s[2502]! } + public var ChatSettings_AutoDownloadTitle: String { return self._s[2503]! } + public var Conversation_UnpinMessageAlert: String { return self._s[2504]! } + public func LiveLocationUpdated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2505]!, self._r[2505]!, [_0]) + } + public func Call_RemoteVideoPaused(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2506]!, self._r[2506]!, [_0]) + } + public var TwoFactorSetup_Done_Text: String { return self._s[2507]! } + public func LastSeen_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2508]!, self._r[2508]!, [_0]) + } + public var Wallet_Words_Title: String { return self._s[2509]! } + public var NetworkUsageSettings_BytesSent: String { return self._s[2510]! } + public var OwnershipTransfer_Transfer: String { return self._s[2511]! } + public func Notification_Exceptions_Sound(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2512]!, self._r[2512]!, [_0]) + } + public var Passport_Language_pt: String { return self._s[2513]! } + public var PrivacySettings_WebSessions: String { return self._s[2514]! } + public var PrivacyPolicy_DeclineDeleteNow: String { return self._s[2516]! } + public var TwoFactorSetup_Hint_Title: String { return self._s[2517]! } + public func Notification_Joined(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2518]!, self._r[2518]!, [_0]) + } + public var Group_Username_RemoveExistingUsernamesInfo: String { return self._s[2519]! } + public var PrivacyLastSeenSettings_CustomShareSettings_Delete: String { return self._s[2520]! } + public var AutoNightTheme_Scheduled: String { return self._s[2521]! } + public var CreatePoll_ExplanationHeader: String { return self._s[2522]! } + public var Calls_TabTitle: String { return self._s[2523]! } + public var ChatList_UndoArchiveHiddenText: String { return self._s[2524]! } + public var Notification_VideoCallCanceled: String { return self._s[2525]! } + public var Login_CodeSentInternal: String { return self._s[2526]! } + public var SettingsSearch_Synonyms_Proxy_AddProxy: String { return self._s[2527]! } + public var Call_RecordingDisabledMessage: String { return self._s[2529]! } + public var AutoDownloadSettings_TypeChannels: String { return self._s[2531]! } + public var Wallet_Configuration_BlockchainNameChangedProceed: String { return self._s[2532]! } + public var Channel_Info_Stickers: String { return self._s[2533]! } + public var Passport_DeleteAddressConfirmation: String { return self._s[2534]! } + public func Conversation_PeerNearbyDistance(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2535]!, self._r[2535]!, [_1, _2]) + } + public var ChannelMembers_WhoCanAddMembers_Admins: String { return self._s[2536]! } + public func Call_StatusOngoing(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2537]!, self._r[2537]!, [_0]) + } + public var Passport_DiscardMessageTitle: String { return self._s[2538]! } + public var Localization_LanguageOther: String { return self._s[2539]! } + public var Conversation_EncryptionCanceled: String { return self._s[2540]! } + public var ChatSettings_AutomaticPhotoDownload: String { return self._s[2541]! } + public func Notification_SecretChatMessageScreenshot(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2543]!, self._r[2543]!, [_0]) + } + public var Target_InviteToGroupErrorAlreadyInvited: String { return self._s[2545]! } + public var SocksProxySetup_SavedProxies: String { return self._s[2546]! } + public func ApplyLanguage_ChangeLanguageAlreadyActive(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2547]!, self._r[2547]!, [_1]) + } + public var Conversation_ScamWarning: String { return self._s[2548]! } + public var Channel_AdminLog_InfoPanelAlertTitle: String { return self._s[2549]! } + public var LocalGroup_Title: String { return self._s[2550]! } + public var SettingsSearch_Synonyms_Notifications_MessageNotificationsAlert: String { return self._s[2551]! } + public var SettingsSearch_Synonyms_Privacy_PasscodeAndFaceId: String { return self._s[2552]! } + public var Login_PhoneFloodError: String { return self._s[2553]! } + public var Username_InvalidTaken: String { return self._s[2555]! } + public var SocksProxySetup_AddProxy: String { return self._s[2557]! } + public var PrivacyLastSeenSettings_WhoCanSeeMyTimestamp: String { return self._s[2558]! } + public var MediaPicker_UngroupDescription: String { return self._s[2559]! } + public var Login_CodeExpired: String { return self._s[2560]! } + public var Localization_ChooseLanguage: String { return self._s[2561]! } + public var Checkout_NewCard_PostcodePlaceholder: String { return self._s[2562]! } + public func ChangePhone_ErrorOccupied(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2563]!, self._r[2563]!, [_0]) + } + public func Channel_DiscussionGroup_HeaderSet(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2564]!, self._r[2564]!, [_0]) + } + public var ReportPeer_ReasonOther_Title: String { return self._s[2566]! } + public var Conversation_ScheduleMessage_Title: String { return self._s[2567]! } + public var PeerInfo_ButtonDiscuss: String { return self._s[2568]! } + public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedPublicGroups: String { return self._s[2569]! } + public var Call_StatusNoAnswer: String { return self._s[2570]! } + public var ScheduledMessages_DeleteMany: String { return self._s[2572]! } + public var Channel_DiscussionGroupInfo: String { return self._s[2573]! } + public var Conversation_UnarchiveDone: String { return self._s[2574]! } + public var LogoutOptions_AddAccountText: String { return self._s[2575]! } + public var Message_PinnedContactMessage: String { return self._s[2576]! } + public func FileSize_GB(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2578]!, self._r[2578]!, [_0]) + } + public var Stats_GroupLanguagesTitle: String { return self._s[2579]! } + public var Passport_FieldAddressHelp: String { return self._s[2580]! } + public func Passport_FieldOneOf_Or(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2581]!, self._r[2581]!, [_1, _2]) + } + public var ChatSettings_OpenLinksIn: String { return self._s[2583]! } + public var TwoFactorSetup_Hint_SkipAction: String { return self._s[2584]! } + public var Message_Photo: String { return self._s[2585]! } + public var MediaPicker_AddCaption: String { return self._s[2587]! } + public var LogoutOptions_Title: String { return self._s[2588]! } + public func PUSH_PINNED_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2589]!, self._r[2589]!, [_1]) + } + public var Conversation_StatusKickedFromGroup: String { return self._s[2590]! } + public var Channel_AdminLogFilter_AdminsTitle: String { return self._s[2591]! } + public var ChatList_DeleteSavedMessagesConfirmationTitle: String { return self._s[2592]! } + public var Channel_AdminLogFilter_Title: String { return self._s[2593]! } + public var Passport_Address_TypeRentalAgreementUploadScan: String { return self._s[2594]! } + public func Wallet_Info_TransactionDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2595]!, self._r[2595]!, [_1, _2]) + } + public var Compose_GroupTokenListPlaceholder: String { return self._s[2596]! } + public var Wallet_Words_NotDoneResponse: String { return self._s[2597]! } + public var Notifications_MessageNotificationsExceptions: String { return self._s[2598]! } + public var ChannelIntro_Title: String { return self._s[2599]! } + public var Stickers_Install: String { return self._s[2600]! } + public func VoiceOver_Chat_FileFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2601]!, self._r[2601]!, [_0]) + } + public var EditTheme_Create_Preview_IncomingReplyText: String { return self._s[2602]! } + public var Conversation_SwipeToReplyHintTitle: String { return self._s[2604]! } + public var Settings_Username: String { return self._s[2607]! } + public var FastTwoStepSetup_Title: String { return self._s[2608]! } + public var Notifications_Badge_CountUnreadMessages_InfoOff: String { return self._s[2609]! } + public var SettingsSearch_Synonyms_Privacy_Title: String { return self._s[2610]! } + public var Passport_Identity_IssueDatePlaceholder: String { return self._s[2611]! } + public var CallFeedback_ReasonEcho: String { return self._s[2612]! } + public func Time_MonthOfYear_m1(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2613]!, self._r[2613]!, [_0]) + } + public var Conversation_OpenBotLinkTitle: String { return self._s[2614]! } + public var SocksProxySetup_Title: String { return self._s[2615]! } + public var CallFeedback_Success: String { return self._s[2616]! } + public var WallpaperPreview_SwipeTopText: String { return self._s[2618]! } + public var InstantPage_AutoNightTheme: String { return self._s[2620]! } + public var Watch_Conversation_Reply: String { return self._s[2621]! } + public var WallpaperPreview_Pattern: String { return self._s[2622]! } + public var CheckoutInfo_ReceiverInfoEmail: String { return self._s[2623]! } + public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[2624]! } + public func Conversation_DeleteMessagesFor(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2625]!, self._r[2625]!, [_0]) + } + public var AutoDownloadSettings_TypeGroupChats: String { return self._s[2626]! } + public var DialogList_SavedMessagesTooltip: String { return self._s[2628]! } + public var Update_Title: String { return self._s[2629]! } + public var Conversation_ShareMyPhoneNumber: String { return self._s[2630]! } + public func Wallet_WordCheck_Text(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2631]!, self._r[2631]!, [_1, _2, _3]) + } + public var Wallet_SecureStorageReset_BiometryTouchId: String { return self._s[2632]! } + public var WallpaperPreview_CropTopText: String { return self._s[2634]! } + public var Channel_EditMessageErrorGeneric: String { return self._s[2635]! } + public var AccessDenied_LocationAlwaysDenied: String { return self._s[2636]! } + public var ChatListFolder_DiscardCancel: String { return self._s[2637]! } + public var Message_PinnedPhotoMessage: String { return self._s[2638]! } + public var Appearance_ThemeDayClassic: String { return self._s[2639]! } + public var SocksProxySetup_ProxySocks5: String { return self._s[2640]! } + public var AccessDenied_Wallpapers: String { return self._s[2645]! } + public func Channel_AdminLog_MessageChangedGroupAbout(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2646]!, self._r[2646]!, [_0]) + } + public var Weekday_Sunday: String { return self._s[2647]! } + public var SettingsSearch_Synonyms_Privacy_GroupsAndChannels: String { return self._s[2649]! } + public var PeopleNearby_MakeVisibleDescription: String { return self._s[2650]! } + public var AccessDenied_LocationDisabled: String { return self._s[2651]! } + public var Tour_Text3: String { return self._s[2652]! } + public var AuthSessions_AddDevice_ScanTitle: String { return self._s[2653]! } + public func Time_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2654]!, self._r[2654]!, [_0]) + } + public var Privacy_SecretChatsLinkPreviewsHelp: String { return self._s[2655]! } + public var Conversation_ClearCache: String { return self._s[2656]! } + public var StickerPacksSettings_ArchivedMasks_Info: String { return self._s[2657]! } + public var ChatList_Tabs_AllChats: String { return self._s[2658]! } + public var DialogList_RecentTitlePeople: String { return self._s[2659]! } + public var Stickers_AddToFavorites: String { return self._s[2660]! } + public var ChatList_Context_RemoveFromFolder: String { return self._s[2661]! } + public var Settings_RemoveVideo: String { return self._s[2662]! } + public var PhotoEditor_CropAspectRatioSquare: String { return self._s[2663]! } + public var ConversationProfile_LeaveDeleteAndExit: String { return self._s[2664]! } + public var VoiceOver_Chat_YourFile: String { return self._s[2665]! } + public var SettingsSearch_Synonyms_Privacy_Forwards: String { return self._s[2666]! } + public var Group_OwnershipTransfer_ErrorPrivacyRestricted: String { return self._s[2667]! } + public var Channel_AdminLog_AddMembers: String { return self._s[2668]! } + public var Map_SendThisLocation: String { return self._s[2670]! } + public var TwoStepAuth_EmailSkipAlert: String { return self._s[2672]! } + public var IntentsSettings_SuggestedChatsPrivateChats: String { return self._s[2673]! } + public var CloudStorage_Title: String { return self._s[2674]! } + public var TwoFactorSetup_Password_Action: String { return self._s[2675]! } + public var TwoStepAuth_ConfirmationText: String { return self._s[2676]! } + public var Passport_Address_EditTemporaryRegistration: String { return self._s[2678]! } + public var Undo_LeftGroup: String { return self._s[2679]! } + public var Conversation_StopLiveLocation: String { return self._s[2681]! } + public var NotificationSettings_ShowNotificationsFromAccountsSection: String { return self._s[2682]! } + public var Message_PinnedInvoice: String { return self._s[2683]! } + public var ApplyLanguage_LanguageNotSupportedError: String { return self._s[2684]! } + public func PUSH_CHAT_MESSAGE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2685]!, self._r[2685]!, [_1, _2]) + } + public func Notification_PinnedAudioMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2686]!, self._r[2686]!, [_0]) + } + public var Weekday_Tuesday: String { return self._s[2687]! } + public var ChangePhoneNumberCode_Code: String { return self._s[2688]! } + public var VoiceOver_Chat_YourMessage: String { return self._s[2689]! } + public var Calls_CallTabDescription: String { return self._s[2690]! } + public var SocksProxySetup_UseProxy: String { return self._s[2692]! } + public var SettingsSearch_Synonyms_Stickers_Title: String { return self._s[2693]! } + public var PasscodeSettings_AlphanumericCode: String { return self._s[2694]! } + public var VoiceOver_Chat_YourVideo: String { return self._s[2695]! } + public var ChannelMembers_WhoCanAddMembersAdminsHelp: String { return self._s[2697]! } + public var SettingsSearch_Synonyms_Privacy_DeleteAccountIfAwayFor: String { return self._s[2698]! } + public var Exceptions_AddToExceptions: String { return self._s[2699]! } + public var UserInfo_Title: String { return self._s[2700]! } + public var Passport_DeleteDocumentConfirmation: String { return self._s[2702]! } + public var ChatList_Unmute: String { return self._s[2704]! } + public var SettingsSearch_Synonyms_Privacy_Data_ContactsSync: String { return self._s[2705]! } + public var Stats_GroupTopPostersTitle: String { return self._s[2706]! } + public var Username_CheckingUsername: String { return self._s[2707]! } + public var WallpaperColors_SetCustomColor: String { return self._s[2708]! } + public var AuthSessions_AddedDeviceTerminate: String { return self._s[2712]! } + public var Privacy_ProfilePhoto_CustomHelp: String { return self._s[2713]! } + public var Settings_ChangePhoneNumber: String { return self._s[2714]! } + public var PeerInfo_PaneLinks: String { return self._s[2715]! } + public var Appearance_ThemePreview_ChatList_1_Text: String { return self._s[2718]! } + public var Channel_EditAdmin_PermissionInviteSubscribers: String { return self._s[2720]! } + public var LogoutOptions_ChangePhoneNumberText: String { return self._s[2721]! } + public var VoiceOver_Media_PlaybackPause: String { return self._s[2722]! } + public var Wallet_RestoreFailed_Title: String { return self._s[2723]! } + public var Stats_FollowersBySourceTitle: String { return self._s[2725]! } + public func Conversation_ScheduleMessage_SendOn(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2726]!, self._r[2726]!, [_0, _1]) + } + public var Compose_NewEncryptedChatTitle: String { return self._s[2727]! } + public func ShareFileTip_Text(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2732]!, self._r[2732]!, [_0]) + } + public func PUSH_MESSAGE_AUDIO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2733]!, self._r[2733]!, [_1]) + } + public var Group_Setup_BasicHistoryHiddenHelp: String { return self._s[2734]! } + public func TwoStepAuth_RecoveryEmailUnavailable(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2735]!, self._r[2735]!, [_0]) + } + public var Conversation_OpenBotLinkOpen: String { return self._s[2736]! } + public var VoiceOver_Chat_RecordModeVoiceMessage: String { return self._s[2737]! } + public var PrivacySettings_LastSeen: String { return self._s[2739]! } + public var SettingsSearch_Synonyms_Privacy_Passcode: String { return self._s[2740]! } + public var Theme_Colors_Proceed: String { return self._s[2741]! } + public var UserInfo_ScamBotWarning: String { return self._s[2742]! } + public var LogoutOptions_LogOut: String { return self._s[2743]! } + public var Conversation_SendMessage: String { return self._s[2744]! } + public var Passport_Address_Region: String { return self._s[2746]! } + public var MediaPicker_CameraRoll: String { return self._s[2748]! } + public func VoiceOver_Chat_ForwardedFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2749]!, self._r[2749]!, [_0]) + } + public var Call_ReportSend: String { return self._s[2751]! } + public var Month_ShortJune: String { return self._s[2752]! } + public var AutoDownloadSettings_GroupChats: String { return self._s[2753]! } + public func Channel_AdminLog_CaptionEdited(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2755]!, self._r[2755]!, [_0]) + } + public var TwoStepAuth_DisableSuccess: String { return self._s[2756]! } + public var Cache_KeepMedia: String { return self._s[2757]! } + public func Date_ChatDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2758]!, self._r[2758]!, [_1, _2, _3]) + } + public var Wallet_Alert_OK: String { return self._s[2759]! } + public var Appearance_LargeEmoji: String { return self._s[2760]! } + public func Notification_NewAuthDetected(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String, _ _6: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2761]!, self._r[2761]!, [_1, _2, _3, _4, _5, _6]) + } + public var Chat_AttachmentMultipleForwardDisabled: String { return self._s[2762]! } + public var Wallet_Navigation_Close: String { return self._s[2763]! } + public var Call_CameraConfirmationText: String { return self._s[2764]! } + public func AuthSessions_AppUnofficial(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2766]!, self._r[2766]!, [_0]) + } + public var VoiceOver_MessageContextReport: String { return self._s[2768]! } + public var ChatListFolder_ExcludeChatsTitle: String { return self._s[2769]! } + public var NotificationsSound_Tritone: String { return self._s[2771]! } + public var Wallet_Configuration_BlockchainIdHeader: String { return self._s[2772]! } + public var Notifications_InAppNotificationsPreview: String { return self._s[2775]! } + public var Stats_GroupTopAdmin_Actions: String { return self._s[2776]! } + public var PeerInfo_AddToContacts: String { return self._s[2777]! } + public var AccessDenied_Title: String { return self._s[2778]! } + public var Tour_Title1: String { return self._s[2779]! } + public var VoiceOver_AttachMedia: String { return self._s[2780]! } + public func SharedMedia_SearchNoResultsDescription(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2782]!, self._r[2782]!, [_0]) + } + public var Chat_Gifs_SavedSectionHeader: String { return self._s[2783]! } + public var LogoutOptions_ChangePhoneNumberTitle: String { return self._s[2784]! } + public func Passport_Scans_ScanIndex(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2785]!, self._r[2785]!, [_0]) + } + public var Channel_AdminLog_MessagePreviousLink: String { return self._s[2786]! } + public var Wallet_Send_AddressText: String { return self._s[2787]! } + public var OldChannels_Title: String { return self._s[2788]! } + public var LoginPassword_FloodError: String { return self._s[2789]! } + public var Checkout_ErrorPaymentFailed: String { return self._s[2791]! } + public func Time_MonthOfYear_m7(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2792]!, self._r[2792]!, [_0]) + } + public var VoiceOver_Media_PlaybackPlay: String { return self._s[2795]! } + public var Passport_CorrectErrors: String { return self._s[2797]! } + public func PUSH_CHAT_PHOTO_EDITED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2798]!, self._r[2798]!, [_1, _2]) + } + public var ChatListFolderSettings_Title: String { return self._s[2799]! } + public func AutoDownloadSettings_UpToFor(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2800]!, self._r[2800]!, [_1, _2]) + } + public var PhotoEditor_HighlightsTool: String { return self._s[2801]! } + public var Contacts_NotRegisteredSection: String { return self._s[2804]! } + public func PUSH_PINNED_DOC(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2805]!, self._r[2805]!, [_1]) + } + public var User_DeletedAccount: String { return self._s[2806]! } + public var Conversation_ViewContactDetails: String { return self._s[2807]! } + public var WebSearch_GIFs: String { return self._s[2808]! } + public var ChatList_DeleteSavedMessagesConfirmationAction: String { return self._s[2809]! } + public var Appearance_PreviewOutgoingText: String { return self._s[2810]! } + public var Calls_CallTabTitle: String { return self._s[2811]! } + public func LastSeen_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2812]!, self._r[2812]!, [_0]) + } + public var Channel_Status: String { return self._s[2813]! } + public var Conversation_SendMessageErrorGroupRestricted: String { return self._s[2815]! } + public var VoiceOver_Chat_OptionSelected: String { return self._s[2816]! } + public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsAlert: String { return self._s[2817]! } + public func ClearCache_Success(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2818]!, self._r[2818]!, [_0, _1]) + } + public var Passport_Identity_ExpiryDateNone: String { return self._s[2820]! } + public var Your_cards_expiration_month_is_invalid: String { return self._s[2822]! } + public var Month_ShortDecember: String { return self._s[2823]! } + public var Username_Help: String { return self._s[2824]! } + public var Login_InfoAvatarAdd: String { return self._s[2825]! } + public var Month_ShortMay: String { return self._s[2826]! } + public var DialogList_UnknownPinLimitError: String { return self._s[2827]! } + public var PasscodeSettings_AutoLock_IfAwayFor_5hours: String { return self._s[2828]! } + public var TwoStepAuth_EnabledSuccess: String { return self._s[2829]! } + public var Weekday_ShortSunday: String { return self._s[2830]! } + public var Channel_Username_InvalidTooShort: String { return self._s[2831]! } + public var AuthSessions_TerminateSession: String { return self._s[2832]! } + public var Passport_Identity_FilesTitle: String { return self._s[2833]! } + public func Notification_PinnedRoundMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2834]!, self._r[2834]!, [_0]) + } + public var PeopleNearby_MakeVisible: String { return self._s[2836]! } + public func Conversation_RestrictedMediaTimed(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2837]!, self._r[2837]!, [_0]) + } + public func Notification_MessageLifetimeChanged(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2838]!, self._r[2838]!, [_1, _2]) + } + public func GroupInfo_AddParticipantConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2839]!, self._r[2839]!, [_0]) + } + public var PrivacyPolicy_DeclineDeclineAndDelete: String { return self._s[2840]! } + public var Conversation_ContextMenuForward: String { return self._s[2841]! } + public func PUSH_CHAT_MESSAGE_QUIZ(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2843]!, self._r[2843]!, [_1, _2, _3]) + } + public var Notification_GroupInviterSelf: String { return self._s[2844]! } + public var Privacy_Forwards_NeverLink: String { return self._s[2845]! } + public var AuthSessions_CurrentSession: String { return self._s[2846]! } + public var Passport_Address_EditPassportRegistration: String { return self._s[2847]! } + public var ChannelInfo_DeleteChannelConfirmation: String { return self._s[2848]! } + public var ChatSearch_ResultsTooltip: String { return self._s[2850]! } + public var CheckoutInfo_Pay: String { return self._s[2851]! } + public func Channel_AdminLog_MessageChangedChannelUsername(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2853]!, self._r[2853]!, [_0]) + } + public var GroupInfo_AddParticipant: String { return self._s[2854]! } + public var GroupPermission_ApplyAlertAction: String { return self._s[2855]! } + public var ChatList_UndoArchiveText1: String { return self._s[2856]! } + public var Localization_LanguageCustom: String { return self._s[2857]! } + public var SettingsSearch_Synonyms_Passport: String { return self._s[2858]! } + public var Settings_UsernameEmpty: String { return self._s[2859]! } + public var Settings_FAQ_URL: String { return self._s[2860]! } + public var Common_Select: String { return self._s[2862]! } + public var Notification_MessageLifetimeRemovedOutgoing: String { return self._s[2863]! } + public var Notification_PassportValueAddress: String { return self._s[2864]! } + public var Conversation_MessageDialogDelete: String { return self._s[2865]! } + public var Map_OpenInYandexNavigator: String { return self._s[2867]! } + public var DialogList_SearchSectionDialogs: String { return self._s[2868]! } + public var AccessDenied_Contacts: String { return self._s[2869]! } + public var SettingsSearch_Synonyms_Privacy_Data_DeleteDrafts: String { return self._s[2871]! } + public var Passport_ScanPassportHelp: String { return self._s[2872]! } + public var ChatListFolder_NameChannels: String { return self._s[2873]! } + public var Appearance_ThemePreview_Chat_5_Text: String { return self._s[2874]! } + public func Channel_OwnershipTransfer_TransferCompleted(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2875]!, self._r[2875]!, [_1, _2]) + } + public var Checkout_ErrorInvoiceAlreadyPaid: String { return self._s[2876]! } + public var Conversation_GifTooltip: String { return self._s[2877]! } + public var Passport_Identity_TypeDriversLicenseUploadScan: String { return self._s[2879]! } + public var AutoDownloadSettings_OffForAll: String { return self._s[2880]! } + public var Privacy_GroupsAndChannels_InviteToChannelMultipleError: String { return self._s[2881]! } + public var AutoDownloadSettings_PreloadVideo: String { return self._s[2882]! } + public var CreatePoll_Quiz: String { return self._s[2883]! } + public var TwoFactorSetup_Email_Placeholder: String { return self._s[2884]! } + public var Watch_Message_Invoice: String { return self._s[2885]! } + public var Settings_AddAnotherAccount_Help: String { return self._s[2886]! } + public var Watch_Message_Unsupported: String { return self._s[2887]! } + public func Call_CameraOff(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2889]!, self._r[2889]!, [_0]) + } + public var AuthSessions_TerminateOtherSessions: String { return self._s[2890]! } + public var CreatePoll_AllOptionsAdded: String { return self._s[2892]! } + public var TwoStepAuth_RecoveryEmailTitle: String { return self._s[2893]! } + public var Call_IncomingVoiceCall: String { return self._s[2894]! } + public func Channel_AdminLog_MessageTransferedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2895]!, self._r[2895]!, [_1, _2]) + } + public var PrivacySettings_DeleteAccountHelp: String { return self._s[2896]! } + public var Passport_Address_TypePassportRegistrationUploadScan: String { return self._s[2897]! } + public var Group_EditAdmin_RankOwnerPlaceholder: String { return self._s[2898]! } + public var Group_ErrorAccessDenied: String { return self._s[2899]! } + public var PasscodeSettings_HelpTop: String { return self._s[2900]! } + public var Watch_ChatList_NoConversationsTitle: String { return self._s[2901]! } + public var AddContact_SharedContactException: String { return self._s[2902]! } + public var AccessDenied_MicrophoneRestricted: String { return self._s[2903]! } + public var Privacy_TopPeers: String { return self._s[2904]! } + public var Web_OpenExternal: String { return self._s[2905]! } + public var Group_ErrorSendRestrictedStickers: String { return self._s[2906]! } + public var Channel_Management_LabelAdministrator: String { return self._s[2907]! } + public func ChangePhoneNumberCode_CallTimer(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2908]!, self._r[2908]!, [_0]) + } + public var Permissions_Skip: String { return self._s[2909]! } + public var Notifications_GroupNotificationsExceptions: String { return self._s[2910]! } + public var PeopleNearby_Title: String { return self._s[2911]! } + public var GroupInfo_SharedMediaNone: String { return self._s[2912]! } + public func PUSH_MESSAGE_GEOLIVE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2914]!, self._r[2914]!, [_1]) + } + public var Profile_MessageLifetime1w: String { return self._s[2915]! } + public func Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2916]!, self._r[2916]!, [_1, _2, _3]) + } + public var WebBrowser_DefaultBrowser: String { return self._s[2917]! } + public var EditTheme_Edit_BottomInfo: String { return self._s[2919]! } + public var Privacy_Forwards_Preview: String { return self._s[2920]! } + public var Settings_EditAccount: String { return self._s[2921]! } + public func Conversation_RestrictedInlineTimed(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2922]!, self._r[2922]!, [_0]) + } + public var TwoFactorSetup_Intro_Title: String { return self._s[2923]! } + public func Channel_AdminLog_MessagePromotedName(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2925]!, self._r[2925]!, [_1]) + } + public var PeerInfo_ButtonVideoCall: String { return self._s[2926]! } + public func DialogList_SingleUploadingPhotoSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2927]!, self._r[2927]!, [_0]) + } + public var Login_InfoHelp: String { return self._s[2928]! } + public var Notification_SecretChatMessageScreenshotSelf: String { return self._s[2929]! } + public var Profile_MessageLifetime1d: String { return self._s[2930]! } + public var Group_UpgradeConfirmation: String { return self._s[2931]! } + public func PUSH_PINNED_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2932]!, self._r[2932]!, [_1, _2]) + } + public var Appearance_RemoveThemeColor: String { return self._s[2933]! } + public var Channel_AdminLog_TitleSelectedEvents: String { return self._s[2934]! } + public var Wallet_Configuration_BlockchainIdInfo: String { return self._s[2935]! } + public func Call_AnsweringWithAccount(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2936]!, self._r[2936]!, [_0]) + } + public var UserInfo_BotSettings: String { return self._s[2937]! } + public func Notification_ChannelInviter(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2939]!, self._r[2939]!, [_0]) + } + public var Permissions_ContactsText_v0: String { return self._s[2940]! } + public var SettingsSearch_Synonyms_Privacy_TwoStepAuth: String { return self._s[2942]! } + public var SharedMedia_SearchNoResults: String { return self._s[2944]! } + public func Login_EmailPhoneSubject(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2946]!, self._r[2946]!, [_0]) + } + public func Conversation_ShareMyPhoneNumber_StatusSuccess(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2948]!, self._r[2948]!, [_0]) + } + public var ReportPeer_ReasonOther_Placeholder: String { return self._s[2949]! } + public var ContactInfo_PhoneLabelHomeFax: String { return self._s[2950]! } + public var Call_AudioRouteHeadphones: String { return self._s[2951]! } + public func PUSH_AUTH_UNKNOWN(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2952]!, self._r[2952]!, [_1]) + } + public var Passport_Identity_FilesView: String { return self._s[2953]! } + public var TwoStepAuth_SetupEmail: String { return self._s[2954]! } + public var Widget_ApplicationStartRequired: String { return self._s[2955]! } + public var PhotoEditor_Original: String { return self._s[2956]! } + public var Call_YourMicrophoneOff: String { return self._s[2957]! } + public var Permissions_ContactsAllow_v0: String { return self._s[2958]! } + public var Notification_Exceptions_PreviewAlwaysOn: String { return self._s[2959]! } + public var PrivacyPolicy_Decline: String { return self._s[2960]! } + public var SettingsSearch_Synonyms_ChatFolders: String { return self._s[2961]! } + public var TwoStepAuth_PasswordRemoveConfirmation: String { return self._s[2962]! } + public var ChatListFolder_IncludeSectionInfo: String { return self._s[2963]! } + public func Map_DirectionsDriveEta(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2964]!, self._r[2964]!, [_0]) + } + public var Passport_Identity_Name: String { return self._s[2965]! } + public var WallpaperPreview_PatternTitle: String { return self._s[2967]! } + public var VoiceOver_Chat_RecordModeVideoMessage: String { return self._s[2968]! } + public var WallpaperSearch_ColorOrange: String { return self._s[2970]! } + public var Appearance_ThemePreview_ChatList_5_Name: String { return self._s[2971]! } + public var GroupInfo_Permissions_SlowmodeInfo: String { return self._s[2972]! } + public var Your_cards_security_code_is_invalid: String { return self._s[2973]! } + public var IntentsSettings_ResetAll: String { return self._s[2974]! } + public var SettingsSearch_Synonyms_Calls_CallTab: String { return self._s[2976]! } + public var Group_EditAdmin_TransferOwnership: String { return self._s[2977]! } + public var Notification_Exceptions_Add: String { return self._s[2978]! } + public var Cache_Help: String { return self._s[2979]! } + public var Call_AudioRouteMute: String { return self._s[2980]! } + public var VoiceOver_Chat_YourVoiceMessage: String { return self._s[2981]! } + public var SocksProxySetup_ProxyEnabled: String { return self._s[2982]! } + public func ApplyLanguage_UnsufficientDataText(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2983]!, self._r[2983]!, [_1]) } public func Call_CallInProgressMessage(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4341]!, self._r[4341]!, [_1, _2]) + return formatWithArgumentRanges(self._s[2984]!, self._r[2984]!, [_1, _2]) } - public var SaveIncomingPhotosSettings_From: String { return self._s[4343]! } - public var VoiceOver_Navigation_Search: String { return self._s[4344]! } - public var Map_LiveLocationTitle: String { return self._s[4345]! } - public var Login_InfoAvatarAdd: String { return self._s[4346]! } - public var Passport_Identity_FilesView: String { return self._s[4347]! } - public var ChatListFolderSettings_Title: String { return self._s[4348]! } - public var UserInfo_GenericPhoneLabel: String { return self._s[4349]! } - public var Privacy_Calls_NeverAllow: String { return self._s[4350]! } - public var VoiceOver_Chat_File: String { return self._s[4351]! } - public var Wallet_Settings_DeleteWalletInfo: String { return self._s[4352]! } - public func Contacts_AddPhoneNumber(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4353]!, self._r[4353]!, [_0]) + public var AutoDownloadSettings_VideoMessagesTitle: String { return self._s[2985]! } + public var Channel_BanUser_PermissionAddMembers: String { return self._s[2986]! } + public var Contacts_MemberSearchSectionTitleGroup: String { return self._s[2987]! } + public func Wallet_Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2988]!, self._r[2988]!, [_1, _2, _3]) } - public var ChatList_EmptyChatList: String { return self._s[4355]! } - public var ContactInfo_PhoneNumberHidden: String { return self._s[4356]! } - public var TwoStepAuth_ConfirmationText: String { return self._s[4357]! } - public var ChatSettings_AutomaticVideoMessageDownload: String { return self._s[4358]! } - public func PUSH_CHAT_MESSAGE_VIDEOS(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4359]!, self._r[4359]!, [_1, _2, _3]) + public var TwoStepAuth_RecoveryCodeHelp: String { return self._s[2989]! } + public var ClearCache_StorageFree: String { return self._s[2990]! } + public func DialogList_SingleRecordingVideoMessageSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2991]!, self._r[2991]!, [_0]) } - public var Channel_AdminLogFilter_AdminsAll: String { return self._s[4360]! } - public var Wallet_Intro_CreateErrorText: String { return self._s[4361]! } - public var Tour_Title2: String { return self._s[4362]! } - public var Wallet_Sent_ViewWallet: String { return self._s[4363]! } - public var Stats_GroupMessagesTitle: String { return self._s[4364]! } - public var Conversation_FileOpenIn: String { return self._s[4365]! } - public var Checkout_ErrorPrecheckoutFailed: String { return self._s[4366]! } - public var Wallet_Send_ErrorInvalidAddress: String { return self._s[4367]! } - public var Wallpaper_Set: String { return self._s[4368]! } - public var Passport_Identity_Translations: String { return self._s[4371]! } - public func Channel_AdminLog_MessageChangedChannelAbout(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4372]!, self._r[4372]!, [_0]) + public var Privacy_Forwards_CustomHelp: String { return self._s[2992]! } + public var Group_ErrorAddTooMuchAdmins: String { return self._s[2994]! } + public var DialogList_Typing: String { return self._s[2995]! } + public func Login_EmailCodeSubject(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[2996]!, self._r[2996]!, [_0]) } - public var Channel_LeaveChannel: String { return self._s[4374]! } - public func PINNED_INVOICE(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4375]!, self._r[4375]!, [_1]) + public var Target_SelectGroup: String { return self._s[2997]! } + public var AuthSessions_IncompleteAttempts: String { return self._s[2998]! } + public var TwoStepAuth_EmailChangeSuccess: String { return self._s[2999]! } + public func Settings_CheckPhoneNumberTitle(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3000]!, self._r[3000]!, [_0]) } - public var SettingsSearch_Synonyms_Proxy_AddProxy: String { return self._s[4377]! } - public var PhotoEditor_HighlightsTint: String { return self._s[4378]! } - public var MessagePoll_LabelPoll: String { return self._s[4379]! } - public var Passport_Email_Delete: String { return self._s[4380]! } - public var Conversation_Mute: String { return self._s[4382]! } - public var Channel_AddBotAsAdmin: String { return self._s[4383]! } - public var Channel_AdminLog_CanSendMessages: String { return self._s[4385]! } - public var Wallet_Configuration_BlockchainNameChangedText: String { return self._s[4386]! } - public var ChatSettings_IntentsSettings: String { return self._s[4388]! } - public var Channel_Management_LabelOwner: String { return self._s[4389]! } - public func Notification_PassportValuesSentMessage(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4390]!, self._r[4390]!, [_1, _2]) + public var Channel_AdminLog_CanSendMessages: String { return self._s[3001]! } + public var TwoFactorSetup_EmailVerification_Title: String { return self._s[3002]! } + public var ChatSettings_TextSize: String { return self._s[3003]! } + public var Channel_AdminLogFilter_EventsEditedMessages: String { return self._s[3005]! } + public var Map_SendThisPlace: String { return self._s[3006]! } + public var Login_PhoneNumberAlreadyAuthorized: String { return self._s[3007]! } + public var ContactInfo_BirthdayLabel: String { return self._s[3008]! } + public var Call_ShareStats: String { return self._s[3009]! } + public var ChatList_UndoArchiveRevealedText: String { return self._s[3011]! } + public var Notifications_GroupNotificationsPreview: String { return self._s[3012]! } + public var Settings_Support: String { return self._s[3013]! } + public var GroupInfo_ChannelListNamePlaceholder: String { return self._s[3014]! } + public func EmptyGroupInfo_Line1(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3016]!, self._r[3016]!, [_0]) } - public var Calls_CallTabDescription: String { return self._s[4391]! } - public var Passport_Identity_NativeNameHelp: String { return self._s[4392]! } - public var Common_No: String { return self._s[4393]! } - public var Weekday_Sunday: String { return self._s[4394]! } - public var Notification_Reply: String { return self._s[4395]! } - public var Conversation_ViewMessage: String { return self._s[4396]! } - public func Checkout_SavePasswordTimeoutAndFaceId(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4397]!, self._r[4397]!, [_0]) - } - public func Map_LiveLocationPrivateDescription(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4398]!, self._r[4398]!, [_0]) + public var Watch_Conversation_GroupInfo: String { return self._s[3017]! } + public var Tour_Text4: String { return self._s[3018]! } + public var PasscodeSettings_AutoLock: String { return self._s[3020]! } + public var Channel_BanList_BlockedTitle: String { return self._s[3021]! } + public var Bot_DescriptionTitle: String { return self._s[3022]! } + public var Map_LocationTitle: String { return self._s[3023]! } + public var ChatListFolder_ExcludeSectionInfo: String { return self._s[3024]! } + public func Notification_MessageLifetimeChangedOutgoing(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3025]!, self._r[3025]!, [_1]) } + public var Login_EmailNotConfiguredError: String { return self._s[3026]! } + public var AutoDownloadSettings_LimitBySize: String { return self._s[3027]! } + public var PrivacySettings_LastSeenNobody: String { return self._s[3028]! } + public var Permissions_CellularDataText_v0: String { return self._s[3029]! } + public var Conversation_EncryptionProcessing: String { return self._s[3030]! } + public var GroupPermission_Delete: String { return self._s[3031]! } + public var Contacts_SortByName: String { return self._s[3032]! } + public var TwoStepAuth_RecoveryUnavailable: String { return self._s[3033]! } + public var Compose_ChannelTokenListPlaceholder: String { return self._s[3034]! } + public var Group_Management_AddModeratorHelp: String { return self._s[3036]! } + public var SettingsSearch_Synonyms_EditProfile_Logout: String { return self._s[3037]! } + public var Forward_ErrorPublicPollDisabledInChannels: String { return self._s[3038]! } public func Wallet_Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4399]!, self._r[4399]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[3040]!, self._r[3040]!, [_1, _2, _3]) } - public var SettingsSearch_Synonyms_EditProfile_AddAccount: String { return self._s[4400]! } - public var Wallet_Send_Title: String { return self._s[4401]! } - public var Message_PinnedDocumentMessage: String { return self._s[4402]! } - public var Wallet_Info_RefreshErrorText: String { return self._s[4403]! } - public var DialogList_TabTitle: String { return self._s[4405]! } - public var ChatSettings_AutoPlayTitle: String { return self._s[4406]! } - public var Passport_FieldEmail: String { return self._s[4407]! } - public var Conversation_UnpinMessageAlert: String { return self._s[4408]! } - public var Passport_Address_TypeBankStatement: String { return self._s[4409]! } - public var Wallet_SecureStorageReset_Title: String { return self._s[4410]! } - public var Passport_Identity_ExpiryDate: String { return self._s[4411]! } - public var Privacy_Calls_P2P: String { return self._s[4412]! } - public func CancelResetAccount_Success(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4414]!, self._r[4414]!, [_0]) + public var CallFeedback_IncludeLogsInfo: String { return self._s[3041]! } + public func PUSH_CHANNEL_MESSAGE_QUIZ(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3042]!, self._r[3042]!, [_1]) } - public var SocksProxySetup_UseForCallsHelp: String { return self._s[4415]! } - public func PUSH_CHAT_ALBUM(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4416]!, self._r[4416]!, [_1, _2]) + public func SecretVideo_NotViewedYet(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3043]!, self._r[3043]!, [_0]) } - public var Stickers_ClearRecent: String { return self._s[4417]! } - public var EnterPasscode_ChangeTitle: String { return self._s[4418]! } - public var TwoFactorSetup_Email_Title: String { return self._s[4419]! } - public var Passport_InfoText: String { return self._s[4420]! } - public var Checkout_NewCard_SaveInfoEnableHelp: String { return self._s[4421]! } - public func Login_InvalidPhoneEmailSubject(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4422]!, self._r[4422]!, [_0]) + public var ChatList_Context_Delete: String { return self._s[3044]! } + public var PrivacyPhoneNumberSettings_CustomDisabledHelp: String { return self._s[3045]! } + public var Conversation_Processing: String { return self._s[3046]! } + public var TwoStepAuth_EmailCodeExpired: String { return self._s[3047]! } + public var ChatSettings_Stickers: String { return self._s[3048]! } + public var AppleWatch_ReplyPresetsHelp: String { return self._s[3049]! } + public var Passport_Language_cs: String { return self._s[3050]! } + public var GroupInfo_InvitationLinkGroupFull: String { return self._s[3052]! } + public var Conversation_Contact: String { return self._s[3053]! } + public var Passport_Identity_ReverseSideHelp: String { return self._s[3054]! } + public var SocksProxySetup_PasteFromClipboard: String { return self._s[3055]! } + public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[3056]! } + public var Theme_Unsupported: String { return self._s[3057]! } + public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[3058]! } + public var Privacy_TopPeersWarning: String { return self._s[3059]! } + public func UserInfo_BlockConfirmationTitle(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3060]!, self._r[3060]!, [_0]) } - public func Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4423]!, self._r[4423]!, [_1, _2, _3]) + public var Conversation_SilentBroadcastTooltipOn: String { return self._s[3061]! } + public var TwoStepAuth_RemovePassword: String { return self._s[3062]! } + public var Settings_CheckPhoneNumberText: String { return self._s[3063]! } + public var PeopleNearby_Users: String { return self._s[3064]! } + public var Appearance_TextSize_UseSystem: String { return self._s[3065]! } + public var Settings_SetProfilePhoto: String { return self._s[3066]! } + public var Conversation_ContextMenuBan: String { return self._s[3067]! } + public var KeyCommand_ScrollUp: String { return self._s[3068]! } + public var Settings_ChatSettings: String { return self._s[3070]! } + public func PUSH_CHAT_MESSAGE_VIDEO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3071]!, self._r[3071]!, [_1, _2]) } - public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedChannels: String { return self._s[4424]! } - public var ScheduledMessages_PollUnavailable: String { return self._s[4425]! } - public var VoiceOver_Navigation_Compose: String { return self._s[4426]! } - public var Passport_Identity_EditDriversLicense: String { return self._s[4427]! } - public var Conversation_TapAndHoldToRecord: String { return self._s[4429]! } - public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedChats: String { return self._s[4430]! } - public func Notification_CallTimeFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4431]!, self._r[4431]!, [_1, _2]) + public var Stats_GroupTopInvitersTitle: String { return self._s[3072]! } + public var Passport_Phone_EnterOtherNumber: String { return self._s[3073]! } + public var Passport_Identity_MiddleNamePlaceholder: String { return self._s[3075]! } + public var Passport_Address_OneOfTypeBankStatement: String { return self._s[3076]! } + public var Stats_GroupTopPoster_Promote: String { return self._s[3077]! } + public var Cache_Title: String { return self._s[3078]! } + public var Clipboard_SendPhoto: String { return self._s[3079]! } + public var Notifications_ExceptionsMessagePlaceholder: String { return self._s[3081]! } + public var TwoStepAuth_EnterPasswordForgot: String { return self._s[3082]! } + public var WatchRemote_AlertTitle: String { return self._s[3083]! } + public var Appearance_ReduceMotion: String { return self._s[3084]! } + public func PUSH_CHAT_MESSAGE_ROUND(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3087]!, self._r[3087]!, [_1, _2]) } - public var Channel_EditAdmin_PermissionInviteViaLink: String { return self._s[4434]! } - public var ChatSettings_OpenLinksIn: String { return self._s[4435]! } - public var Map_HomeAndWorkTitle: String { return self._s[4436]! } - public func Generic_OpenHiddenLinkAlert(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4438]!, self._r[4438]!, [_0]) + public var Notifications_PermissionsSuppressWarningText: String { return self._s[3088]! } + public var ChatList_UndoArchiveHiddenTitle: String { return self._s[3089]! } + public var Passport_Identity_TypePersonalDetails: String { return self._s[3090]! } + public var Wallet_TransactionInfo_CopyAddress: String { return self._s[3092]! } + public func Passport_Identity_UploadOneOfScan(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3093]!, self._r[3093]!, [_0]) } - public var DialogList_Unread: String { return self._s[4439]! } - public func PUSH_CHAT_MESSAGE_GIF(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4440]!, self._r[4440]!, [_1, _2]) + public var ChatListFolder_DiscardConfirmation: String { return self._s[3094]! } + public func Conversation_RestrictedStickersTimed(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3095]!, self._r[3095]!, [_0]) } - public var User_DeletedAccount: String { return self._s[4441]! } - public var ChatList_TabIconFoldersTooltipEmptyFolders: String { return self._s[4442]! } - public var OwnershipTransfer_SetupTwoStepAuth: String { return self._s[4443]! } + public var ChatState_WaitingForNetwork: String { return self._s[3096]! } + public var GroupInfo_Sound: String { return self._s[3097]! } + public var NotificationsSound_Telegraph: String { return self._s[3098]! } + public var NotificationsSound_Hello: String { return self._s[3099]! } + public var Passport_FieldIdentityDetailsHelp: String { return self._s[3100]! } + public var Wallet_Settings_BackupWallet: String { return self._s[3101]! } + public var Group_Members_AddMemberBotErrorNotAllowed: String { return self._s[3102]! } + public var Conversation_HoldForVideo: String { return self._s[3103]! } + public var Wallet_Configuration_ApplyErrorTextURLInvalidData: String { return self._s[3104]! } + public var Wallet_RestoreFailed_EnterWords: String { return self._s[3105]! } + public var Appearance_ShareTheme: String { return self._s[3106]! } + public var TwoStepAuth_SetupHint: String { return self._s[3107]! } + public var Wallet_Created_Text: String { return self._s[3110]! } + public var Stats_GrowthTitle: String { return self._s[3111]! } + public var GroupInfo_InviteLink_ShareLink: String { return self._s[3112]! } + public var Conversation_DefaultRestrictedMedia: String { return self._s[3113]! } + public var Channel_EditAdmin_PermissionPostMessages: String { return self._s[3114]! } + public var GroupPermission_NoSendMessages: String { return self._s[3116]! } + public var Conversation_SetReminder_Title: String { return self._s[3117]! } + public var Privacy_Calls_CustomHelp: String { return self._s[3118]! } + public var CheckoutInfo_ErrorPostcodeInvalid: String { return self._s[3119]! } + public func ClearCache_StorageTitle(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3120]!, self._r[3120]!, [_0]) + } + public var Undo_SecretChatDeleted: String { return self._s[3122]! } + public var PhotoEditor_ContrastTool: String { return self._s[3123]! } + public var Privacy_Forwards: String { return self._s[3124]! } + public var AuthSessions_LoggedInWithTelegram: String { return self._s[3125]! } + public var KeyCommand_SendMessage: String { return self._s[3127]! } + public func InstantPage_RelatedArticleAuthorAndDateTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3128]!, self._r[3128]!, [_1, _2]) + } + public var GroupPermission_NoSendGifs: String { return self._s[3129]! } + public var Wallet_Month_ShortJune: String { return self._s[3130]! } + public var Notification_MessageLifetime2s: String { return self._s[3131]! } + public var Message_Theme: String { return self._s[3132]! } + public var Conversation_Dice_u1F3AF: String { return self._s[3135]! } + public func DialogList_SinglePlayingGameSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3136]!, self._r[3136]!, [_0]) + } + public var Group_UpgradeNoticeHeader: String { return self._s[3138]! } + public var PeerInfo_BioExpand: String { return self._s[3139]! } + public var Passport_DeletePersonalDetails: String { return self._s[3140]! } + public var Widget_NoUsers: String { return self._s[3141]! } + public var TwoStepAuth_AddHintTitle: String { return self._s[3142]! } + public var Login_TermsOfServiceDecline: String { return self._s[3143]! } + public var CreatePoll_QuizTip: String { return self._s[3145]! } + public var Watch_LastSeen_WithinAWeek: String { return self._s[3146]! } + public var MessagePoll_SubmitVote: String { return self._s[3148]! } + public var ChatSettings_AutoDownloadEnabled: String { return self._s[3149]! } + public var Passport_Address_EditRentalAgreement: String { return self._s[3150]! } + public var Conversation_SearchByName_Placeholder: String { return self._s[3151]! } + public var Conversation_UpdateTelegram: String { return self._s[3152]! } + public func FileSize_KB(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3153]!, self._r[3153]!, [_0]) + } + public var UserInfo_About_Placeholder: String { return self._s[3154]! } + public var CallSettings_Always: String { return self._s[3155]! } + public var ChannelInfo_ScamChannelWarning: String { return self._s[3156]! } + public var Login_TermsOfServiceHeader: String { return self._s[3157]! } + public var KeyCommand_ChatInfo: String { return self._s[3158]! } + public var MessagePoll_LabelPoll: String { return self._s[3159]! } + public var Paint_Clear: String { return self._s[3160]! } + public var PeerInfo_ButtonMute: String { return self._s[3161]! } + public var LastSeen_WithinAWeek: String { return self._s[3162]! } + public var Passport_Identity_FrontSide: String { return self._s[3163]! } + public var Stickers_GroupStickers: String { return self._s[3164]! } + public var ChangePhoneNumberNumber_NumberPlaceholder: String { return self._s[3165]! } + public func Map_SearchNoResultsDescription(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3166]!, self._r[3166]!, [_0]) + } + public func PUSH_MESSAGE_GEO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3169]!, self._r[3169]!, [_1]) + } + public var SocksProxySetup_ProxyStatusConnected: String { return self._s[3170]! } + public var Chat_MultipleTextMessagesDisabled: String { return self._s[3171]! } + public func Notification_LeftChat(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3172]!, self._r[3172]!, [_0]) + } + public var Wallet_Send_AmountText: String { return self._s[3173]! } + public var WebSearch_SearchNoResults: String { return self._s[3175]! } + public var Channel_DiscussionGroup_Create: String { return self._s[3176]! } + public var Passport_Language_es: String { return self._s[3177]! } + public var EnterPasscode_EnterCurrentPasscode: String { return self._s[3178]! } + public var Wallet_Intro_Title: String { return self._s[3179]! } + public var Map_LiveLocationShowAll: String { return self._s[3180]! } + public var Cache_MaximumCacheSizeHelp: String { return self._s[3182]! } + public var Map_OpenInGoogleMaps: String { return self._s[3183]! } + public var CheckoutInfo_ErrorNameInvalid: String { return self._s[3185]! } + public var EditTheme_Create_BottomInfo: String { return self._s[3186]! } + public var PhotoEditor_BlurToolLinear: String { return self._s[3187]! } + public func Channel_AdminLog_MessageEdited(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3188]!, self._r[3188]!, [_0]) + } + public var Passport_Phone_Delete: String { return self._s[3189]! } + public var Channel_Username_CreatePrivateLinkHelp: String { return self._s[3190]! } + public var PrivacySettings_PrivacyTitle: String { return self._s[3191]! } + public var CheckoutInfo_ReceiverInfoNamePlaceholder: String { return self._s[3192]! } + public func EncryptionKey_Description(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3193]!, self._r[3193]!, [_1, _2]) + } + public var LogoutOptions_LogOutInfo: String { return self._s[3194]! } + public var Wallet_Month_GenAugust: String { return self._s[3195]! } + public var Cache_ByPeerHeader: String { return self._s[3196]! } + public var Username_InvalidCharacters: String { return self._s[3197]! } + public var Wallet_Qr_Title: String { return self._s[3199]! } + public var Checkout_ShippingAddress: String { return self._s[3200]! } + public func PUSH_CHAT_MESSAGE_GAME_SCORE(_ _1: String, _ _2: String, _ _3: String, _ _4: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3201]!, self._r[3201]!, [_1, _2, _3, _4]) + } + public var Conversation_AddContact: String { return self._s[3203]! } + public var Passport_Address_EditUtilityBill: String { return self._s[3204]! } + public var Message_Video: String { return self._s[3205]! } public func Watch_Time_ShortYesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4444]!, self._r[4444]!, [_0]) + return formatWithArgumentRanges(self._s[3206]!, self._r[3206]!, [_0]) } - public var UserInfo_NotificationsDefault: String { return self._s[4445]! } - public var SharedMedia_CategoryMedia: String { return self._s[4446]! } - public var SocksProxySetup_ProxyStatusUnavailable: String { return self._s[4447]! } - public var Channel_AdminLog_MessageRestrictedForever: String { return self._s[4448]! } - public var Watch_ChatList_Compose: String { return self._s[4449]! } - public var Notifications_MessageNotificationsExceptionsHelp: String { return self._s[4450]! } - public var AutoDownloadSettings_Delimeter: String { return self._s[4451]! } - public var Watch_Microphone_Access: String { return self._s[4452]! } - public var Cache_MaximumCacheSize: String { return self._s[4453]! } - public var Group_Setup_HistoryHeader: String { return self._s[4454]! } - public var Map_SetThisLocation: String { return self._s[4455]! } - public var Appearance_ThemePreview_Chat_2_ReplyName: String { return self._s[4456]! } - public var Activity_UploadingPhoto: String { return self._s[4457]! } - public var Conversation_Edit: String { return self._s[4459]! } - public var Group_ErrorSendRestrictedMedia: String { return self._s[4460]! } - public var Login_TermsOfServiceDecline: String { return self._s[4461]! } - public var Message_PinnedContactMessage: String { return self._s[4462]! } - public func Channel_AdminLog_MessageRestrictedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4463]!, self._r[4463]!, [_1, _2]) + public func Conversation_Megabytes(_ _0: Float) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3207]!, self._r[3207]!, ["\(_0)"]) + } + public var Passport_Language_km: String { return self._s[3208]! } + public func PUSH_MESSAGE_CHANNEL_MESSAGE_GAME_SCORE(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3209]!, self._r[3209]!, [_1, _2, _3]) + } + public var EmptyGroupInfo_Line4: String { return self._s[3210]! } + public var Conversation_SendMessageErrorTooMuchScheduled: String { return self._s[3212]! } + public var Notification_CallCanceledShort: String { return self._s[3213]! } + public var PhotoEditor_FadeTool: String { return self._s[3214]! } + public var Group_PublicLink_Info: String { return self._s[3215]! } + public var Contacts_DeselectAll: String { return self._s[3216]! } + public var Conversation_Moderate_Delete: String { return self._s[3217]! } + public var TwoStepAuth_RecoveryCodeInvalid: String { return self._s[3218]! } + public var NotificationsSound_Note: String { return self._s[3221]! } + public func Message_PaymentSent(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3222]!, self._r[3222]!, [_0]) + } + public var Appearance_ThemePreview_ChatList_7_Text: String { return self._s[3223]! } + public var Channel_EditAdmin_PermissionInviteViaLink: String { return self._s[3224]! } + public var DialogList_SearchSectionGlobal: String { return self._s[3225]! } + public var AccessDenied_Settings: String { return self._s[3226]! } + public var Passport_Identity_TypeIdentityCardUploadScan: String { return self._s[3227]! } + public var AuthSessions_EmptyTitle: String { return self._s[3228]! } + public var TwoStepAuth_PasswordChangeSuccess: String { return self._s[3229]! } + public var GroupInfo_GroupType: String { return self._s[3230]! } + public var Calls_Missed: String { return self._s[3231]! } + public var UserInfo_GenericPhoneLabel: String { return self._s[3232]! } + public var Passport_Language_uz: String { return self._s[3233]! } + public var Conversation_StopQuizConfirmationTitle: String { return self._s[3234]! } + public var PhotoEditor_BlurToolPortrait: String { return self._s[3235]! } + public var Map_ChooseLocationTitle: String { return self._s[3236]! } + public var Checkout_EnterPassword: String { return self._s[3237]! } + public var GroupInfo_ConvertToSupergroup: String { return self._s[3238]! } + public var AutoNightTheme_UpdateLocation: String { return self._s[3239]! } + public var NetworkUsageSettings_Title: String { return self._s[3240]! } + public var SettingsSearch_Synonyms_ChatSettings_IntentsSettings: String { return self._s[3241]! } + public var Message_PinnedLiveLocationMessage: String { return self._s[3242]! } + public var Compose_NewChannel: String { return self._s[3243]! } + public var Privacy_PaymentsClearInfo: String { return self._s[3245]! } + public func PUSH_MESSAGE_POLL(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3246]!, self._r[3246]!, [_1]) + } + public var Notification_Exceptions_AlwaysOn: String { return self._s[3247]! } + public var Privacy_GroupsAndChannels_WhoCanAddMe: String { return self._s[3248]! } + public var AutoNightTheme_AutomaticSection: String { return self._s[3251]! } + public var WallpaperSearch_ColorBrown: String { return self._s[3252]! } + public var Appearance_AppIconDefault: String { return self._s[3253]! } + public var Wallet_Month_GenJune: String { return self._s[3255]! } + public var StickerSettings_ContextInfo: String { return self._s[3256]! } + public var Channel_AddBotErrorNoRights: String { return self._s[3257]! } + public var Passport_FieldPhone: String { return self._s[3259]! } + public var Contacts_PermissionsTitle: String { return self._s[3260]! } + public var TwoFactorSetup_Email_SkipConfirmationSkip: String { return self._s[3261]! } + public func Notification_JoinedChat(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3262]!, self._r[3262]!, [_0]) + } + public var Bot_Unblock: String { return self._s[3263]! } + public var PasscodeSettings_SimplePasscode: String { return self._s[3264]! } + public var Passport_PasswordHelp: String { return self._s[3265]! } + public var Watch_Conversation_UserInfo: String { return self._s[3266]! } + public func Channel_AdminLog_MessageChangedGroupGeoLocation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3270]!, self._r[3270]!, [_0]) + } + public var State_Connecting: String { return self._s[3272]! } + public var Passport_Address_TypeTemporaryRegistration: String { return self._s[3273]! } + public var TextFormat_AddLinkPlaceholder: String { return self._s[3274]! } + public var Conversation_Dice_u1F3B2: String { return self._s[3275]! } + public func Call_StatusBar(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3276]!, self._r[3276]!, [_0]) + } + public var Conversation_SendingOptionsTooltip: String { return self._s[3277]! } + public var ChatList_UndoArchiveTitle: String { return self._s[3278]! } + public var ChatList_EmptyChatListNewMessage: String { return self._s[3279]! } + public var WallpaperSearch_ColorGreen: String { return self._s[3281]! } + public var PhotoEditor_BlurToolOff: String { return self._s[3282]! } + public var SocksProxySetup_PortPlaceholder: String { return self._s[3283]! } + public var Weekday_Saturday: String { return self._s[3284]! } + public var DialogList_Unread: String { return self._s[3285]! } + public var Watch_LastSeen_ALongTimeAgo: String { return self._s[3286]! } + public var Stats_GroupPosters: String { return self._s[3287]! } + public func PUSH_ENCRYPTION_REQUEST(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3288]!, self._r[3288]!, [_1]) + } + public func Target_ShareGameConfirmationGroup(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3291]!, self._r[3291]!, [_0]) + } + public var ReportPeer_ReasonChildAbuse: String { return self._s[3292]! } + public func Channel_AdminLog_MessageUnkickedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3293]!, self._r[3293]!, [_1, _2]) + } + public var InfoPlist_NSContactsUsageDescription: String { return self._s[3294]! } + public var AutoNightTheme_UseSunsetSunrise: String { return self._s[3296]! } + public var Channel_OwnershipTransfer_ChangeOwner: String { return self._s[3297]! } + public var Passport_Language_dv: String { return self._s[3298]! } + public var GroupPermission_AddSuccess: String { return self._s[3301]! } + public var Passport_Email_Help: String { return self._s[3302]! } + public var Call_ReportPlaceholder: String { return self._s[3303]! } + public var CreatePoll_AddOption: String { return self._s[3304]! } + public var MessagePoll_LabelAnonymousQuiz: String { return self._s[3305]! } + public var PeerInfo_ButtonLeave: String { return self._s[3306]! } + public var PhotoEditor_TiltShift: String { return self._s[3309]! } + public var SecretGif_Title: String { return self._s[3311]! } + public var PhotoEditor_QualityVeryLow: String { return self._s[3312]! } + public var SocksProxySetup_Connecting: String { return self._s[3313]! } + public var PrivacySettings_PasscodeAndFaceId: String { return self._s[3314]! } + public var ContactInfo_PhoneLabelWork: String { return self._s[3315]! } + public var Stats_GroupTopHoursTitle: String { return self._s[3316]! } + public var Compose_NewMessage: String { return self._s[3317]! } + public var NotificationsSound_Synth: String { return self._s[3318]! } + public var Conversation_FileOpenIn: String { return self._s[3319]! } + public var AutoDownloadSettings_WifiTitle: String { return self._s[3320]! } + public var UserInfo_SendMessage: String { return self._s[3321]! } + public var Checkout_PayWithFaceId: String { return self._s[3322]! } + public func Map_LiveLocationShortHour(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3323]!, self._r[3323]!, [_0]) + } + public var TextFormat_Strikethrough: String { return self._s[3324]! } + public var SettingsSearch_Synonyms_Notifications_DisplayNamesOnLockScreen: String { return self._s[3325]! } + public var Conversation_ViewChannel: String { return self._s[3326]! } + public func Message_ForwardedMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3327]!, self._r[3327]!, [_0]) + } + public var Channel_Stickers_Placeholder: String { return self._s[3328]! } + public var Channel_OwnershipTransfer_PasswordPlaceholder: String { return self._s[3329]! } + public var Camera_FlashAuto: String { return self._s[3330]! } + public var Conversation_EncryptedDescription1: String { return self._s[3331]! } + public var LocalGroup_Text: String { return self._s[3332]! } + public var SettingsSearch_Synonyms_Data_Storage_KeepMedia: String { return self._s[3333]! } + public var UserInfo_FirstNamePlaceholder: String { return self._s[3334]! } + public var Conversation_SendMessageErrorFlood: String { return self._s[3335]! } + public var Conversation_EncryptedDescription2: String { return self._s[3336]! } + public var Notification_GroupActivated: String { return self._s[3337]! } + public var LastSeen_Lately: String { return self._s[3338]! } + public var Conversation_EncryptedDescription3: String { return self._s[3339]! } + public var SettingsSearch_Synonyms_Privacy_ProfilePhoto: String { return self._s[3340]! } + public var Conversation_SwipeToReplyHintText: String { return self._s[3341]! } + public var Conversation_EncryptedDescription4: String { return self._s[3342]! } + public var SharedMedia_EmptyTitle: String { return self._s[3343]! } + public var Wallet_Configuration_Apply: String { return self._s[3344]! } + public var Appearance_CreateTheme: String { return self._s[3345]! } + public var Stats_SharesPerPost: String { return self._s[3346]! } + public var Contacts_TabTitle: String { return self._s[3347]! } + public var Weekday_ShortThursday: String { return self._s[3348]! } + public var MessageTimer_Forever: String { return self._s[3349]! } + public var ChatListFolder_CategoryArchived: String { return self._s[3350]! } + public var Channel_EditAdmin_PermissionDeleteMessages: String { return self._s[3351]! } + public var EditTheme_Create_TopInfo: String { return self._s[3353]! } + public var Month_GenDecember: String { return self._s[3354]! } + public var EnterPasscode_EnterPasscode: String { return self._s[3355]! } + public var SettingsSearch_Synonyms_Appearance_LargeEmoji: String { return self._s[3356]! } + public var PeopleNearby_CreateGroup: String { return self._s[3358]! } + public var Group_EditAdmin_PermissionChangeInfo: String { return self._s[3359]! } + public var Paint_ClearConfirm: String { return self._s[3360]! } + public var ChatList_ReadAll: String { return self._s[3361]! } + public var ChatSettings_IntentsSettings: String { return self._s[3362]! } + public var Passport_PassportInformation: String { return self._s[3364]! } + public var Login_CheckOtherSessionMessages: String { return self._s[3366]! } + public var PhotoEditor_ExposureTool: String { return self._s[3369]! } + public var Group_Username_CreatePrivateLinkHelp: String { return self._s[3370]! } + public var SettingsSearch_Synonyms_Watch: String { return self._s[3371]! } + public var Stats_GroupTopPoster_History: String { return self._s[3372]! } + public var UserInfo_AddPhone: String { return self._s[3373]! } + public var Media_SendWithTimer: String { return self._s[3375]! } + public var SettingsSearch_Synonyms_Notifications_Title: String { return self._s[3376]! } + public var Channel_EditAdmin_PermissionEnabledByDefault: String { return self._s[3377]! } + public var PasscodeSettings_AutoLock_Disabled: String { return self._s[3378]! } + public var ChatList_Context_Unarchive: String { return self._s[3380]! } + public func DialogList_LiveLocationSharingTo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3381]!, self._r[3381]!, [_0]) + } + public var BlockedUsers_Title: String { return self._s[3383]! } + public var TwoStepAuth_EmailPlaceholder: String { return self._s[3384]! } + public var Media_ShareThisPhoto: String { return self._s[3385]! } + public var Notifications_DisplayNamesOnLockScreen: String { return self._s[3386]! } + public var Conversation_FilePhotoOrVideo: String { return self._s[3387]! } + public var Appearance_ThemePreview_Chat_2_ReplyName: String { return self._s[3391]! } + public var CallFeedback_ReasonNoise: String { return self._s[3393]! } + public var WebBrowser_Title: String { return self._s[3394]! } + public func Checkout_SavePasswordTimeoutAndTouchId(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3395]!, self._r[3395]!, [_0]) + } + public var Notification_MessageLifetime5s: String { return self._s[3396]! } + public var Passport_Address_AddResidentialAddress: String { return self._s[3397]! } + public var Profile_MessageLifetime1m: String { return self._s[3398]! } + public var Stats_LoadingTitle: String { return self._s[3400]! } + public var Passport_ScanPassport: String { return self._s[3401]! } + public var Passport_Address_AddTemporaryRegistration: String { return self._s[3403]! } + public var Permissions_NotificationsAllow_v0: String { return self._s[3404]! } + public var Login_InvalidFirstNameError: String { return self._s[3405]! } + public var Undo_ChatCleared: String { return self._s[3407]! } + public func ApplyLanguage_ChangeLanguageUnofficialText(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3409]!, self._r[3409]!, [_1, _2]) } public func Login_PhoneBannedEmailBody(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4464]!, self._r[4464]!, [_1, _2, _3, _4, _5]) + return formatWithArgumentRanges(self._s[3410]!, self._r[3410]!, [_1, _2, _3, _4, _5]) } - public var Appearance_LargeEmoji: String { return self._s[4465]! } - public var TwoStepAuth_AdditionalPassword: String { return self._s[4467]! } - public var EditTheme_Edit_Preview_IncomingReplyText: String { return self._s[4468]! } - public func PUSH_CHAT_DELETE_YOU(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4469]!, self._r[4469]!, [_1, _2]) + public func PUSH_MESSAGE_FWD(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3411]!, self._r[3411]!, [_1]) } - public var Passport_Phone_EnterOtherNumber: String { return self._s[4470]! } - public var Message_PinnedPhotoMessage: String { return self._s[4471]! } - public var Passport_FieldPhone: String { return self._s[4472]! } - public var TwoStepAuth_RecoveryEmailAddDescription: String { return self._s[4473]! } - public var Stats_NotificationsTitle: String { return self._s[4474]! } - public var ChatSettings_AutoPlayGifs: String { return self._s[4475]! } - public var InfoPlist_NSCameraUsageDescription: String { return self._s[4477]! } - public var Conversation_Call: String { return self._s[4478]! } - public var Common_TakePhoto: String { return self._s[4480]! } - public var Group_EditAdmin_RankTitle: String { return self._s[4481]! } - public var Wallet_Receive_CommentHeader: String { return self._s[4482]! } - public var Channel_NotificationLoading: String { return self._s[4483]! } - public func Notification_Exceptions_Sound(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4484]!, self._r[4484]!, [_0]) + public var Share_MultipleMessagesDisabled: String { return self._s[3412]! } + public var TwoStepAuth_EmailInvalid: String { return self._s[3413]! } + public var EnterPasscode_ChangeTitle: String { return self._s[3415]! } + public func Wallet_Send_ConfirmationText(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3416]!, self._r[3416]!, [_1, _2, _3]) } - public func ScheduledMessages_ScheduledDate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4485]!, self._r[4485]!, [_0]) + public var CallSettings_RecentCalls: String { return self._s[3417]! } + public var GroupInfo_DeactivatedStatus: String { return self._s[3418]! } + public var AuthSessions_OtherSessions: String { return self._s[3419]! } + public var PrivacyLastSeenSettings_CustomHelp: String { return self._s[3420]! } + public var Tour_Text5: String { return self._s[3421]! } + public var Login_PadPhoneHelp: String { return self._s[3422]! } + public var Wallpaper_PhotoLibrary: String { return self._s[3424]! } + public var Conversation_ViewGroup: String { return self._s[3425]! } + public var PeopleNearby_MakeVisibleTitle: String { return self._s[3427]! } + public var VoiceOver_Chat_YourContact: String { return self._s[3428]! } + public var Watch_AuthRequired: String { return self._s[3429]! } + public var VoiceOver_Chat_ForwardedFromYou: String { return self._s[3430]! } + public var Conversation_ForwardContacts: String { return self._s[3431]! } + public var Conversation_InputTextPlaceholder: String { return self._s[3432]! } + public func PUSH_CHANNEL_MESSAGE_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3433]!, self._r[3433]!, [_1]) } - public func PUSH_CHANNEL_MESSAGE_VIDEO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4486]!, self._r[4486]!, [_1]) + public func Conversation_MessageViaUser(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3434]!, self._r[3434]!, [_0]) } - public var Permissions_SiriTitle_v0: String { return self._s[4487]! } - public func VoiceOver_Chat_VoiceMessageFrom(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4488]!, self._r[4488]!, [_0]) + public var Channel_Setup_TypePrivate: String { return self._s[3435]! } + public func Conversation_NoticeInvitedByInChannel(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3436]!, self._r[3436]!, [_0]) } - public func Login_ResetAccountProtected_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4489]!, self._r[4489]!, [_0]) + public var InfoPlist_NSSiriUsageDescription: String { return self._s[3437]! } + public var Wallet_ContextMenuCopy: String { return self._s[3438]! } + public var EmptyGroupInfo_Subtitle: String { return self._s[3439]! } + public var AutoDownloadSettings_Delimeter: String { return self._s[3440]! } + public var UserInfo_StartSecretChatStart: String { return self._s[3441]! } + public func GroupPermission_AddedInfo(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3442]!, self._r[3442]!, [_1, _2]) } - public var Channel_MessagePhotoRemoved: String { return self._s[4490]! } - public var Wallet_Info_ReceiveGrams: String { return self._s[4491]! } - public var ClearCache_FreeSpace: String { return self._s[4492]! } - public var Appearance_BubbleCorners_Apply: String { return self._s[4493]! } - public var Common_edit: String { return self._s[4494]! } - public var PrivacySettings_AuthSessions: String { return self._s[4495]! } - public func PUSH_VIDEO_CALL_REQUEST(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4496]!, self._r[4496]!, [_1]) + public func Channel_AdminLog_MessageRestricted(_ _0: String, _ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3443]!, self._r[3443]!, [_0, _1, _2]) + } + public var PrivacySettings_AutoArchiveTitle: String { return self._s[3444]! } + public var GroupInfo_InviteLink_LinkSection: String { return self._s[3445]! } + public var FastTwoStepSetup_EmailPlaceholder: String { return self._s[3446]! } + public var Wallet_SecureStorageReset_BiometryFaceId: String { return self._s[3447]! } + public var StickerPacksSettings_ArchivedMasks: String { return self._s[3449]! } + public var NewContact_Title: String { return self._s[3452]! } + public var Appearance_ThemeCarouselTintedNight: String { return self._s[3453]! } + public var Notifications_PermissionsKeepDisabled: String { return self._s[3454]! } + public func Time_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3455]!, self._r[3455]!, [_0]) } - public var Month_ShortJune: String { return self._s[4497]! } - public var PrivacyLastSeenSettings_AlwaysShareWith_Placeholder: String { return self._s[4498]! } - public var Call_ReportSend: String { return self._s[4499]! } - public var Watch_LastSeen_JustNow: String { return self._s[4500]! } - public var Notifications_MessageNotifications: String { return self._s[4501]! } - public var WallpaperSearch_ColorGreen: String { return self._s[4502]! } - public var BroadcastListInfo_AddRecipient: String { return self._s[4504]! } - public var Group_Status: String { return self._s[4505]! } public func AutoNightTheme_LocationHelp(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4506]!, self._r[4506]!, [_0, _1]) + return formatWithArgumentRanges(self._s[3456]!, self._r[3456]!, [_0, _1]) } - public var TextFormat_AddLinkTitle: String { return self._s[4507]! } - public var ShareMenu_ShareTo: String { return self._s[4508]! } - public var Conversation_Moderate_Ban: String { return self._s[4509]! } - public func Conversation_DeleteMessagesFor(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4510]!, self._r[4510]!, [_0]) + public var Chat_SlowmodeTooltipPending: String { return self._s[3457]! } + public var Wallet_WordCheck_TryAgain: String { return self._s[3458]! } + public var CallFeedback_ReasonInterruption: String { return self._s[3460]! } + public var ContactInfo_PhoneLabelHome: String { return self._s[3461]! } + public var Passport_Identity_OneOfTypeDriversLicense: String { return self._s[3462]! } + public var Conversation_MessageEditedLabel: String { return self._s[3464]! } + public var Wallet_Settings_DeleteWalletInfo: String { return self._s[3465]! } + public var SocksProxySetup_PasswordPlaceholder: String { return self._s[3466]! } + public var ChatList_Context_AddToContacts: String { return self._s[3467]! } + public var Passport_Language_is: String { return self._s[3468]! } + public var Notification_PassportValueProofOfIdentity: String { return self._s[3469]! } + public var Wallet_Month_ShortOctober: String { return self._s[3470]! } + public var PhotoEditor_CurvesBlue: String { return self._s[3471]! } + public func FileSize_MB(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3472]!, self._r[3472]!, [_0]) } - public var SharedMedia_ViewInChat: String { return self._s[4511]! } - public var Map_LiveLocationFor8Hours: String { return self._s[4512]! } - public func PUSH_PINNED_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4513]!, self._r[4513]!, [_1]) + public var SocksProxySetup_Username: String { return self._s[3473]! } + public var Login_SmsRequestState3: String { return self._s[3474]! } + public var Message_PinnedVideoMessage: String { return self._s[3475]! } + public var SharedMedia_TitleLink: String { return self._s[3476]! } + public var Passport_FieldIdentity: String { return self._s[3477]! } + public var Wallet_Configuration_SourceInfo: String { return self._s[3478]! } + public func Conversation_EncryptedPlaceholderTitleOutgoing(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3482]!, self._r[3482]!, [_0]) } - public func PUSH_PINNED_POLL(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4514]!, self._r[4514]!, [_1, _2]) + public var DialogList_ProxyConnectionIssuesTooltip: String { return self._s[3485]! } + public var ReportSpam_DeleteThisChat: String { return self._s[3486]! } + public var Checkout_NewCard_CardholderNamePlaceholder: String { return self._s[3487]! } + public var Passport_Identity_DateOfBirth: String { return self._s[3488]! } + public var Call_StatusIncoming: String { return self._s[3489]! } + public var Wallet_TransactionInfo_NoAddress: String { return self._s[3490]! } + public var ChatAdmins_AdminLabel: String { return self._s[3491]! } + public var Wallet_WordCheck_IncorrectHeader: String { return self._s[3492]! } + public func Time_MonthOfYear_m10(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3494]!, self._r[3494]!, [_0]) } - public func Map_AccurateTo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4516]!, self._r[4516]!, [_0]) + public var Message_PinnedAnimationMessage: String { return self._s[3495]! } + public var Conversation_ReportSpamAndLeave: String { return self._s[3496]! } + public var Preview_CopyAddress: String { return self._s[3497]! } + public var MediaPlayer_UnknownTrack: String { return self._s[3498]! } + public var Login_CancelSignUpConfirmation: String { return self._s[3499]! } + public var Map_OpenInYandexMaps: String { return self._s[3501]! } + public func Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3504]!, self._r[3504]!, [_1, _2, _3]) } - public var Map_OpenInHereMaps: String { return self._s[4517]! } - public var Appearance_ReduceMotion: String { return self._s[4518]! } - public func PUSH_MESSAGE_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[4519]!, self._r[4519]!, [_1, _2]) + public var GroupRemoved_Remove: String { return self._s[3505]! } + public var ChatListFolder_TitleCreate: String { return self._s[3506]! } + public func InstantPage_AuthorAndDateTitle(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3508]!, self._r[3508]!, [_1, _2]) } - public var Channel_Setup_TypePublicHelp: String { return self._s[4520]! } - public var Passport_Identity_EditInternalPassport: String { return self._s[4521]! } - public var PhotoEditor_Skip: String { return self._s[4522]! } - public func Stats_GroupShowMoreTopPosters(_ value: Int32) -> String { + public var Watch_UserInfo_MuteTitle: String { return self._s[3509]! } + public var Group_UpgradeNoticeText2: String { return self._s[3511]! } + public var Stats_GroupGrowthTitle: String { return self._s[3512]! } + public var CreatePoll_CancelConfirmation: String { return self._s[3515]! } + public var Month_GenOctober: String { return self._s[3516]! } + public var Settings_Appearance: String { return self._s[3517]! } + public func Time_MonthOfYear_m6(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3518]!, self._r[3518]!, [_0]) + } + public var Wallet_Completed_Title: String { return self._s[3519]! } + public var UserInfo_AddToExisting: String { return self._s[3520]! } + public var Call_PhoneCallInProgressMessage: String { return self._s[3521]! } + public var Map_HomeAndWorkInfo: String { return self._s[3522]! } + public var Paint_Arrow: String { return self._s[3523]! } + public var CancelResetAccount_Title: String { return self._s[3524]! } + public var NotificationsSound_Circles: String { return self._s[3525]! } + public var Notifications_GroupNotificationsExceptionsHelp: String { return self._s[3526]! } + public var ChatState_Connecting: String { return self._s[3528]! } + public var Profile_MessageLifetime5s: String { return self._s[3529]! } + public func DialogList_AwaitingEncryption(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3530]!, self._r[3530]!, [_0]) + } + public var PrivacyPolicy_AgeVerificationTitle: String { return self._s[3531]! } + public var Channel_Username_CreatePublicLinkHelp: String { return self._s[3532]! } + public var AutoNightTheme_ScheduledTo: String { return self._s[3533]! } + public var Conversation_DefaultRestrictedStickers: String { return self._s[3534]! } + public var TwoStepAuth_ConfirmationTitle: String { return self._s[3535]! } + public func Chat_UnsendMyMessagesAlertTitle(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3536]!, self._r[3536]!, [_0]) + } + public var Passport_Phone_Help: String { return self._s[3537]! } + public var Privacy_ContactsSync: String { return self._s[3538]! } + public var CheckoutInfo_ReceiverInfoPhone: String { return self._s[3539]! } + public var Channel_AdminLogFilter_EventsLeavingSubscribers: String { return self._s[3540]! } + public var Map_SendMyCurrentLocation: String { return self._s[3541]! } + public var Map_AddressOnMap: String { return self._s[3542]! } + public func Wallet_Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3543]!, self._r[3543]!, [_1, _2, _3]) + } + public var DialogList_SearchLabel: String { return self._s[3545]! } + public var Notification_Exceptions_NewException_NotificationHeader: String { return self._s[3546]! } + public var ConversationProfile_UnknownAddMemberError: String { return self._s[3547]! } + public var ChatList_Search_ShowMore: String { return self._s[3548]! } + public var DialogList_EncryptionRejected: String { return self._s[3549]! } + public var Wallet_WordImport_Text: String { return self._s[3550]! } + public var DialogList_DeleteBotConfirmation: String { return self._s[3551]! } + public var Privacy_TopPeersDelete: String { return self._s[3552]! } + public var AttachmentMenu_SendAsFile: String { return self._s[3553]! } + public var ChatList_GenericPsaAlert: String { return self._s[3555]! } + public var SecretTimer_ImageDescription: String { return self._s[3557]! } + public func Conversation_SetReminder_RemindOn(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3558]!, self._r[3558]!, [_0, _1]) + } + public var ChatSettings_TextSizeUnits: String { return self._s[3559]! } + public var Notification_RenamedGroup: String { return self._s[3560]! } + public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[3561]! } + public var Tour_Title2: String { return self._s[3562]! } + public var Settings_CopyUsername: String { return self._s[3563]! } + public var Compose_NewEncryptedChat: String { return self._s[3564]! } + public var Conversation_CloudStorageInfo_Title: String { return self._s[3565]! } + public var Month_ShortSeptember: String { return self._s[3566]! } + public var AutoDownloadSettings_OnForAll: String { return self._s[3567]! } + public var ChatList_DeleteForEveryoneConfirmationText: String { return self._s[3568]! } + public var Settings_Wallet: String { return self._s[3569]! } + public var Call_StatusConnecting: String { return self._s[3571]! } + public var Privacy_GroupsAndChannels_NeverAllow_Placeholder: String { return self._s[3572]! } + public var Map_ShareLiveLocationHelp: String { return self._s[3573]! } + public var Cache_Files: String { return self._s[3574]! } + public var Notifications_Reset: String { return self._s[3575]! } + public func Settings_KeepPhoneNumber(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3576]!, self._r[3576]!, [_0]) + } + public var Privacy_GroupsAndChannels_AlwaysAllow_Title: String { return self._s[3577]! } + public func Conversation_OpenBotLinkLogin(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3578]!, self._r[3578]!, [_1, _2]) + } + public var Notification_CallIncomingShort: String { return self._s[3579]! } + public var UserInfo_BotPrivacy: String { return self._s[3581]! } + public var Appearance_BubbleCorners_Apply: String { return self._s[3582]! } + public var WebSearch_RecentClearConfirmation: String { return self._s[3583]! } + public var Conversation_ContextMenuLookUp: String { return self._s[3584]! } + public var Calls_RatingTitle: String { return self._s[3585]! } + public var SecretImage_Title: String { return self._s[3586]! } + public var Weekday_Monday: String { return self._s[3587]! } + public func Passport_PrivacyPolicy(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3589]!, self._r[3589]!, [_1, _2]) + } + public var KeyCommand_JumpToPreviousChat: String { return self._s[3590]! } + public func Wallet_Updated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3591]!, self._r[3591]!, [_0]) + } + public func DialogList_SearchSubtitleFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3592]!, self._r[3592]!, [_1, _2]) + } + public var Stats_GroupMembers: String { return self._s[3593]! } + public var Camera_Retake: String { return self._s[3594]! } + public var Conversation_SearchPlaceholder: String { return self._s[3596]! } + public func Passport_Identity_NativeNameGenericHelp(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3597]!, self._r[3597]!, [_0]) + } + public var Channel_DiscussionGroup_Info: String { return self._s[3598]! } + public var SocksProxySetup_Hostname: String { return self._s[3599]! } + public var Wallet_Send_OwnAddressAlertProceed: String { return self._s[3600]! } + public var PrivacyLastSeenSettings_EmpryUsersPlaceholder: String { return self._s[3601]! } + public var Privacy_DeleteDrafts: String { return self._s[3602]! } + public func Checkout_LiabilityAlert(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3603]!, self._r[3603]!, [_1, _1, _1, _2]) + } + public var Wallet_RestoreFailed_Text: String { return self._s[3604]! } + public var Wallet_Settings_DeleteWallet: String { return self._s[3605]! } + public var Login_CancelPhoneVerification: String { return self._s[3606]! } + public var TwoStepAuth_ResetAccountHelp: String { return self._s[3608]! } + public func SocksProxySetup_ProxyStatusPing(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3609]!, self._r[3609]!, [_0]) + } + public var TwoStepAuth_EmailSent: String { return self._s[3610]! } + public var Cache_Indexing: String { return self._s[3611]! } + public var Notifications_ExceptionsNone: String { return self._s[3612]! } + public var MessagePoll_LabelQuiz: String { return self._s[3613]! } + public var Call_EncryptionKey_Title: String { return self._s[3614]! } + public var Common_Yes: String { return self._s[3615]! } + public var Channel_ErrorAddBlocked: String { return self._s[3616]! } + public var Month_GenJanuary: String { return self._s[3617]! } + public var Checkout_NewCard_Title: String { return self._s[3618]! } + public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[3619]! } + public func TwoStepAuth_EnterPasswordHint(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3620]!, self._r[3620]!, [_0]) + } + public var PasscodeSettings_AutoLock_IfAwayFor_1hour: String { return self._s[3622]! } + public var Conversation_SendDice: String { return self._s[3623]! } + public func ChatSettings_AutoDownloadSettings_TypeVideo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3624]!, self._r[3624]!, [_0]) + } + public func VoiceOver_Chat_VideoFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3625]!, self._r[3625]!, [_0]) + } + public var Weekday_Wednesday: String { return self._s[3626]! } + public var ReportPeer_ReasonOther_Send: String { return self._s[3627]! } + public var PasscodeSettings_EncryptDataHelp: String { return self._s[3628]! } + public var PrivacyLastSeenSettings_CustomShareSettingsHelp: String { return self._s[3629]! } + public var OldChannels_NoticeTitle: String { return self._s[3630]! } + public var TwoStepAuth_ChangeEmail: String { return self._s[3631]! } + public var PasscodeSettings_PasscodeOptions: String { return self._s[3632]! } + public var InfoPlist_NSPhotoLibraryUsageDescription: String { return self._s[3633]! } + public var Passport_Address_AddUtilityBill: String { return self._s[3634]! } + public func Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3636]!, self._r[3636]!, [_1, _2, _3]) + } + public var TwoFactorSetup_EmailVerification_ResendAction: String { return self._s[3638]! } + public var Stats_GroupTopAdminsTitle: String { return self._s[3639]! } + public var Paint_Regular: String { return self._s[3640]! } + public var Message_Contact: String { return self._s[3641]! } + public var NetworkUsageSettings_MediaVideoDataSection: String { return self._s[3642]! } + public var VoiceOver_Chat_YourPhoto: String { return self._s[3643]! } + public var Notification_Mute1hMin: String { return self._s[3644]! } + public func Login_BannedPhoneSubject(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3645]!, self._r[3645]!, [_0]) + } + public var Profile_MessageLifetime1h: String { return self._s[3646]! } + public var TwoStepAuth_GenericHelp: String { return self._s[3647]! } + public var TextFormat_Monospace: String { return self._s[3648]! } + public var VoiceOver_Media_PlaybackRateChange: String { return self._s[3650]! } + public var Conversation_DeleteMessagesForMe: String { return self._s[3651]! } + public var ChatList_DeleteChat: String { return self._s[3652]! } + public var Channel_OwnershipTransfer_EnterPasswordText: String { return self._s[3655]! } + public func Settings_ApplyProxyAlertCredentials(_ _1: String, _ _2: String, _ _3: String, _ _4: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3656]!, self._r[3656]!, [_1, _2, _3, _4]) + } + public var Login_CancelPhoneVerificationStop: String { return self._s[3657]! } + public var Appearance_ThemePreview_ChatList_4_Name: String { return self._s[3658]! } + public var MediaPicker_MomentsDateRangeSameMonthYearFormat: String { return self._s[3659]! } + public var Wallet_Settings_Configuration: String { return self._s[3660]! } + public var Notifications_Badge_IncludeChannels: String { return self._s[3661]! } + public func Channel_AdminLog_MessageToggleInvitesOn(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3662]!, self._r[3662]!, [_0]) + } + public var Wallet_Sent_ViewWallet: String { return self._s[3663]! } + public var StickerPack_ViewPack: String { return self._s[3666]! } + public var FastTwoStepSetup_PasswordConfirmationPlaceholder: String { return self._s[3668]! } + public var EditTheme_Expand_Preview_IncomingText: String { return self._s[3669]! } + public var Notifications_Title: String { return self._s[3670]! } + public var Wallet_WordImport_Continue: String { return self._s[3671]! } + public var GroupInfo_PublicLink: String { return self._s[3672]! } + public var VoiceOver_DiscardPreparedContent: String { return self._s[3673]! } + public var Conversation_Moderate_Ban: String { return self._s[3677]! } + public func Activity_RemindAboutGroup(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3678]!, self._r[3678]!, [_0]) + } + public var TextFormat_Underline: String { return self._s[3679]! } + public func DownloadingStatus(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3680]!, self._r[3680]!, [_0, _1]) + } + public func PUSH_PINNED_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3681]!, self._r[3681]!, [_1]) + } + public var PollResults_Collapse: String { return self._s[3683]! } + public var Contacts_GlobalSearch: String { return self._s[3684]! } + public func Conversation_EncryptionWaiting(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3685]!, self._r[3685]!, [_0]) + } + public var Channel_Management_LabelEditor: String { return self._s[3686]! } + public var SettingsSearch_Synonyms_Stickers_FeaturedPacks: String { return self._s[3688]! } + public var Conversation_Theme: String { return self._s[3689]! } + public var Conversation_LinkDialogSave: String { return self._s[3690]! } + public var EnterPasscode_TouchId: String { return self._s[3691]! } + public var Stats_MessageOverview: String { return self._s[3692]! } + public var Privacy_Calls_P2PAlways: String { return self._s[3694]! } + public var Message_Sticker: String { return self._s[3695]! } + public var Conversation_Mute: String { return self._s[3697]! } + public var ContactInfo_Title: String { return self._s[3698]! } + public func PUSH_CHANNEL_MESSAGE_CONTACT(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3699]!, self._r[3699]!, [_1]) + } + public var Channel_Setup_TypeHeader: String { return self._s[3700]! } + public var AuthSessions_LogOut: String { return self._s[3701]! } + public var Wallet_WordCheck_ViewWords: String { return self._s[3702]! } + public var ChatSettings_AutoDownloadReset: String { return self._s[3703]! } + public var ChatListFolderSettings_NewFolder: String { return self._s[3705]! } + public var Appearance_ThemePreview_ChatList_3_AuthorName: String { return self._s[3706]! } + public var CreatePoll_Title: String { return self._s[3707]! } + public var EditTheme_EditTitle: String { return self._s[3708]! } + public var ChatListFolderSettings_RecommendedFoldersSection: String { return self._s[3709]! } + public var TwoStepAuth_SetPassword: String { return self._s[3710]! } + public var Wallet_Words_Done: String { return self._s[3711]! } + public func Login_InvalidPhoneEmailSubject(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3712]!, self._r[3712]!, [_0]) + } + public var BlockedUsers_Info: String { return self._s[3713]! } + public var AuthSessions_Sessions: String { return self._s[3714]! } + public var Group_EditAdmin_RankTitle: String { return self._s[3715]! } + public func Wallet_Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3716]!, self._r[3716]!, [_1, _2, _3]) + } + public var Common_ActionNotAllowedError: String { return self._s[3717]! } + public var WebPreview_GettingLinkInfo: String { return self._s[3718]! } + public var Appearance_AppIconFilledX: String { return self._s[3719]! } + public var Wallet_TransactionInfo_StorageFeeInfo: String { return self._s[3720]! } + public var Passport_Email_EmailPlaceholder: String { return self._s[3721]! } + public var FeaturedStickers_OtherSection: String { return self._s[3722]! } + public var EditTheme_Edit_Preview_OutgoingText: String { return self._s[3723]! } + public var Profile_Username: String { return self._s[3724]! } + public var Appearance_RemoveTheme: String { return self._s[3725]! } + public var TwoStepAuth_SetupPasswordConfirmPassword: String { return self._s[3726]! } + public var Message_PinnedStickerMessage: String { return self._s[3727]! } + public var AccessDenied_VideoMicrophone: String { return self._s[3728]! } + public var WallpaperPreview_CustomColorBottomText: String { return self._s[3729]! } + public var Passport_Address_RegionPlaceholder: String { return self._s[3730]! } + public var SettingsSearch_Synonyms_Data_Storage_Title: String { return self._s[3731]! } + public var TwoStepAuth_Title: String { return self._s[3732]! } + public var Checkout_WebConfirmation_Title: String { return self._s[3733]! } + public var AutoDownloadSettings_VoiceMessagesInfo: String { return self._s[3734]! } + public var ChatListFolder_CategoryGroups: String { return self._s[3736]! } + public var Stats_GroupTopInviter_Promote: String { return self._s[3737]! } + public var Month_GenJuly: String { return self._s[3738]! } + public var Passport_Identity_Gender: String { return self._s[3739]! } + public var Channel_DiscussionGroup_UnlinkGroup: String { return self._s[3740]! } + public var Notification_Exceptions_DeleteAll: String { return self._s[3741]! } + public func Conversation_FileHowToText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3742]!, self._r[3742]!, [_0]) + } + public func Channel_AdminLog_MessageAdmin(_ _0: String, _ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3743]!, self._r[3743]!, [_0, _1, _2]) + } + public var Login_CodeSentSms: String { return self._s[3744]! } + public func VoiceOver_Chat_ReplyFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3746]!, self._r[3746]!, [_0]) + } + public var Login_CallRequestState2: String { return self._s[3747]! } + public var Channel_DiscussionGroup_Header: String { return self._s[3748]! } + public func Channel_AdminLog_MessageToggleInvitesOff(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3749]!, self._r[3749]!, [_0]) + } + public var Passport_Language_ms: String { return self._s[3750]! } + public var PeopleNearby_MakeInvisible: String { return self._s[3752]! } + public var ChatList_Search_FilterVoice: String { return self._s[3754]! } + public var Camera_TapAndHoldForVideo: String { return self._s[3756]! } + public var Permissions_NotificationsAllowInSettings_v0: String { return self._s[3757]! } + public func Notification_LeftChannel(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3758]!, self._r[3758]!, [_0]) + } + public func Wallet_Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3759]!, self._r[3759]!, [_1, _2, _3]) + } + public var Wallet_Info_TransactionTo: String { return self._s[3760]! } + public var Map_Locating: String { return self._s[3761]! } + public func Checkout_SavePasswordTimeout(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3763]!, self._r[3763]!, [_0]) + } + public var Passport_Identity_TypeInternalPassport: String { return self._s[3765]! } + public var Appearance_ThemePreview_Chat_4_Text: String { return self._s[3766]! } + public var SettingsSearch_Synonyms_EditProfile_Username: String { return self._s[3767]! } + public var Stickers_Installed: String { return self._s[3768]! } + public var Notifications_PermissionsAllowInSettings: String { return self._s[3769]! } + public var StickerPackActionInfo_RemovedTitle: String { return self._s[3770]! } + public var CallSettings_Never: String { return self._s[3772]! } + public var Wallet_AccessDenied_Camera: String { return self._s[3773]! } + public var Channel_Setup_TypePublicHelp: String { return self._s[3774]! } + public func ChatList_DeleteForEveryone(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3776]!, self._r[3776]!, [_0]) + } + public var Message_Game: String { return self._s[3777]! } + public var Call_Message: String { return self._s[3778]! } + public func PUSH_CHANNEL_MESSAGE_VIDEO(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3779]!, self._r[3779]!, [_1]) + } + public var ChannelIntro_Text: String { return self._s[3780]! } + public var StickerPack_Send: String { return self._s[3781]! } + public var Share_AuthDescription: String { return self._s[3782]! } + public var PasscodeSettings_AutoLock_IfAwayFor_5minutes: String { return self._s[3783]! } + public var CallFeedback_WhatWentWrong: String { return self._s[3784]! } + public var Common_Create: String { return self._s[3787]! } + public var Passport_Language_hy: String { return self._s[3788]! } + public var CreatePoll_Explanation: String { return self._s[3789]! } + public var GroupPermission_AddMembersNotAvailable: String { return self._s[3790]! } + public var Undo_ChatClearedForBothSides: String { return self._s[3791]! } + public var DialogList_NoMessagesTitle: String { return self._s[3792]! } + public var GroupInfo_Title: String { return self._s[3794]! } + public var Channel_AdminLog_CanBanUsers: String { return self._s[3795]! } + public var PhoneNumberHelp_Help: String { return self._s[3796]! } + public var TwoStepAuth_AdditionalPassword: String { return self._s[3797]! } + public var Settings_Logout: String { return self._s[3798]! } + public var Privacy_PaymentsTitle: String { return self._s[3799]! } + public var StickerPacksSettings_StickerPacksSection: String { return self._s[3800]! } + public var Tour_Text6: String { return self._s[3801]! } + public var Channel_Username_Help: String { return self._s[3803]! } + public var Wallet_Info_RefreshErrorTitle: String { return self._s[3804]! } + public var VoiceOver_Chat_RecordModeVoiceMessageInfo: String { return self._s[3805]! } + public var AttachmentMenu_Poll: String { return self._s[3806]! } + public var EditTheme_Create_Preview_IncomingReplyName: String { return self._s[3807]! } + public var Conversation_ReportSpamChannelConfirmation: String { return self._s[3808]! } + public var Passport_DeletePassport: String { return self._s[3809]! } + public var Login_Code: String { return self._s[3810]! } + public var Notification_SecretChatScreenshot: String { return self._s[3811]! } + public var Login_CodeFloodError: String { return self._s[3812]! } + public func Notification_PinnedAnimationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3813]!, self._r[3813]!, [_0]) + } + public func Channel_Username_UsernameIsAvailable(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3814]!, self._r[3814]!, [_0]) + } + public var Watch_Stickers_Recents: String { return self._s[3815]! } + public var Generic_ErrorMoreInfo: String { return self._s[3816]! } + public func Call_AccountIsLoggedOnCurrentDevice(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3817]!, self._r[3817]!, [_0]) + } + public var AutoDownloadSettings_DataUsage: String { return self._s[3818]! } + public var Conversation_ViewTheme: String { return self._s[3819]! } + public var Contacts_InviteSearchLabel: String { return self._s[3820]! } + public var Settings_CancelUpload: String { return self._s[3822]! } + public var Settings_AppLanguage_Unofficial: String { return self._s[3823]! } + public func ChatList_ClearChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3824]!, self._r[3824]!, [_0]) + } + public var ChatList_AddFolder: String { return self._s[3825]! } + public var Conversation_Location: String { return self._s[3827]! } + public var Appearance_BubbleCorners_AdjustAdjacent: String { return self._s[3828]! } + public var DialogList_AdLabel: String { return self._s[3829]! } + public func Time_TomorrowAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3831]!, self._r[3831]!, [_0]) + } + public var Message_InvoiceLabel: String { return self._s[3832]! } + public var Channel_TooMuchBots: String { return self._s[3833]! } + public func Channel_AdminLog_MessageRemovedChannelUsername(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3834]!, self._r[3834]!, [_0]) + } + public var Wallet_Month_ShortAugust: String { return self._s[3835]! } + public var Call_IncomingVideoCall: String { return self._s[3836]! } + public var Conversation_LiveLocation: String { return self._s[3837]! } + public var TwoStepAuth_SetupPasswordEnterPasswordChange: String { return self._s[3838]! } + public var Passport_Identity_EditPassport: String { return self._s[3839]! } + public var Permissions_CellularDataTitle_v0: String { return self._s[3841]! } + public var ChatList_Search_NoResultsFitlerVoice: String { return self._s[3842]! } + public var GroupInfo_Permissions_AddException: String { return self._s[3843]! } + public var Channel_AdminLog_CanInviteUsers: String { return self._s[3845]! } + public var Channel_MessageVideoUpdated: String { return self._s[3846]! } + public var GroupInfo_Permissions_EditingDisabled: String { return self._s[3847]! } + public var AccessDenied_Camera: String { return self._s[3850]! } + public func Target_InviteToGroupConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3851]!, self._r[3851]!, [_0]) + } + public var Theme_Context_ChangeColors: String { return self._s[3852]! } + public var PrivacySettings_TwoStepAuth: String { return self._s[3853]! } + public var Privacy_Forwards_PreviewMessageText: String { return self._s[3854]! } + public var Login_CodeExpiredError: String { return self._s[3855]! } + public var State_ConnectingToProxy: String { return self._s[3856]! } + public var TextFormat_Link: String { return self._s[3857]! } + public var Passport_Language_lv: String { return self._s[3858]! } + public var AccessDenied_VoiceMicrophone: String { return self._s[3859]! } + public var WallpaperPreview_SwipeBottomText: String { return self._s[3860]! } + public var ProfilePhoto_SetMainVideo: String { return self._s[3861]! } + public var AutoDownloadSettings_Cellular: String { return self._s[3863]! } + public var ChatSettings_AutoDownloadVoiceMessages: String { return self._s[3864]! } + public func Channel_AdminLog_MessageKickedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3865]!, self._r[3865]!, [_1, _2]) + } + public var ChatList_EmptyChatListFilterTitle: String { return self._s[3866]! } + public var Checkout_PayNone: String { return self._s[3867]! } + public var NotificationsSound_Complete: String { return self._s[3869]! } + public var TwoStepAuth_ConfirmEmailCodePlaceholder: String { return self._s[3870]! } + public var AuthSessions_DevicesTitle: String { return self._s[3871]! } + public func DialogList_MultipleTyping(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3872]!, self._r[3872]!, [_0, _1]) + } + public var Message_LiveLocation: String { return self._s[3873]! } + public var Watch_Suggestion_BRB: String { return self._s[3874]! } + public var Channel_BanUser_Title: String { return self._s[3875]! } + public var SettingsSearch_Synonyms_Privacy_Data_Title: String { return self._s[3876]! } + public var Conversation_Dice_u1F3C0: String { return self._s[3877]! } + public var Conversation_ClearSelfHistory: String { return self._s[3878]! } + public var ProfilePhoto_OpenGallery: String { return self._s[3879]! } + public var PrivacySettings_LastSeenTitle: String { return self._s[3880]! } + public var Weekday_Thursday: String { return self._s[3881]! } + public var BroadcastListInfo_AddRecipient: String { return self._s[3882]! } + public var Privacy_ProfilePhoto: String { return self._s[3884]! } + public var StickerPacksSettings_ArchivedPacks_Info: String { return self._s[3885]! } + public func Channel_AdminLog_MessageChangedUnlinkedGroup(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3886]!, self._r[3886]!, [_1, _2]) + } + public var Message_Audio: String { return self._s[3887]! } + public var Conversation_Info: String { return self._s[3888]! } + public var Cache_Videos: String { return self._s[3889]! } + public var Appearance_ThemePreview_ChatList_6_Text: String { return self._s[3890]! } + public var Channel_ErrorAddTooMuch: String { return self._s[3891]! } + public func ChatList_DeleteSecretChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3892]!, self._r[3892]!, [_0]) + } + public var ChannelMembers_ChannelAdminsTitle: String { return self._s[3894]! } + public var ScheduledMessages_Title: String { return self._s[3896]! } + public var ShareFileTip_Title: String { return self._s[3899]! } + public var Chat_Gifs_TrendingSectionHeader: String { return self._s[3900]! } + public var ChatList_RemoveFolderConfirmation: String { return self._s[3901]! } + public func PUSH_CHAT_MESSAGE_GEOLIVE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3902]!, self._r[3902]!, [_1, _2]) + } + public var Channel_DiscussionGroup_SearchPlaceholder: String { return self._s[3904]! } + public var PasscodeSettings_Title: String { return self._s[3905]! } + public var Channel_AdminLog_SendPolls: String { return self._s[3906]! } + public var LastSeen_ALongTimeAgo: String { return self._s[3907]! } + public func PUSH_CHANNEL_MESSAGE_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3908]!, self._r[3908]!, [_1]) + } + public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedChannels: String { return self._s[3909]! } + public var CallFeedback_VideoReasonLowQuality: String { return self._s[3910]! } + public var SocksProxySetup_AddProxyTitle: String { return self._s[3911]! } + public var Passport_Identity_AddInternalPassport: String { return self._s[3912]! } + public func ChatList_RemovedFromFolderTooltip(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3913]!, self._r[3913]!, [_1, _2]) + } + public func Conversation_SetReminder_RemindToday(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3914]!, self._r[3914]!, [_0]) + } + public var Passport_Identity_GenderFemale: String { return self._s[3915]! } + public var ConvertToSupergroup_HelpTitle: String { return self._s[3918]! } + public var SharedMedia_TitleAll: String { return self._s[3919]! } + public var Settings_Context_Logout: String { return self._s[3920]! } + public var GroupInfo_SetGroupPhotoDelete: String { return self._s[3922]! } + public var Settings_About_Title: String { return self._s[3923]! } + public var StickerSettings_ContextHide: String { return self._s[3924]! } + public func AutoDownloadSettings_UpTo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3925]!, self._r[3925]!, [_0]) + } + public func Conversation_LiveLocationYouAndOther(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3926]!, self._r[3926]!, [_0]) + } + public var Common_Cancel: String { return self._s[3928]! } + public var CallFeedback_Title: String { return self._s[3930]! } + public func Notification_PinnedContactMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3931]!, self._r[3931]!, [_0]) + } + public var Activity_UploadingVideoMessage: String { return self._s[3932]! } + public var Wallet_TransactionInfo_OtherFeeInfo: String { return self._s[3933]! } + public var MediaPicker_Send: String { return self._s[3934]! } + public var PasscodeSettings_AutoLock_IfAwayFor_1minute: String { return self._s[3935]! } + public var Conversation_LiveLocationYou: String { return self._s[3936]! } + public var Notifications_ExceptionsUnmuted: String { return self._s[3937]! } + public func Channel_AdminLog_MessageGroupPreHistoryHidden(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3938]!, self._r[3938]!, [_0]) + } + public func PUSH_CHAT_ADD_YOU(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3939]!, self._r[3939]!, [_1, _2]) + } + public var Conversation_ViewBackground: String { return self._s[3940]! } + public var ChatSettings_PrivateChats: String { return self._s[3943]! } + public var Conversation_ErrorInaccessibleMessage: String { return self._s[3944]! } + public var Wallet_Receive_AmountInfo: String { return self._s[3945]! } + public var Appearance_ThemeNight: String { return self._s[3946]! } + public var Common_Search: String { return self._s[3947]! } + public var TwoStepAuth_ReEnterPasswordTitle: String { return self._s[3948]! } + public var ChangePhoneNumberNumber_Help: String { return self._s[3950]! } + public var Stickers_SuggestAdded: String { return self._s[3951]! } + public var Conversation_DiscardVoiceMessageDescription: String { return self._s[3954]! } + public var NetworkUsageSettings_Cellular: String { return self._s[3955]! } + public var CheckoutInfo_Title: String { return self._s[3956]! } + public var Conversation_ShareBotLocationConfirmationTitle: String { return self._s[3957]! } + public var Channel_BotDoesntSupportGroups: String { return self._s[3958]! } + public func DialogList_SingleRecordingAudioSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3959]!, self._r[3959]!, [_0]) + } + public var MaskStickerSettings_Info: String { return self._s[3960]! } + public var GroupRemoved_DeleteUser: String { return self._s[3961]! } + public var Contacts_ShareTelegram: String { return self._s[3962]! } + public var Group_UpgradeNoticeText1: String { return self._s[3963]! } + public func PUSH_PHONE_CALL_REQUEST(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3964]!, self._r[3964]!, [_1]) + } + public var PrivacyLastSeenSettings_Title: String { return self._s[3965]! } + public var SettingsSearch_Synonyms_Support: String { return self._s[3969]! } + public var PhotoEditor_TintTool: String { return self._s[3970]! } + public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[3972]! } + public var GroupPermission_NoSendPolls: String { return self._s[3973]! } + public var NotificationsSound_None: String { return self._s[3974]! } + public func LOCAL_CHANNEL_MESSAGE_FWDS(_ _1: String, _ _2: Int) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[3975]!, self._r[3975]!, [_1, "\(_2)"]) + } + public var CheckoutInfo_ShippingInfoCityPlaceholder: String { return self._s[3977]! } + public var ExplicitContent_AlertChannel: String { return self._s[3979]! } + public var Conversation_ClousStorageInfo_Description1: String { return self._s[3980]! } + public var Contacts_SortedByPresence: String { return self._s[3981]! } + public var WallpaperSearch_ColorGray: String { return self._s[3982]! } + public var Channel_AdminLogFilter_EventsNewSubscribers: String { return self._s[3983]! } + public var Conversation_ReportSpam: String { return self._s[3984]! } + public var ChatList_Search_NoResultsFilter: String { return self._s[3987]! } + public var WallpaperSearch_ColorBlack: String { return self._s[3988]! } + public var ArchivedChats_IntroTitle3: String { return self._s[3989]! } + public var Conversation_DefaultRestrictedText: String { return self._s[3990]! } + public var Settings_Devices: String { return self._s[3991]! } + public var Call_AudioRouteSpeaker: String { return self._s[3992]! } + public var GroupInfo_InviteLink_CopyLink: String { return self._s[3993]! } + public var Passport_Address_Country: String { return self._s[3995]! } + public var Cache_MaximumCacheSize: String { return self._s[3996]! } + public var Notifications_Badge_IncludePublicGroups: String { return self._s[3997]! } + public var Wallet_Receive_CreateInvoice: String { return self._s[3999]! } + public var ChatSettings_AutoDownloadUsingWiFi: String { return self._s[4000]! } + public var Login_TermsOfServiceLabel: String { return self._s[4001]! } + public var Calls_NoMissedCallsPlacehoder: String { return self._s[4002]! } + public var SocksProxySetup_RequiredCredentials: String { return self._s[4003]! } + public var VoiceOver_MessageContextOpenMessageMenu: String { return self._s[4004]! } + public var AutoNightTheme_ScheduledFrom: String { return self._s[4005]! } + public var ChatSettings_AutoDownloadDocuments: String { return self._s[4006]! } + public var ConvertToSupergroup_Note: String { return self._s[4008]! } + public var Settings_SetNewProfilePhotoOrVideo: String { return self._s[4009]! } + public var PrivacySettings_PasscodeAndTouchId: String { return self._s[4010]! } + public var Common_More: String { return self._s[4011]! } + public var ShareMenu_SelectChats: String { return self._s[4013]! } + public func Conversation_ScheduleMessage_SendToday(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4015]!, self._r[4015]!, [_0]) + } + public func Channel_AdminLog_MessageRemovedGroupStickerPack(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4016]!, self._r[4016]!, [_0]) + } + public var Contacts_PermissionsKeepDisabled: String { return self._s[4018]! } + public func Call_ParticipantVersionOutdatedError(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4019]!, self._r[4019]!, [_0]) + } + public var WatchRemote_AlertOpen: String { return self._s[4020]! } + public func PUSH_CHAT_ADD_MEMBER(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4021]!, self._r[4021]!, [_1, _2, _3]) + } + public var Channel_Members_AddMembersHelp: String { return self._s[4022]! } + public var Shortcut_SwitchAccount: String { return self._s[4023]! } + public var Map_LiveLocationFor8Hours: String { return self._s[4024]! } + public func AutoNightTheme_AutomaticHelp(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4025]!, self._r[4025]!, [_0]) + } + public var Compose_NewGroupTitle: String { return self._s[4026]! } + public var DialogList_You: String { return self._s[4027]! } + public var ReportPeer_ReasonViolence: String { return self._s[4028]! } + public func PUSH_CHANNEL_MESSAGE_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4029]!, self._r[4029]!, [_1, _2]) + } + public var KeyCommand_ScrollDown: String { return self._s[4033]! } + public var ChatSettings_DownloadInBackground: String { return self._s[4034]! } + public var Wallpaper_ResetWallpapers: String { return self._s[4035]! } + public var Channel_BanList_RestrictedTitle: String { return self._s[4036]! } + public var ArchivedChats_IntroText3: String { return self._s[4037]! } + public var HashtagSearch_AllChats: String { return self._s[4039]! } + public var Channel_Info_BlackList: String { return self._s[4041]! } + public var Contacts_SearchUsersAndGroupsLabel: String { return self._s[4042]! } + public var PrivacyPhoneNumberSettings_DiscoveryHeader: String { return self._s[4043]! } + public var Paint_Neon: String { return self._s[4045]! } + public var SettingsSearch_Synonyms_AppLanguage: String { return self._s[4046]! } + public var AutoDownloadSettings_AutoDownload: String { return self._s[4047]! } + public func Notification_PinnedVideoMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4049]!, self._r[4049]!, [_0]) + } + public var Map_StopLiveLocation: String { return self._s[4050]! } + public var SettingsSearch_Synonyms_Data_SaveEditedPhotos: String { return self._s[4051]! } + public var Channel_Username_InvalidCharacters: String { return self._s[4052]! } + public var InstantPage_Reference: String { return self._s[4053]! } + public var ChatList_HideAction: String { return self._s[4055]! } + public var Conversation_FileICloudDrive: String { return self._s[4057]! } + public func PUSH_PINNED_GEOLIVE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4058]!, self._r[4058]!, [_1]) + } + public var Passport_PasswordReset: String { return self._s[4060]! } + public var ChatList_Context_UnhideArchive: String { return self._s[4062]! } + public var ConvertToSupergroup_HelpText: String { return self._s[4063]! } + public var Calls_AddTab: String { return self._s[4064]! } + public var TwoStepAuth_ConfirmEmailResendCode: String { return self._s[4065]! } + public var SettingsSearch_Synonyms_Stickers_SuggestStickers: String { return self._s[4066]! } + public var Privacy_GroupsAndChannels: String { return self._s[4068]! } + public var AutoNightTheme_Disabled: String { return self._s[4069]! } + public var CreatePoll_MultipleChoice: String { return self._s[4070]! } + public func PINNED_INVOICE(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4071]!, self._r[4071]!, [_1]) + } + public var Watch_Bot_Restart: String { return self._s[4073]! } + public func Conversation_Kilobytes(_ _0: Int) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4074]!, self._r[4074]!, ["\(_0)"]) + } + public var GroupInfo_ScamGroupWarning: String { return self._s[4075]! } + public var Conversation_EditingMessagePanelMedia: String { return self._s[4076]! } + public var Appearance_PreviewIncomingText: String { return self._s[4077]! } + public var Notifications_ChannelNotificationsExceptionsHelp: String { return self._s[4078]! } + public var ChatList_UndoArchiveRevealedTitle: String { return self._s[4080]! } + public var Stats_GroupOverview: String { return self._s[4082]! } + public var ScheduledMessages_EditTime: String { return self._s[4084]! } + public var Month_GenFebruary: String { return self._s[4085]! } + public var ChatList_AutoarchiveSuggestion_OpenSettings: String { return self._s[4086]! } + public var Stickers_ClearRecent: String { return self._s[4087]! } + public var TwoStepAuth_EnterPasswordPassword: String { return self._s[4088]! } + public func Checkout_PayPrice(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4089]!, self._r[4089]!, [_0]) + } + public var Login_TermsOfServiceSignupDecline: String { return self._s[4090]! } + public var CheckoutInfo_ErrorCityInvalid: String { return self._s[4091]! } + public var VoiceOver_Chat_PlayHint: String { return self._s[4092]! } + public var ChatAdmins_AllMembersAreAdminsOffHelp: String { return self._s[4093]! } + public var CheckoutInfo_ShippingInfoTitle: String { return self._s[4095]! } + public var CreatePoll_Create: String { return self._s[4096]! } + public var ChatList_Search_FilterLinks: String { return self._s[4097]! } + public var Your_cards_number_is_invalid: String { return self._s[4098]! } + public var Month_ShortApril: String { return self._s[4099]! } + public var SocksProxySetup_UseForCalls: String { return self._s[4100]! } + public var Conversation_EditingCaptionPanelTitle: String { return self._s[4101]! } + public var SocksProxySetup_Status: String { return self._s[4102]! } + public var ChannelInfo_DeleteGroupConfirmation: String { return self._s[4103]! } + public var ChatListFolder_CategoryBots: String { return self._s[4104]! } + public var Passport_FieldIdentitySelfieHelp: String { return self._s[4106]! } + public var GroupInfo_BroadcastListNamePlaceholder: String { return self._s[4107]! } + public var Wallpaper_ResetWallpapersInfo: String { return self._s[4108]! } + public var Conversation_TitleUnmute: String { return self._s[4109]! } + public var Group_Setup_TypeHeader: String { return self._s[4110]! } + public var Stats_ViewsPerPost: String { return self._s[4111]! } + public var CheckoutInfo_ShippingInfoCountry: String { return self._s[4112]! } + public var Passport_Identity_TranslationHelp: String { return self._s[4113]! } + public func PUSH_CHANNEL_MESSAGE_FWD(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4114]!, self._r[4114]!, [_1]) + } + public var GroupInfo_Administrators_Title: String { return self._s[4115]! } + public func Channel_AdminLog_MessageRankName(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4116]!, self._r[4116]!, [_1, _2]) + } + public func PUSH_CHAT_MESSAGE_POLL(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4117]!, self._r[4117]!, [_1, _2, _3]) + } + public var Wallet_Receive_Title: String { return self._s[4118]! } + public var CheckoutInfo_ShippingInfoState: String { return self._s[4119]! } + public var Passport_Language_my: String { return self._s[4121]! } + public var PrivacyLastSeenSettings_AlwaysShareWith_Title: String { return self._s[4122]! } + public var Map_PlacesNearby: String { return self._s[4123]! } + public var Channel_About_Help: String { return self._s[4124]! } + public var LogoutOptions_AddAccountTitle: String { return self._s[4125]! } + public var ChatSettings_AutomaticAudioDownload: String { return self._s[4126]! } + public var Channel_Username_Title: String { return self._s[4127]! } + public var Activity_RecordingVideoMessage: String { return self._s[4128]! } + public func StickerPackActionInfo_RemovedText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4129]!, self._r[4129]!, [_0]) + } + public var CheckoutInfo_ShippingInfoCity: String { return self._s[4130]! } + public var Passport_DiscardMessageDescription: String { return self._s[4131]! } + public var Conversation_LinkDialogOpen: String { return self._s[4132]! } + public var ChatList_Context_HideArchive: String { return self._s[4133]! } + public func Message_AuthorPinnedGame(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4134]!, self._r[4134]!, [_0]) + } + public var Privacy_GroupsAndChannels_CustomShareHelp: String { return self._s[4135]! } + public var Conversation_Admin: String { return self._s[4136]! } + public var DialogList_TabTitle: String { return self._s[4137]! } + public func PUSH_CHAT_ALBUM(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4138]!, self._r[4138]!, [_1, _2]) + } + public var Notifications_PermissionsUnreachableText: String { return self._s[4139]! } + public var Passport_Identity_GenderMale: String { return self._s[4141]! } + public var SettingsSearch_Synonyms_Privacy_BlockedUsers: String { return self._s[4143]! } + public var PhoneNumberHelp_Alert: String { return self._s[4144]! } + public var EnterPasscode_EnterNewPasscodeChange: String { return self._s[4145]! } + public var Notifications_InAppNotifications: String { return self._s[4146]! } + public func Update_AppVersion(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4147]!, self._r[4147]!, [_0]) + } + public var Notification_VideoCallOutgoing: String { return self._s[4148]! } + public var Login_InvalidCodeError: String { return self._s[4149]! } + public var Conversation_PrivateChannelTimeLimitedAlertJoin: String { return self._s[4150]! } + public func LastSeen_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4151]!, self._r[4151]!, [_0]) + } + public var Conversation_InputTextCaptionPlaceholder: String { return self._s[4152]! } + public var ReportPeer_Report: String { return self._s[4153]! } + public var Camera_FlashOff: String { return self._s[4155]! } + public var Conversation_InputTextBroadcastPlaceholder: String { return self._s[4158]! } + public var PrivacyPolicy_DeclineTitle: String { return self._s[4160]! } + public var SettingsSearch_Synonyms_Privacy_PasscodeAndTouchId: String { return self._s[4161]! } + public var Passport_FieldEmail: String { return self._s[4162]! } + public func Channel_AdminLog_MessageKickedName(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4163]!, self._r[4163]!, [_1]) + } + public var Notifications_ExceptionsResetToDefaults: String { return self._s[4164]! } + public var PeerInfo_PaneVoiceAndVideo: String { return self._s[4165]! } + public var Group_OwnershipTransfer_Title: String { return self._s[4166]! } + public var Conversation_DefaultRestrictedInline: String { return self._s[4167]! } + public var Login_PhoneNumberHelp: String { return self._s[4169]! } + public var Channel_AdminLogFilter_EventsNewMembers: String { return self._s[4170]! } + public var Conversation_PinnedQuiz: String { return self._s[4171]! } + public var CreateGroup_SoftUserLimitAlert: String { return self._s[4172]! } + public var Login_PhoneNumberAlreadyAuthorizedSwitch: String { return self._s[4173]! } + public var Group_MessagePhotoUpdated: String { return self._s[4174]! } + public var LoginPassword_PasswordPlaceholder: String { return self._s[4175]! } + public var Passport_Identity_Translations: String { return self._s[4177]! } + public var ChatAdmins_AllMembersAreAdmins: String { return self._s[4178]! } + public var ChannelInfo_DeleteChannel: String { return self._s[4180]! } + public var PasscodeSettings_HelpBottom: String { return self._s[4181]! } + public var Channel_Members_AddMembers: String { return self._s[4182]! } + public var AutoDownloadSettings_LastDelimeter: String { return self._s[4183]! } + public var Notification_Exceptions_DeleteAllConfirmation: String { return self._s[4185]! } + public var Conversation_HoldForAudio: String { return self._s[4186]! } + public var Watch_LastSeen_Lately: String { return self._s[4188]! } + public var ChatList_Context_MarkAsRead: String { return self._s[4189]! } + public var Conversation_PinnedMessage: String { return self._s[4190]! } + public var SettingsSearch_Synonyms_Appearance_ColorTheme: String { return self._s[4191]! } + public var Passport_UpdateRequiredError: String { return self._s[4193]! } + public var PrivacySettings_Passcode: String { return self._s[4194]! } + public func Call_EmojiDescription(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4195]!, self._r[4195]!, [_0]) + } + public var AutoNightTheme_NotAvailable: String { return self._s[4196]! } + public var Conversation_PressVolumeButtonForSound: String { return self._s[4197]! } + public var LoginPassword_InvalidPasswordError: String { return self._s[4198]! } + public var ChatListFolder_IncludedSectionHeader: String { return self._s[4199]! } + public var Channel_SignMessages_Help: String { return self._s[4200]! } + public var ChatList_DeleteForEveryoneConfirmationTitle: String { return self._s[4201]! } + public var MediaPicker_LivePhotoDescription: String { return self._s[4202]! } + public var GroupInfo_Permissions: String { return self._s[4203]! } + public var GroupPermission_NoSendLinks: String { return self._s[4206]! } + public var Passport_Identity_ResidenceCountry: String { return self._s[4207]! } + public var Appearance_ThemeCarouselNightBlue: String { return self._s[4209]! } + public var ChatList_ArchiveAction: String { return self._s[4210]! } + public func Channel_AdminLog_DisabledSlowmode(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4211]!, self._r[4211]!, [_0]) + } + public var GroupInfo_GroupHistory: String { return self._s[4212]! } + public func Channel_Management_ErrorNotMember(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4214]!, self._r[4214]!, [_0]) + } + public var Privacy_Forwards_LinkIfAllowed: String { return self._s[4216]! } + public var Channel_Info_Banned: String { return self._s[4217]! } + public var Paint_RecentStickers: String { return self._s[4218]! } + public var VoiceOver_MessageContextSend: String { return self._s[4219]! } + public var Group_ErrorNotMutualContact: String { return self._s[4220]! } + public var ReportPeer_ReasonOther: String { return self._s[4222]! } + public var Channel_BanUser_PermissionChangeGroupInfo: String { return self._s[4223]! } + public var SocksProxySetup_ShareQRCodeInfo: String { return self._s[4225]! } + public var KeyCommand_Find: String { return self._s[4226]! } + public func Channel_MessageTitleUpdated(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4227]!, self._r[4227]!, [_0]) + } + public var ChatList_Context_Unmute: String { return self._s[4228]! } + public var Chat_SlowmodeAttachmentLimitReached: String { return self._s[4229]! } + public var Stickers_GroupStickersHelp: String { return self._s[4230]! } + public var Wallet_Configuration_BlockchainIdPlaceholder: String { return self._s[4231]! } + public var Checkout_Title: String { return self._s[4232]! } + public var Activity_RecordingAudio: String { return self._s[4233]! } + public var SettingsSearch_Synonyms_Notifications_GroupNotificationsPreview: String { return self._s[4234]! } + public var BlockedUsers_BlockTitle: String { return self._s[4235]! } + public var Wallet_Month_ShortFebruary: String { return self._s[4237]! } + public var Calls_All: String { return self._s[4238]! } + public var DialogList_SavedMessagesHelp: String { return self._s[4240]! } + public var Settings_FAQ_Button: String { return self._s[4241]! } + public func Time_MonthOfYear_m5(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4243]!, self._r[4243]!, [_0]) + } + public var Conversation_ReportGroupLocation: String { return self._s[4244]! } + public var Passport_Scans_Upload: String { return self._s[4245]! } + public var Channel_EditAdmin_PermissionPinMessages: String { return self._s[4247]! } + public var ChatList_UnarchiveAction: String { return self._s[4248]! } + public var Stats_GroupTopInviter_History: String { return self._s[4249]! } + public var GroupInfo_Permissions_Title: String { return self._s[4250]! } + public var Passport_Language_el: String { return self._s[4251]! } + public func Wallet_Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4252]!, self._r[4252]!, [_1, _2, _3]) + } + public var GroupInfo_ActionPromote: String { return self._s[4253]! } + public var Group_OwnershipTransfer_ErrorLocatedGroupsTooMuch: String { return self._s[4254]! } + public func TwoStepAuth_PendingEmailHelp(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4255]!, self._r[4255]!, [_0]) + } + public var VoiceOver_Chat_Reply: String { return self._s[4256]! } + public var Month_GenMay: String { return self._s[4257]! } + public var DialogList_DeleteBotConversationConfirmation: String { return self._s[4258]! } + public var Chat_PsaTooltip_covid: String { return self._s[4259]! } + public var Watch_Suggestion_CantTalk: String { return self._s[4260]! } + public var Privacy_GroupsAndChannels_NeverAllow_Title: String { return self._s[4261]! } + public var AppUpgrade_Running: String { return self._s[4262]! } + public var PasscodeSettings_UnlockWithFaceId: String { return self._s[4265]! } + public var Notification_Exceptions_PreviewAlwaysOff: String { return self._s[4266]! } + public var SharedMedia_EmptyText: String { return self._s[4267]! } + public var Passport_Address_EditResidentialAddress: String { return self._s[4268]! } + public var SettingsSearch_Synonyms_Notifications_GroupNotificationsAlert: String { return self._s[4269]! } + public var Message_PinnedGame: String { return self._s[4270]! } + public var KeyCommand_SearchInChat: String { return self._s[4271]! } + public var Appearance_ThemeCarouselNewNight: String { return self._s[4272]! } + public var ChatList_Search_FilterMedia: String { return self._s[4273]! } + public var Message_PinnedAudioMessage: String { return self._s[4274]! } + public var ChannelInfo_ConfirmLeave: String { return self._s[4275]! } + public func Channel_AdminLog_MessagePromotedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4276]!, self._r[4276]!, [_1, _2]) + } + public var SocksProxySetup_ProxyStatusUnavailable: String { return self._s[4277]! } + public func Passport_Email_CodeHelp(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4278]!, self._r[4278]!, [_0]) + } + public var Wallet_Receive_AddressCopied: String { return self._s[4279]! } + public func Message_PinnedTextMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4280]!, self._r[4280]!, [_0]) + } + public var Settings_AddAccount: String { return self._s[4281]! } + public var Channel_AdminLog_CanDeleteMessages: String { return self._s[4282]! } + public var Conversation_DiscardVoiceMessageTitle: String { return self._s[4283]! } + public var Channel_JoinChannel: String { return self._s[4284]! } + public var Watch_UserInfo_Unblock: String { return self._s[4285]! } + public var PhoneLabel_Title: String { return self._s[4286]! } + public var Group_Setup_HistoryHiddenHelp: String { return self._s[4288]! } + public var Privacy_ProfilePhoto_AlwaysShareWith_Title: String { return self._s[4289]! } + public func Login_PhoneGenericEmailBody(_ _1: String, _ _2: String, _ _3: String, _ _4: String, _ _5: String, _ _6: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4290]!, self._r[4290]!, [_1, _2, _3, _4, _5, _6]) + } + public var Wallet_Month_GenOctober: String { return self._s[4291]! } + public var Channel_AddBotErrorHaveRights: String { return self._s[4292]! } + public var ChatList_TabIconFoldersTooltipNonEmptyFolders: String { return self._s[4293]! } + public var DialogList_EncryptionProcessing: String { return self._s[4294]! } + public var WatchRemote_NotificationText: String { return self._s[4295]! } + public var EditTheme_ChangeColors: String { return self._s[4296]! } + public var GroupRemoved_ViewUserInfo: String { return self._s[4297]! } + public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[4298]! } + public var CallSettings_OnMobile: String { return self._s[4300]! } + public var Month_ShortFebruary: String { return self._s[4302]! } + public var VoiceOver_MessageContextReply: String { return self._s[4303]! } + public func PUSH_VIDEO_CALL_REQUEST(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4304]!, self._r[4304]!, [_1]) + } + public var Group_Location_ChangeLocation: String { return self._s[4305]! } + public var Passport_Address_TypeBankStatementUploadScan: String { return self._s[4306]! } + public var Wallet_Send_EncryptComment: String { return self._s[4307]! } + public var VoiceOver_Media_PlaybackStop: String { return self._s[4308]! } + public var SettingsSearch_Synonyms_Data_SaveIncomingPhotos: String { return self._s[4309]! } + public func Channel_AdminLog_MessageRestrictedUntil(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4311]!, self._r[4311]!, [_0]) + } + public var PhotoEditor_WarmthTool: String { return self._s[4312]! } + public var Login_InfoAvatarPhoto: String { return self._s[4313]! } + public var Notification_Exceptions_NewException_MessagePreviewHeader: String { return self._s[4314]! } + public var Permissions_CellularDataAllowInSettings_v0: String { return self._s[4315]! } + public var Map_PlacesInThisArea: String { return self._s[4316]! } + public var VoiceOver_Chat_ContactEmail: String { return self._s[4317]! } + public var Notifications_InAppNotificationsSounds: String { return self._s[4318]! } + public func PUSH_PINNED_NOTEXT(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4319]!, self._r[4319]!, [_1]) + } + public var ShareMenu_Send: String { return self._s[4320]! } + public var Username_InvalidStartsWithNumber: String { return self._s[4321]! } + public var Appearance_AppIconClassicX: String { return self._s[4322]! } + public func PUSH_CHANNEL_MESSAGE_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4323]!, self._r[4323]!, [_1]) + } + public var Conversation_StopPoll: String { return self._s[4324]! } + public var InfoPlist_NSLocationAlwaysUsageDescription: String { return self._s[4326]! } + public var Passport_Identity_EditIdentityCard: String { return self._s[4327]! } + public var Appearance_ThemePreview_ChatList_3_Name: String { return self._s[4328]! } + public var Wallet_WordCheck_Title: String { return self._s[4329]! } + public var Conversation_Timer_Title: String { return self._s[4330]! } + public var Common_Next: String { return self._s[4331]! } + public var Notification_Exceptions_NewException: String { return self._s[4332]! } + public func Generic_OpenHiddenLinkAlert(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4333]!, self._r[4333]!, [_0]) + } + public var AccessDenied_CallMicrophone: String { return self._s[4334]! } + public var SettingsSearch_Synonyms_Data_AutoDownloadUsingCellular: String { return self._s[4335]! } + public var ChangePhoneNumberCode_Help: String { return self._s[4336]! } + public var Passport_Identity_OneOfTypeIdentityCard: String { return self._s[4337]! } + public var Channel_AdminLogFilter_EventsLeaving: String { return self._s[4338]! } + public var BlockedUsers_LeavePrefix: String { return self._s[4339]! } + public func Passport_RequestHeader(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4340]!, self._r[4340]!, [_0]) + } + public var Group_About_Help: String { return self._s[4341]! } + public var TwoStepAuth_ChangePasswordDescription: String { return self._s[4342]! } + public var Tour_Title3: String { return self._s[4343]! } + public var Watch_Conversation_Unblock: String { return self._s[4344]! } + public var Watch_UserInfo_Block: String { return self._s[4345]! } + public var Notifications_ChannelNotificationsAlert: String { return self._s[4346]! } + public var TwoFactorSetup_Hint_Action: String { return self._s[4347]! } + public var IntentsSettings_SuggestedChatsInfo: String { return self._s[4348]! } + public var Wallet_Alert_Cancel: String { return self._s[4349]! } + public var TextFormat_AddLinkTitle: String { return self._s[4350]! } + public var GroupInfo_InviteLink_RevokeAlert_Revoke: String { return self._s[4351]! } + public var TwoStepAuth_EnterPasswordTitle: String { return self._s[4352]! } + public var FastTwoStepSetup_PasswordSection: String { return self._s[4353]! } + public var Compose_ChannelMembers: String { return self._s[4354]! } + public var Conversation_ForwardTitle: String { return self._s[4355]! } + public func Wallet_Updated_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4356]!, self._r[4356]!, [_0]) + } + public var Conversation_PinnedPoll: String { return self._s[4358]! } + public func VoiceOver_Chat_AnonymousPollFrom(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4359]!, self._r[4359]!, [_0]) + } + public var SettingsSearch_Synonyms_EditProfile_AddAccount: String { return self._s[4360]! } + public var Conversation_ContextMenuStickerPackAdd: String { return self._s[4361]! } + public var Stats_Overview: String { return self._s[4362]! } + public var Map_HomeAndWorkTitle: String { return self._s[4363]! } + public var Wallet_Intro_Terms: String { return self._s[4364]! } + public func Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4365]!, self._r[4365]!, [_1, _2, _3]) + } + public var Passport_Address_CityPlaceholder: String { return self._s[4366]! } + public var InfoPlist_NSLocationAlwaysAndWhenInUseUsageDescription: String { return self._s[4367]! } + public var Privacy_PhoneNumber: String { return self._s[4368]! } + public var ChatList_Search_FilterFiles: String { return self._s[4369]! } + public var ChatList_DeleteForEveryoneConfirmationAction: String { return self._s[4370]! } + public var ChannelIntro_CreateChannel: String { return self._s[4371]! } + public var Conversation_InputTextAnonymousPlaceholder: String { return self._s[4372]! } + public func Login_EmailCodeBody(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4373]!, self._r[4373]!, [_0]) + } + public var Weekday_ShortMonday: String { return self._s[4374]! } + public var Passport_Language_ar: String { return self._s[4376]! } + public var SettingsSearch_Synonyms_EditProfile_Title: String { return self._s[4377]! } + public var TwoFactorSetup_Done_Title: String { return self._s[4378]! } + public var Calls_RatingFeedback: String { return self._s[4379]! } + public var SettingsSearch_Synonyms_Notifications_ChannelNotificationsPreview: String { return self._s[4380]! } + public var AutoDownloadSettings_ResetSettings: String { return self._s[4383]! } + public var Watch_Compose_Send: String { return self._s[4384]! } + public var PasscodeSettings_ChangePasscode: String { return self._s[4385]! } + public var WebSearch_RecentSectionClear: String { return self._s[4386]! } + public func Contacts_AccessDeniedHelpPortrait(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4387]!, self._r[4387]!, [_0]) + } + public var WallpaperSearch_ColorTeal: String { return self._s[4388]! } + public var Wallpaper_SetCustomBackgroundInfo: String { return self._s[4389]! } + public var Permissions_ContactsTitle_v0: String { return self._s[4390]! } + public var Checkout_PasswordEntry_Pay: String { return self._s[4392]! } + public var Settings_SavedMessages: String { return self._s[4393]! } + public var TwoStepAuth_ReEnterPasswordDescription: String { return self._s[4394]! } + public var Month_ShortMarch: String { return self._s[4395]! } + public var Message_Location: String { return self._s[4396]! } + public func PUSH_MESSAGE_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4397]!, self._r[4397]!, [_1]) + } + public func Notification_CallTimeFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4398]!, self._r[4398]!, [_1, _2]) + } + public var VoiceOver_Chat_VoiceMessage: String { return self._s[4400]! } + public func Channel_AdminLog_MessageChangedUnlinkedChannel(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4401]!, self._r[4401]!, [_1, _2]) + } + public var GroupPermission_NoSendMedia: String { return self._s[4402]! } + public var Conversation_ClousStorageInfo_Description2: String { return self._s[4403]! } + public var SharedMedia_CategoryDocs: String { return self._s[4404]! } + public var Appearance_RemoveThemeConfirmation: String { return self._s[4405]! } + public var Paint_Framed: String { return self._s[4406]! } + public var Channel_EditAdmin_PermissionAddAdmins: String { return self._s[4407]! } + public var Passport_Identity_DoesNotExpire: String { return self._s[4408]! } + public var Channel_SignMessages: String { return self._s[4409]! } + public var Contacts_AccessDeniedHelpON: String { return self._s[4410]! } + public var Conversation_ContextMenuStickerPackInfo: String { return self._s[4411]! } + public func PUSH_CHAT_LEFT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4412]!, self._r[4412]!, [_1, _2]) + } + public var GroupInfo_UpgradeButton: String { return self._s[4413]! } + public var Channel_EditAdmin_PermissionInviteMembers: String { return self._s[4414]! } + public var AutoDownloadSettings_Files: String { return self._s[4415]! } + public func Notification_ChangedGroupName(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4416]!, self._r[4416]!, [_0, _1]) + } + public var Login_SendCodeViaSms: String { return self._s[4418]! } + public var Update_UpdateApp: String { return self._s[4419]! } + public var Channel_Setup_TypePublic: String { return self._s[4420]! } + public var Watch_Compose_CreateMessage: String { return self._s[4421]! } + public func PUSH_CHAT_MESSAGE_VIDEOS(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4422]!, self._r[4422]!, [_1, _2, _3]) + } + public var StickerPacksSettings_ManagingHelp: String { return self._s[4423]! } + public func Wallet_Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4424]!, self._r[4424]!, [_1, _2, _3]) + } + public var VoiceOver_Chat_Video: String { return self._s[4425]! } + public var Forward_ChannelReadOnly: String { return self._s[4426]! } + public var StickerPack_HideStickers: String { return self._s[4427]! } + public var ChatListFolder_NameContacts: String { return self._s[4428]! } + public var Profile_BotInfo: String { return self._s[4429]! } + public var Document_TargetConfirmationFormat: String { return self._s[4430]! } + public var GroupInfo_InviteByLink: String { return self._s[4431]! } + public var Channel_AdminLog_BanSendStickersAndGifs: String { return self._s[4432]! } + public var Watch_Stickers_RecentPlaceholder: String { return self._s[4433]! } + public var Broadcast_AdminLog_EmptyText: String { return self._s[4434]! } + public var Passport_NotLoggedInMessage: String { return self._s[4435]! } + public var Conversation_StopQuizConfirmation: String { return self._s[4436]! } + public var Checkout_PaymentMethod: String { return self._s[4437]! } + public var ChatList_ArchivedChatsTitle: String { return self._s[4441]! } + public var TwoStepAuth_SetupPasswordConfirmFailed: String { return self._s[4442]! } + public var VoiceOver_Chat_RecordPreviewVoiceMessage: String { return self._s[4443]! } + public var PrivacyLastSeenSettings_GroupsAndChannelsHelp: String { return self._s[4444]! } + public var SettingsSearch_Synonyms_Privacy_Data_ContactsReset: String { return self._s[4445]! } + public var Camera_Title: String { return self._s[4446]! } + public var Map_Directions: String { return self._s[4447]! } + public var Wallet_Intro_ImportExisting: String { return self._s[4448]! } + public var Stats_MessagePublicForwardsTitle: String { return self._s[4449]! } + public var Privacy_ProfilePhoto_WhoCanSeeMyPhoto: String { return self._s[4451]! } + public var Profile_EncryptionKey: String { return self._s[4452]! } + public func LOCAL_CHAT_MESSAGE_FWDS(_ _1: String, _ _2: Int) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4453]!, self._r[4453]!, [_1, "\(_2)"]) + } + public func Compatibility_SecretMediaVersionTooLow(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4454]!, self._r[4454]!, [_0, _1]) + } + public var Passport_Identity_TypePassport: String { return self._s[4455]! } + public var CreatePoll_QuizOptionsHeader: String { return self._s[4457]! } + public var Common_No: String { return self._s[4458]! } + public var Conversation_SendMessage_ScheduleMessage: String { return self._s[4459]! } + public var SettingsSearch_Synonyms_Privacy_LastSeen: String { return self._s[4460]! } + public var Settings_AboutEmpty: String { return self._s[4461]! } + public var TwoStepAuth_FloodError: String { return self._s[4463]! } + public var SettingsSearch_Synonyms_Appearance_TextSize: String { return self._s[4464]! } + public func Channel_AdminLog_MessageUnkickedName(_ _1: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4466]!, self._r[4466]!, [_1]) + } + public var Conversation_Edit: String { return self._s[4469]! } + public var CheckoutInfo_SaveInfo: String { return self._s[4470]! } + public var VoiceOver_Chat_AnonymousPoll: String { return self._s[4471]! } + public var Call_CameraTooltip: String { return self._s[4473]! } + public var InstantPage_FeedbackButtonShort: String { return self._s[4474]! } + public var Contacts_InviteToTelegram: String { return self._s[4475]! } + public var Wallet_WordImport_CanNotRemember: String { return self._s[4476]! } + public var Notifications_ResetAllNotifications: String { return self._s[4477]! } + public var Calls_NewCall: String { return self._s[4478]! } + public var VoiceOver_Chat_Music: String { return self._s[4481]! } + public var Channel_Members_AddAdminErrorNotAMember: String { return self._s[4482]! } + public var Channel_Edit_AboutItem: String { return self._s[4483]! } + public var Message_VideoExpired: String { return self._s[4484]! } + public var Passport_Address_TypeTemporaryRegistrationUploadScan: String { return self._s[4485]! } + public func PUSH_CHAT_RETURNED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4486]!, self._r[4486]!, [_1, _2]) + } + public var NotificationsSound_Input: String { return self._s[4488]! } + public var Notifications_ClassicTones: String { return self._s[4489]! } + public var Conversation_StatusTyping: String { return self._s[4490]! } + public var Checkout_ErrorProviderAccountInvalid: String { return self._s[4491]! } + public var ChatSettings_AutoDownloadSettings_Delimeter: String { return self._s[4492]! } + public var Wallet_Month_ShortSeptember: String { return self._s[4493]! } + public var SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedChats: String { return self._s[4494]! } + public var UserInfo_TapToCall: String { return self._s[4495]! } + public var EnterPasscode_EnterNewPasscodeNew: String { return self._s[4496]! } + public var Conversation_ClearAll: String { return self._s[4498]! } + public var UserInfo_NotificationsDefault: String { return self._s[4499]! } + public var Wallet_Send_OwnAddressAlertText: String { return self._s[4500]! } + public var Map_ChooseAPlace: String { return self._s[4501]! } + public func Wallet_Receive_ShareInvoiceUrlInfo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4502]!, self._r[4502]!, [_0]) + } + public var GroupInfo_AddParticipantTitle: String { return self._s[4503]! } + public var ChatList_PeerTypeNonContact: String { return self._s[4504]! } + public var Conversation_SlideToCancel: String { return self._s[4505]! } + public var Month_ShortJuly: String { return self._s[4506]! } + public var SocksProxySetup_ProxyType: String { return self._s[4507]! } + public func ChatList_DeleteChatConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4508]!, self._r[4508]!, [_0]) + } + public var ChatList_EditFolders: String { return self._s[4509]! } + public var TwoStepAuth_SetPasswordHelp: String { return self._s[4510]! } + public var Wallet_Send_ConfirmationConfirm: String { return self._s[4512]! } + public var Wallet_Created_ExportErrorTitle: String { return self._s[4513]! } + public func GroupPermission_ApplyAlertText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[4514]!, self._r[4514]!, [_0]) + } + public var Permissions_PeopleNearbyTitle_v0: String { return self._s[4515]! } + public var ScheduledMessages_RemindersTitle: String { return self._s[4516]! } + public var Your_cards_expiration_year_is_invalid: String { return self._s[4517]! } + public var Wallet_Info_TransactionPendingHeader: String { return self._s[4519]! } + public var UserInfo_ShareMyContactInfo: String { return self._s[4520]! } + public var Passport_DeleteAddress: String { return self._s[4522]! } + public var Passport_DeletePassportConfirmation: String { return self._s[4523]! } + public var Passport_Identity_ReverseSide: String { return self._s[4524]! } + public var CheckoutInfo_ErrorEmailInvalid: String { return self._s[4525]! } + public var Login_InfoLastNamePlaceholder: String { return self._s[4526]! } + public var Passport_FieldAddress: String { return self._s[4527]! } + public var SettingsSearch_Synonyms_Calls_Title: String { return self._s[4528]! } + public var Passport_Identity_ResidenceCountryPlaceholder: String { return self._s[4530]! } + public var Map_Home: String { return self._s[4532]! } + public var PollResults_Title: String { return self._s[4533]! } + public var ArchivedChats_IntroText2: String { return self._s[4535]! } + public var PasscodeSettings_SimplePasscodeHelp: String { return self._s[4536]! } + public var VoiceOver_Chat_ContactPhoneNumber: String { return self._s[4537]! } + public var CallFeedback_ReasonSilentRemote: String { return self._s[4539]! } + public var Passport_Identity_AddPersonalDetails: String { return self._s[4541]! } + public var Group_Info_AdminLog: String { return self._s[4543]! } + public var ChatSettings_AutoPlayTitle: String { return self._s[4544]! } + public var Appearance_Animations: String { return self._s[4545]! } + public var Appearance_TextSizeSetting: String { return self._s[4546]! } + public func Call_ShortMinutes(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue) } - public func MuteFor_Days(_ value: Int32) -> String { + public func AttachmentMenu_SendPhoto(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue) } - public func MessageTimer_Hours(_ value: Int32) -> String { + public func Notification_GameScoreSimple(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[2 * 6 + Int(form.rawValue)]!, stringValue) } - public func StickerPack_AddStickerCount(_ value: Int32) -> String { + public func MessagePoll_QuizCount(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[3 * 6 + Int(form.rawValue)]!, stringValue) } - public func Call_ShortSeconds(_ value: Int32) -> String { + public func MessageTimer_ShortMinutes(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[4 * 6 + Int(form.rawValue)]!, stringValue) } - public func SharedMedia_Link(_ value: Int32) -> String { + public func Notifications_ExceptionMuteExpires_Days(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[5 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_CHANNEL_MESSAGE_FWDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[6 * 6 + Int(form.rawValue)]!, _1, _2) + public func OldChannels_InactiveYear(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[6 * 6 + Int(form.rawValue)]!, stringValue) } - public func MessageTimer_ShortMinutes(_ value: Int32) -> String { + public func Stats_MessageViews(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[7 * 6 + Int(form.rawValue)]!, stringValue) } - public func Contacts_InviteContacts(_ value: Int32) -> String { + public func Media_ShareItem(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[8 * 6 + Int(form.rawValue)]!, stringValue) } - public func VoiceOver_Chat_ContactPhoneNumberCount(_ value: Int32) -> String { + public func Stats_GroupTopInviterInvites(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[9 * 6 + Int(form.rawValue)]!, stringValue) } - public func Conversation_LiveLocationMembersCount(_ value: Int32) -> String { + public func Stats_GroupTopAdminDeletions(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[10 * 6 + Int(form.rawValue)]!, stringValue) } - public func ForwardedLocations(_ value: Int32) -> String { + public func SharedMedia_File(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[11 * 6 + Int(form.rawValue)]!, stringValue) } - public func Stats_MessageForwards(_ value: Int32) -> String { + public func MessageTimer_ShortWeeks(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[12 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_MESSAGE_PHOTOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[13 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func Notification_GameScoreSelfExtended(_ value: Int32) -> String { + public func StickerPack_RemoveMaskCount(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[14 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Stats_GroupTopAdminBans(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[15 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Media_ShareVideo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[16 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedVideos(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[17 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Watch_LastSeen_HoursAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[18 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedContacts(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[19 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MuteExpires_Days(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[20 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[13 * 6 + Int(form.rawValue)]!, stringValue) } public func ChatList_SelectedChats(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[21 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[14 * 6 + Int(form.rawValue)]!, stringValue) } - public func MessageTimer_Years(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[22 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_Months(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[23 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Stats_GroupTopPosterChars(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[24 * 6 + Int(form.rawValue)]!, stringValue) - } - public func LiveLocation_MenuChatsCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[25 * 6 + Int(form.rawValue)]!, stringValue) - } - public func VoiceOver_Chat_PollOptionCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[26 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_ShortDays(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[27 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Stats_GroupShowMoreTopAdmins(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[28 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ChatList_DeleteConfirmation(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[29 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHANNEL_MESSAGES(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + public func PUSH_MESSAGE_ROUNDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[30 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func Stats_GroupShowMoreTopInviters(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[31 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_Weeks(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[32 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ChatList_MessagePhotos(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[33 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Invitation_Members(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[34 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Map_ETAHours(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[35 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Watch_LastSeen_MinutesAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[36 * 6 + Int(form.rawValue)]!, stringValue) - } - public func GroupInfo_ParticipantCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[37 * 6 + Int(form.rawValue)]!, stringValue) - } - public func OldChannels_Leave(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[38 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Theme_UsersCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[39 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ServiceMessage_GameScoreExtended(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[40 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ServiceMessage_GameScoreSimple(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[41 * 6 + Int(form.rawValue)]!, stringValue) - } - public func StickerPack_RemoveStickerCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[42 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedVideoMessages(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[43 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Stats_GroupTopAdminDeletions(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[44 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Conversation_StatusOnline(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[45 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Media_SharePhoto(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[46 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHAT_MESSAGE_PHOTOS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[47 * 6 + Int(form.rawValue)]!, _2, _1, _3) - } - public func Watch_UserInfo_Mute(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[48 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_Minutes(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[49 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Wallet_Updated_HoursAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[50 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Notifications_ExceptionMuteExpires_Days(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[51 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_ShortHours(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[52 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MessageTimer_Seconds(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[53 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MuteExpires_Hours(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[54 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[55 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PasscodeSettings_FailedAttempts(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[56 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Stats_GroupTopPosterMessages(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[57 * 6 + Int(form.rawValue)]!, stringValue) - } - public func StickerPack_StickerCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[58 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[15 * 6 + Int(form.rawValue)]!, _1, _2) } public func AttachmentMenu_SendVideo(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[59 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[16 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Stats_GroupShowMoreTopPosters(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[17 * 6 + Int(form.rawValue)]!, stringValue) + } + public func GroupInfo_ShowMoreMembers(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[18 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ChatList_MessagePhotos(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[19 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_Seconds(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[20 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_Years(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[21 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Wallet_Updated_HoursAgo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[22 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_MESSAGES(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[23 * 6 + Int(form.rawValue)]!, _1, _2) } public func CreatePoll_AddMoreOptions(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[60 * 6 + Int(form.rawValue)]!, stringValue) - } - public func AttachmentMenu_SendItem(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[61 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ServiceMessage_GameScoreSelfSimple(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[62 * 6 + Int(form.rawValue)]!, stringValue) - } - public func StickerPack_AddMaskCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[63 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Media_ShareItem(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[64 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHANNEL_MESSAGE_ROUNDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[65 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func PrivacyLastSeenSettings_AddUsers(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[66 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Notification_GameScoreSimple(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[67 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHAT_MESSAGE_ROUNDS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[68 * 6 + Int(form.rawValue)]!, _2, _1, _3) - } - public func Conversation_StatusSubscribers(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[69 * 6 + Int(form.rawValue)]!, stringValue) - } - public func MuteExpires_Minutes(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[70 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Stats_GroupTopAdminKicks(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[71 * 6 + Int(form.rawValue)]!, stringValue) - } - public func SharedMedia_File(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[72 * 6 + Int(form.rawValue)]!, stringValue) - } - public func OldChannels_InactiveYear(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[73 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHAT_MESSAGE_FWDS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[74 * 6 + Int(form.rawValue)]!, _2, _1, _3) - } - public func AttachmentMenu_SendPhoto(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[75 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Wallpaper_DeleteConfirmation(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[76 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedAuthorsOthers(_ selector: Int32, _ _0: String, _ _1: String) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[77 * 6 + Int(form.rawValue)]!, _0, _1) - } - public func MessageTimer_ShortSeconds(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[78 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PeopleNearby_ShowMorePeople(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[79 * 6 + Int(form.rawValue)]!, stringValue) - } - public func Conversation_StatusMembers(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[80 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[24 * 6 + Int(form.rawValue)]!, stringValue) } public func LastSeen_HoursAgo(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[81 * 6 + Int(form.rawValue)]!, stringValue) - } - public func StickerPack_RemoveMaskCount(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[82 * 6 + Int(form.rawValue)]!, stringValue) - } - public func ForwardedPhotos(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[83 * 6 + Int(form.rawValue)]!, stringValue) - } - public func PUSH_CHANNEL_MESSAGE_PHOTOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[84 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func VoiceOver_Chat_PollVotes(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[85 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[25 * 6 + Int(form.rawValue)]!, stringValue) } public func SharedMedia_Video(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[86 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[26 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_Days(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[27 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ChatListFilter_ShowMoreChats(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[28 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Notification_GameScoreSelfExtended(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[29 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHAT_MESSAGE_FWDS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[30 * 6 + Int(form.rawValue)]!, _2, _1, _3) + } + public func MessageTimer_Minutes(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[31 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedFiles(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[32 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Call_Minutes(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[33 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Stats_MessageForwards(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[34 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_ShortSeconds(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[35 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Forward_ConfirmMultipleFiles(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[36 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ServiceMessage_GameScoreExtended(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[37 * 6 + Int(form.rawValue)]!, stringValue) + } + public func LiveLocationUpdated_MinutesAgo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[38 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Notifications_ExceptionMuteExpires_Hours(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[39 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedPolls(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[40 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Notifications_Exceptions(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[41 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessagePoll_VotedCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[42 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedLocations(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[43 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PeopleNearby_ShowMorePeople(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[44 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ServiceMessage_GameScoreSimple(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[45 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Call_ShortSeconds(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[46 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Stats_GroupTopPosterMessages(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[47 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PasscodeSettings_FailedAttempts(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[48 * 6 + Int(form.rawValue)]!, stringValue) + } + public func OldChannels_InactiveMonth(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[49 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Watch_LastSeen_HoursAgo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[50 * 6 + Int(form.rawValue)]!, stringValue) + } + public func VoiceOver_Chat_ContactEmailCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[51 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Map_ETAHours(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[52 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Watch_UserInfo_Mute(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[53 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ForwardedVideos(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[54 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_Months(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[55 * 6 + Int(form.rawValue)]!, stringValue) + } + public func StickerPack_RemoveStickerCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[56 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Passport_Scans(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[57 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Conversation_StatusOnline(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[58 * 6 + Int(form.rawValue)]!, stringValue) + } + public func StickerPack_StickerCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[59 * 6 + Int(form.rawValue)]!, stringValue) + } + public func UserCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[60 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Call_Seconds(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[61 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_MESSAGE_VIDEOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[62 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func Invitation_Members(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[63 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHANNEL_MESSAGES(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[64 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func SharedMedia_DeleteItemsConfirmation(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[65 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ServiceMessage_GameScoreSelfSimple(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[66 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHANNEL_MESSAGE_PHOTOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[67 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func AttachmentMenu_SendItem(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[68 * 6 + Int(form.rawValue)]!, stringValue) + } + public func SharedMedia_Generic(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[69 * 6 + Int(form.rawValue)]!, stringValue) + } + public func StickerPack_AddMaskCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[70 * 6 + Int(form.rawValue)]!, stringValue) + } + public func VoiceOver_Chat_ContactPhoneNumberCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[71 * 6 + Int(form.rawValue)]!, stringValue) + } + public func OldChannels_GroupFormat(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[72 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_Hours(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[73 * 6 + Int(form.rawValue)]!, stringValue) + } + public func LiveLocation_MenuChatsCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[74 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Stats_GroupTopAdminKicks(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[75 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Stats_GroupTopAdminBans(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[76 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ChatList_DeleteConfirmation(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[77 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Media_SharePhoto(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[78 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Stats_GroupTopPosterChars(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[79 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MessageTimer_Weeks(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[80 * 6 + Int(form.rawValue)]!, stringValue) + } + public func MuteFor_Days(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[81 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHAT_MESSAGE_PHOTOS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[82 * 6 + Int(form.rawValue)]!, _2, _1, _3) + } + public func MuteExpires_Hours(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[83 * 6 + Int(form.rawValue)]!, stringValue) + } + public func Notification_GameScoreExtended(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[84 * 6 + Int(form.rawValue)]!, stringValue) + } + public func PUSH_CHANNEL_MESSAGE_VIDEOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[85 * 6 + Int(form.rawValue)]!, _1, _2) + } + public func PUSH_CHAT_MESSAGES(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[86 * 6 + Int(form.rawValue)]!, _2, _1, _3) } public func InviteText_ContactsCountText(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[87 * 6 + Int(form.rawValue)]!, stringValue) } - public func Notifications_ExceptionMuteExpires_Minutes(_ value: Int32) -> String { + public func Wallpaper_DeleteConfirmation(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[88 * 6 + Int(form.rawValue)]!, stringValue) } - public func ChatList_MessageVideos(_ value: Int32) -> String { + public func SharedMedia_Photo(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[89 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_MESSAGES(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[90 * 6 + Int(form.rawValue)]!, _1, _2) - } - public func MessagePoll_QuizCount(_ value: Int32) -> String { + public func ForwardedContacts(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[91 * 6 + Int(form.rawValue)]!, stringValue) + return String(format: self._ps[90 * 6 + Int(form.rawValue)]!, stringValue) } - public func Notification_GameScoreExtended(_ value: Int32) -> String { + public func PUSH_CHAT_MESSAGE_VIDEOS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[91 * 6 + Int(form.rawValue)]!, _2, _1, _3) + } + public func OldChannels_Leave(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[92 * 6 + Int(form.rawValue)]!, stringValue) } - public func MuteFor_Hours(_ value: Int32) -> String { + public func MuteExpires_Minutes(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[93 * 6 + Int(form.rawValue)]!, stringValue) } - public func Stats_MessageViews(_ value: Int32) -> String { + public func SharedMedia_Link(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[94 * 6 + Int(form.rawValue)]!, stringValue) } - public func Notifications_ExceptionMuteExpires_Hours(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[95 * 6 + Int(form.rawValue)]!, stringValue) + public func PUSH_CHAT_MESSAGE_ROUNDS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[95 * 6 + Int(form.rawValue)]!, _2, _1, _3) } - public func AttachmentMenu_SendGif(_ value: Int32) -> String { + public func MuteExpires_Days(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[96 * 6 + Int(form.rawValue)]!, stringValue) } - public func Conversation_SelectedMessages(_ value: Int32) -> String { + public func ChatList_Search_Messages(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[97 * 6 + Int(form.rawValue)]!, stringValue) } - public func VoiceOver_Chat_ContactEmailCount(_ value: Int32) -> String { + public func Map_ETAMinutes(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[98 * 6 + Int(form.rawValue)]!, stringValue) } - public func Map_ETAMinutes(_ value: Int32) -> String { + public func InstantPage_Views(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[99 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_MESSAGE_VIDEOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[100 * 6 + Int(form.rawValue)]!, _1, _2) + public func Theme_UsersCount(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[100 * 6 + Int(form.rawValue)]!, stringValue) } - public func ForwardedFiles(_ value: Int32) -> String { + public func Watch_LastSeen_MinutesAgo(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[101 * 6 + Int(form.rawValue)]!, stringValue) } - public func ChatList_DeletedChats(_ value: Int32) -> String { + public func ForwardedPhotos(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[102 * 6 + Int(form.rawValue)]!, stringValue) } - public func DialogList_LiveLocationChatsCount(_ value: Int32) -> String { + public func Stats_GroupShowMoreTopInviters(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[103 * 6 + Int(form.rawValue)]!, stringValue) } - public func OldChannels_InactiveWeek(_ value: Int32) -> String { + public func ForwardedGifs(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[104 * 6 + Int(form.rawValue)]!, stringValue) } - public func ForwardedAudios(_ value: Int32) -> String { + public func ServiceMessage_GameScoreSelfExtended(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[105 * 6 + Int(form.rawValue)]!, stringValue) } - public func ChatListFilter_ShowMoreChats(_ value: Int32) -> String { + public func QuickSend_Photos(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[106 * 6 + Int(form.rawValue)]!, stringValue) } - public func Stats_GroupTopInviterInvites(_ value: Int32) -> String { + public func Conversation_SelectedMessages(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[107 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_MESSAGE_ROUNDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[108 * 6 + Int(form.rawValue)]!, _1, _2) + public func Notifications_ExceptionMuteExpires_Minutes(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[108 * 6 + Int(form.rawValue)]!, stringValue) } - public func SharedMedia_DeleteItemsConfirmation(_ value: Int32) -> String { + public func StickerPack_AddStickerCount(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[109 * 6 + Int(form.rawValue)]!, stringValue) } - public func MessageTimer_Days(_ value: Int32) -> String { + public func Conversation_LiveLocationMembersCount(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[110 * 6 + Int(form.rawValue)]!, stringValue) } - public func MessageTimer_ShortWeeks(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[111 * 6 + Int(form.rawValue)]!, stringValue) + public func ForwardedAuthorsOthers(_ selector: Int32, _ _0: String, _ _1: String) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[111 * 6 + Int(form.rawValue)]!, _0, _1) } - public func ServiceMessage_GameScoreSelfExtended(_ value: Int32) -> String { + public func PrivacyLastSeenSettings_AddUsers(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[112 * 6 + Int(form.rawValue)]!, stringValue) } - public func ForwardedMessages(_ value: Int32) -> String { + public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[113 * 6 + Int(form.rawValue)]!, stringValue) } - public func UserCount(_ value: Int32) -> String { + public func ForwardedAudios(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[114 * 6 + Int(form.rawValue)]!, stringValue) } - public func LiveLocationUpdated_MinutesAgo(_ value: Int32) -> String { + public func OldChannels_InactiveWeek(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[115 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_MESSAGE_FWDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[116 * 6 + Int(form.rawValue)]!, _1, _2) + public func ForwardedMessages(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[116 * 6 + Int(form.rawValue)]!, stringValue) } - public func SharedMedia_Photo(_ value: Int32) -> String { + public func MessageTimer_ShortDays(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[117 * 6 + Int(form.rawValue)]!, stringValue) } - public func ForwardedGifs(_ value: Int32) -> String { + public func AttachmentMenu_SendGif(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[118 * 6 + Int(form.rawValue)]!, stringValue) } - public func Call_ShortMinutes(_ value: Int32) -> String { + public func Notification_GameScoreSelfSimple(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[119 * 6 + Int(form.rawValue)]!, stringValue) } - public func ForwardedStickers(_ value: Int32) -> String { + public func Stats_GroupShowMoreTopAdmins(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[120 * 6 + Int(form.rawValue)]!, stringValue) } - public func MessagePoll_VotedCount(_ value: Int32) -> String { + public func MuteFor_Hours(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[121 * 6 + Int(form.rawValue)]!, stringValue) } - public func InstantPage_Views(_ value: Int32) -> String { + public func Contacts_InviteContacts(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[122 * 6 + Int(form.rawValue)]!, stringValue) } - public func GroupInfo_ShowMoreMembers(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[123 * 6 + Int(form.rawValue)]!, stringValue) + public func PUSH_CHANNEL_MESSAGE_FWDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[123 * 6 + Int(form.rawValue)]!, _1, _2) } - public func Notifications_Exceptions(_ value: Int32) -> String { + public func ForwardedStickers(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[124 * 6 + Int(form.rawValue)]!, stringValue) } - public func Forward_ConfirmMultipleFiles(_ value: Int32) -> String { + public func LastSeen_MinutesAgo(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[125 * 6 + Int(form.rawValue)]!, stringValue) } - public func Notification_GameScoreSelfSimple(_ value: Int32) -> String { + public func GroupInfo_ParticipantCount(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[126 * 6 + Int(form.rawValue)]!, stringValue) } - public func SharedMedia_Generic(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[127 * 6 + Int(form.rawValue)]!, stringValue) + public func PUSH_MESSAGE_PHOTOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[127 * 6 + Int(form.rawValue)]!, _1, _2) } - public func Call_Seconds(_ value: Int32) -> String { + public func ForwardedVideoMessages(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[128 * 6 + Int(form.rawValue)]!, stringValue) } - public func OldChannels_InactiveMonth(_ value: Int32) -> String { + public func Chat_DeleteMessagesConfirmation(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[129 * 6 + Int(form.rawValue)]!, stringValue) } - public func PollResults_ShowMore(_ value: Int32) -> String { + public func DialogList_LiveLocationChatsCount(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[130 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_CHAT_MESSAGES(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { + public func PUSH_CHANNEL_MESSAGE_ROUNDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[131 * 6 + Int(form.rawValue)]!, _2, _1, _3) + return String(format: self._ps[131 * 6 + Int(form.rawValue)]!, _1, _2) } - public func OldChannels_GroupFormat(_ value: Int32) -> String { + public func PollResults_ShowMore(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[132 * 6 + Int(form.rawValue)]!, stringValue) } - public func ForwardedPolls(_ value: Int32) -> String { + public func Conversation_StatusMembers(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[133 * 6 + Int(form.rawValue)]!, stringValue) } - public func QuickSend_Photos(_ value: Int32) -> String { - let form = getPluralizationForm(self.lc, value) - let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) - return String(format: self._ps[134 * 6 + Int(form.rawValue)]!, stringValue) + public func PUSH_MESSAGE_FWDS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { + let form = getPluralizationForm(self.lc, selector) + return String(format: self._ps[134 * 6 + Int(form.rawValue)]!, _1, _2) } - public func Call_Minutes(_ value: Int32) -> String { + public func Conversation_StatusSubscribers(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[135 * 6 + Int(form.rawValue)]!, stringValue) } - public func Chat_DeleteMessagesConfirmation(_ value: Int32) -> String { + public func VoiceOver_Chat_PollVotes(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[136 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_CHAT_MESSAGE_VIDEOS(_ selector: Int32, _ _2: String, _ _1: String, _ _3: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[137 * 6 + Int(form.rawValue)]!, _2, _1, _3) + public func ChatList_MessageVideos(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[137 * 6 + Int(form.rawValue)]!, stringValue) } - public func Passport_Scans(_ value: Int32) -> String { + public func VoiceOver_Chat_PollOptionCount(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[138 * 6 + Int(form.rawValue)]!, stringValue) } - public func LastSeen_MinutesAgo(_ value: Int32) -> String { + public func MessageTimer_ShortHours(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[139 * 6 + Int(form.rawValue)]!, stringValue) @@ -5697,9 +5718,15 @@ public final class PresentationStrings: Equatable { let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[140 * 6 + Int(form.rawValue)]!, stringValue) } - public func PUSH_CHANNEL_MESSAGE_VIDEOS(_ selector: Int32, _ _1: String, _ _2: Int32) -> String { - let form = getPluralizationForm(self.lc, selector) - return String(format: self._ps[141 * 6 + Int(form.rawValue)]!, _1, _2) + public func Media_ShareVideo(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[141 * 6 + Int(form.rawValue)]!, stringValue) + } + public func ChatList_DeletedChats(_ value: Int32) -> String { + let form = getPluralizationForm(self.lc, value) + let stringValue = presentationStringsFormattedNumber(value, self.groupingSeparator) + return String(format: self._ps[142 * 6 + Int(form.rawValue)]!, stringValue) } public init(primaryComponent: PresentationStringsComponent, secondaryComponent: PresentationStringsComponent?, groupingSeparator: String) { diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift index 065fbfd7b5..f3f64c5426 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift @@ -47,7 +47,7 @@ private func generateClockMinImage(color: UIColor) -> UIImage? { }) } -private func chatBubbleActionButtonImage(fillColor: UIColor, strokeColor: UIColor, foregroundColor: UIColor, image: UIImage?, iconOffset: CGPoint = CGPoint()) -> UIImage? { +public func chatBubbleActionButtonImage(fillColor: UIColor, strokeColor: UIColor, foregroundColor: UIColor, image: UIImage?, iconOffset: CGPoint = CGPoint()) -> UIImage? { return generateImage(CGSize(width: 29.0, height: 29.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(fillColor.cgColor) @@ -162,6 +162,10 @@ public final class PrincipalThemeEssentialGraphics { public let outgoingDateAndStatusImpressionIcon: UIImage public let mediaImpressionIcon: UIImage public let freeImpressionIcon: UIImage + public let incomingDateAndStatusRepliesIcon: UIImage + public let outgoingDateAndStatusRepliesIcon: UIImage + public let mediaRepliesIcon: UIImage + public let freeRepliesIcon: UIImage public let dateStaticBackground: UIImage public let dateFloatingBackground: UIImage @@ -315,6 +319,12 @@ public final class PrincipalThemeEssentialGraphics { self.mediaImpressionIcon = generateTintedImage(image: impressionCountImage, color: .white)! self.freeImpressionIcon = generateTintedImage(image: impressionCountImage, color: serviceColor.primaryText)! + let repliesImage = UIImage(bundleImageName: "Chat/Message/ReplyCount")! + self.incomingDateAndStatusRepliesIcon = generateTintedImage(image: repliesImage, color: theme.message.incoming.secondaryTextColor)! + self.outgoingDateAndStatusRepliesIcon = generateTintedImage(image: repliesImage, color: theme.message.outgoing.secondaryTextColor)! + self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)! + self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)! + self.radialIndicatorFileIconIncoming = emptyImage self.radialIndicatorFileIconOutgoing = emptyImage } else { @@ -410,6 +420,12 @@ public final class PrincipalThemeEssentialGraphics { self.mediaImpressionIcon = generateTintedImage(image: impressionCountImage, color: .white)! self.freeImpressionIcon = generateTintedImage(image: impressionCountImage, color: serviceColor.primaryText)! + let repliesImage = UIImage(bundleImageName: "Chat/Message/ReplyCount")! + self.incomingDateAndStatusRepliesIcon = generateTintedImage(image: repliesImage, color: theme.message.incoming.secondaryTextColor)! + self.outgoingDateAndStatusRepliesIcon = generateTintedImage(image: repliesImage, color: theme.message.outgoing.secondaryTextColor)! + self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)! + self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)! + self.radialIndicatorFileIconIncoming = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocument"), color: .black)! self.radialIndicatorFileIconOutgoing = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocument"), color: .black)! } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 8ba11022da..402d5d6a56 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -229,6 +229,9 @@ public enum PresentationResourceKey: Int32 { case groupInfoMembersIcon case emptyChatListCheckIcon + + case chatFreeCommentButtonBackground + case chatFreeCommentButtonIcon } public enum PresentationResourceParameterKey: Hashable { @@ -256,4 +259,8 @@ public enum PresentationResourceParameterKey: Hashable { case chatMessageLike(incoming: Bool, isSelected: Bool) case chatMessageFreeLike(isSelected: Bool) case chatMessageMediaLike(isSelected: Bool) + + case chatMessageCommentsIcon(incoming: Bool) + case chatMessageCommentsArrowIcon(incoming: Bool) + case chatMessageRepliesIcon(incoming: Bool) } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 42eaae3348..d19f50e512 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -1090,4 +1090,41 @@ public struct PresentationResourcesChat { } }) } + + public static func chatMessageCommentsIcon(_ theme: PresentationTheme, incoming: Bool) -> UIImage? { + return theme.image(PresentationResourceParameterKey.chatMessageCommentsIcon(incoming: incoming), { theme in + let messageTheme = incoming ? theme.chat.message.incoming : theme.chat.message.outgoing + + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BubbleComments"), color: messageTheme.accentTextColor) + }) + } + + public static func chatMessageRepliesIcon(_ theme: PresentationTheme, incoming: Bool) -> UIImage? { + return theme.image(PresentationResourceParameterKey.chatMessageRepliesIcon(incoming: incoming), { theme in + let messageTheme = incoming ? theme.chat.message.incoming : theme.chat.message.outgoing + + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BubbleReplies"), color: messageTheme.accentTextColor) + }) + } + + public static func chatMessageCommentsArrowIcon(_ theme: PresentationTheme, incoming: Bool) -> UIImage? { + return theme.image(PresentationResourceParameterKey.chatMessageCommentsArrowIcon(incoming: incoming), { theme in + let messageTheme = incoming ? theme.chat.message.incoming : theme.chat.message.outgoing + + return generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: messageTheme.accentTextColor) + }) + } + + public static func chatFreeCommentButtonBackground(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? { + return theme.image(PresentationResourceKey.chatFreeCommentButtonBackground.rawValue, { _ in + let strokeColor = bubbleVariableColor(variableColor: theme.chat.message.shareButtonStrokeColor, wallpaper: wallpaper) + return generateStretchableFilledCircleImage(diameter: 30.0, color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonFillColor, wallpaper: wallpaper), strokeColor: strokeColor, strokeWidth: strokeColor.alpha.isZero ? nil : 1.0) + }) + } + + public static func chatFreeCommentButtonIcon(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? { + return theme.image(PresentationResourceKey.chatFreeCommentButtonIcon.rawValue, { _ in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FreeRepliesIcon"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper)) + }) + } } diff --git a/submodules/TelegramUI/BUCK b/submodules/TelegramUI/BUCK index 0689ad24b9..3ecb8c9add 100644 --- a/submodules/TelegramUI/BUCK +++ b/submodules/TelegramUI/BUCK @@ -206,6 +206,11 @@ framework( "//submodules/ManagedAnimationNode:ManagedAnimationNode", "//submodules/TooltipUI:TooltipUI", "//submodules/AuthTransferUI:AuthTransferUI", + "//submodules/ListMessageItem:ListMessageItem", + "//submodules/FileMediaResourceStatus:FileMediaResourceStatus", + "//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge", + "//submodules/GalleryData:GalleryData", + "//submodules/ChatInterfaceState:ChatInterfaceState", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index b52fe63c9b..5c56e9e3df 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -201,6 +201,11 @@ swift_library( "//submodules/ManagedAnimationNode:ManagedAnimationNode", "//submodules/TooltipUI:TooltipUI", "//submodules/AuthTransferUI:AuthTransferUI", + "//submodules/ListMessageItem:ListMessageItem", + "//submodules/FileMediaResourceStatus:FileMediaResourceStatus", + "//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge", + "//submodules/GalleryData:GalleryData", + "//submodules/ChatInterfaceState:ChatInterfaceState", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Images.xcassets/Avatar/RepliesMessagesIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Avatar/RepliesMessagesIcon.imageset/Contents.json new file mode 100644 index 0000000000..bbb2d808f4 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Avatar/RepliesMessagesIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "repliesavatar.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Clear.imageset/Clear.pdf b/submodules/TelegramUI/Images.xcassets/Avatar/RepliesMessagesIcon.imageset/repliesavatar.pdf similarity index 74% rename from submodules/TelegramUI/Images.xcassets/Components/Search Bar/Clear.imageset/Clear.pdf rename to submodules/TelegramUI/Images.xcassets/Avatar/RepliesMessagesIcon.imageset/repliesavatar.pdf index f597a23849298ea7286986842f3d1fd68cb09cde..a3a8d81f109473d3d0f60987f7e5335c458705ce 100644 GIT binary patch delta 864 zcmcbp@L7IBK)vrI+q`H;o|fO`T-P+sPSzLFHf7zZD$Z>S9ytbh!4N-lLe*t*J)Wu1%WkGV|=ym^Xfxr?0VFS-o=i zCxg!xviFwpOz2>3pI>b6kP!OoTG?u$Xx5Ys3pUzBb<6R1Z#*G*^g*chiJXnDPcsUu zowz)1`aRwot!^@5AzFn z-Rk!)!#kXNPH&X?^7BxH5ueN2v}4bzN~IRr-MdxG75~KP{g+?8|1KTVKlgwC{4Z7s zM?ZG&`@h2|cHQ&mzTtbO^s+M*ZwiumR?vB7cjIx6HLQ2@zkHHc2%B=9D>W~rSfZpT zH8B^&%umYVGF3280MW+g7Lys7{5GdDxpK3b87P<;OkU3$F*%J-q+TO8G1)&@AuP41 zI5R&_!O%d@&{WS%Ax6WgG&3hf!O+s($iT=jMpGfMG_k0pN+H0-%{f1>B(xfu(_|p^34%fmxJ+fjSTY)%oV9fRrd;Dv^e%v9p6Yx}qpG zjmtp6&~WljKAFj#{KDb}CYHtu1|Xo2r@#ef7#N!vO#aWOR&Qc%f-YrXYL2Pg7>hbH z6GL>p#>S=?<{6t<8X(k_6eVWnq!w}6*eK`+XR=nMf?Rr$Uok$_)WX6%&A>b@H96JD w3g@4{>F8dGdf6`Z;TYCDhNk+Nt z@&1XEkA%JW5V=f{PtVlW_0NfyN1oc`HE=zN5SSl$Y}>>TZiz$P^??g7_i)8)m&U$` z4PIGvG;qc`t}jK?+&d0TmDB01=v=q;^A0g(eYKT_$p<1=HI{8p4A$}x+PbDJCb`>l z@y!MMnp*t&dA{|k1TqC0ZTw%M`K9%Ipm$hj`4u6)@3sjAWvWXToKgER;bDtM%CQ&a zD@%7j;uBEWu2XRLE3XdEC!zYnoD@e#iD@S35imblSV8 zq?&%3z}TVlJ?{U~X^t_zTlU7TT69Hu&B8-`PG!0>;p_Q!C38wgF55fxIN$s^8!FWg z3N}<+^lD5?eR#6kBJI7^?}S;mog0PE+T9No{?Q_99`oS$^pDjSzI-gMsom?`W@7EP zZT@e|xHa*281LENKlc6OH*v$rMa^8Pc`3yuMX8CoAXa`-7MH1lfdYs&F)*9_l+jO2 z-#0ZSGtnu(LLu4!C}n5@M6q0(jhS4zS;3siwY(9MhEQD^zKO{S{=o_n3Wf%HhDLg3 zlLh&NwP0o&8o`vgSQ!}@nj2Ueniv@vnHri!85pPok>=zxyt4I%Ak%^r^qqn3D#^^x zb4e^oRRHS+3i{@!AoK0)V1`!|rKWKK9cavD00#n-gqw^_Ex}^CD1v4tNEVkAC1&QN7IE3wDCh@gRsjRZcycknV!Va1NpfORT1twE ziKUUDd9snAVTzGSvRR^`nR!a8MY0{24M8Oeu?oO&EiOqcDglS8vALN6m#V6(zZ(|- Dn(I$q diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Arrow.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Arrow.imageset/Contents.json new file mode 100644 index 0000000000..9952bcc0b6 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Arrow.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_addresult.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Arrow.imageset/ic_addresult.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Arrow.imageset/ic_addresult.pdf new file mode 100644 index 0000000000000000000000000000000000000000..61096012ce17feabf108e180cd6068be96e54a42 GIT binary patch literal 4138 zcmai%c|6oz`^N`U7_yXzRNvf&tTSU~F!p7TC2M47Fc^DG*2YqnY}vCELiV+rB5U@F z#}Gn=WXqOF*5o%@es}l%JkRU-zRt||yguhT*PJ=m=a2UV)y1lcAViT6XvggO>{8+9 z`~4kV5EK9h+-;m7GBN;63-9JYbOg{8Ngsfz5?qLQPs-Z`N5o_Cw(fR#Ku!+gMfAku zTp_+xc!M784me}ume6%oo>9N~UJj4}q*zg{zK0N=!-x@9|Iub;ue1 zD9MAaG_fy%@S;lU6M`KUb%I4l7EdeK6A0b|njrhdu|C!>0%6z%8VikhsJOpNS6Z6X zBRa7wj35MW0Pra4X`7u?LA!9(tuZYQt=oEKEic@pL)u*2p3#J_hmIPzErMfbK>iJn zR%PbS=y<^-!6fMNM8Zv3wi?>Ji3ALhIX4h}tf!Vf}iu6_d!x=x)eR6E1fc<_@%YYE+xSZazOn}e!MoZ0EFNWcSo6a|AHGq@?~8|1vES@)+0O$Hz;tmA z-@iTaZbSh2!y5W{FZXMnwsdlX$ z!FCOAgj;m^yj~pyCGbcn^Bx63REEW60{J**sgn(=J2}M;z%MpZ1jND-V(dBexn(@l z9hEMdg>ym;&(l)*W^%Yp`Zya=SuqW|6B*E{yclqfc^dScL*8{-eA8w14AAV>g~7q? zds-kkTe?;6JYSAWq?igD=f}u(Y7kv=2d2@JDS8Lvm<94JaF~e#N2=Qtu#Th=WD1bK-eG-D-lOGs`esU_j$MwawvA)z^e2#T zm{nU6ox(FZngLZ~y3=b|rFdgrD%SJP2|N#|i&Zk>nI4>e5_>V>>I}`%h#XwnDfTYT z=?IgUDH|i)D;o{?aGGtJkrv0CxIGIpWamb{&G5qlWBxLEZ+daoyRjl2M%HoGW>u*V zX-09&(%eklPdVRndj!^9QwQh=`|sj)tz`O_+n$E=jA;G}URsk~By#wIpQf=hW%vOp&M8Z&0Z^Uf zkyM{lE75FztMfq`-wM9gQR}rTv>JruRT<@|PSv!@hN-J$-434QFbwj zD}$?2c)LYGw)5|W;e$b-UVixq` zX+QAVI$H5=Yf(;AP75vzmp5hmM@d5IfMA|!o^+R_?c=LaYjV?Od{vQ|>WS*H&o3E_ z4ozIPt|!-!8J*`G`B0_khVI;|wydrEM zvLt-YM8M>Tv1`p@)o%OijtplDTT*wp)3j}MU%~j?qw->rN$-{wl5~be)i`^Db_02n zX_IGt97ajt6fqFP)SwMOf?oZ zsh&}dF^vHVWZBQ1J{iF4?OD9_a$GZFdigzM`lBe){7&`qYtKis#XaPTc&k>EtAb}w zpF1jOi({X9RC{9arzhQ_CWiFx z>s2*dJ65mGbtyej(wk}$=`&+;>2x{ivNCR8UGDTImB(`6i%rk_sP{V1w~0%WCZsDn zQ+q<5-UfXbGx!DM3cGa4h5N8)x zBUbchlSZYJ+Hm3GgT)Z8xQw}`#emwC%-7v-Mp`Cag9fQ$X*y};&a!bg20h!-buL$Q z?ZmyEDr+Y%^71^)f)&By(wQKmVs-oA=_{6L%#pcOE= z5ImT9rtgf1{N01=8!?;d8PzWwG|35lC-2?GWcviJD|LmfMMrW(NL?Yy)z{3t%&e%Y znCJ;ft+ZTw;#vHrY459HYNLp3k64qdZ#l$;*D1peQg$kJ+K-Tn$wI0%s*7snYUG%mm~sDy z8=aqeVp0dE!&(HsJ>E(gD9sM^*=usJdEW8Kd0;7BUSp!D8MFIp({=NFNfKzh!1Ge23&~ z6!Jy@FinD;7X_XV5uK9lH$X-lhW|aIGLDFIad-HE?_NK-{Vyy>{I-FD<#yL>DV^R0 zSDdYm0bqpp^dh*s0SLG#0xgOL%r7WiBe>WB2#hKMjxZMh^seDNiT(hE;lE?QFHwMk z?v$pGNZEy@a5&@wR!R&m28T@~ literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Calendar.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Calendar.imageset/Contents.json new file mode 100644 index 0000000000..65cbf323d2 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Calendar.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_search_calendar (2).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Calendar.imageset/ic_search_calendar (2).pdf b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Calendar.imageset/ic_search_calendar (2).pdf new file mode 100644 index 0000000000000000000000000000000000000000..278f33aad875b83c8ddf90e60aaac8f8447ad448 GIT binary patch literal 4358 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Contents.json new file mode 100644 index 0000000000..6e965652df --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Files.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Files.imageset/Contents.json new file mode 100644 index 0000000000..9c2974799f --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Files.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_search_docs.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/Files.imageset/ic_search_docs.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/Search/Files.imageset/ic_search_docs.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bd6b9cd16c2fc2f6d3747aa9c4bffa3c7aece788 GIT binary patch literal 4216 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Contents.json new file mode 100644 index 0000000000..e4bf34c537 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Files.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Files.png b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Files.png new file mode 100644 index 0000000000000000000000000000000000000000..9785f3ba7c148bc3d5bfdffab29ae7906c2b33c9 GIT binary patch literal 18218 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 WsKVVMK#sxx1ZT^R&3VQLkN!Vv3v@gH literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Contents.json new file mode 100644 index 0000000000..1169595a34 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Links.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Links.png b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Links.png new file mode 100644 index 0000000000000000000000000000000000000000..cd6782f0c9f4acadb1cb25d1a901c3faef764f40 GIT binary patch literal 17164 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Contents.json new file mode 100644 index 0000000000..573448e1ce --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Music.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Music.png b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Music.png new file mode 100644 index 0000000000000000000000000000000000000000..7e65a1657c1bf6edaa69cbe9a024ec92664c6677 GIT binary patch literal 23163 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 UYAt^pw9Hu;6f7d5sjmh87o$@_>Hq)$ literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/Contents.json new file mode 100644 index 0000000000..e381a41f26 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_viewreplies.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/ic_viewreplies.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Replies.imageset/ic_viewreplies.pdf new file mode 100644 index 0000000000000000000000000000000000000000..db1722552c52859579aa1c55bfd2daacf73450f3 GIT binary patch literal 4676 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 vFbXOGvlh2Tf-x8j3T-W64Uq-?Z^^GM@YW)n literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/FreeRepliesIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/FreeRepliesIcon.imageset/Contents.json new file mode 100644 index 0000000000..c8af669573 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/FreeRepliesIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "replies_sticker.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/FreeRepliesIcon.imageset/replies_sticker.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/FreeRepliesIcon.imageset/replies_sticker.pdf new file mode 100644 index 0000000000000000000000000000000000000000..adbf0484c61c6410e34668aad4f204dc100e3429 GIT binary patch literal 3999 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/Contents.json new file mode 100644 index 0000000000..358678e10d --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "replies.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/replies.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/ReplyCount.imageset/replies.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f55a72204ea6de627d9b6d809a60809d47017a07 GIT binary patch literal 4117 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Resources/Animations/ChatListNoResults.tgs b/submodules/TelegramUI/Resources/Animations/ChatListNoResults.tgs new file mode 100644 index 0000000000000000000000000000000000000000..62a21279fb766aa9aa25ddc077936f2f51f34031 GIT binary patch literal 8590 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Resources/PresentationStrings.mapping b/submodules/TelegramUI/Resources/PresentationStrings.mapping index 9861c49aa27ba097e2e8052a4c7bed5e31592dbb..6326d969a3e38fa86cdb10779c35ad9d34b76cac 100644 GIT binary patch delta 52520 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

02y@J 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 LLgup;TFw6l8df0) diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index c74900e0b3..76c6453490 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -298,6 +298,45 @@ public final class AccountContextImpl: AccountContext { return nil } } + + public func chatLocationInput(for location: ChatLocation, contextHolder: Atomic) -> ChatLocationInput { + switch location { + case let .peer(peerId): + return .peer(peerId) + case let .replyThread(messageId, _, maxReadMessageId): + let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxReadMessageId: maxReadMessageId) + return .external(messageId.peerId, context.state) + } + } + + public func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic, messageIndex: MessageIndex) { + switch location { + case .peer: + let _ = applyMaxReadIndexInteractively(postbox: self.account.postbox, stateManager: self.account.stateManager, index: messageIndex).start() + case let .replyThread(messageId, _, maxReadMessageId): + let context = chatLocationContext(holder: contextHolder, account: self.account, messageId: messageId, maxReadMessageId: maxReadMessageId) + context.applyMaxReadIndex(messageIndex: messageIndex) + } + } +} + +private func chatLocationContext(holder: Atomic, account: Account, messageId: MessageId, maxReadMessageId: MessageId?) -> ReplyThreadHistoryContext { + let holder = holder.modify { current in + if let current = current as? ChatLocationContextHolderImpl { + return current + } else { + return ChatLocationContextHolderImpl(account: account, messageId: messageId, maxReadMessageId: maxReadMessageId) + } + } as! ChatLocationContextHolderImpl + return holder.context +} + +private final class ChatLocationContextHolderImpl: ChatLocationContextHolder { + let context: ReplyThreadHistoryContext + + init(account: Account, messageId: MessageId, maxReadMessageId: MessageId?) { + self.context = ReplyThreadHistoryContext(account: account, peerId: messageId.peerId, threadMessageId: messageId, maxReadMessageId: maxReadMessageId) + } } func getAppConfiguration(transaction: Transaction) -> AppConfiguration { diff --git a/submodules/TelegramUI/Sources/AudioWaveformNode.swift b/submodules/TelegramUI/Sources/AudioWaveformNode.swift index 1d490215c5..691bab2228 100644 --- a/submodules/TelegramUI/Sources/AudioWaveformNode.swift +++ b/submodules/TelegramUI/Sources/AudioWaveformNode.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit private final class AudioWaveformNodeParameters: NSObject { - let waveform: AudioWaveform? let color: UIColor? let gravity: AudioWaveformNode.Gravity? @@ -21,9 +20,7 @@ private final class AudioWaveformNodeParameters: NSObject { } final class AudioWaveformNode: ASDisplayNode { - enum Gravity { - case bottom case center } diff --git a/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift index 33a435c828..8cece700e8 100644 --- a/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift @@ -46,7 +46,11 @@ private func actionForPeer(peer: Peer, isMuted: Bool) -> SubscriberAction? { } } } else { - return nil + if isMuted { + return .unmuteNotifications + } else { + return .muteNotifications + } } } @@ -178,7 +182,7 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { self.badgeBackground.image = PresentationResourcesChatList.badgeBackgroundActive(interfaceState.theme, diameter: 20.0) } - if previousState?.peerDiscussionId != interfaceState.peerDiscussionId { + /*if previousState?.peerDiscussionId != interfaceState.peerDiscussionId { let signal: Signal if let peerDiscussionId = interfaceState.peerDiscussionId, let context = self.context { let key = PostboxViewKey.unreadCounts(items: [.peer(peerDiscussionId)]) @@ -222,9 +226,9 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { strongSelf.badgeText.isHidden = false } })) - } + }*/ - if let peer = interfaceState.renderedPeer?.peer, previousState?.renderedPeer?.peer == nil || !peer.isEqual(previousState!.renderedPeer!.peer!) || previousState?.theme !== interfaceState.theme || previousState?.strings !== interfaceState.strings || previousState?.peerIsMuted != interfaceState.peerIsMuted || previousState?.peerDiscussionId != interfaceState.peerDiscussionId { + if let peer = interfaceState.renderedPeer?.peer, previousState?.renderedPeer?.peer == nil || !peer.isEqual(previousState!.renderedPeer!.peer!) || previousState?.theme !== interfaceState.theme || previousState?.strings !== interfaceState.strings || previousState?.peerIsMuted != interfaceState.peerIsMuted /*|| previousState?.peerDiscussionId != interfaceState.peerDiscussionId*/ { if let action = actionForPeer(peer: peer, isMuted: interfaceState.peerIsMuted) { self.action = action let (title, color) = titleAndColorForAction(action, theme: interfaceState.theme, strings: interfaceState.strings) @@ -233,12 +237,12 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { self.action = nil } - if interfaceState.peerDiscussionId != nil { + /*if interfaceState.peerDiscussionId != nil { self.discussButtonText.attributedText = NSAttributedString(string: interfaceState.strings.Channel_DiscussionGroup_HeaderLabel, font: Font.regular(17.0), textColor: interfaceState.theme.chat.inputPanel.panelControlAccentColor) self.discussButton.isHidden = false - } else { + } else {*/ self.discussButton.isHidden = true - } + //} } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 7f473217a4..725eaeb617 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -60,6 +60,20 @@ import UrlWhitelist import TelegramIntents import TooltipUI import StatisticsUI +import MediaResources +import GalleryData +import ChatInterfaceState + +extension ChatLocation { + var peerId: PeerId { + switch self { + case let .peer(peerId): + return peerId + case let .replyThread(messageId, _, _): + return messageId.peerId + } + } +} public enum ChatControllerPeekActions { case standard @@ -75,6 +89,7 @@ public final class ChatControllerOverlayPresentationData { private enum ChatLocationInfoData { case peer(Promise) + case replyThread(Promise) //case group(Promise) } @@ -274,6 +289,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private var checkedPeerChatServiceActions = false + private var didAppear = false + private var scheduledActivateInput = false + private var raiseToListen: RaiseToListenManager? private var voicePlaylistDidEndTimestamp: Double = 0.0 @@ -326,6 +344,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private var hasEmbeddedTitleContent = false private var isEmbeddedTitleContentHidden = false + + private let chatLocationContextHolder = Atomic(value: nil) public override var customData: Any? { return self.chatLocation @@ -350,9 +370,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case let .peer(peerId): locationBroadcastPanelSource = .peer(peerId) self.chatLocationInfoData = .peer(Promise()) - /*case .group: + case let .replyThread(messageId, _, _): locationBroadcastPanelSource = .none - self.chatLocationInfoData = .group(Promise())*/ + let promise = Promise() + let key = PostboxViewKey.messages([messageId]) + promise.set(context.account.postbox.combinedView(keys: [key]) + |> map { views -> Message? in + guard let view = views.views[key] as? MessagesView else { + return nil + } + return view.messages[messageId] + }) + self.chatLocationInfoData = .replyThread(promise) } self.presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -450,7 +479,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, message: message, standalone: false, reverseMessageGalleryOrder: false, mode: mode, navigationController: strongSelf.effectiveNavigationController, dismissInput: { + return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: strongSelf.chatLocation, chatLocationContextHolder: strongSelf.chatLocationContextHolder, message: message, standalone: false, reverseMessageGalleryOrder: false, mode: mode, navigationController: strongSelf.effectiveNavigationController, dismissInput: { self?.chatDisplayNode.dismissInput() }, present: { c, a in self?.present(c, in: .window(.root), with: a, blockInteraction: true) @@ -680,7 +709,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, openMessageContextActions: { message, node, rect, gesture in gesture?.cancel() }, navigateToMessage: { [weak self] fromId, id in - self?.navigateToMessage(from: fromId, to: .id(id)) + self?.navigateToMessage(from: fromId, to: .id(id), forceInCurrentChat: fromId.peerId == id.peerId) }, tapMessage: nil, clickThroughMessage: { [weak self] in self?.chatDisplayNode.dismissInput() }, toggleMessagesSelection: { [weak self] ids, value in @@ -1082,8 +1111,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if (peerId.namespace == Namespaces.Peer.CloudChannel || peerId.namespace == Namespaces.Peer.CloudGroup) { postAsReply = true } - /*case .group: - postAsReply = true*/ + case .replyThread: + postAsReply = true } } @@ -1140,11 +1169,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return .single(nil) } } - } else if case let .peer(peerId) = strongSelf.chatLocation { - resolveSignal = context.account.postbox.loadedPeerWithId(peerId) - |> map(Optional.init) } else { - resolveSignal = .single(nil) + resolveSignal = context.account.postbox.loadedPeerWithId(strongSelf.chatLocation.peerId) + |> map(Optional.init) } var cancelImpl: (() -> Void)? let presentationData = strongSelf.presentationData @@ -1424,12 +1451,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G actionSheet?.dismissAnimated() if let strongSelf = self { let peerSignal: Signal - if case let .peer(peerId) = strongSelf.chatLocation { - peerSignal = strongSelf.context.account.postbox.loadedPeerWithId(peerId) - |> map(Optional.init) - } else { - peerSignal = .single(nil) - } + peerSignal = strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.chatLocation.peerId) + |> map(Optional.init) let _ = (peerSignal |> deliverOnMainQueue).start(next: { peer in if let strongSelf = self { @@ -1605,6 +1628,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch strongSelf.chatLocation { case let .peer(peerId): strongSelf.navigateToMessage(from: nil, to: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp - Int32(NSTimeZone.local.secondsFromGMT()))), scrollPosition: .bottom(0.0), rememberInStack: false, animated: true, completion: nil) + case let .replyThread(messageId, _, _): + let peerId = messageId.peerId + strongSelf.navigateToMessage(from: nil, to: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp - Int32(NSTimeZone.local.secondsFromGMT()))), scrollPosition: .bottom(0.0), rememberInStack: false, animated: true, completion: nil) } }, requestRedeliveryOfFailedMessages: { [weak self] id in guard let strongSelf = self else { @@ -2148,6 +2174,40 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node)), items: items, reactionItems: [], gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) }) + }, openMessageReplies: { [weak self] messageId in + guard let strongSelf = self else { + return + } + + let foundIndex = Promise() + foundIndex.set(fetchChannelReplyThreadMessage(account: strongSelf.context.account, messageId: messageId)) + + var cancelImpl: (() -> Void)? + let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + strongSelf.present(statusController, in: .window(.root)) + + let disposable = (foundIndex.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak statusController] result in + statusController?.dismiss() + + guard let strongSelf = self else { + return + } + + if let result = result { + if let navigationController = strongSelf.navigationController as? NavigationController { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(threadMessageId: result.messageId, isChannelPost: true, maxReadMessageId: result.maxReadMessageId), activateInput: true, keepStack: .always)) + } + } + }) + + cancelImpl = { [weak statusController] in + disposable.dispose() + statusController?.dismiss() + } }, requestMessageUpdate: { [weak self] id in if let strongSelf = self { strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) @@ -2207,7 +2267,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let chatInfoButtonItem: UIBarButtonItem switch chatLocation { - case .peer: + case .peer, .replyThread: let avatarNode = ChatAvatarNavigationNode() avatarNode.chatController = self avatarNode.contextAction = { [weak self] node, gesture in @@ -2278,290 +2338,497 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) - switch chatLocation { - case let .peer(peerId): - if case let .peer(peerView) = self.chatLocationInfoData { - peerView.set(context.account.viewTracker.peerView(peerId)) - var onlineMemberCount: Signal = .single(nil) - var hasScheduledMessages: Signal = .single(false) - - if peerId.namespace == Namespaces.Peer.CloudChannel { - let recentOnlineSignal: Signal = peerView.get() - |> map { view -> Bool? in - if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { - if case .broadcast = peer.info { - return nil - } else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 { - return true - } else { - return false - } - } else { - return false - } - } - |> distinctUntilChanged - |> mapToSignal { isLarge -> Signal in - if let isLarge = isLarge { - if isLarge { - return context.peerChannelMemberCategoriesContextsManager.recentOnline(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) - |> map(Optional.init) - } else { - return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) - |> map(Optional.init) - } - } else { - return .single(nil) - } - } - onlineMemberCount = recentOnlineSignal - - self.reportIrrelvantGeoNoticePromise.set(context.account.postbox.transaction { transaction -> Bool? in - if let _ = transaction.getNoticeEntry(key: ApplicationSpecificNotice.irrelevantPeerGeoReportKey(peerId: peerId)) as? ApplicationSpecificBoolNotice { + let chatLocationPeerId: PeerId = chatLocation.peerId + + do { + let peerId = chatLocationPeerId + if case let .peer(peerView) = self.chatLocationInfoData { + peerView.set(context.account.viewTracker.peerView(peerId)) + var onlineMemberCount: Signal = .single(nil) + var hasScheduledMessages: Signal = .single(false) + + if peerId.namespace == Namespaces.Peer.CloudChannel { + let recentOnlineSignal: Signal = peerView.get() + |> map { view -> Bool? in + if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { + if case .broadcast = peer.info { + return nil + } else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 { return true } else { return false } - }) - } else { - self.reportIrrelvantGeoNoticePromise.set(.single(nil)) + } else { + return false + } } - - if !isScheduledMessages && peerId.namespace != Namespaces.Peer.SecretChat { - hasScheduledMessages = peerView.get() - |> take(1) - |> mapToSignal { view -> Signal in - if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendMessages) { - return .single(false) + |> distinctUntilChanged + |> mapToSignal { isLarge -> Signal in + if let isLarge = isLarge { + if isLarge { + return context.peerChannelMemberCategoriesContextsManager.recentOnline(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) + |> map(Optional.init) } else { - return context.account.viewTracker.scheduledMessagesViewForLocation(chatLocation) - |> map { view, _, _ in - return !view.entries.isEmpty + return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) + |> map(Optional.init) + } + } else { + return .single(nil) + } + } + onlineMemberCount = recentOnlineSignal + + self.reportIrrelvantGeoNoticePromise.set(context.account.postbox.transaction { transaction -> Bool? in + if let _ = transaction.getNoticeEntry(key: ApplicationSpecificNotice.irrelevantPeerGeoReportKey(peerId: peerId)) as? ApplicationSpecificBoolNotice { + return true + } else { + return false + } + }) + } else { + self.reportIrrelvantGeoNoticePromise.set(.single(nil)) + } + + if case .peer = chatLocation, !isScheduledMessages, peerId.namespace != Namespaces.Peer.SecretChat { + let chatLocationContextHolder = self.chatLocationContextHolder + hasScheduledMessages = peerView.get() + |> take(1) + |> mapToSignal { view -> Signal in + if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendMessages) { + return .single(false) + } else { + return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder)) + |> map { view, _, _ in + return !view.entries.isEmpty + } + } + } + } + + let isReplyThread: Bool + let replyThreadType: ChatTitleContent.ReplyThreadType? + switch chatLocation { + case let .peer(peerId): + //TODO:localize + isReplyThread = peerId.isReplies + replyThreadType = nil + case let .replyThread(_, _, readMessageId): + isReplyThread = true + if readMessageId != nil { + replyThreadType = .comments + } else { + replyThreadType = .replies + } + } + + self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, hasScheduledMessages, self.reportIrrelvantGeoNoticePromise.get()) + |> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, hasScheduledMessages, peerReportNotice in + if let strongSelf = self { + if let peer = peerViewMainPeer(peerView) { + strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages) + let imageOverride: AvatarNodeImageOverride? + if strongSelf.context.account.peerId == peer.id { + imageOverride = .savedMessagesIcon + } else if peer.id.isReplies { + imageOverride = .repliesIcon + } else if peer.isDeleted { + imageOverride = .deletedIcon + } else { + imageOverride = nil + } + (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: peer, overrideImage: imageOverride) + (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil + } + + if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages { + return + } + + strongSelf.reportIrrelvantGeoNotice = peerReportNotice + strongSelf.hasScheduledMessages = hasScheduledMessages + + var upgradedToPeerId: PeerId? + if let previous = strongSelf.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.migrationReference == nil, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, let migrationReference = updatedGroup.migrationReference { + upgradedToPeerId = migrationReference.peerId + } + var wasGroupChannel: Bool? + if let previousPeerView = strongSelf.peerView, let info = (previousPeerView.peers[previousPeerView.peerId] as? TelegramChannel)?.info { + if case .group = info { + wasGroupChannel = true + } else { + wasGroupChannel = false + } + } + var isGroupChannel: Bool? + if let info = (peerView.peers[peerView.peerId] as? TelegramChannel)?.info { + if case .group = info { + isGroupChannel = true + } else { + isGroupChannel = false + } + } + let firstTime = strongSelf.peerView == nil + strongSelf.peerView = peerView + if wasGroupChannel != isGroupChannel { + if let isGroupChannel = isGroupChannel, isGroupChannel { + let (recentDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.recent(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) + let (adminsDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.admins(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) + let disposable = DisposableSet() + disposable.add(recentDisposable) + disposable.add(adminsDisposable) + strongSelf.chatAdditionalDataDisposable.set(disposable) + } else { + strongSelf.chatAdditionalDataDisposable.set(nil) + } + } + if strongSelf.isNodeLoaded { + strongSelf.chatDisplayNode.peerView = peerView + } + var peerIsMuted = false + if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { + if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + peerIsMuted = true + } + } + var peerDiscussionId: PeerId? + var peerGeoLocation: PeerGeoLocation? + if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { + if case .broadcast = peer.info { + if case let .known(value) = cachedData.linkedDiscussionPeerId { + peerDiscussionId = value + } + } else { + peerGeoLocation = cachedData.peerGeoLocation + } + } + var renderedPeer: RenderedPeer? + var contactStatus: ChatContactStatus? + if let peer = peerView.peers[peerView.peerId] { + if let cachedData = peerView.cachedData as? CachedUserData { + contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil) + } else if let cachedData = peerView.cachedData as? CachedGroupData { + var invitedBy: Peer? + if let invitedByPeerId = cachedData.invitedBy { + if let peer = peerView.peers[invitedByPeerId] { + invitedBy = peer + } + } + contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) + } else if let cachedData = peerView.cachedData as? CachedChannelData { + var canReportIrrelevantLocation = true + if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member { + canReportIrrelevantLocation = false + } + if let peerReportNotice = peerReportNotice, peerReportNotice { + canReportIrrelevantLocation = false + } + var invitedBy: Peer? + if let invitedByPeerId = cachedData.invitedBy { + if let peer = peerView.peers[invitedByPeerId] { + invitedBy = peer + } + } + contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) + } + + var peers = SimpleDictionary() + peers[peer.id] = peer + if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] { + peers[associatedPeer.id] = associatedPeer + } + renderedPeer = RenderedPeer(peerId: peer.id, peers: peers) + } + + var isNotAccessible: Bool = false + if let cachedChannelData = peerView.cachedData as? CachedChannelData { + isNotAccessible = cachedChannelData.isNotAccessible + } + + if firstTime && isNotAccessible { + strongSelf.context.account.viewTracker.forceUpdateCachedPeerData(peerId: peerView.peerId) + } + + var hasBots: Bool = false + if let peer = peerView.peers[peerView.peerId] { + if let cachedGroupData = peerView.cachedData as? CachedGroupData { + if !cachedGroupData.botInfos.isEmpty { + hasBots = true + } + } else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let channel = peer as? TelegramChannel, case .group = channel.info { + if !cachedChannelData.botInfos.isEmpty { + hasBots = true + } + } + } + + let isArchived: Bool = peerView.groupId == Namespaces.PeerGroup.archive + + var explicitelyCanPinMessages: Bool = false + if let cachedUserData = peerView.cachedData as? CachedUserData { + explicitelyCanPinMessages = cachedUserData.canPinMessages + } else if peerView.peerId == context.account.peerId { + explicitelyCanPinMessages = true + } + + var animated = false + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, let updated = renderedPeer?.peer as? TelegramSecretChat, peer.embeddedState != updated.embeddedState { + animated = true + } + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, let updated = renderedPeer?.peer as? TelegramChannel { + if peer.participationStatus != updated.participationStatus { + animated = true + } + } + + var didDisplayActionsPanel = false + if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { + if !peerStatusSettings.flags.isEmpty { + if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { + didDisplayActionsPanel = true + } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { + didDisplayActionsPanel = true + } else if peerStatusSettings.contains(.canShareContact) { + didDisplayActionsPanel = true + } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { + didDisplayActionsPanel = true + } + } + } + + var displayActionsPanel = false + if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { + if !peerStatusSettings.flags.isEmpty { + if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.canShareContact) { + displayActionsPanel = true + } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { + displayActionsPanel = true + } + } + } + + if displayActionsPanel != didDisplayActionsPanel { + animated = true + } + + if strongSelf.preloadHistoryPeerId != peerDiscussionId { + strongSelf.preloadHistoryPeerId = peerDiscussionId + if let peerDiscussionId = peerDiscussionId { + strongSelf.preloadHistoryPeerIdDisposable.set(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId)) + } else { + strongSelf.preloadHistoryPeerIdDisposable.set(nil) + } + } + + strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, { + return $0.updatedPeer { _ in + return renderedPeer + }.updatedIsNotAccessible(isNotAccessible).updatedContactStatus(contactStatus).updatedHasBots(hasBots).updatedIsArchived(isArchived).updatedPeerIsMuted(peerIsMuted).updatedPeerDiscussionId(peerDiscussionId).updatedPeerGeoLocation(peerGeoLocation).updatedExplicitelyCanPinMessages(explicitelyCanPinMessages).updatedHasScheduledMessages(hasScheduledMessages) + }) + if !strongSelf.didSetChatLocationInfoReady { + strongSelf.didSetChatLocationInfoReady = true + strongSelf._chatLocationInfoReady.set(.single(true)) + } + strongSelf.updateReminderActivity() + if let upgradedToPeerId = upgradedToPeerId { + if let navigationController = strongSelf.effectiveNavigationController { + var viewControllers = navigationController.viewControllers + if let index = viewControllers.firstIndex(where: { $0 === strongSelf }) { + viewControllers[index] = ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(upgradedToPeerId)) + navigationController.setViewControllers(viewControllers, animated: false) } } } } - - self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, hasScheduledMessages, self.reportIrrelvantGeoNoticePromise.get()) - |> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, hasScheduledMessages, peerReportNotice in - if let strongSelf = self { - if let peer = peerViewMainPeer(peerView) { - strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages) - let imageOverride: AvatarNodeImageOverride? - if strongSelf.context.account.peerId == peer.id { - imageOverride = .savedMessagesIcon - } else if peer.isDeleted { - imageOverride = .deletedIcon - } else { - imageOverride = nil + })) + } else if case let .replyThread(messagePromise) = self.chatLocationInfoData { + let onlineMemberCount: Signal = .single(nil) + let hasScheduledMessages: Signal = .single(false) + self.reportIrrelvantGeoNoticePromise.set(.single(nil)) + + let isReplyThread: Bool + let replyThreadType: ChatTitleContent.ReplyThreadType + switch chatLocation { + case .peer: + replyThreadType = .replies + case let .replyThread(_, _, readMessageId): + isReplyThread = true + if readMessageId != nil { + replyThreadType = .comments + } else { + replyThreadType = .replies + } + } + + let peerView = context.account.viewTracker.peerView(peerId) + + self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(), + peerView, + messagePromise.get(), + hasScheduledMessages + ) + |> deliverOnMainQueue).start(next: { [weak self] peerView, message, onlineMemberCount in + if let strongSelf = self { + strongSelf.chatTitleView?.titleContent = .replyThread(type: replyThreadType, text: message?.text ?? "") + + let firstTime = strongSelf.peerView == nil + strongSelf.peerView = peerView + + if strongSelf.isNodeLoaded { + strongSelf.chatDisplayNode.peerView = peerView + } + var peerIsMuted = false + if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { + if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + peerIsMuted = true + } + } + var peerDiscussionId: PeerId? + var peerGeoLocation: PeerGeoLocation? + if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { + if case .broadcast = peer.info { + if case let .known(value) = cachedData.linkedDiscussionPeerId { + peerDiscussionId = value } - (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: peer, overrideImage: imageOverride) - (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil + } else { + peerGeoLocation = cachedData.peerGeoLocation } - - if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages { - return - } - - strongSelf.reportIrrelvantGeoNotice = peerReportNotice - strongSelf.hasScheduledMessages = hasScheduledMessages - - var upgradedToPeerId: PeerId? - if let previous = strongSelf.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.migrationReference == nil, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, let migrationReference = updatedGroup.migrationReference { - upgradedToPeerId = migrationReference.peerId - } - var wasGroupChannel: Bool? - if let previousPeerView = strongSelf.peerView, let info = (previousPeerView.peers[previousPeerView.peerId] as? TelegramChannel)?.info { - if case .group = info { - wasGroupChannel = true - } else { - wasGroupChannel = false - } - } - var isGroupChannel: Bool? - if let info = (peerView.peers[peerView.peerId] as? TelegramChannel)?.info { - if case .group = info { - isGroupChannel = true - } else { - isGroupChannel = false - } - } - let firstTime = strongSelf.peerView == nil - strongSelf.peerView = peerView - if wasGroupChannel != isGroupChannel { - if let isGroupChannel = isGroupChannel, isGroupChannel { - let (recentDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.recent(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) - let (adminsDisposable, _) = strongSelf.context.peerChannelMemberCategoriesContextsManager.admins(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) - let disposable = DisposableSet() - disposable.add(recentDisposable) - disposable.add(adminsDisposable) - strongSelf.chatAdditionalDataDisposable.set(disposable) - } else { - strongSelf.chatAdditionalDataDisposable.set(nil) - } - } - if strongSelf.isNodeLoaded { - strongSelf.chatDisplayNode.peerView = peerView - } - var peerIsMuted = false - if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { - peerIsMuted = true - } - } - var peerDiscussionId: PeerId? - var peerGeoLocation: PeerGeoLocation? - if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { - if case .broadcast = peer.info { - peerDiscussionId = cachedData.linkedDiscussionPeerId - } else { - peerGeoLocation = cachedData.peerGeoLocation - } - } - var renderedPeer: RenderedPeer? - var contactStatus: ChatContactStatus? - if let peer = peerView.peers[peerView.peerId] { - if let cachedData = peerView.cachedData as? CachedUserData { - contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil) - } else if let cachedData = peerView.cachedData as? CachedGroupData { - var invitedBy: Peer? - if let invitedByPeerId = cachedData.invitedBy { - if let peer = peerView.peers[invitedByPeerId] { - invitedBy = peer - } - } - contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) - } else if let cachedData = peerView.cachedData as? CachedChannelData { - var canReportIrrelevantLocation = true - if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member { - canReportIrrelevantLocation = false - } - if let peerReportNotice = peerReportNotice, peerReportNotice { - canReportIrrelevantLocation = false - } - var invitedBy: Peer? - if let invitedByPeerId = cachedData.invitedBy { - if let peer = peerView.peers[invitedByPeerId] { - invitedBy = peer - } - } - contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) - } - - var peers = SimpleDictionary() - peers[peer.id] = peer - if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] { - peers[associatedPeer.id] = associatedPeer - } - renderedPeer = RenderedPeer(peerId: peer.id, peers: peers) - } - - var isNotAccessible: Bool = false - if let cachedChannelData = peerView.cachedData as? CachedChannelData { - isNotAccessible = cachedChannelData.isNotAccessible - } - - if firstTime && isNotAccessible { - strongSelf.context.account.viewTracker.forceUpdateCachedPeerData(peerId: peerView.peerId) - } - - var hasBots: Bool = false - if let peer = peerView.peers[peerView.peerId] { - if let cachedGroupData = peerView.cachedData as? CachedGroupData { - if !cachedGroupData.botInfos.isEmpty { - hasBots = true - } - } else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let channel = peer as? TelegramChannel, case .group = channel.info { - if !cachedChannelData.botInfos.isEmpty { - hasBots = true + } + var renderedPeer: RenderedPeer? + var contactStatus: ChatContactStatus? + if let peer = peerView.peers[peerView.peerId] { + if let cachedData = peerView.cachedData as? CachedUserData { + contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil) + } else if let cachedData = peerView.cachedData as? CachedGroupData { + var invitedBy: Peer? + if let invitedByPeerId = cachedData.invitedBy { + if let peer = peerView.peers[invitedByPeerId] { + invitedBy = peer } } - } - - let isArchived: Bool = peerView.groupId == Namespaces.PeerGroup.archive - - var explicitelyCanPinMessages: Bool = false - if let cachedUserData = peerView.cachedData as? CachedUserData { - explicitelyCanPinMessages = cachedUserData.canPinMessages - } else if peerView.peerId == context.account.peerId { - explicitelyCanPinMessages = true - } - - var animated = false - if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, let updated = renderedPeer?.peer as? TelegramSecretChat, peer.embeddedState != updated.embeddedState { - animated = true - } - if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, let updated = renderedPeer?.peer as? TelegramChannel { - if peer.participationStatus != updated.participationStatus { - animated = true + contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) + } else if let cachedData = peerView.cachedData as? CachedChannelData { + var canReportIrrelevantLocation = true + if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member { + canReportIrrelevantLocation = false } - } - - var didDisplayActionsPanel = false - if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { - if !peerStatusSettings.flags.isEmpty { - if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { - didDisplayActionsPanel = true - } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { - didDisplayActionsPanel = true - } else if peerStatusSettings.contains(.canShareContact) { - didDisplayActionsPanel = true - } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { - didDisplayActionsPanel = true + canReportIrrelevantLocation = false + var invitedBy: Peer? + if let invitedByPeerId = cachedData.invitedBy { + if let peer = peerView.peers[invitedByPeerId] { + invitedBy = peer } } + contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) } - var displayActionsPanel = false - if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { - if !peerStatusSettings.flags.isEmpty { - if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.canShareContact) { - displayActionsPanel = true - } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { - displayActionsPanel = true - } + var peers = SimpleDictionary() + peers[peer.id] = peer + if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] { + peers[associatedPeer.id] = associatedPeer + } + renderedPeer = RenderedPeer(peerId: peer.id, peers: peers) + } + + var isNotAccessible: Bool = false + if let cachedChannelData = peerView.cachedData as? CachedChannelData { + isNotAccessible = cachedChannelData.isNotAccessible + } + + if firstTime && isNotAccessible { + strongSelf.context.account.viewTracker.forceUpdateCachedPeerData(peerId: peerView.peerId) + } + + var hasBots: Bool = false + if let peer = peerView.peers[peerView.peerId] { + if let cachedGroupData = peerView.cachedData as? CachedGroupData { + if !cachedGroupData.botInfos.isEmpty { + hasBots = true } - } - - if displayActionsPanel != didDisplayActionsPanel { - animated = true - } - - if strongSelf.preloadHistoryPeerId != peerDiscussionId { - strongSelf.preloadHistoryPeerId = peerDiscussionId - if let peerDiscussionId = peerDiscussionId { - strongSelf.preloadHistoryPeerIdDisposable.set(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId)) - } else { - strongSelf.preloadHistoryPeerIdDisposable.set(nil) - } - } - - strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, { - return $0.updatedPeer { _ in - return renderedPeer - }.updatedIsNotAccessible(isNotAccessible).updatedContactStatus(contactStatus).updatedHasBots(hasBots).updatedIsArchived(isArchived).updatedPeerIsMuted(peerIsMuted).updatedPeerDiscussionId(peerDiscussionId).updatedPeerGeoLocation(peerGeoLocation).updatedExplicitelyCanPinMessages(explicitelyCanPinMessages).updatedHasScheduledMessages(hasScheduledMessages) - }) - if !strongSelf.didSetChatLocationInfoReady { - strongSelf.didSetChatLocationInfoReady = true - strongSelf._chatLocationInfoReady.set(.single(true)) - } - strongSelf.updateReminderActivity() - if let upgradedToPeerId = upgradedToPeerId { - if let navigationController = strongSelf.effectiveNavigationController { - var viewControllers = navigationController.viewControllers - if let index = viewControllers.firstIndex(where: { $0 === strongSelf }) { - viewControllers[index] = ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(upgradedToPeerId)) - navigationController.setViewControllers(viewControllers, animated: false) - } + } else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let channel = peer as? TelegramChannel, case .group = channel.info { + if !cachedChannelData.botInfos.isEmpty { + hasBots = true } } } - })) - } + + let isArchived: Bool = peerView.groupId == Namespaces.PeerGroup.archive + + var explicitelyCanPinMessages: Bool = false + if let cachedUserData = peerView.cachedData as? CachedUserData { + explicitelyCanPinMessages = cachedUserData.canPinMessages + } else if peerView.peerId == context.account.peerId { + explicitelyCanPinMessages = true + } + + var animated = false + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, let updated = renderedPeer?.peer as? TelegramSecretChat, peer.embeddedState != updated.embeddedState { + animated = true + } + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, let updated = renderedPeer?.peer as? TelegramChannel { + if peer.participationStatus != updated.participationStatus { + animated = true + } + } + + var didDisplayActionsPanel = false + if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { + if !peerStatusSettings.flags.isEmpty { + if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { + didDisplayActionsPanel = true + } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { + didDisplayActionsPanel = true + } else if peerStatusSettings.contains(.canShareContact) { + didDisplayActionsPanel = true + } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { + didDisplayActionsPanel = true + } + } + } + + var displayActionsPanel = false + if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { + if !peerStatusSettings.flags.isEmpty { + if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.canShareContact) { + displayActionsPanel = true + } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { + displayActionsPanel = true + } + } + } + + if displayActionsPanel != didDisplayActionsPanel { + animated = true + } + + if strongSelf.preloadHistoryPeerId != peerDiscussionId { + strongSelf.preloadHistoryPeerId = peerDiscussionId + if let peerDiscussionId = peerDiscussionId { + strongSelf.preloadHistoryPeerIdDisposable.set(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId)) + } else { + strongSelf.preloadHistoryPeerIdDisposable.set(nil) + } + } + + strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, { + return $0.updatedPeer { _ in + return renderedPeer + }.updatedIsNotAccessible(isNotAccessible).updatedContactStatus(contactStatus).updatedHasBots(hasBots).updatedIsArchived(isArchived).updatedPeerIsMuted(peerIsMuted).updatedPeerDiscussionId(peerDiscussionId).updatedPeerGeoLocation(peerGeoLocation).updatedExplicitelyCanPinMessages(explicitelyCanPinMessages).updatedHasScheduledMessages(false) + }) + if !strongSelf.didSetChatLocationInfoReady { + strongSelf.didSetChatLocationInfoReady = true + strongSelf._chatLocationInfoReady.set(.single(true)) + } + } + })) + } } self.botCallbackAlertMessageDisposable = (self.botCallbackAlertMessage.get() @@ -2926,7 +3193,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } override public func loadDisplayNode() { - self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, controller: self) + self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, controller: self) self.chatDisplayNode.historyNode.didScrollWithOffset = { [weak self] offset, transition, itemNode in guard let strongSelf = self else { @@ -2951,7 +3218,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self, let combinedInitialData = combinedInitialData else { return } - if let interfaceState = combinedInitialData.initialData?.chatInterfaceState as? ChatInterfaceState { + if var interfaceState = combinedInitialData.initialData?.chatInterfaceState as? ChatInterfaceState { + switch strongSelf.chatLocation { + case .peer: + break + default: + interfaceState = ChatInterfaceState() + } + var pinnedMessageId: MessageId? var peerIsBlocked: Bool = false var callsAvailable: Bool = true @@ -2973,6 +3247,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G pinnedMessageId = cachedData.pinnedMessageId } else if let _ = combinedInitialData.cachedData as? CachedSecretChatData { } + + if case .replyThread = strongSelf.chatLocation { + pinnedMessageId = nil + } + var pinnedMessage: Message? if let pinnedMessageId = pinnedMessageId { if let cachedDataMessages = combinedInitialData.cachedDataMessages { @@ -3071,21 +3350,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) let hasPendingMessages: Signal - if case let .peer(peerId) = self.chatLocation { - hasPendingMessages = self.context.account.pendingMessageManager.hasPendingMessages - |> mapToSignal { peerIds -> Signal in - let value = peerIds.contains(peerId) - if value { - return .single(true) - } else { - return .single(false) - |> delay(0.1, queue: .mainQueue()) - } + let chatLocationPeerId = self.chatLocation.peerId + hasPendingMessages = self.context.account.pendingMessageManager.hasPendingMessages + |> mapToSignal { peerIds -> Signal in + let value = peerIds.contains(chatLocationPeerId) + if value { + return .single(true) + } else { + return .single(false) + |> delay(0.1, queue: .mainQueue()) } - |> distinctUntilChanged - } else { - hasPendingMessages = .single(false) } + |> distinctUntilChanged self.cachedDataDisposable = combineLatest(queue: .mainQueue(), self.chatDisplayNode.historyNode.cachedPeerDataAndMessages, hasPendingMessages).start(next: { [weak self] cachedDataAndMessages, hasPendingMessages in if let strongSelf = self { @@ -3115,6 +3391,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else if let _ = cachedData as? CachedSecretChatData { } + if case .replyThread = strongSelf.chatLocation { + pinnedMessageId = nil + } + var pinnedMessage: Message? if let pinnedMessageId = pinnedMessageId { pinnedMessage = messages?[pinnedMessageId] @@ -3131,7 +3411,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let callsDataUpdated = strongSelf.presentationInterfaceState.callsAvailable != callsAvailable || strongSelf.presentationInterfaceState.callsPrivate != callsPrivate - if strongSelf.presentationInterfaceState.pinnedMessageId != pinnedMessageId || strongSelf.presentationInterfaceState.pinnedMessage?.stableVersion != pinnedMessage?.stableVersion || strongSelf.presentationInterfaceState.peerIsBlocked != peerIsBlocked || pinnedMessageUpdated || callsDataUpdated || strongSelf.presentationInterfaceState.slowmodeState != slowmodeState { + if strongSelf.presentationInterfaceState.pinnedMessageId != pinnedMessageId || strongSelf.presentationInterfaceState.pinnedMessage?.stableVersion != pinnedMessage?.stableVersion || strongSelf.presentationInterfaceState.peerIsBlocked != peerIsBlocked || pinnedMessageUpdated || callsDataUpdated || strongSelf.presentationInterfaceState.slowmodeState != slowmodeState { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in return state .updatedPinnedMessageId(pinnedMessageId) @@ -3315,7 +3595,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } self.chatDisplayNode.sendMessages = { [weak self] messages, silentPosting, scheduleTime, isAnyMessageTextPartitioned in - if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation { + if let strongSelf = self { + let peerId = strongSelf.chatLocation.peerId strongSelf.commitPurposefulAction() if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isRestrictedBySlowmode { @@ -3349,15 +3630,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G transformedMessages = strongSelf.transformEnqueueMessages(messages) } - let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: transformedMessages) - |> deliverOnMainQueue).start(next: { messageIds in - if let strongSelf = self { - if strongSelf.presentationInterfaceState.isScheduledMessages { - } else { - strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() - } - } - }) + for message in transformedMessages { + let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: [message]) + |> deliverOnMainQueue).start(next: { messageIds in + + }) + + } + +// let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: transformedMessages) +// |> deliverOnMainQueue).start(next: { messageIds in +// if let strongSelf = self { +// if strongSelf.presentationInterfaceState.isScheduledMessages { +// } else { +// strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() +// } +// } +// }) donateSendMessageIntent(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, intentContext: .chat, peerIds: [peerId]) } @@ -3381,7 +3670,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } return } - if case .peer = strongSelf.chatLocation, let messageId = strongSelf.presentationInterfaceState.interfaceState.editMessage?.messageId { + if let messageId = strongSelf.presentationInterfaceState.interfaceState.editMessage?.messageId { let _ = (strongSelf.context.account.postbox.transaction { transaction -> Message? in return transaction.getMessage(messageId) } |> deliverOnMainQueue).start(next: { message in @@ -3461,6 +3750,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() } else if case let .peer(peerId) = strongSelf.chatLocation { strongSelf.navigateToMessage(messageLocation: .upperBound(peerId), animated: true) + } else if case .replyThread = strongSelf.chatLocation { + strongSelf.scrollToEndOfHistory() } else { strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() } @@ -3895,15 +4186,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.updateItemNodesSearchTextHighlightStates() if let navigateIndex = navigateIndex { switch strongSelf.chatLocation { - case .peer: + case .peer, .replyThread: strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex), forceInCurrentChat: true) - /*case .group: - strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex))*/ } } } }, openCalendarSearch: { [weak self] in - if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation { + if let strongSelf = self { + let peerId = strongSelf.chatLocation.peerId strongSelf.chatDisplayNode.dismissInput() let controller = ChatDateSelectionSheet(presentationData: strongSelf.presentationData, completion: { timestamp in @@ -3942,8 +4232,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) strongSelf.updateItemNodesSearchTextHighlightStates() } - }, navigateToMessage: { [weak self] messageId in - self?.navigateToMessage(from: nil, to: .id(messageId)) + }, navigateToMessage: { [weak self] messageId, dropStack in + self?.navigateToMessage(from: nil, to: .id(messageId), dropStack: dropStack) }, navigateToChat: { [weak self] peerId in guard let strongSelf = self else { return @@ -3959,7 +4249,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, openPeerInfo: { [weak self] in self?.navigationButtonAction(.openChatInfo(expandAvatar: false)) }, togglePeerNotifications: { [weak self] in - if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation { + if let strongSelf = self { + let peerId = strongSelf.chatLocation.peerId let _ = togglePeerMuted(account: strongSelf.context.account, peerId: peerId).start() } }, sendContextResult: { [weak self] results, result, node, rect in @@ -4734,150 +5025,160 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G unarchiveAutomaticallyArchivedPeer(account: strongSelf.context.account, peerId: peerId) strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .succeed(text: strongSelf.presentationData.strings.Conversation_UnarchiveDone), elevatedLayout: false, action: { _ in return false }), in: .current) + }, viewReplies: { [weak self] sourceMessageId, replyThreadResult in + guard let strongSelf = self else { + return + } + + if let navigationController = strongSelf.effectiveNavigationController { + let subject: ChatControllerSubject? = sourceMessageId.flatMap(ChatControllerSubject.message) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(threadMessageId: replyThreadResult.messageId, isChannelPost: false, maxReadMessageId: replyThreadResult.maxReadMessageId), subject: subject, keepStack: .always)) + } }, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get(), inlineSearch: self.performingInlineSearch.get())) - switch self.chatLocation { - case let .peer(peerId): - if let subject = self.subject, case .scheduledMessages = subject { - } else { - let unreadCountsKey: PostboxViewKey = .unreadCounts(items: [.peer(peerId), .total(nil)]) - let notificationSettingsKey: PostboxViewKey = .peerNotificationSettings(peerIds: Set([peerId])) - self.chatUnreadCountDisposable = (self.context.account.postbox.combinedView(keys: [unreadCountsKey, notificationSettingsKey]) - |> deliverOnMainQueue).start(next: { [weak self] views in - if let strongSelf = self { - var unreadCount: Int32 = 0 - var totalChatCount: Int32 = 0 - - let inAppSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 } - if let view = views.views[unreadCountsKey] as? UnreadMessageCountsView { - if let count = view.count(for: .peer(peerId)) { - unreadCount = count - } - if let (_, state) = view.total() { - let (count, _) = renderedTotalUnreadCount(inAppSettings: inAppSettings, totalUnreadState: state) - totalChatCount = count - } - } - - strongSelf.chatDisplayNode.navigateButtons.unreadCount = unreadCount - - if let view = views.views[notificationSettingsKey] as? PeerNotificationSettingsView, let notificationSettings = view.notificationSettings[peerId] { - var globalRemainingUnreadChatCount = totalChatCount - if !notificationSettings.isRemovedFromTotalUnreadCount(default: false) && unreadCount > 0 { - if case .messages = inAppSettings.totalUnreadCountDisplayCategory { - globalRemainingUnreadChatCount -= unreadCount - } else { - globalRemainingUnreadChatCount -= 1 - } - } - - if globalRemainingUnreadChatCount > 0 { - strongSelf.navigationItem.badge = "\(globalRemainingUnreadChatCount)" - } else { - strongSelf.navigationItem.badge = "" - } - } - } - }) - - self.chatUnreadMentionCountDisposable = (self.context.account.viewTracker.unseenPersonalMessagesCount(peerId: peerId) |> deliverOnMainQueue).start(next: { [weak self] count in - if let strongSelf = self { - if case let .standard(previewing) = strongSelf.presentationInterfaceState.mode, previewing { - strongSelf.chatDisplayNode.navigateButtons.mentionCount = 0 - } else { - strongSelf.chatDisplayNode.navigateButtons.mentionCount = count - } - } - }) - - let postbox = self.context.account.postbox - let previousPeerCache = Atomic<[PeerId: Peer]>(value: [:]) - self.peerInputActivitiesDisposable = (self.context.account.peerInputActivities(peerId: peerId) - |> mapToSignal { activities -> Signal<[(Peer, PeerInputActivity)], NoError> in - var foundAllPeers = true - var cachedResult: [(Peer, PeerInputActivity)] = [] - previousPeerCache.with { dict -> Void in - for (peerId, activity) in activities { - if let peer = dict[peerId] { - cachedResult.append((peer, activity)) - } else { - foundAllPeers = false - break - } - } - } - if foundAllPeers { - return .single(cachedResult) - } else { - return postbox.transaction { transaction -> [(Peer, PeerInputActivity)] in - var result: [(Peer, PeerInputActivity)] = [] - var peerCache: [PeerId: Peer] = [:] - for (peerId, activity) in activities { - if let peer = transaction.getPeer(peerId) { - result.append((peer, activity)) - peerCache[peerId] = peer - } - } - let _ = previousPeerCache.swap(peerCache) - return result - } - } - } - |> deliverOnMainQueue).start(next: { [weak self] activities in - if let strongSelf = self { - strongSelf.chatTitleView?.inputActivities = (peerId, activities) - } - }) - } - - self.sentMessageEventsDisposable.set((self.context.account.pendingMessageManager.deliveredMessageEvents(peerId: peerId) - |> deliverOnMainQueue).start(next: { [weak self] namespace in + do { + let peerId = self.chatLocation.peerId + if let subject = self.subject, case .scheduledMessages = subject { + } else if case .replyThread = self.chatLocation { + } else { + let unreadCountsKey: PostboxViewKey = .unreadCounts(items: [.peer(peerId), .total(nil)]) + let notificationSettingsKey: PostboxViewKey = .peerNotificationSettings(peerIds: Set([peerId])) + self.chatUnreadCountDisposable = (self.context.account.postbox.combinedView(keys: [unreadCountsKey, notificationSettingsKey]) + |> deliverOnMainQueue).start(next: { [weak self] views in if let strongSelf = self { - let inAppNotificationSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 } - if inAppNotificationSettings.playSounds { - serviceSoundManager.playMessageDeliveredSound() + var unreadCount: Int32 = 0 + var totalChatCount: Int32 = 0 + + let inAppSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 } + if let view = views.views[unreadCountsKey] as? UnreadMessageCountsView { + if let count = view.count(for: .peer(peerId)) { + unreadCount = count + } + if let (_, state) = view.total() { + let (count, _) = renderedTotalUnreadCount(inAppSettings: inAppSettings, totalUnreadState: state) + totalChatCount = count + } } - if !strongSelf.presentationInterfaceState.isScheduledMessages && namespace == Namespaces.Message.ScheduledCloud { - strongSelf.openScheduledMessages() + + strongSelf.chatDisplayNode.navigateButtons.unreadCount = unreadCount + + if let view = views.views[notificationSettingsKey] as? PeerNotificationSettingsView, let notificationSettings = view.notificationSettings[peerId] { + var globalRemainingUnreadChatCount = totalChatCount + if !notificationSettings.isRemovedFromTotalUnreadCount(default: false) && unreadCount > 0 { + if case .messages = inAppSettings.totalUnreadCountDisplayCategory { + globalRemainingUnreadChatCount -= unreadCount + } else { + globalRemainingUnreadChatCount -= 1 + } + } + + if globalRemainingUnreadChatCount > 0 { + strongSelf.navigationItem.badge = "\(globalRemainingUnreadChatCount)" + } else { + strongSelf.navigationItem.badge = "" + } } } - })) + }) - self.failedMessageEventsDisposable.set((self.context.account.pendingMessageManager.failedMessageEvents(peerId: peerId) - |> deliverOnMainQueue).start(next: { [weak self] reason in - if let strongSelf = self, strongSelf.currentFailedMessagesAlertController == nil { - let text: String - let moreInfo: Bool - switch reason { - case .flood: - text = strongSelf.presentationData.strings.Conversation_SendMessageErrorFlood - moreInfo = true - case .publicBan: - text = strongSelf.presentationData.strings.Conversation_SendMessageErrorGroupRestricted - moreInfo = true - case .mediaRestricted: - strongSelf.interfaceInteraction?.displayRestrictedInfo(.mediaRecording, .alert) - return - case .slowmodeActive: - text = strongSelf.presentationData.strings.Chat_SlowmodeSendError - moreInfo = false - case .tooMuchScheduled: - text = strongSelf.presentationData.strings.Conversation_SendMessageErrorTooMuchScheduled - moreInfo = false - } - let actions: [TextAlertAction] - if moreInfo { - actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Generic_ErrorMoreInfo, action: { - self?.openPeerMention("spambot", navigation: .chat(textInputState: nil, subject: nil, peekData: nil)) - }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})] + self.chatUnreadMentionCountDisposable = (self.context.account.viewTracker.unseenPersonalMessagesCount(peerId: peerId) |> deliverOnMainQueue).start(next: { [weak self] count in + if let strongSelf = self { + if case let .standard(previewing) = strongSelf.presentationInterfaceState.mode, previewing { + strongSelf.chatDisplayNode.navigateButtons.mentionCount = 0 } else { - actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})] + strongSelf.chatDisplayNode.navigateButtons.mentionCount = count } - let controller = textAlertController(context: strongSelf.context, title: nil, text: text, actions: actions) - strongSelf.currentFailedMessagesAlertController = controller - strongSelf.present(controller, in: .window(.root)) } - })) + }) + + let postbox = self.context.account.postbox + let previousPeerCache = Atomic<[PeerId: Peer]>(value: [:]) + self.peerInputActivitiesDisposable = (self.context.account.peerInputActivities(peerId: peerId) + |> mapToSignal { activities -> Signal<[(Peer, PeerInputActivity)], NoError> in + var foundAllPeers = true + var cachedResult: [(Peer, PeerInputActivity)] = [] + previousPeerCache.with { dict -> Void in + for (peerId, activity) in activities { + if let peer = dict[peerId] { + cachedResult.append((peer, activity)) + } else { + foundAllPeers = false + break + } + } + } + if foundAllPeers { + return .single(cachedResult) + } else { + return postbox.transaction { transaction -> [(Peer, PeerInputActivity)] in + var result: [(Peer, PeerInputActivity)] = [] + var peerCache: [PeerId: Peer] = [:] + for (peerId, activity) in activities { + if let peer = transaction.getPeer(peerId) { + result.append((peer, activity)) + peerCache[peerId] = peer + } + } + let _ = previousPeerCache.swap(peerCache) + return result + } + } + } + |> deliverOnMainQueue).start(next: { [weak self] activities in + if let strongSelf = self { + strongSelf.chatTitleView?.inputActivities = (peerId, activities) + } + }) + } + + self.sentMessageEventsDisposable.set((self.context.account.pendingMessageManager.deliveredMessageEvents(peerId: peerId) + |> deliverOnMainQueue).start(next: { [weak self] namespace in + if let strongSelf = self { + let inAppNotificationSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 } + if inAppNotificationSettings.playSounds { + serviceSoundManager.playMessageDeliveredSound() + } + if !strongSelf.presentationInterfaceState.isScheduledMessages && namespace == Namespaces.Message.ScheduledCloud { + strongSelf.openScheduledMessages() + } + } + })) + + self.failedMessageEventsDisposable.set((self.context.account.pendingMessageManager.failedMessageEvents(peerId: peerId) + |> deliverOnMainQueue).start(next: { [weak self] reason in + if let strongSelf = self, strongSelf.currentFailedMessagesAlertController == nil { + let text: String + let moreInfo: Bool + switch reason { + case .flood: + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorFlood + moreInfo = true + case .publicBan: + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorGroupRestricted + moreInfo = true + case .mediaRestricted: + strongSelf.interfaceInteraction?.displayRestrictedInfo(.mediaRecording, .alert) + return + case .slowmodeActive: + text = strongSelf.presentationData.strings.Chat_SlowmodeSendError + moreInfo = false + case .tooMuchScheduled: + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorTooMuchScheduled + moreInfo = false + } + let actions: [TextAlertAction] + if moreInfo { + actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Generic_ErrorMoreInfo, action: { + self?.openPeerMention("spambot", navigation: .chat(textInputState: nil, subject: nil, peekData: nil)) + }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})] + } else { + actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})] + } + let controller = textAlertController(context: strongSelf.context, title: nil, text: text, actions: actions) + strongSelf.currentFailedMessagesAlertController = controller + strongSelf.present(controller, in: .window(.root)) + } + })) } self.chatDisplayNode.updateHasEmbeddedTitleContent = { [weak self] in @@ -5007,11 +5308,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G override public func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + + if self.scheduledActivateInput { + self.scheduledActivateInput = false + + self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in + return state.updatedInputMode({ _ in .text }) + }) + } } override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + self.didAppear = true + self.chatDisplayNode.historyNode.preloadPages = true self.chatDisplayNode.historyNode.experimentalSnapScrollToItem = false self.chatDisplayNode.historyNode.canReadHistory.set(combineLatest(context.sharedContext.applicationBindings.applicationInForeground, self.canReadHistory.get()) |> map { a, b in @@ -5272,6 +5583,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G ), in: .window(.root)) })) } + + if self.scheduledActivateInput { + self.scheduledActivateInput = false + + self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in + return state.updatedInputMode({ _ in .text }) + }) + } } override public func viewWillDisappear(_ animated: Bool) { @@ -5339,10 +5658,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.validLayout = layout self.chatTitleView?.layout = layout - if self.hasScheduledMessages, let h = layout.inputHeight, h > 100.0 { - print() - } - switch self.presentationInterfaceState.mode { case .standard, .inline: break @@ -5786,6 +6101,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private func navigationButtonAction(_ action: ChatNavigationButtonAction) { switch action { + case .spacer: + break case .cancelMessageSelection: self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) case .clearHistory: @@ -5927,7 +6244,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: true) { strongSelf.effectiveNavigationController?.pushViewController(infoController) } - //strongSelf.effectiveNavigationController?.pushViewController(PeerMediaCollectionController(context: strongSelf.context, peerId: strongSelf.context.account.peerId)) } else { var expandAvatar = expandAvatar if peer.smallProfileImage == nil { @@ -5942,6 +6258,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } })) + case .replyThread: + break } case .search: self.interfaceInteraction?.beginMessageSearch(.everything, "") @@ -6146,6 +6464,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) })) + case .replyThread: + break } case .toggleInfoPanel: self.updateChatPresentationInterfaceState(animated: true, interactive: true, { @@ -6193,10 +6513,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func editMessageMediaWithLegacySignals(_ signals: [Any]) { - guard case .peer = self.chatLocation else { - return - } - let _ = (legacyAssetPickerEnqueueMessages(account: self.context.account, signals: signals) |> deliverOnMainQueue).start(next: { [weak self] messages in self?.editMessageMediaWithMessages(messages) @@ -6399,10 +6715,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, presentTimerPicker: { [weak self] done in if let strongSelf = self { - strongSelf.presentTimerPicker(style: .media, completion: { [weak self] time in - if let strongSelf = self { - done(time) - } + strongSelf.presentTimerPicker(style: .media, completion: { time in + done(time) }) } }, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in @@ -6612,7 +6926,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, presentTimerPicker: { [weak self] done in if let strongSelf = self { - strongSelf.presentTimerPicker(style: .media, completion: { [weak self] time in + strongSelf.presentTimerPicker(style: .media, completion: { time in done(time) }) } @@ -7104,7 +7418,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func presentPollCreation(isQuiz: Bool? = nil) { - if case .peer = self.chatLocation, let peer = self.presentationInterfaceState.renderedPeer?.peer { + if let peer = self.presentationInterfaceState.renderedPeer?.peer { self.effectiveNavigationController?.pushViewController(createPollController(context: self.context, peer: peer, isQuiz: isQuiz, completion: { [weak self] message in guard let strongSelf = self else { return @@ -7170,7 +7484,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func transformEnqueueMessages(_ messages: [EnqueueMessage], silentPosting: Bool, scheduleTime: Int32? = nil) -> [EnqueueMessage] { + var defaultReplyMessageId: MessageId? + switch self.chatLocation { + case .peer: + break + case let .replyThread(messageId, _, _): + defaultReplyMessageId = messageId + } + return messages.map { message in + var message = message + + if let defaultReplyMessageId = defaultReplyMessageId { + switch message { + case let .message(text, attributes, mediaReference, replyToMessageId, localGroupingKey): + if replyToMessageId == nil { + message = .message(text: text, attributes: attributes, mediaReference: mediaReference, replyToMessageId: defaultReplyMessageId, localGroupingKey: localGroupingKey) + } + case .forward: + break + } + } + if silentPosting || scheduleTime != nil { return message.withUpdatedAttributes { attributes in var attributes = attributes @@ -7196,8 +7531,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func sendMessages(_ messages: [EnqueueMessage], commit: Bool = false) { - guard case let .peer(peerId) = self.chatLocation else { - return + let peerId: PeerId + switch self.chatLocation { + case let .peer(peerIdValue): + peerId = peerIdValue + case let .replyThread(messageId, _, _): + peerId = messageId.peerId } if commit || !self.presentationInterfaceState.isScheduledMessages { @@ -7221,23 +7560,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func enqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil) { - if case .peer = self.chatLocation { - self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(account: self.context.account, signals: signals!) - |> deliverOnMainQueue).start(next: { [weak self] messages in - if let strongSelf = self { - let messages = strongSelf.transformEnqueueMessages(messages, silentPosting: silentPosting, scheduleTime: scheduleTime) - 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(messages.map { $0.withUpdatedReplyToMessageId(replyMessageId) }) - } - })) - } + self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(account: self.context.account, signals: signals!) + |> deliverOnMainQueue).start(next: { [weak self] messages in + if let strongSelf = self { + let messages = strongSelf.transformEnqueueMessages(messages, silentPosting: silentPosting, scheduleTime: scheduleTime) + 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(messages.map { $0.withUpdatedReplyToMessageId(replyMessageId) }) + } + })) } private func displayPasteMenu(_ images: [UIImage]) { @@ -7315,9 +7652,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false) { - guard case let .peer(peerId) = self.chatLocation else { - return - } + let peerId = self.chatLocation.peerId if let message = outgoingMessageWithChatContextResult(to: peerId, results: results, result: result, hideVia: hideVia), canSendMessagesToChat(self.presentationInterfaceState) { let replyMessageId = self.presentationInterfaceState.interfaceState.replyMessageId @@ -7397,9 +7732,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func requestVideoRecorder() { - guard case let .peer(peerId) = self.chatLocation else { - return - } + let peerId = self.chatLocation.peerId if self.videoRecorderValue == nil { if let currentInputPanelFrame = self.chatDisplayNode.currentInputPanelFrame() { @@ -7620,21 +7953,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } return nil } + var searchTopMsgId: MessageId? + switch self.chatLocation { + case .peer: + break + case let .replyThread(messageId, _, _): + searchTopMsgId = messageId + } switch search.domain { case .everything: - switch self.chatLocation { - case let .peer(peerId): - derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: nil, tags: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) - } + derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: self.chatLocation.peerId, fromId: nil, tags: nil, topMsgId: searchTopMsgId, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) case .members: derivedSearchState = nil case let .member(peer): - switch self.chatLocation { - case let .peer(peerId): - derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: peer.id, tags: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) - /*case .group: - derivedSearchState = nil*/ - } + derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: self.chatLocation.peerId, fromId: peer.id, tags: nil, topMsgId: searchTopMsgId, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) } } @@ -7645,7 +7977,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if previousSearchState?.query != searchState.query || previousSearchState?.location != searchState.location { var queryIsEmpty = false if searchState.query.isEmpty { - if case let .peer(_, fromId, _) = searchState.location { + if case let .peer(_, fromId, _, _, _, _) = searchState.location { if fromId == nil { queryIsEmpty = true } @@ -7705,10 +8037,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) if let navigateIndex = navigateIndex { switch strongSelf.chatLocation { - case .peer: + case .peer, .replyThread: strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex), forceInCurrentChat: true) - /*case .group: - strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex))*/ } } strongSelf.updateItemNodesSearchTextHighlightStates() @@ -7767,7 +8097,67 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } func scrollToEndOfHistory() { - self.chatDisplayNode.historyNode.scrollToEndOfHistory() + let locationInput = ChatHistoryLocationInput(content: .Scroll(index: .upperBound, anchorIndex: .upperBound, sourceIndex: .lowerBound, scrollPosition: .top(0.0), animated: true), id: 0) + + let historyView = preloadedChatHistoryViewForLocation(locationInput, context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) + let signal = historyView + |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in + switch historyView { + case .Loading: + return .single((nil, true)) + case .HistoryView: + return .single((nil, false)) + } + } + |> take(until: { index in + return SignalTakeAction(passthrough: true, complete: !index.1) + }) + + var cancelImpl: (() -> Void)? + let presentationData = self.presentationData + let displayTime = CACurrentMediaTime() + let progressSignal = Signal { [weak self] subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + if CACurrentMediaTime() - displayTime > 1.5 { + cancelImpl?() + } + })) + self?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.05, queue: Queue.mainQueue()) + let progressDisposable = MetaDisposable() + var progressStarted = false + self.messageIndexDisposable.set((signal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + |> deliverOnMainQueue).start(next: { [weak self] index in + if index.1 { + if !progressStarted { + progressStarted = true + progressDisposable.set(progressSignal.start()) + } + } + }, completed: { [weak self] in + if let strongSelf = self { + strongSelf.loadingMessage.set(false) + strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() + } + })) + cancelImpl = { [weak self] in + if let strongSelf = self { + strongSelf.loadingMessage.set(false) + strongSelf.messageIndexDisposable.set(nil) + } + } } func updateDownButtonVisibility() { @@ -7783,17 +8173,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } - public func navigateToMessage(messageLocation: NavigateToMessageLocation, animated: Bool, forceInCurrentChat: Bool = false, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil) { + public func navigateToMessage(messageLocation: NavigateToMessageLocation, animated: Bool, forceInCurrentChat: Bool = false, dropStack: Bool = false, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil) { let scrollPosition: ListViewScrollPosition if case .upperBound = messageLocation { scrollPosition = .top(0.0) } else { scrollPosition = .center(.bottom) } - self.navigateToMessage(from: nil, to: messageLocation, scrollPosition: scrollPosition, rememberInStack: false, forceInCurrentChat: forceInCurrentChat, animated: animated, completion: completion, customPresentProgress: customPresentProgress) + self.navigateToMessage(from: nil, to: messageLocation, scrollPosition: scrollPosition, rememberInStack: false, forceInCurrentChat: forceInCurrentChat, dropStack: dropStack, animated: animated, completion: completion, customPresentProgress: customPresentProgress) } - private func navigateToMessage(from fromId: MessageId?, to messageLocation: NavigateToMessageLocation, scrollPosition: ListViewScrollPosition = .center(.bottom), rememberInStack: Bool = true, forceInCurrentChat: Bool = false, animated: Bool = true, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil) { + private func navigateToMessage(from fromId: MessageId?, to messageLocation: NavigateToMessageLocation, scrollPosition: ListViewScrollPosition = .center(.bottom), rememberInStack: Bool = true, forceInCurrentChat: Bool = false, dropStack: Bool = false, animated: Bool = true, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil) { if self.isNodeLoaded { var fromIndex: MessageIndex? @@ -7805,11 +8195,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + var forceInCurrentChat = forceInCurrentChat + if case let .peer(peerId) = self.chatLocation, messageLocation.peerId == peerId { forceInCurrentChat = true + } + if case let .peer(peerId) = self.chatLocation, let messageId = messageLocation.messageId, (messageId.peerId != peerId && !forceInCurrentChat) || (self.presentationInterfaceState.isScheduledMessages && messageId.id != 0 && !Namespaces.Message.allScheduled.contains(messageId.namespace)) { if let navigationController = self.effectiveNavigationController { self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageId.peerId), subject: .message(messageId), keepStack: .always)) } - } else if case let .peer(peerId) = self.chatLocation, (messageLocation.peerId == peerId || forceInCurrentChat) { + } else if forceInCurrentChat { if let _ = fromId, let fromIndex = fromIndex, rememberInStack { self.historyNavigationStack.add(fromIndex) } @@ -7840,9 +8234,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case let .index(index): searchLocation = .index(index) case .upperBound: - searchLocation = .index(MessageIndex.upperBound(peerId: peerId)) + searchLocation = .index(MessageIndex.upperBound(peerId: self.chatLocation.peerId)) } - let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50), id: 0), account: self.context.account, chatLocation: self.chatLocation, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) + let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50), id: 0), context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) let signal = historyView |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in switch historyView { @@ -7934,7 +8328,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.historyNavigationStack.add(fromIndex) } self.loadingMessage.set(true) - let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50), id: 0), account: self.context.account, chatLocation: self.chatLocation, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) + let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50), id: 0), context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) let signal = historyView |> mapToSignal { historyView -> Signal in switch historyView { @@ -7956,7 +8350,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.historyNode.scrollToMessage(from: fromIndex, to: index, animated: animated, scrollPosition: scrollPosition) completion?() } else { - strongSelf.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(messageLocation.peerId), subject: messageLocation.messageId.flatMap { .message($0) })) + if let navigationController = strongSelf.effectiveNavigationController { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(messageLocation.peerId), subject: messageLocation.messageId.flatMap { .message($0) })) + } completion?() } } @@ -7965,6 +8361,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.loadingMessage.set(false) } })) + } else { + if let navigationController = self.effectiveNavigationController { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(messageLocation.peerId), subject: messageLocation.messageId.flatMap { .message($0) })) + } + completion?() } } } else { @@ -8122,60 +8523,58 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } else { if let peerId = peerId { - switch self.chatLocation { - case let .peer(selfPeerId): - switch navigation { - case .info, .default: - let peerSignal: Signal - if let fromMessage = fromMessage { - peerSignal = loadedPeerFromMessage(account: self.context.account, peerId: peerId, messageId: fromMessage.id) - } else { - peerSignal = self.context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) - } - self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self, let peer = peer { - var mode: PeerInfoControllerMode = .generic - if let _ = fromMessage { - mode = .group(selfPeerId) - } - var expandAvatar = expandAvatar - if peer.smallProfileImage == nil { - expandAvatar = false - } - if let validLayout = strongSelf.validLayout, validLayout.deviceMetrics.type == .tablet { - expandAvatar = false - } - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: false) { - strongSelf.effectiveNavigationController?.pushViewController(infoController) - } + do { + let selfPeerId = self.chatLocation.peerId + switch navigation { + case .info, .default: + let peerSignal: Signal + if let fromMessage = fromMessage { + peerSignal = loadedPeerFromMessage(account: self.context.account, peerId: peerId, messageId: fromMessage.id) + } else { + peerSignal = self.context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) + } + self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in + if let strongSelf = self, let peer = peer { + var mode: PeerInfoControllerMode = .generic + if let _ = fromMessage { + mode = .group(selfPeerId) } - })) - case let .chat(textInputState, subject, peekData): - if let textInputState = textInputState { - let _ = (self.context.account.postbox.transaction({ transaction -> Void in - transaction.updatePeerChatInterfaceState(peerId, update: { currentState in - if let currentState = currentState as? ChatInterfaceState { - return currentState.withUpdatedComposeInputState(textInputState) - } else { - return ChatInterfaceState().withUpdatedComposeInputState(textInputState) - } - }) - }) - |> deliverOnMainQueue).start(completed: { [weak self] in - if let strongSelf = self, let navigationController = strongSelf.effectiveNavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject, updateTextInputState: textInputState, peekData: peekData)) + var expandAvatar = expandAvatar + if peer.smallProfileImage == nil { + expandAvatar = false + } + if let validLayout = strongSelf.validLayout, validLayout.deviceMetrics.type == .tablet { + expandAvatar = false + } + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: false) { + strongSelf.effectiveNavigationController?.pushViewController(infoController) + } + } + })) + case let .chat(textInputState, subject, peekData): + if let textInputState = textInputState { + let _ = (self.context.account.postbox.transaction({ transaction -> Void in + transaction.updatePeerChatInterfaceState(peerId, update: { currentState in + if let currentState = currentState as? ChatInterfaceState { + return currentState.withUpdatedComposeInputState(textInputState) + } else { + return ChatInterfaceState().withUpdatedComposeInputState(textInputState) } }) - } else { - self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), subject: subject)) - } - case let .withBotStartPayload(botStart): - self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), botStart: botStart)) - default: - break - } - /*case .group: - (self.navigationController as? NavigationController)?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), messageId: fromMessage?.id, botStart: nil))*/ + }) + |> deliverOnMainQueue).start(completed: { [weak self] in + if let strongSelf = self, let navigationController = strongSelf.effectiveNavigationController { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject, updateTextInputState: textInputState, peekData: peekData)) + } + }) + } else { + self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), subject: subject)) + } + case let .withBotStartPayload(botStart): + self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(peerId), botStart: botStart)) + default: + break + } } } else { switch navigation { @@ -8708,7 +9107,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let selectedTransitionNode = selectedTransitionNode { - if let previewData = chatMessagePreviewControllerData(context: self.context, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: self.effectiveNavigationController) { + if let previewData = chatMessagePreviewControllerData(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: self.effectiveNavigationController) { switch previewData { case let .gallery(gallery): gallery.setHintWillBePresentedInPreviewingContext(true) @@ -8798,113 +9197,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - @available(iOSApplicationExtension 9.0, iOS 9.0, *) - override public var previewActionItems: [UIPreviewActionItem] { - struct PreviewActionsData { - let notificationSettings: PeerNotificationSettings? - let peer: Peer? - } - let chatLocation = self.chatLocation - let data = Atomic(value: nil) - let semaphore = DispatchSemaphore(value: 0) - let _ = self.context.account.postbox.transaction({ transaction -> Void in - switch chatLocation { - case let .peer(peerId): - let _ = data.swap(PreviewActionsData(notificationSettings: transaction.getPeerNotificationSettings(peerId), peer: transaction.getPeer(peerId))) - /*case .group: - let _ = data.swap(PreviewActionsData(notificationSettings: nil, peer: nil))*/ - } - semaphore.signal() - }).start() - semaphore.wait() - - return data.with { [weak self] data -> [UIPreviewActionItem] in - var items: [UIPreviewActionItem] = [] - if let data = data, let strongSelf = self { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - - switch strongSelf.peekActions { - case .standard: - if let peer = data.peer, peer.id != strongSelf.context.account.peerId { - if let _ = data.peer as? TelegramUser { - items.append(UIPreviewAction(title: "👍", style: .default, handler: { _, _ in - if let strongSelf = self { - let _ = enqueueMessages(account: strongSelf.context.account, peerId: peer.id, messages: strongSelf.transformEnqueueMessages([.message(text: "👍", attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)])).start() - } - })) - } - - if let notificationSettings = data.notificationSettings as? TelegramPeerNotificationSettings { - if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { - items.append(UIPreviewAction(title: presentationData.strings.Conversation_Unmute, style: .default, handler: { _, _ in - if let strongSelf = self { - let _ = togglePeerMuted(account: strongSelf.context.account, peerId: peer.id).start() - } - })) - } else { - let muteInterval: Int32 - if let _ = data.peer as? TelegramChannel { - muteInterval = Int32.max - } else { - muteInterval = 1 * 60 * 60 - } - let title: String - if muteInterval == Int32.max { - title = presentationData.strings.Conversation_Mute - } else { - title = muteForIntervalString(strings: presentationData.strings, value: muteInterval) - } - - items.append(UIPreviewAction(title: title, style: .default, handler: { _, _ in - if let strongSelf = self { - let _ = updatePeerMuteSetting(account: strongSelf.context.account, peerId: peer.id, muteInterval: muteInterval).start() - } - })) - } - } - } - case let .remove(action): - items.append(UIPreviewAction(title: presentationData.strings.Common_Delete, style: .destructive, handler: { _, _ in - action() - })) - } - } - return items - } - } - - private func debugStreamSingleVideo(_ id: MessageId) { - let gallery = GalleryController(context: self.context, source: .peerMessagesAtId(id), streamSingleVideo: true, replaceRootController: { [weak self] controller, ready in - if let strongSelf = self { - strongSelf.effectiveNavigationController?.replaceTopController(controller, animated: false, ready: ready) - } - }, baseNavigationController: self.effectiveNavigationController) - - self.chatDisplayNode.dismissInput() - self.present(gallery, in: .window(.root), with: GalleryControllerPresentationArguments(transitionArguments: { [weak self] messageId, media in - if let strongSelf = self { - var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? - strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView { - if let result = itemNode.transitionNode(id: messageId, media: media) { - transitionNode = result - } - } - } - if let transitionNode = transitionNode { - return GalleryTransitionArguments(transitionNode: transitionNode, addToTransitionSurface: { view in - if let strongSelf = self { - strongSelf.chatDisplayNode.historyNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.chatDisplayNode.historyNode.view) - } - }) - } - } - return nil - })) - } - private func presentBanMessageOptions(accountPeerId: PeerId, author: Peer, messageIds: Set, options: ChatAvailableMessageActionOptions) { - if case let .peer(peerId) = self.chatLocation { + let peerId = self.chatLocation.peerId + do { self.navigationActionDisposable.set((fetchChannelParticipant(account: self.context.account, peerId: peerId, participantId: author.id) |> deliverOnMainQueue).start(next: { [weak self] participant in if let strongSelf = self { @@ -9416,9 +9711,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } func activateInput() { - self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in - return state.updatedInputMode({ _ in .text }) - }) + if self.didAppear { + self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in + return state.updatedInputMode({ _ in .text }) + }) + } else { + self.scheduledActivateInput = true + } } private func clearInputText() { @@ -9595,115 +9894,3 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent func animatedIn() { } } - -func parseUrl(url: String, wasConcealed: Bool) -> (string: String, concealed: Bool) { - var parsedUrlValue: URL? - if url.hasPrefix("tel:") { - return (url, false) - } else if let parsed = URL(string: url) { - parsedUrlValue = parsed - } else if let parsed = URL(string: "https://" + url) { - parsedUrlValue = parsed - } else if let encoded = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let parsed = URL(string: encoded) { - parsedUrlValue = parsed - } - let host = parsedUrlValue?.host ?? url - - let rawHost = (host as NSString).removingPercentEncoding ?? host - var latin = CharacterSet() - latin.insert(charactersIn: "A"..."Z") - latin.insert(charactersIn: "a"..."z") - latin.insert(charactersIn: "0"..."9") - var punctuation = CharacterSet() - punctuation.insert(charactersIn: ".-/+_") - var hasLatin = false - var hasNonLatin = false - for c in rawHost { - if c.unicodeScalars.allSatisfy(punctuation.contains) { - } else if c.unicodeScalars.allSatisfy(latin.contains) { - hasLatin = true - } else { - hasNonLatin = true - } - } - var concealed = wasConcealed - if hasLatin && hasNonLatin { - concealed = true - } - - var rawDisplayUrl: String - if hasNonLatin { - rawDisplayUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? url - } else { - rawDisplayUrl = url - } - - if let parsedUrlValue = parsedUrlValue, isConcealedUrlWhitelisted(parsedUrlValue) { - concealed = false - } - - let whitelistedSchemes: [String] = [ - "tel", - ] - if let parsedUrlValue = parsedUrlValue, let scheme = parsedUrlValue.scheme, whitelistedSchemes.contains(scheme) { - concealed = false - } - - return (rawDisplayUrl, concealed) -} - -func openUserGeneratedUrl(context: AccountContext, url: String, concealed: Bool, present: @escaping (ViewController) -> Void, openResolved: @escaping (ResolvedUrl) -> Void) { - var concealed = concealed - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - let openImpl: () -> Void = { - let disposable = MetaDisposable() - var cancelImpl: (() -> Void)? - let progressSignal = Signal { subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { - cancelImpl?() - })) - present(controller) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = progressSignal.start() - - cancelImpl = { - disposable.dispose() - } - disposable.set((context.sharedContext.resolveUrl(account: context.account, url: url) - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - |> deliverOnMainQueue).start(next: { result in - openResolved(result) - })) - } - - let (parsedString, parsedConcealed) = parseUrl(url: url, wasConcealed: concealed) - concealed = parsedConcealed - - if concealed { - var rawDisplayUrl: String = parsedString - let maxLength = 180 - if rawDisplayUrl.count > maxLength { - rawDisplayUrl = String(rawDisplayUrl[.. Void let greetingStickerNode: () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)? let openPeerContextMenu: (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void + let openMessageReplies: (MessageId) -> Void let requestMessageUpdate: (MessageId) -> Void let cancelInteractiveKeyboardGestures: () -> Void @@ -138,7 +130,78 @@ public final class ChatControllerInteraction { var searchTextHighightState: (String, [MessageIndex])? var seenOneTimeAnimatedMedia = Set() - init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, tapMessage: ((Message) -> Void)?, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @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?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId, Bool) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId, Bool) -> Void, requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, NSAttributedString, TextSelectionAction) -> Void, updateMessageLike: @escaping (MessageId, Bool) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, dismissReplyMarkupMessage: @escaping (Message) -> Void, openMessagePollResults: @escaping (MessageId, Data) -> Void, openPollCreation: @escaping (Bool?) -> Void, displayPollSolution: @escaping (TelegramMediaPollResults.Solution, ASDisplayNode) -> Void, displayPsa: @escaping (String, ASDisplayNode) -> Void, displayDiceTooltip: @escaping (TelegramMediaDice) -> Void, animateDiceSuccess: @escaping () -> Void, greetingStickerNode: @escaping () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)?, openPeerContextMenu: @escaping (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) { + init( + openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, + openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, + openPeerMention: @escaping (String) -> Void, + openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, + openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, + navigateToMessage: @escaping (MessageId, MessageId) -> Void, + tapMessage: ((Message) -> Void)?, + clickThroughMessage: @escaping () -> Void, + toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, + sendCurrentMessage: @escaping (Bool) -> Void, + sendMessage: @escaping (String) -> Void, + sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, + sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, + sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, + requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void, + requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, + activateSwitchInline: @escaping (PeerId?, String) -> Void, + openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, + shareCurrentLocation: @escaping () -> Void, + shareAccountContact: @escaping () -> Void, + sendBotCommand: @escaping (MessageId?, String) -> Void, + openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, + openWallpaper: @escaping (Message) -> Void, + openTheme: @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?, + chatControllerNode: @escaping () -> ASDisplayNode?, + reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, + presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, + callPeer: @escaping (PeerId, Bool) -> Void, + longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, + openCheckoutOrReceipt: @escaping (MessageId) -> Void, + openSearch: @escaping () -> Void, + setupReply: @escaping (MessageId) -> Void, + canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction, + navigateToFirstDateMessage: @escaping(Int32) ->Void, + requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, + addContact: @escaping (String) -> Void, + rateCall: @escaping (Message, CallId, Bool) -> Void, + requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, + requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, + openAppStorePage: @escaping () -> Void, + displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, + seekToTimecode: @escaping (Message, Double, Bool) -> Void, + scheduleCurrentMessage: @escaping () -> Void, + sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, + editScheduledMessagesTime: @escaping ([MessageId]) -> Void, + performTextSelectionAction: @escaping (UInt32, NSAttributedString, TextSelectionAction) -> Void, + updateMessageLike: @escaping (MessageId, Bool) -> Void, + openMessageReactions: @escaping (MessageId) -> Void, + displaySwipeToReplyHint: @escaping () -> Void, + dismissReplyMarkupMessage: @escaping (Message) -> Void, + openMessagePollResults: @escaping (MessageId, Data) -> Void, + openPollCreation: @escaping (Bool?) -> Void, + displayPollSolution: @escaping (TelegramMediaPollResults.Solution, ASDisplayNode) -> Void, + displayPsa: @escaping (String, ASDisplayNode) -> Void, + displayDiceTooltip: @escaping (TelegramMediaDice) -> Void, + animateDiceSuccess: @escaping () -> Void, + greetingStickerNode: @escaping () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)?, + openPeerContextMenu: @escaping (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void, + openMessageReplies: @escaping (MessageId) -> Void, + requestMessageUpdate: @escaping (MessageId) -> Void, + cancelInteractiveKeyboardGestures: @escaping () -> Void, + automaticMediaDownloadSettings: MediaAutoDownloadSettings, + pollActionState: ChatInterfacePollActionState, + stickerSettings: ChatInterfaceStickerSettings + ) { self.openMessage = openMessage self.openPeer = openPeer self.openPeerMention = openPeerMention @@ -203,6 +266,7 @@ public final class ChatControllerInteraction { self.animateDiceSuccess = animateDiceSuccess self.greetingStickerNode = greetingStickerNode self.openPeerContextMenu = openPeerContextMenu + self.openMessageReplies = openMessageReplies self.requestMessageUpdate = requestMessageUpdate self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures @@ -251,6 +315,7 @@ public final class ChatControllerInteraction { }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in + }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 7f61599a74..0a39157275 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -13,6 +13,7 @@ import AccountContext import TelegramNotices import ReactionSelectionNode import TelegramUniversalVideoContent +import ChatInterfaceState final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode @@ -414,11 +415,29 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private var derivedLayoutState: ChatControllerNodeDerivedLayoutState? - private var isLoading: Bool = false { - didSet { - if self.isLoading != oldValue { - if self.isLoading { - self.historyNodeContainer.supernode?.insertSubnode(self.loadingNode, aboveSubnode: self.historyNodeContainer) + private var isLoadingValue: Bool = false + private func updateIsLoading(isLoading: Bool, animated: Bool) { + if isLoading != self.isLoadingValue { + self.isLoadingValue = isLoading + if isLoading { + self.historyNodeContainer.supernode?.insertSubnode(self.loadingNode, aboveSubnode: self.historyNodeContainer) + self.loadingNode.layer.removeAllAnimations() + self.loadingNode.alpha = 1.0 + if animated { + self.loadingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + } + } else { + self.loadingNode.alpha = 0.0 + if animated { + self.loadingNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false) + self.loadingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { [weak self] completed in + if let strongSelf = self { + strongSelf.loadingNode.layer.removeAllAnimations() + if completed { + strongSelf.loadingNode.removeFromSupernode() + } + } + }) } else { self.loadingNode.removeFromSupernode() } @@ -441,7 +460,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } private var didProcessExperimentalEmbedUrl: String? - init(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, chatPresentationInterfaceState: ChatPresentationInterfaceState, automaticMediaDownloadSettings: MediaAutoDownloadSettings, navigationBar: NavigationBar?, controller: ChatControllerImpl?) { + init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, chatPresentationInterfaceState: ChatPresentationInterfaceState, automaticMediaDownloadSettings: MediaAutoDownloadSettings, navigationBar: NavigationBar?, controller: ChatControllerImpl?) { self.context = context self.chatLocation = chatLocation self.controllerInteraction = controllerInteraction @@ -458,7 +477,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.inputContextPanelContainer = ChatControllerTitlePanelNodeContainer() - self.historyNode = ChatHistoryListNode(context: context, chatLocation: chatLocation, tagMask: nil, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) + self.historyNode = ChatHistoryListNode(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tagMask: nil, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) self.historyNode.rotated = true self.historyNodeContainer = ASDisplayNode() self.historyNodeContainer.addSubnode(self.historyNode) @@ -521,9 +540,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.historyNode.setLoadStateUpdated { [weak self] loadState, animated in if let strongSelf = self { if case .loading = loadState { - strongSelf.isLoading = true + strongSelf.updateIsLoading(isLoading: true, animated: animated) } else { - strongSelf.isLoading = false + strongSelf.updateIsLoading(isLoading: false, animated: animated) } var isEmpty = false @@ -2137,11 +2156,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { func loadInputPanels(theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) { if self.inputMediaNode == nil { - var peerId: PeerId? - if case let .peer(id) = self.chatPresentationInterfaceState.chatLocation { - peerId = id - } - let inputNode = ChatMediaInputNode(context: self.context, peerId: peerId, controllerInteraction: self.controllerInteraction, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, theme: theme, strings: strings, fontSize: fontSize, gifPaneIsActiveUpdated: { [weak self] value in + let peerId: PeerId? = self.chatPresentationInterfaceState.chatLocation.peerId + let inputNode = ChatMediaInputNode(context: self.context, peerId: peerId, chatLocation: self.chatPresentationInterfaceState.chatLocation, controllerInteraction: self.controllerInteraction, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, theme: theme, strings: strings, fontSize: fontSize, gifPaneIsActiveUpdated: { [weak self] value in if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { interfaceInteraction.updateInputModeAndDismissedButtonKeyboardMessageId { state in if case let .media(_, expanded) = state.inputMode { @@ -2694,7 +2710,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let effectiveInputText = effectivePresentationInterfaceState.interfaceState.composeInputState.inputText let trimmedInputText = effectiveInputText.string.trimmingCharacters(in: .whitespacesAndNewlines) - if case let .peer(peerId) = effectivePresentationInterfaceState.chatLocation, peerId.namespace != Namespaces.Peer.SecretChat, let interactiveEmojis = self.interactiveEmojis, interactiveEmojis.emojis.contains(trimmedInputText) { + let peerId = effectivePresentationInterfaceState.chatLocation.peerId + if peerId.namespace != Namespaces.Peer.SecretChat, let interactiveEmojis = self.interactiveEmojis, interactiveEmojis.emojis.contains(trimmedInputText) { messages.append(.message(text: "", attributes: [], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: trimmedInputText)), replyToMessageId: self.chatPresentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)) } else { let inputText = convertMarkdownToAttributes(effectiveInputText) @@ -2746,9 +2763,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - if case .peer = self.chatLocation { - self.sendMessages(messages, silentPosting, scheduleTime, messages.count > 1) - } + self.sendMessages(messages, silentPosting, scheduleTime, messages.count > 1) } } } diff --git a/submodules/TelegramUI/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Sources/ChatEmptyNode.swift index 6ce1a049f6..64de0c4ed3 100644 --- a/submodules/TelegramUI/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Sources/ChatEmptyNode.swift @@ -38,7 +38,17 @@ private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNod self.currentStrings = interfaceState.strings let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) - self.textNode.attributedText = NSAttributedString(string: interfaceState.isScheduledMessages ? interfaceState.strings.ScheduledMessages_EmptyPlaceholder : interfaceState.strings.Conversation_EmptyPlaceholder, font: messageFont, textColor: serviceColor.primaryText) + + let text: String + switch interfaceState.chatLocation { + case .peer: + text = interfaceState.isScheduledMessages ? interfaceState.strings.ScheduledMessages_EmptyPlaceholder : interfaceState.strings.Conversation_EmptyPlaceholder + case .replyThread: + //TODO:localize + text = "No comments here yet" + } + + self.textNode.attributedText = NSAttributedString(string: text, font: messageFont, textColor: serviceColor.primaryText) } let insets = UIEdgeInsets(top: 6.0, left: 10.0, bottom: 6.0, right: 10.0) @@ -639,7 +649,9 @@ final class ChatEmptyNode: ASDisplayNode { } let contentType: ChatEmptyNodeContentType - if let peer = interfaceState.renderedPeer?.peer, !interfaceState.isScheduledMessages { + if case .replyThread = interfaceState.chatLocation { + contentType = .regular + } else if let peer = interfaceState.renderedPeer?.peer, !interfaceState.isScheduledMessages { if peer.id == self.account.peerId { contentType = .cloud } else if let _ = peer as? TelegramSecretChat { diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index a896a83966..e2ee6a6a68 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -4,6 +4,9 @@ import TelegramCore import SyncCore import TemporaryCachedPeerDataManager import Emoji +import AccountContext +import TelegramPresentationData + func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, includeUnreadEntry: Bool, includeEmptyEntry: Bool, includeChatInfoEntry: Bool, includeSearchEntry: Bool, reverse: Bool, groupMessages: Bool, selectedMessages: Set?, presentationData: ChatPresentationData, historyAppearsCleared: Bool, associatedData: ChatMessageItemAssociatedData, updatingMedia: [MessageId: ChatUpdatingMessageMedia]) -> [ChatHistoryEntry] { if historyAppearsCleared { @@ -110,6 +113,53 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, } } + var addedThreadHead = false + if case let .replyThread(messageId, isChannelPost, _) = location, view.earlierId == nil, !view.isLoading { + loop: for entry in view.additionalData { + switch entry { + case let .message(id, message) where id == messageId: + if let message = message { + let selection: ChatHistoryMessageSelection + if let selectedMessages = selectedMessages { + selection = .selectable(selected: selectedMessages.contains(message.id)) + } else { + selection = .none + } + + var adminRank: CachedChannelAdminRank? + if let author = message.author { + adminRank = adminRanks[author.id] + } + + var contentTypeHint: ChatMessageEntryContentType = .generic + if presentationData.largeEmoji, message.media.isEmpty { + if stickersEnabled && message.text.count == 1, let _ = associatedData.animatedEmojiStickers[message.text.basicEmoji.0] { + contentTypeHint = .animatedEmoji + } else if message.text.count < 10 && messageIsElligibleForLargeEmoji(message) { + contentTypeHint = .largeEmoji + } + } + + var replyCount = 0 + for attribute in message.attributes { + if let attribute = attribute as? ReplyThreadMessageAttribute { + replyCount = Int(attribute.count) + } + } + + addedThreadHead = true + entries.insert(.MessageEntry(message, presentationData, false, nil, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id])), at: 0) + if view.entries.count > 0 { + entries.insert(.ReplyCountEntry(message.index, isChannelPost, replyCount, presentationData), at: 1) + } + } + break loop + default: + break + } + } + } + if includeChatInfoEntry { if view.earlierId == nil { var cachedPeerData: CachedPeerData? @@ -151,6 +201,9 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, } else { isEmpty = false } + if addedThreadHead { + isEmpty = false + } if isEmpty { entries.removeAll() } diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntry.swift b/submodules/TelegramUI/Sources/ChatHistoryEntry.swift index 05488d9409..02c589c44a 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntry.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntry.swift @@ -4,28 +4,7 @@ import SyncCore import TelegramPresentationData import MergeLists import TemporaryCachedPeerDataManager - -public enum ChatHistoryMessageSelection: Equatable { - case none - case selectable(selected: Bool) - - public static func ==(lhs: ChatHistoryMessageSelection, rhs: ChatHistoryMessageSelection) -> Bool { - switch lhs { - case .none: - if case .none = rhs { - return true - } else { - return false - } - case let .selectable(selected): - if case .selectable(selected) = rhs { - return true - } else { - return false - } - } - } -} +import AccountContext public enum ChatMessageEntryContentType { case generic @@ -58,6 +37,7 @@ enum ChatHistoryEntry: Identifiable, Comparable { case MessageEntry(Message, ChatPresentationData, Bool, MessageHistoryEntryMonthLocation?, ChatHistoryMessageSelection, ChatMessageEntryAttributes) case MessageGroupEntry(MessageGroupInfo, [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)], ChatPresentationData) case UnreadEntry(MessageIndex, ChatPresentationData) + case ReplyCountEntry(MessageIndex, Bool, Int, ChatPresentationData) case ChatInfoEntry(String, ChatPresentationData) case SearchEntry(PresentationTheme, PresentationStrings) @@ -78,10 +58,12 @@ enum ChatHistoryEntry: Identifiable, Comparable { return UInt64(groupInfo.stableId) | ((UInt64(2) << 40)) case .UnreadEntry: return UInt64(4) << 40 - case .ChatInfoEntry: + case .ReplyCountEntry: return UInt64(5) << 40 - case .SearchEntry: + case .ChatInfoEntry: return UInt64(6) << 40 + case .SearchEntry: + return UInt64(7) << 40 } } @@ -93,6 +75,8 @@ enum ChatHistoryEntry: Identifiable, Comparable { return messages[messages.count - 1].0.index case let .UnreadEntry(index, _): return index + case let .ReplyCountEntry(index, _, _, _): + return index case .ChatInfoEntry: return MessageIndex.absoluteLowerBound() case .SearchEntry: @@ -203,6 +187,12 @@ enum ChatHistoryEntry: Identifiable, Comparable { } else { return false } + case let .ReplyCountEntry(lhsIndex, lhsIsComments, lhsCount, lhsPresentationData): + if case let .ReplyCountEntry(rhsIndex, rhsIsComments, rhsCount, rhsPresentationData) = rhs, lhsIndex == rhsIndex, lhsIsComments == rhsIsComments, lhsCount == rhsCount, lhsPresentationData === rhsPresentationData { + return true + } else { + return false + } case let .ChatInfoEntry(lhsText, lhsPresentationData): if case let .ChatInfoEntry(rhsText, rhsPresentationData) = rhs, lhsText == rhsText, lhsPresentationData === rhsPresentationData { return true diff --git a/submodules/TelegramUI/Sources/ChatHistoryGridNode.swift b/submodules/TelegramUI/Sources/ChatHistoryGridNode.swift deleted file mode 100644 index f66ed95d0d..0000000000 --- a/submodules/TelegramUI/Sources/ChatHistoryGridNode.swift +++ /dev/null @@ -1,513 +0,0 @@ -import Foundation -import UIKit -import Postbox -import SwiftSignalKit -import Display -import AsyncDisplayKit -import TelegramCore -import SyncCore -import TelegramPresentationData -import TelegramUIPreferences -import AccountContext - -private class ChatGridLiveSelectorRecognizer: UIPanGestureRecognizer { - private let selectionGestureActivationThreshold: CGFloat = 2.0 - private let selectionGestureVerticalFailureThreshold: CGFloat = 5.0 - - var validatedGesture: Bool? = nil - var firstLocation: CGPoint = CGPoint() - - var shouldBegin: (() -> Bool)? - - override init(target: Any?, action: Selector?) { - super.init(target: target, action: action) - - self.maximumNumberOfTouches = 1 - } - - override func reset() { - super.reset() - - self.validatedGesture = nil - } - - override func touchesBegan(_ touches: Set, with event: UIEvent) { - super.touchesBegan(touches, with: event) - - if let shouldBegin = self.shouldBegin, !shouldBegin() { - self.state = .failed - } else { - let touch = touches.first! - self.firstLocation = touch.location(in: self.view) - } - } - - - override func touchesMoved(_ touches: Set, with event: UIEvent) { - let location = touches.first!.location(in: self.view) - let translation = CGPoint(x: location.x - self.firstLocation.x, y: location.y - self.firstLocation.y) - - if self.validatedGesture == nil { - if (fabs(translation.y) >= selectionGestureVerticalFailureThreshold) { - self.validatedGesture = false - } - else if (fabs(translation.x) >= selectionGestureActivationThreshold) { - self.validatedGesture = true - } - } - - if let validatedGesture = self.validatedGesture { - if validatedGesture { - super.touchesMoved(touches, with: event) - } - } - } -} - -struct ChatHistoryGridViewTransition { - let historyView: ChatHistoryView - let topOffsetWithinMonth: Int - let deleteItems: [Int] - let insertItems: [GridNodeInsertItem] - let updateItems: [GridNodeUpdateItem] - let scrollToItem: GridNodeScrollToItem? - let stationaryItems: GridNodeStationaryItems -} - -private func mappedInsertEntries(context: AccountContext, peerId: PeerId, controllerInteraction: ChatControllerInteraction, entries: [ChatHistoryViewTransitionInsertEntry], theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) -> [GridNodeInsertItem] { - return entries.map { entry -> GridNodeInsertItem in - switch entry.entry { - case let .MessageEntry(message, _, _, _, _, _): - return GridNodeInsertItem(index: entry.index, item: GridMessageItem(theme: theme, strings: strings, fontSize: fontSize, context: context, message: message, controllerInteraction: controllerInteraction), previousIndex: entry.previousIndex) - case .MessageGroupEntry: - return GridNodeInsertItem(index: entry.index, item: GridHoleItem(), previousIndex: entry.previousIndex) - case .UnreadEntry: - assertionFailure() - return GridNodeInsertItem(index: entry.index, item: GridHoleItem(), previousIndex: entry.previousIndex) - case .ChatInfoEntry, .SearchEntry: - assertionFailure() - return GridNodeInsertItem(index: entry.index, item: GridHoleItem(), previousIndex: entry.previousIndex) - } - } -} - -private func mappedUpdateEntries(context: AccountContext, peerId: PeerId, controllerInteraction: ChatControllerInteraction, entries: [ChatHistoryViewTransitionUpdateEntry], theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) -> [GridNodeUpdateItem] { - return entries.map { entry -> GridNodeUpdateItem in - switch entry.entry { - case let .MessageEntry(message, _, _, _, _, _): - return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridMessageItem(theme: theme, strings: strings, fontSize: fontSize, context: context, message: message, controllerInteraction: controllerInteraction)) - case .MessageGroupEntry: - return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridHoleItem()) - case .UnreadEntry: - assertionFailure() - return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridHoleItem()) - case .ChatInfoEntry, .SearchEntry: - assertionFailure() - return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridHoleItem()) - } - } -} - -private func mappedChatHistoryViewListTransition(context: AccountContext, peerId: PeerId, controllerInteraction: ChatControllerInteraction, transition: ChatHistoryViewTransition, from: ChatHistoryView?, presentationData: ChatPresentationData) -> ChatHistoryGridViewTransition { - var mappedScrollToItem: GridNodeScrollToItem? - if let scrollToItem = transition.scrollToItem { - let mappedPosition: GridNodeScrollToItemPosition - switch scrollToItem.position { - case .top: - mappedPosition = .top(0.0) - case .center: - mappedPosition = .center(0.0) - case .bottom: - mappedPosition = .bottom(0.0) - case .visible: - mappedPosition = .bottom(0.0) - } - let scrollTransition: ContainedViewLayoutTransition - if scrollToItem.animated { - switch scrollToItem.curve { - case .Default: - scrollTransition = .animated(duration: 0.3, curve: .easeInOut) - case let .Spring(duration): - scrollTransition = .animated(duration: duration, curve: .spring) - } - } else { - scrollTransition = .immediate - } - let directionHint: GridNodePreviousItemsTransitionDirectionHint - switch scrollToItem.directionHint { - case .Up: - directionHint = .up - case .Down: - directionHint = .down - } - mappedScrollToItem = GridNodeScrollToItem(index: scrollToItem.index, position: mappedPosition, transition: scrollTransition, directionHint: directionHint, adjustForSection: true, adjustForTopInset: true) - } - - var stationaryItems: GridNodeStationaryItems = .none - if let previousView = from { - if let stationaryRange = transition.stationaryItemRange { - var fromStableIds = Set() - for i in 0 ..< previousView.filteredEntries.count { - if i >= stationaryRange.0 && i <= stationaryRange.1 { - fromStableIds.insert(previousView.filteredEntries[i].stableId) - } - } - var index = 0 - var indices = Set() - for entry in transition.historyView.filteredEntries { - if fromStableIds.contains(entry.stableId) { - indices.insert(transition.historyView.filteredEntries.count - 1 - index) - } - index += 1 - } - stationaryItems = .indices(indices) - } else { - var fromStableIds = Set() - for i in 0 ..< previousView.filteredEntries.count { - fromStableIds.insert(previousView.filteredEntries[i].stableId) - } - var index = 0 - var indices = Set() - for entry in transition.historyView.filteredEntries { - if fromStableIds.contains(entry.stableId) { - indices.insert(transition.historyView.filteredEntries.count - 1 - index) - } - index += 1 - } - stationaryItems = .indices(indices) - } - } - - var topOffsetWithinMonth: Int = 0 - if let lastEntry = transition.historyView.filteredEntries.last { - switch lastEntry { - case let .MessageEntry(_, _, _, monthLocation, _, _): - if let monthLocation = monthLocation { - topOffsetWithinMonth = Int(monthLocation.indexInMonth) - } - default: - break - } - } - - return ChatHistoryGridViewTransition(historyView: transition.historyView, topOffsetWithinMonth: topOffsetWithinMonth, deleteItems: transition.deleteItems.map { $0.index }, insertItems: mappedInsertEntries(context: context, peerId: peerId, controllerInteraction: controllerInteraction, entries: transition.insertEntries, theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize), updateItems: mappedUpdateEntries(context: context, peerId: peerId, controllerInteraction: controllerInteraction, entries: transition.updateEntries, theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize), scrollToItem: mappedScrollToItem, stationaryItems: stationaryItems) -} - -private func gridNodeLayoutForContainerLayout(size: CGSize) -> GridNodeLayoutType { - let side = floorToScreenPixels((size.width - 3.0) / 4.0) - return .fixed(itemSize: CGSize(width: side, height: side), fillWidth: true, lineSpacing: 1.0, itemSpacing: 1.0) -} - -public final class ChatHistoryGridNode: GridNode, ChatHistoryNode { - private let context: AccountContext - private let peerId: PeerId - private let messageId: MessageId? - private let tagMask: MessageTags? - - private var historyView: ChatHistoryView? - - private let historyDisposable = MetaDisposable() - - private let messageViewQueue = Queue() - - private var dequeuedInitialTransitionOnLayout = false - private var enqueuedHistoryViewTransition: (ChatHistoryGridViewTransition, () -> Void)? - var layoutActionOnViewTransition: ((ChatHistoryGridViewTransition) -> (ChatHistoryGridViewTransition, ListViewUpdateSizeAndInsets?))? - - public let historyState = ValuePromise() - private var currentHistoryState: ChatHistoryNodeHistoryState? - - public var preloadPages: Bool = true { - didSet { - if self.preloadPages != oldValue { - - } - } - } - - private let _chatHistoryLocation = ValuePromise(ignoreRepeated: true) - private var chatHistoryLocation: Signal { - return self._chatHistoryLocation.get() - } - - private let galleryHiddenMesageAndMediaDisposable = MetaDisposable() - - private var presentationData: PresentationData - private let chatPresentationDataPromise = Promise() - - public private(set) var loadState: ChatHistoryNodeLoadState? - private var loadStateUpdated: ((ChatHistoryNodeLoadState, Bool) -> Void)? - private let controllerInteraction: ChatControllerInteraction - - public init(context: AccountContext, peerId: PeerId, messageId: MessageId?, tagMask: MessageTags?, controllerInteraction: ChatControllerInteraction) { - self.context = context - self.peerId = peerId - self.messageId = messageId - self.tagMask = tagMask - self.controllerInteraction = controllerInteraction - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - - super.init() - - self.chatPresentationDataPromise.set(context.sharedContext.presentationData - |> map { presentationData in - return ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper), fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations, largeEmoji: presentationData.largeEmoji, chatBubbleCorners: presentationData.chatBubbleCorners) - }) - - self.floatingSections = true - - let messageViewQueue = self.messageViewQueue - - let historyViewUpdate = self.chatHistoryLocation - |> distinctUntilChanged - |> mapToSignal { location in - return chatHistoryViewForLocation(ChatHistoryLocationInput(content: location, id: 0), account: context.account, chatLocation: .peer(peerId), scheduled: false, fixedCombinedReadStates: nil, tagMask: tagMask, additionalData: [], orderStatistics: [.locationWithinMonth]) - } - - let previousView = Atomic(value: nil) - - let historyViewTransition = combineLatest(queue: messageViewQueue, historyViewUpdate, self.chatPresentationDataPromise.get()) - |> mapToQueue { [weak self] update, chatPresentationData -> Signal in - switch update { - case .Loading: - Queue.mainQueue().async { [weak self] in - if let strongSelf = self { - let loadState: ChatHistoryNodeLoadState = .loading - if strongSelf.loadState != loadState { - strongSelf.loadState = loadState - strongSelf.loadStateUpdated?(loadState, false) - } - - let historyState: ChatHistoryNodeHistoryState = .loading - if strongSelf.currentHistoryState != historyState { - strongSelf.currentHistoryState = historyState - strongSelf.historyState.set(historyState) - } - } - } - return .complete() - case let .HistoryView(view, type, scrollPosition, flashIndicators, _, _, id): - let reason: ChatHistoryViewTransitionReason - switch type { - case let .Initial(fadeIn): - reason = ChatHistoryViewTransitionReason.Initial(fadeIn: fadeIn) - case let .Generic(genericType): - switch genericType { - case .InitialUnread, .Initial: - reason = ChatHistoryViewTransitionReason.Initial(fadeIn: false) - case .Generic: - reason = ChatHistoryViewTransitionReason.InteractiveChanges - case .UpdateVisible: - reason = ChatHistoryViewTransitionReason.Reload - case .FillHole: - reason = ChatHistoryViewTransitionReason.Reload - } - } - - let associatedData = ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: false) - let processedView = ChatHistoryView(originalView: view, filteredEntries: chatHistoryEntriesForView(location: .peer(peerId), view: view, includeUnreadEntry: false, includeEmptyEntry: false, includeChatInfoEntry: false, includeSearchEntry: false, reverse: false, groupMessages: false, selectedMessages: nil, presentationData: chatPresentationData, historyAppearsCleared: false, associatedData: associatedData, updatingMedia: [:]), associatedData: associatedData, lastHeaderId: 0, id: id) - let previous = previousView.swap(processedView) - - let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: false, chatLocation: .peer(peerId), controllerInteraction: controllerInteraction, scrollPosition: scrollPosition, initialData: nil, keyboardButtonsMessage: nil, cachedData: nil, cachedDataMessages: nil, readStateData: nil, flashIndicators: flashIndicators, updatedMessageSelection: false) - let mappedTransition = mappedChatHistoryViewListTransition(context: context, peerId: peerId, controllerInteraction: controllerInteraction, transition: rawTransition, from: previous, presentationData: chatPresentationData) - return .single(mappedTransition) - } - } - - let appliedTransition = historyViewTransition |> deliverOnMainQueue |> mapToQueue { [weak self] transition -> Signal in - if let strongSelf = self { - return strongSelf.enqueueHistoryViewTransition(transition) - } - return .complete() - } - - self.historyDisposable.set(appliedTransition.start()) - - if let messageId = messageId { - self._chatHistoryLocation.set(ChatHistoryLocation.InitialSearch(location: .id(messageId), count: 100)) - } else { - self._chatHistoryLocation.set(ChatHistoryLocation.Initial(count: 100)) - } - - self.visibleItemsUpdated = { [weak self] visibleItems in - if let strongSelf = self, let historyView = strongSelf.historyView, let top = visibleItems.top, let bottom = visibleItems.bottom, let visibleTop = visibleItems.topVisible, let visibleBottom = visibleItems.bottomVisible { - if top.0 < 5 && historyView.originalView.laterId != nil { - let lastEntry = historyView.filteredEntries[historyView.filteredEntries.count - 1 - visibleTop.0] - strongSelf._chatHistoryLocation.set(ChatHistoryLocation.Navigation(index: .message(lastEntry.index), anchorIndex: .message(lastEntry.index), count: 100)) - } else if bottom.0 >= historyView.filteredEntries.count - 5 && historyView.originalView.earlierId != nil { - let firstEntry = historyView.filteredEntries[historyView.filteredEntries.count - 1 - visibleBottom.0] - strongSelf._chatHistoryLocation.set(ChatHistoryLocation.Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: 100)) - } - } - } - - let selectorRecogizner = ChatGridLiveSelectorRecognizer(target: self, action: #selector(self.panGesture(_:))) - selectorRecogizner.shouldBegin = { [weak controllerInteraction] in - return controllerInteraction?.selectionState != nil - } - self.view.addGestureRecognizer(selectorRecogizner) - } - - public override func didLoad() { - super.didLoad() - } - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.historyDisposable.dispose() - } - - public func setLoadStateUpdated(_ f: @escaping (ChatHistoryNodeLoadState, Bool) -> Void) { - self.loadStateUpdated = f - } - - public func scrollToStartOfHistory() { - self._chatHistoryLocation.set(ChatHistoryLocation.Scroll(index: .lowerBound, anchorIndex: .lowerBound, sourceIndex: .upperBound, scrollPosition: .bottom(0.0), animated: true)) - } - - public func scrollToEndOfHistory() { - self._chatHistoryLocation.set(ChatHistoryLocation.Scroll(index: .upperBound, anchorIndex: .upperBound, sourceIndex: .lowerBound, scrollPosition: .top(0.0), animated: true)) - } - - public func scrollToMessage(from fromIndex: MessageIndex, to toIndex: MessageIndex, scrollPosition: ListViewScrollPosition = .center(.bottom)) { - self._chatHistoryLocation.set(ChatHistoryLocation.Scroll(index: .message(toIndex), anchorIndex: .message(toIndex), sourceIndex: .message(fromIndex), scrollPosition: .center(.bottom), animated: true)) - } - - public func messageInCurrentHistoryView(_ id: MessageId) -> Message? { - if let historyView = self.historyView { - for case let .MessageEntry(message, _, _, _, _, _) in historyView.filteredEntries where message.id == id { - return message - } - } - return nil - } - - private func enqueueHistoryViewTransition(_ transition: ChatHistoryGridViewTransition) -> Signal { - return Signal { [weak self] subscriber in - if let strongSelf = self { - if let _ = strongSelf.enqueuedHistoryViewTransition { - preconditionFailure() - } - - strongSelf.enqueuedHistoryViewTransition = (transition, { - subscriber.putCompletion() - }) - - if strongSelf.isNodeLoaded { - strongSelf.dequeueHistoryViewTransition() - } else { - let loadState: ChatHistoryNodeLoadState - if transition.historyView.filteredEntries.isEmpty { - loadState = .empty - } else { - loadState = .messages - } - if strongSelf.loadState != loadState { - strongSelf.loadState = loadState - strongSelf.loadStateUpdated?(loadState, false) - } - - let historyState: ChatHistoryNodeHistoryState = .loaded(isEmpty: transition.historyView.originalView.entries.isEmpty) - if strongSelf.currentHistoryState != historyState { - strongSelf.currentHistoryState = historyState - strongSelf.historyState.set(historyState) - } - } - } else { - subscriber.putCompletion() - } - - return EmptyDisposable - } |> runOn(Queue.mainQueue()) - } - - private func dequeueHistoryViewTransition() { - if let (transition, completion) = self.enqueuedHistoryViewTransition { - self.enqueuedHistoryViewTransition = nil - - let completion: (GridNodeDisplayedItemRange) -> Void = { [weak self] visibleRange in - if let strongSelf = self { - strongSelf.historyView = transition.historyView - - let loadState: ChatHistoryNodeLoadState - if let historyView = strongSelf.historyView { - if historyView.filteredEntries.isEmpty { - loadState = .empty - } else { - loadState = .messages - } - } else { - loadState = .loading - } - - if strongSelf.loadState != loadState { - strongSelf.loadState = loadState - strongSelf.loadStateUpdated?(loadState, false) - } - - let historyState: ChatHistoryNodeHistoryState = .loaded(isEmpty: transition.historyView.originalView.entries.isEmpty) - if strongSelf.currentHistoryState != historyState { - strongSelf.currentHistoryState = historyState - strongSelf.historyState.set(historyState) - } - - completion() - } - } - - if let layoutActionOnViewTransition = self.layoutActionOnViewTransition { - self.layoutActionOnViewTransition = nil - let (mappedTransition, updateSizeAndInsets) = layoutActionOnViewTransition(transition) - - var updateLayout: GridNodeUpdateLayout? - if let updateSizeAndInsets = updateSizeAndInsets { - updateLayout = GridNodeUpdateLayout(layout: GridNodeLayout(size: updateSizeAndInsets.size, insets: updateSizeAndInsets.insets, preloadSize: 400.0, type: .fixed(itemSize: CGSize(width: 200.0, height: 200.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: .immediate) - } - - self.transaction(GridNodeTransaction(deleteItems: mappedTransition.deleteItems, insertItems: mappedTransition.insertItems, updateItems: mappedTransition.updateItems, scrollToItem: mappedTransition.scrollToItem, updateLayout: updateLayout, itemTransition: .immediate, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: mappedTransition.topOffsetWithinMonth), completion: completion) - } else { - self.transaction(GridNodeTransaction(deleteItems: transition.deleteItems, insertItems: transition.insertItems, updateItems: transition.updateItems, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.topOffsetWithinMonth, synchronousLoads: true), completion: completion) - } - } - } - - public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) { - self.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: updateSizeAndInsets.size, insets: updateSizeAndInsets.insets, preloadSize: 400.0, type: gridNodeLayoutForContainerLayout(size: updateSizeAndInsets.size)), transition: .immediate), itemTransition: .immediate, stationaryItems: .none,updateFirstIndexInSectionOffset: nil), completion: { _ in }) - - if !self.dequeuedInitialTransitionOnLayout { - self.dequeuedInitialTransitionOnLayout = true - self.dequeueHistoryViewTransition() - } - - } - - public func disconnect() { - self.historyDisposable.set(nil) - } - - private var selectionPanState: (selecting: Bool, currentMessageId: MessageId)? - - @objc private func panGesture(_ recognizer: UIGestureRecognizer) -> Void { - guard let selectionState = self.controllerInteraction.selectionState else {return} - - switch recognizer.state { - case .began: - if let itemNode = self.itemNodeAtPoint(recognizer.location(in: self.view)) as? GridMessageItemNode, let messageId = itemNode.messageId { - self.selectionPanState = (selecting: !selectionState.selectedIds.contains(messageId), currentMessageId: messageId) - self.controllerInteraction.toggleMessagesSelection([messageId], !selectionState.selectedIds.contains(messageId)) - } - case .changed: - if let selectionPanState = self.selectionPanState, let itemNode = self.itemNodeAtPoint(recognizer.location(in: self.view)) as? GridMessageItemNode, let messageId = itemNode.messageId, messageId != selectionPanState.currentMessageId { - self.controllerInteraction.toggleMessagesSelection([messageId], selectionPanState.selecting) - self.selectionPanState?.currentMessageId = messageId - } - case .ended, .failed, .cancelled: - self.selectionPanState = nil - case .possible: - break - } - } -} diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 33aca30807..1dea8b502d 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -14,6 +14,9 @@ import TemporaryCachedPeerDataManager import ChatListSearchItemNode import Emoji import AppBundle +import ListMessageItem +import AccountContext +import ChatInterfaceState private class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer { private let selectionGestureActivationThreshold: CGFloat = 5.0 @@ -74,7 +77,7 @@ public enum ChatHistoryListDisplayHeaders { public enum ChatHistoryListMode: Equatable { case bubbles - case list(search: Bool, reversed: Bool, displayHeaders: ChatHistoryListDisplayHeaders) + case list(search: Bool, reversed: Bool, displayHeaders: ChatHistoryListDisplayHeaders, hintLinks: Bool, isGlobalSearch: Bool) } enum ChatHistoryViewScrollPosition { @@ -219,6 +222,26 @@ private func maxMessageIndexForEntries(_ view: ChatHistoryView, indexRange: (Int return (incoming, overall) } +extension ListMessageItemInteraction { + convenience init(controllerInteraction: ChatControllerInteraction) { + self.init(openMessage: { message, mode -> Bool in + return controllerInteraction.openMessage(message, mode) + }, openMessageContextMenu: { message, bool, node, rect, gesture in + controllerInteraction.openMessageContextMenu(message, bool, node, rect, gesture) + }, toggleMessagesSelection: { messageId, selected in + controllerInteraction.toggleMessagesSelection(messageId, selected) + }, openUrl: { url, param1, param2, message in + controllerInteraction.openUrl(url, param1, param2, message) + }, openInstantPage: { message, data in + controllerInteraction.openInstantPage(message, data) + }, longTap: { action, message in + controllerInteraction.longTap(action, message) + }, getHiddenMedia: { + return controllerInteraction.hiddenMedia + }) + } +} + private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, entries: [ChatHistoryViewTransitionInsertEntry]) -> [ListViewInsertItem] { return entries.map { entry -> ListViewInsertItem in switch entry.entry { @@ -227,7 +250,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca switch mode { case .bubbles: item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes)) - case let .list(_, _, displayHeaders): + case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { case .none: @@ -237,8 +260,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca case .allButLast: displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != lastHeaderId } - - item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize, dateTimeFormat: presentationData.dateTimeFormat, context: context, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: message, selection: selection, displayHeader: displayHeader) + item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) } return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .MessageGroupEntry(_, messages, presentationData): @@ -246,13 +268,15 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca switch mode { case .bubbles: item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages)) - case let .list(_, _, _): + case let .list(_, _, _, _, _): assertionFailure() - item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize, dateTimeFormat: presentationData.dateTimeFormat, context: context, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: messages[0].0, selection: .none, displayHeader: false) + item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false) } return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .UnreadEntry(_, presentationData): return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatUnreadItem(index: entry.entry.index, presentationData: presentationData, context: context), directionHint: entry.directionHint) + case let .ReplyCountEntry(_, isComments, count, presentationData): + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatReplyCountItem(index: entry.entry.index, isComments: isComments, count: count, presentationData: presentationData, context: context), directionHint: entry.directionHint) case let .ChatInfoEntry(text, presentationData): return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatBotInfoItem(text: text, controllerInteraction: controllerInteraction, presentationData: presentationData), directionHint: entry.directionHint) case let .SearchEntry(theme, strings): @@ -271,7 +295,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca switch mode { case .bubbles: item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes)) - case let .list(_, _, displayHeaders): + case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { case .none: @@ -281,7 +305,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca case .allButLast: displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != lastHeaderId } - item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize, dateTimeFormat: presentationData.dateTimeFormat, context: context, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: message, selection: selection, displayHeader: displayHeader) + item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) } return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .MessageGroupEntry(_, messages, presentationData): @@ -289,13 +313,15 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca switch mode { case .bubbles: item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages)) - case let .list(_, _, _): + case let .list(_, _, _, _, _): assertionFailure() - item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize, dateTimeFormat: presentationData.dateTimeFormat, context: context, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: messages[0].0, selection: .none, displayHeader: false) + item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false) } return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .UnreadEntry(_, presentationData): return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatUnreadItem(index: entry.entry.index, presentationData: presentationData, context: context), directionHint: entry.directionHint) + case let .ReplyCountEntry(_, isComments, count, presentationData): + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatReplyCountItem(index: entry.entry.index, isComments: isComments, count: count, presentationData: presentationData, context: context), directionHint: entry.directionHint) case let .ChatInfoEntry(text, presentationData): return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatBotInfoItem(text: text, controllerInteraction: controllerInteraction, presentationData: presentationData), directionHint: entry.directionHint) case let .SearchEntry(theme, strings): @@ -321,6 +347,7 @@ private final class ChatHistoryTransactionOpaqueState { private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], isScheduledMessages: Bool) -> ChatMessageItemAssociatedData { var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel var contactsPeerIds: Set = Set() + var channelDiscussionGroup: ChatMessageItemAssociatedData.ChannelDiscussionGroupStatus = .unknown if case let .peer(peerId) = chatLocation { if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat { var isContact = false @@ -345,7 +372,15 @@ private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHist if let channel = value as? TelegramChannel, case .group = channel.info { automaticMediaDownloadPeerType = .group } - break + } else if case let .cachedPeerData(dataPeerId, cachedData) = entry, dataPeerId == peerId { + if let cachedData = cachedData as? CachedChannelData { + switch cachedData.linkedDiscussionPeerId { + case let .known(value): + channelDiscussionGroup = .known(value) + case .unknown: + channelDiscussionGroup = .unknown + } + } } } if automaticMediaDownloadPeerType == .group { @@ -357,7 +392,7 @@ private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHist } } } - let associatedData = ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, isScheduledMessages: isScheduledMessages, contactsPeerIds: contactsPeerIds, animatedEmojiStickers: animatedEmojiStickers) + let associatedData = ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, isScheduledMessages: isScheduledMessages, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers) return associatedData } @@ -397,6 +432,7 @@ private struct ChatHistoryAnimatedEmojiConfiguration { public final class ChatHistoryListNode: ListView, ChatHistoryNode { private let context: AccountContext private let chatLocation: ChatLocation + private let chatLocationContextHolder: Atomic private let subject: ChatControllerSubject? private let tagMask: MessageTags? private let controllerInteraction: ChatControllerInteraction @@ -507,9 +543,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { private var loadedMessagesFromCachedDataDisposable: Disposable? - public init(context: AccountContext, chatLocation: ChatLocation, tagMask: MessageTags?, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles) { + public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tagMask: MessageTags?, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode = .bubbles) { self.context = context self.chatLocation = chatLocation + self.chatLocationContextHolder = chatLocationContextHolder self.subject = subject self.tagMask = tagMask self.controllerInteraction = controllerInteraction @@ -558,9 +595,13 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { let fixedCombinedReadStates = Atomic(value: nil) - var scheduled = false + var isScheduledMessages = false if let subject = subject, case .scheduledMessages = subject { - scheduled = true + isScheduledMessages = true + } + var isAuxiliaryChat = isScheduledMessages + if case .replyThread = chatLocation { + isAuxiliaryChat = true } var additionalData: [AdditionalMessageHistoryViewData] = [] @@ -576,16 +617,26 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { additionalData.append(.peerIsContact(peerId)) } } - if !scheduled { + if !isAuxiliaryChat { additionalData.append(.totalUnreadState) } + if case let .replyThread(messageId, _, _) = chatLocation { + additionalData.append(.cachedPeerData(messageId.peerId)) + additionalData.append(.peerNotificationSettings(messageId.peerId)) + if messageId.peerId.namespace == Namespaces.Peer.CloudChannel { + additionalData.append(.cacheEntry(cachedChannelAdminRanksEntryId(peerId: messageId.peerId))) + additionalData.append(.peer(messageId.peerId)) + } + + additionalData.append(.message(messageId)) + } let currentViewVersion = Atomic(value: nil) let historyViewUpdate = self.chatHistoryLocationPromise.get() |> distinctUntilChanged |> mapToSignal { location in - return chatHistoryViewForLocation(location, account: context.account, chatLocation: chatLocation, scheduled: scheduled, fixedCombinedReadStates: fixedCombinedReadStates.with { $0 }, tagMask: tagMask, additionalData: additionalData) + return chatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, scheduled: isScheduledMessages, fixedCombinedReadStates: fixedCombinedReadStates.with { $0 }, tagMask: tagMask, additionalData: additionalData) |> beforeNext { viewUpdate in switch viewUpdate { case let .HistoryView(view, _, _, _, _, _, _): @@ -677,6 +728,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } else { if let subject = subject, case let .message(messageId) = subject { strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0) + } else if var chatHistoryLocation = strongSelf.chatHistoryLocationValue { + chatHistoryLocation.id += 1 + strongSelf.chatHistoryLocationValue = chatHistoryLocation } else { strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Initial(count: 60), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0) } @@ -703,7 +757,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { strongSelf._initialData.set(.single(combinedInitialData)) } - strongSelf._cachedPeerDataAndMessages.set(.single((nil, nil))) + let cachedData = initialData?.cachedData + let cachedDataMessages = initialData?.cachedDataMessages + + strongSelf._cachedPeerDataAndMessages.set(.single((cachedData, cachedDataMessages))) let loadState: ChatHistoryNodeLoadState = .loading if strongSelf.loadState != loadState { @@ -730,7 +787,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { var reverse = false var includeSearchEntry = false - if case let .list(search, reverseValue, _) = mode { + if case let .list(search, reverseValue, _, _, _) = mode { includeSearchEntry = search reverse = reverseValue } @@ -824,9 +881,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } if apply { switch chatLocation { - case .peer: - if !context.sharedContext.immediateExperimentalUISettings.skipReadHistory { - let _ = applyMaxReadIndexInteractively(postbox: context.account.postbox, stateManager: context.account.stateManager, index: messageIndex).start() + case .peer, .replyThread: + if !context.sharedContext.immediateExperimentalUISettings.skipReadHistory { + context.applyMaxReadIndex(for: chatLocation, contextHolder: chatLocationContextHolder, messageIndex: messageIndex) } } } @@ -1014,6 +1071,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if message.id.namespace == Namespaces.Message.Cloud { messageIdsWithViewCount.append(message.id) } + } else if attribute is ReplyThreadMessageAttribute { + if message.id.namespace == Namespaces.Message.Cloud { + messageIdsWithViewCount.append(message.id) + } } else if let attribute = attribute as? ConsumableContentMessageAttribute, !attribute.consumed { hasUnconsumedContent = true } else if let _ = attribute as? ContentRequiresValidationMessageAttribute { @@ -1058,6 +1119,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if message.id.namespace == Namespaces.Message.Cloud { messageIdsWithViewCount.append(message.id) } + } else if attribute is ReplyThreadMessageAttribute { + if message.id.namespace == Namespaces.Message.Cloud { + messageIdsWithViewCount.append(message.id) + } } else if let attribute = attribute as? ConsumableContentMessageAttribute, !attribute.consumed { hasUnconsumedContent = true } @@ -1177,7 +1242,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if loaded.firstIndex < 5 && historyView.originalView.laterId != nil { self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .message(lastEntry.index), anchorIndex: .message(lastEntry.index), count: historyMessageCount), id: self.takeNextHistoryLocationId()) } else if loaded.firstIndex < 5, historyView.originalView.laterId == nil, !historyView.originalView.holeLater, let chatHistoryLocationValue = self.chatHistoryLocationValue, !chatHistoryLocationValue.isAtUpperBound, historyView.originalView.anchorIndex != .upperBound { + //TODO:localize + #if !DEBUG self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .upperBound, anchorIndex: .upperBound, count: historyMessageCount), id: self.takeNextHistoryLocationId()) + #endif } else if loaded.lastIndex >= historyView.filteredEntries.count - 5 && historyView.originalView.earlierId != nil { self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: historyMessageCount), id: self.takeNextHistoryLocationId()) } @@ -1251,7 +1319,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { case .known(0.0): break default: - self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(index: .upperBound, anchorIndex: .upperBound, sourceIndex: .lowerBound, scrollPosition: .top(0.0), animated: true), id: self.takeNextHistoryLocationId()) + let locationInput = ChatHistoryLocationInput(content: .Scroll(index: .upperBound, anchorIndex: .upperBound, sourceIndex: .lowerBound, scrollPosition: .top(0.0), animated: true), id: self.takeNextHistoryLocationId()) + self.chatHistoryLocationValue = locationInput } } @@ -1443,12 +1512,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } strongSelf.loadState = loadState - strongSelf.loadStateUpdated?(loadState, animated) + strongSelf.loadStateUpdated?(loadState, animated || transition.animateIn || animateIn) } - if let range = visibleRange.loadedRange { + if let _ = visibleRange.loadedRange { if let visible = visibleRange.visibleRange { - var visibleFirstIndex = visible.firstIndex + let visibleFirstIndex = visible.firstIndex /*if !visible.firstIndexFullyVisible { visibleFirstIndex += 1 }*/ @@ -1487,12 +1556,16 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if transition.animateIn || animateIn { let heightNorm = strongSelf.bounds.height - strongSelf.insets.top strongSelf.forEachVisibleItemNode { itemNode in + let delayFactor = itemNode.frame.minY / heightNorm + let delay = Double(delayFactor * 0.1) + if let itemNode = itemNode as? ChatMessageItemView { - let delayFactor = itemNode.frame.minY / heightNorm - let delay = Double(delayFactor * 0.1) - itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay) - itemNode.layer.animateScale(from: 0.9, to: 1.0, duration: 0.4, delay: delay, timingFunction: kCAMediaTimingFunctionSpring) + itemNode.layer.animateScale(from: 0.94, to: 1.0, duration: 0.4, delay: delay, timingFunction: kCAMediaTimingFunctionSpring) + } else if let itemNode = itemNode as? ChatUnreadItemNode { + itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay) + } else if let itemNode = itemNode as? ChatReplyCountItemNode { + itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay) } } strongSelf.forEachItemHeaderNode { itemNode in @@ -1500,7 +1573,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { let delay = Double(delayFactor * 0.2) itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay) - itemNode.layer.animateScale(from: 0.9, to: 1.0, duration: 0.4, delay: delay, timingFunction: kCAMediaTimingFunctionSpring) + itemNode.layer.animateScale(from: 0.94, to: 1.0, duration: 0.4, delay: delay, timingFunction: kCAMediaTimingFunctionSpring) } } @@ -1703,7 +1776,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { switch self.mode { case .bubbles: item = ChatMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes)) - case let .list(_, _, displayHeaders): + case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { case .none: @@ -1713,7 +1786,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { case .allButLast: displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId } - item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize, dateTimeFormat: presentationData.dateTimeFormat, context: self.context, chatLocation: self.chatLocation, controllerInteraction: self.controllerInteraction, message: message, selection: selection, displayHeader: displayHeader) + item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) } let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil) self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) diff --git a/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift b/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift index a29c6a25d9..6d49b2ea67 100644 --- a/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift @@ -11,6 +11,7 @@ import MergeLists import AccountContext import SearchUI import TelegramUIPreferences +import ListMessageItem private enum ChatHistorySearchEntryStableId: Hashable { case messageId(MessageId) @@ -88,7 +89,7 @@ private enum ChatHistorySearchEntry: Comparable, Identifiable { func item(context: AccountContext, peerId: PeerId, interaction: ChatControllerInteraction) -> ListViewItem { switch self { case let .message(message, theme, strings, dateTimeFormat, fontSize): - return ListMessageItem(theme: theme, strings: strings, fontSize: fontSize, dateTimeFormat: dateTimeFormat, context: context, chatLocation: .peer(peerId), controllerInteraction: interaction, message: message, selection: .none, displayHeader: true) + return ListMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: .builtin(WallpaperSettings())), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(peerId), interaction: ListMessageItemInteraction(controllerInteraction: interaction), message: message, selection: .none, displayHeader: true) } } } @@ -185,7 +186,7 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode { if let strongSelf = self { let signal: Signal<([ChatHistorySearchEntry], [MessageId: Message])?, NoError> if let query = query, !query.isEmpty { - let foundRemoteMessages: Signal<[Message], NoError> = searchMessages(account: context.account, location: .peer(peerId: peerId, fromId: nil, tags: tagMask), query: query, state: nil) + let foundRemoteMessages: Signal<[Message], NoError> = searchMessages(account: context.account, location: .peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: nil, maxDate: nil), query: query, state: nil) |> map { $0.0.messages } |> delay(0.2, queue: Queue.concurrentDefaultQueue()) diff --git a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift index 75ed081bcb..79d5ffc514 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift @@ -6,9 +6,10 @@ import SyncCore import SwiftSignalKit import Display import AccountContext +import ChatInterfaceState -func preloadedChatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal { - return chatHistoryViewForLocation(location, account: account, chatLocation: chatLocation, scheduled: false, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, additionalData: additionalData, orderStatistics: orderStatistics) +func preloadedChatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal { + return chatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, scheduled: false, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, additionalData: additionalData, orderStatistics: orderStatistics) |> castError(Bool.self) |> mapToSignal { update -> Signal in switch update { @@ -26,7 +27,8 @@ func preloadedChatHistoryViewForLocation(_ location: ChatHistoryLocationInput, a |> restartIfError } -func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, scheduled: Bool, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal { +func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, scheduled: Bool, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags?, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal { + let account = context.account if scheduled { var first = true var chatScrollPosition: ChatHistoryViewScrollPosition? @@ -34,7 +36,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up chatScrollPosition = .index(index: index, position: position, directionHint: directionHint, animated: animated) } - return account.viewTracker.scheduledMessagesViewForLocation(chatLocation, additionalData: additionalData) + return account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation) @@ -65,9 +67,9 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A var fadeIn = false let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> if let tagMask = tagMask { - signal = account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: .upperBound, anchorIndex: .upperBound, count: count, fixedCombinedReadStates: nil, tagMask: tagMask, orderStatistics: orderStatistics) + signal = account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), index: .upperBound, anchorIndex: .upperBound, count: count, fixedCombinedReadStates: nil, tagMask: tagMask, orderStatistics: orderStatistics) } else { - signal = account.viewTracker.aroundMessageOfInterestHistoryViewForLocation(chatLocation, count: count, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + signal = account.viewTracker.aroundMessageOfInterestHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), count: count, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) } return signal |> map { view, updateType, initialData -> ChatHistoryViewUpdate in @@ -140,9 +142,9 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> switch searchLocation { case let .index(index): - signal = account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: .message(index), anchorIndex: .message(index), count: count, fixedCombinedReadStates: nil, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + signal = account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), index: .message(index), anchorIndex: .message(index), count: count, fixedCombinedReadStates: nil, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) case let .id(id): - signal = account.viewTracker.aroundIdMessageHistoryViewForLocation(chatLocation, count: count, messageId: id, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + signal = account.viewTracker.aroundIdMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), count: count, messageId: id, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) } return signal |> map { view, updateType, initialData -> ChatHistoryViewUpdate in @@ -190,7 +192,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A } case let .Navigation(index, anchorIndex, count): var first = true - return account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: index, anchorIndex: anchorIndex, count: count, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in + return account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), index: index, anchorIndex: anchorIndex, count: count, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation) let genericType: ViewUpdateType @@ -206,10 +208,16 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up let chatScrollPosition = ChatHistoryViewScrollPosition.index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated) var first = true - return account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: index, anchorIndex: anchorIndex, count: 128, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) + return account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), index: index, anchorIndex: anchorIndex, count: 128, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation) + let combinedInitialData = ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData) + + if view.isLoading { + return ChatHistoryViewUpdate.Loading(initialData: combinedInitialData, type: .Generic(type: updateType)) + } + let genericType: ViewUpdateType let scrollPosition: ChatHistoryViewScrollPosition? = first ? chatScrollPosition : nil if first { @@ -218,7 +226,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A } else { genericType = updateType } - return .HistoryView(view: view, type: .Generic(type: genericType), scrollPosition: scrollPosition, flashIndicators: animated, originalScrollPosition: chatScrollPosition, initialData: ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData), id: location.id) + return .HistoryView(view: view, type: .Generic(type: genericType), scrollPosition: scrollPosition, flashIndicators: animated, originalScrollPosition: chatScrollPosition, initialData: combinedInitialData, id: location.id) } } } @@ -228,9 +236,9 @@ private func extractAdditionalData(view: MessageHistoryView, chatLocation: ChatL cachedData: CachedPeerData?, cachedDataMessages: [MessageId: Message]?, readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]? - ) { +) { var cachedData: CachedPeerData? - var cachedDataMessages: [MessageId: Message]? + var cachedDataMessages: [MessageId: Message] = [:] var readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData] = [:] var notificationSettings: PeerNotificationSettings? @@ -248,12 +256,20 @@ private func extractAdditionalData(view: MessageHistoryView, chatLocation: ChatL case let .peerNotificationSettings(value): notificationSettings = value case let .cachedPeerData(peerIdValue, value): - if case .peer(peerIdValue) = chatLocation { + if chatLocation.peerId == peerIdValue { cachedData = value } case let .cachedPeerDataMessages(peerIdValue, value): if case .peer(peerIdValue) = chatLocation { - cachedDataMessages = value + if let value = value { + for (_, message) in value { + cachedDataMessages[message.id] = message + } + } + } + case let .message(_, message): + if let message = message { + cachedDataMessages[message.id] = message } case let .totalUnreadState(totalUnreadState): switch chatLocation { @@ -263,8 +279,8 @@ private func extractAdditionalData(view: MessageHistoryView, chatLocation: ChatL readStateData[peerId] = ChatHistoryCombinedInitialReadStateData(unreadCount: readState.count, totalState: totalUnreadState, notificationSettings: notificationSettings) } } - /*case .group: - break*/ + case .replyThread: + break } default: break diff --git a/submodules/TelegramUI/Sources/ChatInfo.swift b/submodules/TelegramUI/Sources/ChatInfo.swift index 0ddd82763a..126f2d43a1 100644 --- a/submodules/TelegramUI/Sources/ChatInfo.swift +++ b/submodules/TelegramUI/Sources/ChatInfo.swift @@ -5,6 +5,3 @@ import SyncCore import Display import AccountContext -func peerSharedMediaControllerImpl(context: AccountContext, peerId: PeerId) -> ViewController? { - return PeerMediaCollectionController(context: context, peerId: peerId) -} diff --git a/submodules/TelegramUI/Sources/ChatInfoTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatInfoTitlePanelNode.swift index 9f8775fe8b..6d54ce28f0 100644 --- a/submodules/TelegramUI/Sources/ChatInfoTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatInfoTitlePanelNode.swift @@ -163,6 +163,8 @@ final class ChatInfoTitlePanelNode: ChatTitleAccessoryPanelNode { } else { updatedButtons = [] } + case .replyThread: + updatedButtons = [] } var buttonsUpdated = false diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift index e219fad30a..1169d36b0e 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift @@ -6,6 +6,7 @@ import Postbox import Display import AccountContext import Emoji +import ChatInterfaceState struct PossibleContextQueryTypes: OptionSet { var rawValue: Int32 diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift index 0440046386..f59f78096a 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift @@ -21,7 +21,7 @@ func inputNodeForChatPresentationIntefaceState(_ chatPresentationInterfaceState: if case let .peer(id) = chatPresentationInterfaceState.chatLocation { peerId = id } - let inputNode = ChatMediaInputNode(context: context, peerId: peerId, controllerInteraction: controllerInteraction, chatWallpaper: chatPresentationInterfaceState.chatWallpaper, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, gifPaneIsActiveUpdated: { [weak interfaceInteraction] value in + let inputNode = ChatMediaInputNode(context: context, peerId: peerId, chatLocation: chatPresentationInterfaceState.chatLocation, controllerInteraction: controllerInteraction, chatWallpaper: chatPresentationInterfaceState.chatWallpaper, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, gifPaneIsActiveUpdated: { [weak interfaceInteraction] value in if let interfaceInteraction = interfaceInteraction { interfaceInteraction.updateInputModeAndDismissedButtonKeyboardMessageId { state in if case let .media(_, expanded) = state.inputMode { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 3ca84785e2..559b5454a1 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -155,8 +155,8 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS } else { canReply = true } - /*case .group: - break*/ + case .replyThread: + canReply = true } return canReply } @@ -297,12 +297,12 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: let message = messages[0] - if Namespaces.Message.allScheduled.contains(message.id.namespace) { + if Namespaces.Message.allScheduled.contains(message.id.namespace) || message.id.peerId.isReplies { canReply = false canPin = false } else if messages[0].flags.intersection([.Failed, .Unsent]).isEmpty { switch chatPresentationInterfaceState.chatLocation { - case .peer: + case .peer, .replyThread: if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel { if !isAction { canPin = channel.hasPermission(.pinMessages) @@ -371,27 +371,32 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: return transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration) as? LimitsConfiguration ?? LimitsConfiguration.defaultValue } - let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia]), NoError> = combineLatest( + let cachedData = context.account.postbox.transaction { transaction -> CachedPeerData? in + return transaction.getPeerCachedData(peerId: messages[0].id.peerId) + } + + let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], CachedPeerData?), NoError> = combineLatest( loadLimits, loadStickerSaveStatusSignal, loadResourceStatusSignal, context.sharedContext.chatAvailableMessageActions(postbox: context.account.postbox, accountPeerId: context.account.peerId, messageIds: Set(messages.map { $0.id })), context.account.pendingUpdateMessageManager.updatingMessageMedia - |> take(1) + |> take(1), + cachedData ) - |> map { limitsConfiguration, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia]) in + |> map { limitsConfiguration, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, cachedData -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], CachedPeerData?) in var canEdit = false if !isAction { let message = messages[0] canEdit = canEditMessage(context: context, limitsConfiguration: limitsConfiguration, message: message) } - return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia) + return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia, cachedData) } return dataSignal |> deliverOnMainQueue - |> map { data, updatingMessageMedia -> [ContextMenuItem] in + |> map { data, updatingMessageMedia, cachedData -> [ContextMenuItem] in var actions: [ContextMenuItem] = [] if let starStatus = data.starStatus { @@ -459,7 +464,12 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } - if data.canReply { + var isReplyThreadHead = false + if case let .replyThread(messageId, _, _) = chatPresentationInterfaceState.chatLocation { + isReplyThreadHead = messages[0].id == messageId + } + + if !isReplyThreadHead, data.canReply { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuReply, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reply"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in @@ -574,6 +584,72 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } + var threadId: Int64? + var threadMessageCount: Int = 0 + if case .peer = chatPresentationInterfaceState.chatLocation, let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, case .group = channel.info, let cachedData = cachedData as? CachedChannelData, case let .known(maybeValue) = cachedData.linkedDiscussionPeerId, let _ = maybeValue { + if let value = messages[0].threadId { + threadId = value + } else { + for attribute in messages[0].attributes { + if let attribute = attribute as? ReplyThreadMessageAttribute, attribute.count > 0 { + threadId = makeMessageThreadId(messages[0].id) + threadMessageCount = Int(attribute.count) + } + } + } + } + + if let threadId = threadId { + let replyThreadId = makeThreadIdMessageId(peerId: messages[0].id.peerId, threadId: threadId) + //TODO:localize + let text: String + if threadMessageCount != 0 { + if threadMessageCount == 1 { + text = "View 1 reply" + } else { + text = "View \(threadMessageCount) replies" + } + } else { + text = "View Thread" + } + actions.append(.action(ContextMenuActionItem(text: text, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replies"), color: theme.actionSheet.primaryTextColor) + }, action: { c, _ in + let foundIndex = Promise() + if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel, case .broadcast = channel.info { + foundIndex.set(fetchChannelReplyThreadMessage(account: context.account, messageId: messages[0].id)) + } + c.dismiss(completion: { + if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel { + if case .group = channel.info { + interfaceInteraction.viewReplies(messages[0].id, ChatReplyThreadMessage(messageId: replyThreadId, maxReadMessageId: nil)) + } else { + var cancelImpl: (() -> Void)? + let statusController = OverlayStatusController(theme: chatPresentationInterfaceState.theme, type: .loading(cancelled: { + cancelImpl?() + })) + controllerInteraction.presentController(statusController, nil) + + let disposable = (foundIndex.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak statusController] result in + statusController?.dismiss() + + if let result = result { + interfaceInteraction.viewReplies(nil, result) + } + }) + + cancelImpl = { [weak statusController] in + disposable.dispose() + statusController?.dismiss() + } + } + } + }) + }))) + } + if data.canEdit { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_MessageDialogEdit, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor) @@ -610,7 +686,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } - if data.canPin { + if data.canPin, case .peer = chatPresentationInterfaceState.chatLocation { if chatPresentationInterfaceState.pinnedMessage?.id != messages[0].id { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_Pin, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pin"), color: theme.actionSheet.primaryTextColor) @@ -670,11 +746,15 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } - if let message = messages.first, message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, !(message.media.first is TelegramMediaAction) { + if let message = messages.first, message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, !(message.media.first is TelegramMediaAction), !isReplyThreadHead { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopyLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in - let _ = (exportMessageLink(account: context.account, peerId: message.id.peerId, messageId: message.id) + var threadMessageId: MessageId? + if case let .replyThread(replyThread, _, _) = chatPresentationInterfaceState.chatLocation { + threadMessageId = replyThread + } + let _ = (exportMessageLink(account: context.account, peerId: message.id.peerId, messageId: message.id, threadMessageId: threadMessageId) |> map { result -> String? in return result } @@ -683,7 +763,15 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: UIPasteboard.general.string = link let presentationData = context.sharedContext.currentPresentationData.with { $0 } - if channel.addressName == nil { + + var warnAboutPrivate = false + if case let .peer = chatPresentationInterfaceState.chatLocation { + if channel.addressName == nil { + warnAboutPrivate = true + } + } + + if warnAboutPrivate { controllerInteraction.presentGlobalOverlayController(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Conversation_PrivateMessageLinkCopied, true)), nil) } else { controllerInteraction.presentGlobalOverlayController(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.GroupInfo_InviteLink_CopyAlert_Success, false)), nil) @@ -723,7 +811,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } } - if !data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty && isAction { + if !isReplyThreadHead, !data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty && isAction { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor) }, action: { controller, f in @@ -749,94 +837,6 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: }))) } - if canDiscuss { - actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuDiscuss, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor) - }, action: { c, _ in - let timestamp = messages[0].timestamp - let channelMessageId = messages[0].id - - enum DiscussMessageResult { - case message(MessageId) - case peer(PeerId) - } - - let signal = context.account.postbox.transaction { transaction -> PeerId? in - if let cachedData = transaction.getPeerCachedData(peerId: messages[0].id.peerId) as? CachedChannelData { - return cachedData.linkedDiscussionPeerId - } else { - return nil - } - } - |> mapToSignal { peerId -> Signal in - guard let peerId = peerId else { - return .single(nil) - } - let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp)), count: 30), id: 0), account: context.account, chatLocation: .peer(peerId), fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) - return historyView - |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in - switch historyView { - case .Loading: - return .single((nil, true)) - case let .HistoryView(view, _, _, _, _, _, _): - for entry in view.entries { - for attribute in entry.message.attributes { - if let attribute = attribute as? SourceReferenceMessageAttribute { - if attribute.messageId == channelMessageId { - return .single((entry.message.index, false)) - } - } - } - } - return .single((nil, false)) - } - } - |> take(until: { index in - return SignalTakeAction(passthrough: true, complete: !index.1) - }) - |> last - |> map { result -> DiscussMessageResult? in - if let index = result?.0 { - return .message(index.id) - } else { - return .peer(peerId) - } - } - } - - let foundIndex = Promise() - foundIndex.set(signal) - - c.dismiss(completion: { [weak interfaceInteraction] in - var cancelImpl: (() -> Void)? - let statusController = OverlayStatusController(theme: chatPresentationInterfaceState.theme, type: .loading(cancelled: { - cancelImpl?() - })) - controllerInteraction.presentController(statusController, nil) - - let disposable = (foundIndex.get() - |> take(1) - |> deliverOnMainQueue).start(next: { [weak statusController] result in - statusController?.dismiss() - - if let result = result { - switch result { - case let .message(id): - interfaceInteraction?.navigateToMessage(id) - case let .peer(peerId): - interfaceInteraction?.navigateToChat(peerId) - } - } - }) - - cancelImpl = { [weak statusController] in - disposable.dispose() - statusController?.dismiss() - } - }) - }))) - } - if data.messageActions.options.contains(.report) { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuReport, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.actionSheet.primaryTextColor) @@ -846,10 +846,10 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } var clearCacheAsDelete = false - if let peer = message.peers[message.id.peerId] as? TelegramChannel { + if let _ = message.peers[message.id.peerId] as? TelegramChannel { clearCacheAsDelete = true } - if (!data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty || clearCacheAsDelete) && !isAction { + if !isReplyThreadHead, (!data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty || clearCacheAsDelete) && !isAction { let title: String var isSending = false var isEditing = false @@ -1063,7 +1063,7 @@ func chatAvailableMessageActionsImpl(postbox: Postbox, accountPeerId: PeerId, me } } } else if let user = peer as? TelegramUser { - if !isScheduled && message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.containsSecretMedia && !isAction { + if !isScheduled && message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.containsSecretMedia && !isAction && !message.id.peerId.isReplies { if !(message.flags.isSending || message.flags.contains(.Failed)) { optionsMap[id]!.insert(.forward) } @@ -1075,6 +1075,9 @@ func chatAvailableMessageActionsImpl(postbox: Postbox, accountPeerId: PeerId, me } else if limitsConfiguration.canRemoveIncomingMessagesInPrivateChats { canDeleteGlobally = true } + if user.botInfo != nil { + canDeleteGlobally = false + } let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) if isDice && Int64(message.timestamp) + 60 * 60 * 24 > Int64(timestamp) { @@ -1086,7 +1089,7 @@ func chatAvailableMessageActionsImpl(postbox: Postbox, accountPeerId: PeerId, me if canDeleteGlobally { optionsMap[id]!.insert(.deleteGlobally) } - if user.botInfo != nil { + if user.botInfo != nil && !user.id.isReplies { optionsMap[id]!.insert(.report) } } else if let _ = peer as? TelegramSecretChat { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift index a3773f5f6a..7eaca60e1b 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift @@ -71,6 +71,17 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState var displayInputTextPanel = false if let peer = chatPresentationInterfaceState.renderedPeer?.peer { + if peer.id.isReplies { + if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) { + return (currentPanel, nil) + } else { + let panel = ChatChannelSubscriberInputPanelNode() + panel.interfaceInteraction = interfaceInteraction + panel.context = context + return (panel, nil) + } + } + if let secretChat = peer as? TelegramSecretChat { switch secretChat.embeddedState { case .handshake: @@ -109,7 +120,9 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState case .member: isMember = true case .left: - break + if case .replyThread = chatPresentationInterfaceState.chatLocation { + isMember = true + } } if isMember && channel.hasBannedPermission(.banSendMessages) != nil { @@ -140,13 +153,15 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState case .group: switch channel.participationStatus { case .kicked, .left: - if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) { - return (currentPanel, nil) - } else { - let panel = ChatChannelSubscriberInputPanelNode() - panel.interfaceInteraction = interfaceInteraction - panel.context = context - return (panel, nil) + if !isMember { + if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) { + return (currentPanel, nil) + } else { + let panel = ChatChannelSubscriberInputPanelNode() + panel.interfaceInteraction = interfaceInteraction + panel.context = context + return (panel, nil) + } } case .member: break diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift index 597862487c..0dc0a99395 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift @@ -14,6 +14,7 @@ enum ChatNavigationButtonAction: Equatable { case search case dismiss case toggleInfoPanel + case spacer } struct ChatNavigationButton: Equatable { @@ -27,6 +28,9 @@ struct ChatNavigationButton: Equatable { func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: ChatPresentationInterfaceState, subject: ChatControllerSubject?, strings: PresentationStrings, currentButton: ChatNavigationButton?, target: Any?, selector: Selector?) -> ChatNavigationButton? { if let _ = presentationInterfaceState.interfaceState.selectionState { + if case .replyThread = presentationInterfaceState.chatLocation { + return nil + } if let currentButton = currentButton, currentButton.action == .clearHistory { return currentButton } else if let peer = presentationInterfaceState.renderedPeer?.peer { @@ -72,6 +76,50 @@ func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Ch } } + var hasMessages = false + if let chatHistoryState = presentationInterfaceState.chatHistoryState { + if case .loaded(false) = chatHistoryState { + hasMessages = true + } + } + + if case .replyThread = presentationInterfaceState.chatLocation { + if hasMessages { + if case .search = currentButton?.action { + return currentButton + } else { + let buttonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(presentationInterfaceState.theme), style: .plain, target: target, action: selector) + buttonItem.accessibilityLabel = strings.Conversation_Search + return ChatNavigationButton(action: .search, buttonItem: buttonItem) + } + } else { + if case .spacer = currentButton?.action { + return currentButton + } else { + return ChatNavigationButton(action: .spacer, buttonItem: UIBarButtonItem(title: "", style: .plain, target: target, action: selector)) + } + } + } + if case let .peer(peerId) = presentationInterfaceState.chatLocation { + if peerId.isReplies { + if hasMessages { + if case .search = currentButton?.action { + return currentButton + } else { + let buttonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(presentationInterfaceState.theme), style: .plain, target: target, action: selector) + buttonItem.accessibilityLabel = strings.Conversation_Search + return ChatNavigationButton(action: .search, buttonItem: buttonItem) + } + } else { + if case .spacer = currentButton?.action { + return currentButton + } else { + return ChatNavigationButton(action: .spacer, buttonItem: UIBarButtonItem(title: "", style: .plain, target: target, action: selector)) + } + } + } + } + if presentationInterfaceState.isScheduledMessages { return chatInfoNavigationButton } diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index 5302a782b6..076c0acc6f 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -17,6 +17,7 @@ import ContextUI import GalleryUI import OverlayStatusController import PresentationDataUtils +import ChatInterfaceState struct PeerSpecificPackData { let peer: Peer @@ -474,7 +475,7 @@ final class ChatMediaInputNode: ChatInputNode { return self._ready.get() } - init(context: AccountContext, peerId: PeerId?, controllerInteraction: ChatControllerInteraction, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, gifPaneIsActiveUpdated: @escaping (Bool) -> Void) { + init(context: AccountContext, peerId: PeerId?, chatLocation: ChatLocation?, controllerInteraction: ChatControllerInteraction, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, gifPaneIsActiveUpdated: @escaping (Bool) -> Void) { self.context = context self.peerId = peerId self.controllerInteraction = controllerInteraction @@ -752,7 +753,7 @@ final class ChatMediaInputNode: ChatInputNode { let inputNodeInteraction = self.inputNodeInteraction! let peerSpecificPack: Signal<(PeerSpecificPackData?, CanInstallPeerSpecificPack), NoError> - if let peerId = peerId { + if let peerId = peerId, case .peer = chatLocation { self.dismissedPeerSpecificStickerPack.set(context.account.postbox.transaction { transaction -> Bool in guard let state = transaction.getPeerChatInterfaceState(peerId) as? ChatInterfaceState else { return false @@ -1009,7 +1010,7 @@ final class ChatMediaInputNode: ChatInputNode { return } - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: PeerId(namespace: 0, id: 0), namespace: Namespaces.Message.Local, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file.file.media], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: PeerId(namespace: 0, id: 0), namespace: Namespaces.Message.Local, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file.file.media], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: []) let gallery = GalleryController(context: strongSelf.context, source: .standaloneMessage(message), streamSingleVideo: true, replaceRootController: { _, _ in }, baseNavigationController: nil) diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index b27c890dba..91e88121cb 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -32,6 +32,101 @@ extension AnimatedStickerNode: GenericAnimatedStickerNode { } +class ChatMessageShareButton: HighlightableButtonNode { + private let backgroundNode: ASImageNode + private let iconNode: ASImageNode + + private var theme: PresentationTheme? + private var isReplies: Bool = false + + private var textNode: ImmediateTextNode? + + init() { + self.backgroundNode = ASImageNode() + self.iconNode = ASImageNode() + + super.init(pointerStyle: nil) + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.iconNode) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(presentationData: ChatPresentationData, message: Message, account: Account) -> CGSize { + var isReplies = false + var replyCount = 0 + if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { + for attribute in message.attributes { + if let attribute = attribute as? ReplyThreadMessageAttribute { + replyCount = Int(attribute.count) + isReplies = true + break + } + } + } + + if self.theme !== presentationData.theme.theme || self.isReplies != isReplies { + self.theme = presentationData.theme.theme + self.isReplies = isReplies + + let graphics = PresentationResourcesChat.additionalGraphics(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper, bubbleCorners: presentationData.chatBubbleCorners) + var updatedShareButtonBackground: UIImage? + var updatedIconImage: UIImage? + if isReplies { + updatedShareButtonBackground = PresentationResourcesChat.chatFreeCommentButtonBackground(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) + updatedIconImage = PresentationResourcesChat.chatFreeCommentButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) + } else if message.id.peerId.isRepliesOrSavedMessages(accountPeerId: account.peerId) { + updatedShareButtonBackground = graphics.chatBubbleNavigateButtonImage + } else { + updatedShareButtonBackground = graphics.chatBubbleShareButtonImage + } + self.backgroundNode.image = updatedShareButtonBackground + self.iconNode.image = updatedIconImage + } + var size = CGSize(width: 30.0, height: 30.0) + var offsetIcon = false + if isReplies, replyCount > 0 { + offsetIcon = true + + let textNode: ImmediateTextNode + if let current = self.textNode { + textNode = current + } else { + textNode = ImmediateTextNode() + self.textNode = textNode + self.addSubnode(textNode) + } + + let textColor = bubbleVariableColor(variableColor: presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: presentationData.theme.wallpaper) + + let countString: String + if replyCount >= 1000 * 1000 { + countString = "\(replyCount / 1000_000)M" + } else if replyCount >= 1000 { + countString = "\(replyCount / 1000)K" + } else { + countString = "\(replyCount)" + } + + textNode.attributedText = NSAttributedString(string: countString, font: Font.regular(11.0), textColor: textColor) + let textSize = textNode.updateLayout(CGSize(width: 100.0, height: 100.0)) + size.height += textSize.height - 1.0 + textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: size.height - textSize.height - 4.0), size: textSize) + } else if let textNode = self.textNode { + self.textNode = nil + textNode.removeFromSupernode() + } + self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size) + if let image = self.iconNode.image { + self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.width - image.size.width) / 2.0) - (offsetIcon ? 1.0 : 0.0)), size: image.size) + } + return size + } +} + class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { private let contextSourceNode: ContextExtractedContentContainingNode private let containerNode: ContextControllerSourceNode @@ -49,7 +144,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { private var selectionNode: ChatMessageSelectionNode? private var deliveryFailedNode: ChatMessageDeliveryFailedNode? - private var shareButtonNode: HighlightableButtonNode? + private var shareButtonNode: ChatMessageShareButton? var telegramFile: TelegramMediaFile? var emojiFile: TelegramMediaFile? @@ -466,8 +561,21 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } else if incoming { hasAvatar = true } - /*case .group: - hasAvatar = true*/ + case let .replyThread(messageId, _, _): + if messageId.peerId != item.context.account.peerId { + if messageId.peerId.isGroupOrChannel && item.message.author != nil { + var isBroadcastChannel = false + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { + isBroadcastChannel = true + } + + if !isBroadcastChannel { + hasAvatar = true + } + } + } else if incoming { + hasAvatar = true + } } if hasAvatar { @@ -481,7 +589,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var needShareButton = false if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) { needShareButton = false - } else if item.message.id.peerId == item.context.account.peerId { + } else if item.message.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { for attribute in item.content.firstMessage.attributes { if let _ = attribute as? SourceReferenceMessageAttribute { needShareButton = true @@ -558,11 +666,16 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var edited = false var viewCount: Int? = nil + var dateReplies = 0 for attribute in item.message.attributes { if let _ = attribute as? EditedMessageAttribute, isEmoji { edited = true } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -581,7 +694,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .minimal, reactionCount: dateReactionCount) - let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions) + let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) var viaBotApply: (TextNodeLayout, () -> TextNode)? var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)? @@ -629,13 +742,16 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude)) + if case let .replyThread(replyThreadMessageId, _, _) = item.chatLocation, replyThreadMessageId == replyAttribute.messageId { + } else { + replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude)) + } } else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty { replyMarkup = attribute } } - if item.message.id.peerId != item.context.account.peerId { + if item.message.id.peerId != item.context.account.peerId && !item.message.id.peerId.isReplies { for attribute in item.message.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { if let sourcePeer = item.message.peers[attribute.messageId.peerId] { @@ -659,30 +775,12 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { replyBackgroundImage = graphics.chatFreeformContentAdditionalInfoBackgroundImage } - var updatedShareButtonBackground: UIImage? - - var updatedShareButtonNode: HighlightableButtonNode? + var updatedShareButtonNode: ChatMessageShareButton? if needShareButton { - if currentShareButtonNode != nil { + if let currentShareButtonNode = currentShareButtonNode { updatedShareButtonNode = currentShareButtonNode - if item.presentationData.theme !== currentItem?.presentationData.theme { - let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) - if item.message.id.peerId == item.context.account.peerId { - updatedShareButtonBackground = graphics.chatBubbleNavigateButtonImage - } else { - updatedShareButtonBackground = graphics.chatBubbleShareButtonImage - } - } } else { - let buttonNode = HighlightableButtonNode() - let buttonIcon: UIImage? - let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) - if item.message.id.peerId == item.context.account.peerId { - buttonIcon = graphics.chatBubbleNavigateButtonImage - } else { - buttonIcon = graphics.chatBubbleShareButtonImage - } - buttonNode.setBackgroundImage(buttonIcon, for: [.normal]) + let buttonNode = ChatMessageShareButton() updatedShareButtonNode = buttonNode } } @@ -823,18 +921,13 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { strongSelf.addSubnode(updatedShareButtonNode) updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside) } - if let updatedShareButtonBackground = updatedShareButtonBackground { - strongSelf.shareButtonNode?.setBackgroundImage(updatedShareButtonBackground, for: [.normal]) - } + let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, message: item.message, account: item.context.account) + updatedShareButtonNode.frame = CGRect(origin: CGPoint(x: updatedImageFrame.maxX + 8.0, y: updatedImageFrame.maxY - buttonSize.height - 4.0), size: buttonSize) } else if let shareButtonNode = strongSelf.shareButtonNode { shareButtonNode.removeFromSupernode() strongSelf.shareButtonNode = nil } - if let shareButtonNode = strongSelf.shareButtonNode { - shareButtonNode.frame = CGRect(origin: CGPoint(x: updatedImageFrame.maxX + 8.0, y: updatedImageFrame.maxY - 30.0), size: CGSize(width: 29.0, height: 29.0)) - } - dateAndStatusApply(false) strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: max(displayLeftInset, updatedImageFrame.maxX - dateAndStatusSize.width - 4.0), y: updatedImageFrame.maxY - dateAndStatusSize.height - 4.0), size: dateAndStatusSize) @@ -1037,7 +1130,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty { item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame) } else { - if let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { + if !item.message.id.peerId.isReplies, let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { if case .member = channel.participationStatus { } else { item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame) @@ -1153,7 +1246,16 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { @objc func shareButtonPressed() { if let item = self.item { - if item.content.firstMessage.id.peerId == item.context.account.peerId { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { + for attribute in item.message.attributes { + if let _ = attribute as? ReplyThreadMessageAttribute { + item.controllerInteraction.openMessageReplies(item.message.id) + return + } + } + } + + if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { for attribute in item.content.firstMessage.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, attribute.messageId) diff --git a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift index 2b7e30a494..f1e12efd59 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift @@ -13,6 +13,9 @@ import TextFormat import AccountContext import UrlEscaping import PhotoResources +import WebsiteType +import ChatMessageInteractiveMediaBadge +import GalleryData private let buttonFont = Font.semibold(13.0) @@ -314,11 +317,16 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { edited = true } var viewCount: Int? + var dateReplies = 0 for attribute in message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -523,7 +531,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { var additionalImageBadgeContent: ChatMessageInteractiveMediaBadgeContent? switch position { - case .linear(_, .None): + case .linear(_, .None), .linear(_, .Neighbour(true, _)): let imageMode = !((refineContentImageLayout == nil && refineContentFileLayout == nil && contentInstantVideoSizeAndApply == nil) || preferMediaBeforeText) statusInText = !imageMode @@ -564,7 +572,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } } - statusSizeAndApply = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions) + statusSizeAndApply = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies) } default: break diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift index 03517922e4..05783efe5d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift @@ -6,6 +6,7 @@ import Postbox import TelegramCore import SyncCore import TelegramUIPreferences +import TelegramPresentationData import AccountContext enum ChatMessageBubbleContentBackgroundHiding { @@ -40,9 +41,14 @@ enum ChatMessageBubbleMergeStatus { } enum ChatMessageBubbleRelativePosition { + enum NeighbourType { + case media + case freeform + } + case None(ChatMessageBubbleMergeStatus) case BubbleNeighbour - case Neighbour + case Neighbour(Bool, NeighbourType) } enum ChatMessageBubbleContentMosaicNeighbor { diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 0a03a0f7de..6827a33cd1 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -29,54 +29,61 @@ enum InternalBubbleTapAction { case openContextMenu(tapMessage: Message, selectAll: Bool, subFrame: CGRect) } -private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(Message, AnyClass, ChatMessageEntryAttributes)] { - var result: [(Message, AnyClass, ChatMessageEntryAttributes)] = [] +private struct BubbleItemAttributes { + var isAttachment: Bool + var neighborType: ChatMessageBubbleRelativePosition.NeighbourType +} + +private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)] { + var result: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)] = [] var skipText = false var messageWithCaptionToAdd: (Message, ChatMessageEntryAttributes)? var isUnsupportedMedia = false + var isAction = false outer: for (message, itemAttributes) in item.content { for attribute in message.attributes { if let attribute = attribute as? RestrictedContentMessageAttribute, attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) != nil { - result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) break outer } } inner: for media in message.media { if let _ = media as? TelegramMediaImage { - result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media))) } else if let file = media as? TelegramMediaFile { - var isVideo = file.isVideo || (file.isAnimated && file.dimensions != nil) + let isVideo = file.isVideo || (file.isAnimated && file.dimensions != nil) if isVideo { - result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media))) } else { - result.append((message, ChatMessageFileBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageFileBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) } } else if let action = media as? TelegramMediaAction { + isAction = true if case .phoneCall = action.action { - result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) } else { - result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) } } else if let _ = media as? TelegramMediaMap { - result.append((message, ChatMessageMapBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageMapBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media))) } else if let _ = media as? TelegramMediaGame { skipText = true - result.append((message, ChatMessageGameBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageGameBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) break inner } else if let _ = media as? TelegramMediaInvoice { skipText = true - result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) break inner } else if let _ = media as? TelegramMediaContact { - result.append((message, ChatMessageContactBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageContactBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) } else if let _ = media as? TelegramMediaExpiredContent { result.removeAll() - result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) return result } else if let _ = media as? TelegramMediaPoll { - result.append((message, ChatMessagePollBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessagePollBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) } else if let _ = media as? TelegramMediaUnsupported { isUnsupportedMedia = true } @@ -93,7 +100,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [( messageWithCaptionToAdd = (message, itemAttributes) skipText = true } else { - result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) } } else { if case .group = item.content { @@ -105,29 +112,62 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [( inner: for media in message.media { if let webpage = media as? TelegramMediaWebpage { if case .Loaded = webpage.content { - result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) } break inner } } if isUnsupportedMedia { - result.append((message, ChatMessageUnsupportedBubbleContentNode.self, itemAttributes)) + result.append((message, ChatMessageUnsupportedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) } } if let (messageWithCaptionToAdd, itemAttributes) = messageWithCaptionToAdd { - result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes)) + result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) } if let additionalContent = item.additionalContent { switch additionalContent { case let .eventLogPreviousMessage(previousMessage): - result.append((previousMessage, ChatMessageEventLogPreviousMessageContentNode.self, ChatMessageEntryAttributes())) + result.append((previousMessage, ChatMessageEventLogPreviousMessageContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) case let .eventLogPreviousDescription(previousMessage): - result.append((previousMessage, ChatMessageEventLogPreviousDescriptionContentNode.self, ChatMessageEntryAttributes())) + result.append((previousMessage, ChatMessageEventLogPreviousDescriptionContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) case let .eventLogPreviousLink(previousMessage): - result.append((previousMessage, ChatMessageEventLogPreviousLinkContentNode.self, ChatMessageEntryAttributes())) + result.append((previousMessage, ChatMessageEventLogPreviousLinkContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .freeform))) + } + } + + let firstMessage = item.content.firstMessage + if !isAction && !Namespaces.Message.allScheduled.contains(firstMessage.id.namespace) { + var hasDiscussion = false + if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { + hasDiscussion = true + } + + if hasDiscussion { + var canComment = false + if firstMessage.id.namespace == Namespaces.Message.Local { + canComment = true + } else { + for attribute in firstMessage.attributes { + if let attribute = attribute as? ReplyThreadMessageAttribute, let commentsPeerId = attribute.commentsPeerId { + switch item.associatedData.channelDiscussionGroup { + case .unknown: + canComment = true + case let .known(groupId): + canComment = groupId == commentsPeerId + } + break + } + } + } + + if canComment { + result.append((firstMessage, ChatMessageCommentFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform))) + } + } else if firstMessage.id.peerId.isReplies { + result.append((firstMessage, ChatMessageCommentFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform))) } } @@ -752,7 +792,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode), replyInfoLayout: (ChatPresentationData, PresentationStrings, AccountContext, ChatMessageReplyInfoType, Message, CGSize) -> (CGSize, () -> ChatMessageReplyInfoNode), actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (Bool) -> ChatMessageActionButtonsNode)), - mosaicStatusLayout: (AccountContext, ChatPresentationData, Bool, Int?, String, ChatMessageDateAndStatusType, CGSize, [MessageReaction]) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode), + mosaicStatusLayout: (AccountContext, ChatPresentationData, Bool, Int?, String, ChatMessageDateAndStatusType, CGSize, [MessageReaction], Int) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode), currentShareButtonNode: HighlightableButtonNode?, layoutConstants: ChatMessageItemLayoutConstants, currentItem: ChatMessageItem?, @@ -766,7 +806,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let nameFont = Font.medium(fontSize) let inlineBotPrefixFont = Font.regular(fontSize) - let inlineBotNameFont = nameFont let baseWidth = params.width - params.leftInset - params.rightInset @@ -785,7 +824,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode var isCrosspostFromChannel = false if let _ = sourceReference { - if firstMessage.id.peerId != item.context.account.peerId { + if !firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { isCrosspostFromChannel = true } } @@ -798,46 +837,61 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode var hasAvatar = false var allowFullWidth = false + let chatLocationPeerId: PeerId switch item.chatLocation { - case let .peer(peerId): - if item.message.id.peerId == item.context.account.peerId { - if let forwardInfo = item.content.firstMessage.forwardInfo { - ignoreForward = true - effectiveAuthor = forwardInfo.author - if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { - effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: Int32(clamping: authorSignature.persistentHashValue)), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: UserInfoFlags()) - } - } - displayAuthorInfo = !mergedTop.merged && incoming && effectiveAuthor != nil - } else if isCrosspostFromChannel, let sourceReference = sourceReference, let source = firstMessage.peers[sourceReference.messageId.peerId] { - if firstMessage.forwardInfo?.author?.id == source.id { - ignoreForward = true - } - effectiveAuthor = source - displayAuthorInfo = !mergedTop.merged && incoming && effectiveAuthor != nil - } else { - effectiveAuthor = firstMessage.author - displayAuthorInfo = !mergedTop.merged && incoming && peerId.isGroupOrChannel && effectiveAuthor != nil - if let forwardInfo = firstMessage.forwardInfo, forwardInfo.psaType != nil { - displayAuthorInfo = false + case let .peer(peerId): + chatLocationPeerId = peerId + case let .replyThread(messageId, _, _): + chatLocationPeerId = messageId.peerId + } + + do { + let peerId = chatLocationPeerId + if item.message.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { + if let forwardInfo = item.content.firstMessage.forwardInfo { + ignoreForward = true + effectiveAuthor = forwardInfo.author + if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { + effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: Int32(clamping: authorSignature.persistentHashValue)), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: UserInfoFlags()) } } - - if peerId != item.context.account.peerId { - if peerId.isGroupOrChannel && effectiveAuthor != nil { - var isBroadcastChannel = false - if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info { - isBroadcastChannel = true - allowFullWidth = true - } - - if !isBroadcastChannel { - hasAvatar = item.content.firstMessage.effectivelyIncoming(item.context.account.peerId) - } - } - } else if incoming { - hasAvatar = true + displayAuthorInfo = !mergedTop.merged && incoming && effectiveAuthor != nil + } else if isCrosspostFromChannel, let sourceReference = sourceReference, let source = firstMessage.peers[sourceReference.messageId.peerId] { + if firstMessage.forwardInfo?.author?.id == source.id { + ignoreForward = true } + effectiveAuthor = source + displayAuthorInfo = !mergedTop.merged && incoming && effectiveAuthor != nil + } else { + effectiveAuthor = firstMessage.author + + var allowAuthor = incoming + + if let author = firstMessage.author, author is TelegramChannel, author.id == firstMessage.id.peerId, !incoming { + allowAuthor = true + } + + displayAuthorInfo = !mergedTop.merged && allowAuthor && peerId.isGroupOrChannel && effectiveAuthor != nil + if let forwardInfo = firstMessage.forwardInfo, forwardInfo.psaType != nil { + displayAuthorInfo = false + } + } + + if !peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { + if peerId.isGroupOrChannel && effectiveAuthor != nil { + var isBroadcastChannel = false + if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info { + isBroadcastChannel = true + allowFullWidth = true + } + + if !isBroadcastChannel { + hasAvatar = item.content.firstMessage.effectivelyIncoming(item.context.account.peerId) + } + } + } else if incoming { + hasAvatar = true + } } if let forwardInfo = item.content.firstMessage.forwardInfo, forwardInfo.source == nil, forwardInfo.author?.id.namespace == Namespaces.Peer.CloudUser { @@ -860,6 +914,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode if let _ = sourceReference { needShareButton = true } + } else if item.message.id.peerId.isReplies { + needShareButton = false } else if item.message.effectivelyIncoming(item.context.account.peerId) { if let _ = sourceReference { needShareButton = true @@ -930,22 +986,22 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset) - var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = [] + var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, BubbleItemAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = [] var addedContentNodes: [(Message, ChatMessageBubbleContentNode)]? let contentNodeMessagesAndClasses = contentNodeMessagesAndClassesForItem(item) - for (contentNodeMessage, contentNodeClass, attributes) in contentNodeMessagesAndClasses { + for (contentNodeMessage, contentNodeClass, attributes, bubbleAttributes) in contentNodeMessagesAndClasses { var found = false for (currentMessage, currentClass, supportsMosaic, currentLayout) in currentContentClassesPropertiesAndLayouts { if currentClass == contentNodeClass && currentMessage.stableId == contentNodeMessage.stableId { - contentPropertiesAndPrepareLayouts.append((contentNodeMessage, supportsMosaic, attributes, currentLayout)) + contentPropertiesAndPrepareLayouts.append((contentNodeMessage, supportsMosaic, attributes, bubbleAttributes, currentLayout)) found = true break } } if !found { let contentNode = (contentNodeClass as! ChatMessageBubbleContentNode.Type).init() - contentPropertiesAndPrepareLayouts.append((contentNodeMessage, contentNode.supportsMosaic, attributes, contentNode.asyncLayoutContent())) + contentPropertiesAndPrepareLayouts.append((contentNodeMessage, contentNode.supportsMosaic, attributes, bubbleAttributes, contentNode.asyncLayoutContent())) if addedContentNodes == nil { addedContentNodes = [] } @@ -988,7 +1044,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode inlineBotNameString = attribute.title } } else if let attribute = attribute as? ReplyMessageAttribute { - replyMessage = firstMessage.associatedMessages[attribute.messageId] + if case let .replyThread(replyThreadMessageId, _, _) = item.chatLocation, replyThreadMessageId == attribute.messageId { + } else { + replyMessage = firstMessage.associatedMessages[attribute.messageId] + } } else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty && !isPreview { replyMarkup = attribute } @@ -998,7 +1057,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode inlineBotNameString = nil } - var contentPropertiesAndLayouts: [(CGSize?, ChatMessageBubbleContentProperties, ChatMessageBubblePreparePosition, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void)))] = [] + var contentPropertiesAndLayouts: [(CGSize?, ChatMessageBubbleContentProperties, ChatMessageBubblePreparePosition, BubbleItemAttributes, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void)))] = [] let topNodeMergeStatus: ChatMessageBubbleMergeStatus = mergedTop.merged ? (incoming ? .Left : .Right) : .None(incoming ? .Incoming : .Outgoing) let bottomNodeMergeStatus: ChatMessageBubbleMergeStatus = mergedBottom.merged ? (incoming ? .Left : .Right) : .None(incoming ? .Incoming : .Outgoing) @@ -1046,12 +1105,21 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } var index = 0 - for (message, _, attributes, prepareLayout) in contentPropertiesAndPrepareLayouts { + for (message, _, attributes, bubbleAttributes, prepareLayout) in contentPropertiesAndPrepareLayouts { let topPosition: ChatMessageBubbleRelativePosition let bottomPosition: ChatMessageBubbleRelativePosition - topPosition = .Neighbour - bottomPosition = .Neighbour + var topBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform) + var bottomBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform) + if index != 0 { + topBubbleAttributes = contentPropertiesAndPrepareLayouts[index - 1].3 + } + if index != contentPropertiesAndPrepareLayouts.count - 1 { + bottomBubbleAttributes = contentPropertiesAndPrepareLayouts[index + 1].3 + } + + topPosition = .Neighbour(topBubbleAttributes.isAttachment, topBubbleAttributes.neighborType) + bottomPosition = .Neighbour(bottomBubbleAttributes.isAttachment, bottomBubbleAttributes.neighborType) let prepareContentPosition: ChatMessageBubblePreparePosition if let mosaicRange = mosaicRange, mosaicRange.contains(index) { @@ -1060,6 +1128,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let refinedBottomPosition: ChatMessageBubbleRelativePosition if index == contentPropertiesAndPrepareLayouts.count - 1 { refinedBottomPosition = .None(.Left) + } else if index == contentPropertiesAndPrepareLayouts.count - 2 && contentPropertiesAndPrepareLayouts[contentPropertiesAndPrepareLayouts.count - 1].3.isAttachment { + refinedBottomPosition = .None(.Left) } else { refinedBottomPosition = bottomPosition } @@ -1091,7 +1161,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let (properties, unboundSize, maxNodeWidth, nodeLayout) = prepareLayout(contentItem, layoutConstants, prepareContentPosition, itemSelection, CGSize(width: maximumContentWidth, height: CGFloat.greatestFiniteMagnitude)) maximumNodeWidth = min(maximumNodeWidth, maxNodeWidth) - contentPropertiesAndLayouts.append((unboundSize, properties, prepareContentPosition, nodeLayout)) + contentPropertiesAndLayouts.append((unboundSize, properties, prepareContentPosition, bubbleAttributes, nodeLayout)) switch properties.hidesBackground { case .never: @@ -1122,7 +1192,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } else { if inlineBotNameString == nil && (ignoreForward || firstMessage.forwardInfo == nil) && replyMessage == nil { if let first = contentPropertiesAndLayouts.first, first.1.hidesSimpleAuthorHeader { - initialDisplayHeader = false + if let author = firstMessage.author as? TelegramChannel, case .group = author.info, author.id == firstMessage.id.peerId, !incoming { + } else { + initialDisplayHeader = false + } } } } @@ -1133,7 +1206,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode authorNameColor = chatMessagePeerIdColors[Int(peer.id.id % 7)] } else if let effectiveAuthor = effectiveAuthor { authorNameString = effectiveAuthor.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) - authorNameColor = chatMessagePeerIdColors[Int(effectiveAuthor.id.id % 7)] + + if incoming { + authorNameColor = chatMessagePeerIdColors[Int(effectiveAuthor.id.id % 7)] + } else { + authorNameColor = item.presentationData.theme.theme.chat.message.outgoing.accentTextColor + } var isScam = effectiveAuthor.isScam if case let .peer(peerId) = item.chatLocation, let authorPeerId = item.message.author?.id, authorPeerId == peerId { @@ -1177,7 +1255,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let firstNodeTopPosition: ChatMessageBubbleRelativePosition if displayHeader { - firstNodeTopPosition = .Neighbour + firstNodeTopPosition = .Neighbour(false, .freeform) } else { firstNodeTopPosition = .None(topNodeMergeStatus) } @@ -1198,7 +1276,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode maximumNodeWidth = size.width - if mosaicRange.upperBound == contentPropertiesAndLayouts.count { + if mosaicRange.upperBound == contentPropertiesAndLayouts.count || contentPropertiesAndLayouts[contentPropertiesAndLayouts.count - 1].3.isAttachment { let message = item.content.firstMessage var edited = false @@ -1206,11 +1284,16 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode edited = true } var viewCount: Int? + var dateReplies = 0 for attribute in message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -1242,7 +1325,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } } - mosaicStatusSizeAndApply = mosaicStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude), dateReactions) + mosaicStatusSizeAndApply = mosaicStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) } } @@ -1391,7 +1474,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode findRemoved: for i in 0 ..< currentContentClassesPropertiesAndLayouts.count { let currentMessage = currentContentClassesPropertiesAndLayouts[i].0 let currentClass: AnyClass = currentContentClassesPropertiesAndLayouts[i].1 - for (contentNodeMessage, contentNodeClass, _) in contentNodeMessagesAndClasses { + for (contentNodeMessage, contentNodeClass, _, _) in contentNodeMessagesAndClasses { if currentClass == contentNodeClass && currentMessage.stableId == contentNodeMessage.stableId { continue findRemoved } @@ -1415,7 +1498,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } for i in 0 ..< contentPropertiesAndLayouts.count { - let (_, contentNodeProperties, preparePosition, contentNodeLayout) = contentPropertiesAndLayouts[i] + let (_, contentNodeProperties, preparePosition, isAttachment, contentNodeLayout) = contentPropertiesAndLayouts[i] if let mosaicRange = mosaicRange, mosaicRange.contains(i), let (framesAndPositions, size) = calculatedGroupFramesAndSize { let mosaicIndex = i - mosaicRange.lowerBound @@ -1466,7 +1549,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode if mosaicRange.upperBound - 1 == contentNodeCount - 1 { lastMosaicBottomPosition = lastNodeTopPosition } else { - lastMosaicBottomPosition = .Neighbour + lastMosaicBottomPosition = .Neighbour(false, .freeform) } if position.contains(.bottom), case .Neighbour = lastMosaicBottomPosition { @@ -1528,23 +1611,32 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode case .linear: let topPosition: ChatMessageBubbleRelativePosition let bottomPosition: ChatMessageBubbleRelativePosition + + var topBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform) + var bottomBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform) + if i != 0 { + topBubbleAttributes = contentPropertiesAndLayouts[i - 1].3 + } + if i != contentPropertiesAndLayouts.count - 1 { + bottomBubbleAttributes = contentPropertiesAndLayouts[i + 1].3 + } if i == 0 { topPosition = firstNodeTopPosition } else { - topPosition = .Neighbour + topPosition = .Neighbour(topBubbleAttributes.isAttachment, topBubbleAttributes.neighborType) } if i == contentNodeCount - 1 { bottomPosition = lastNodeTopPosition } else { - bottomPosition = .Neighbour + bottomPosition = .Neighbour(bottomBubbleAttributes.isAttachment, bottomBubbleAttributes.neighborType) } contentPosition = .linear(top: topPosition, bottom: bottomPosition) case .mosaic: assertionFailure() - contentPosition = .linear(top: .Neighbour, bottom: .Neighbour) + contentPosition = .linear(top: .Neighbour(false, .freeform), bottom: .Neighbour(false, .freeform)) } let (contentNodeWidth, contentNodeFinalize) = contentNodeLayout(CGSize(width: maximumNodeWidth, height: CGFloat.greatestFiniteMagnitude), contentPosition) #if DEBUG @@ -1650,7 +1742,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode updatedShareButtonNode = currentShareButtonNode if item.presentationData.theme !== currentItem?.presentationData.theme { let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) - if item.message.id.peerId == item.context.account.peerId { + if item.message.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { updatedShareButtonBackground = graphics.chatBubbleNavigateButtonImage } else { updatedShareButtonBackground = graphics.chatBubbleShareButtonImage @@ -1660,7 +1752,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode let buttonNode = HighlightableButtonNode() let buttonIcon: UIImage? let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) - if item.message.id.peerId == item.context.account.peerId { + if item.message.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { buttonIcon = graphics.chatBubbleNavigateButtonImage } else { buttonIcon = graphics.chatBubbleShareButtonImage @@ -1757,7 +1849,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode replyInfoOriginY: CGFloat, removedContentNodeIndices: [Int]?, addedContentNodes: [(Message, ChatMessageBubbleContentNode)]?, - contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes)], + contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)], contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, (ListViewItemUpdateAnimation, Bool) -> Void)], mosaicStatusOrigin: CGPoint?, mosaicStatusSizeAndApply: (CGSize, (Bool) -> ChatMessageDateAndStatusNode)?, @@ -1964,7 +2056,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } var sortedContentNodes: [ChatMessageBubbleContentNode] = [] - outer: for (message, nodeClass, _) in contentNodeMessagesAndClasses { + outer: for (message, nodeClass, _, _) in contentNodeMessagesAndClasses { if let addedContentNodes = addedContentNodes { for (contentNodeMessage, contentNode) in addedContentNodes { if type(of: contentNode) == nodeClass && contentNodeMessage.stableId == message.stableId { @@ -2025,7 +2117,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } if let mosaicStatusOrigin = mosaicStatusOrigin, let (size, apply) = mosaicStatusSizeAndApply { - let mosaicStatusNode = apply(false) + let mosaicStatusNode = apply(transition.isAnimated) if mosaicStatusNode !== strongSelf.mosaicStatusNode { strongSelf.mosaicStatusNode?.removeFromSupernode() strongSelf.mosaicStatusNode = mosaicStatusNode @@ -2398,10 +2490,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode var openPeerId = item.effectiveAuthorId ?? author.id var navigate: ChatControllerInteractionNavigateToPeer - if item.content.firstMessage.id.peerId == item.context.account.peerId { + if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { navigate = .chat(textInputState: nil, subject: nil, peekData: nil) - } else { + } else if openPeerId.namespace == Namespaces.Peer.CloudUser { navigate = .info + } else { + navigate = .chat(textInputState: nil, subject: nil, peekData: nil) } for attribute in item.content.firstMessage.attributes { @@ -2414,10 +2508,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty { item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame) } else { - if item.message.id.peerId == item.context.account.peerId, let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { + if item.message.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId), let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { } else if case .member = channel.participationStatus { - } else { + } else if !item.message.id.peerId.isReplies { item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame) return } @@ -2470,7 +2564,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode if let channel = forwardInfo.author as? TelegramChannel, channel.username == nil { if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { } else if case .member = channel.participationStatus { - } else { + } else if !item.message.id.peerId.isReplies { item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, forwardInfoNode, nil) return } @@ -2981,7 +3075,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode @objc func shareButtonPressed() { if let item = self.item { - if item.content.firstMessage.id.peerId == item.context.account.peerId { + if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { for attribute in item.content.firstMessage.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, attribute.messageId) diff --git a/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift new file mode 100644 index 0000000000..3b22bc1c83 --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift @@ -0,0 +1,294 @@ +import Foundation +import UIKit +import Postbox +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramCore +import SyncCore +import TelegramPresentationData + +final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode { + private let separatorNode: ASDisplayNode + private let textNode: TextNode + private let iconNode: ASImageNode + private let arrowNode: ASImageNode + private let buttonNode: HighlightTrackingButtonNode + private let avatarsNode: MergedAvatarsNode + + required init() { + self.separatorNode = ASDisplayNode() + self.separatorNode.isUserInteractionEnabled = false + + self.textNode = TextNode() + self.textNode.isUserInteractionEnabled = false + self.textNode.contentMode = .topLeft + self.textNode.contentsScale = UIScreenScale + self.textNode.displaysAsynchronously = true + + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + self.iconNode.displayWithoutProcessing = true + self.iconNode.isUserInteractionEnabled = false + + self.arrowNode = ASImageNode() + self.arrowNode.displaysAsynchronously = false + self.arrowNode.displayWithoutProcessing = true + self.arrowNode.isUserInteractionEnabled = false + + self.avatarsNode = MergedAvatarsNode() + self.avatarsNode.isUserInteractionEnabled = false + + self.buttonNode = HighlightTrackingButtonNode() + + super.init() + + self.buttonNode.addSubnode(self.separatorNode) + self.buttonNode.addSubnode(self.textNode) + self.buttonNode.addSubnode(self.iconNode) + self.buttonNode.addSubnode(self.arrowNode) + self.buttonNode.addSubnode(self.avatarsNode) + self.addSubnode(self.buttonNode) + + self.buttonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + let nodes: [ASDisplayNode] = [ + strongSelf.textNode, + strongSelf.iconNode, + strongSelf.avatarsNode, + strongSelf.arrowNode, + ] + for node in nodes { + if highlighted { + node.layer.removeAnimation(forKey: "opacity") + node.alpha = 0.4 + } else { + node.alpha = 1.0 + node.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + } + + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func buttonPressed() { + guard let item = self.item else { + return + } + if item.message.id.peerId.isReplies { + for attribute in item.message.attributes { + if let attribute = attribute as? SourceReferenceMessageAttribute { + item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId) + break + } + } + } else { + item.controllerInteraction.openMessageReplies(item.message.id) + } + } + + override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) { + let textLayout = TextNode.asyncLayout(self.textNode) + + return { item, layoutConstants, preparePosition, _, constrainedSize in + let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) + + let displaySeparator: Bool + let topOffset: CGFloat + if case let .linear(top, _) = preparePosition, case .Neighbour(_, .media) = top { + displaySeparator = false + topOffset = 2.0 + } else { + displaySeparator = true + topOffset = 0.0 + } + + return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in + let incoming = item.message.effectivelyIncoming(item.context.account.peerId) + + let maxTextWidth = CGFloat.greatestFiniteMagnitude + + let horizontalInset = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right + + var dateReplies = 0 + var replyPeers: [Peer] = [] + for attribute in item.message.attributes { + if let attribute = attribute as? ReplyThreadMessageAttribute { + dateReplies = Int(attribute.count) + replyPeers = attribute.latestUsers.compactMap { peerId -> Peer? in + return item.message.peers[peerId] + } + } + } + + //TODO:localize + let rawText: String + + if item.message.id.peerId.isReplies { + rawText = "View Reply" + } else if dateReplies > 0 { + if dateReplies == 1 { + rawText = "1 Comment" + } else { + rawText = "\(dateReplies) Comments" + } + } else { + rawText = "Leave a Comment" + } + + let imageSize: CGFloat = 30.0 + let imageSpacing: CGFloat = 20.0 + + var textLeftInset: CGFloat = 0.0 + if replyPeers.isEmpty { + textLeftInset = 41.0 + } else { + textLeftInset = 15.0 + imageSize * min(1.0, CGFloat(replyPeers.count)) + (imageSpacing) * max(0.0, min(2.0, CGFloat(replyPeers.count - 1))) + } + + let textConstrainedSize = CGSize(width: min(maxTextWidth, constrainedSize.width - horizontalInset - textLeftInset - 28.0), height: constrainedSize.height) + + let attributedText: NSAttributedString + + let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing + + let textFont = item.presentationData.messageFont + + attributedText = NSAttributedString(string: rawText, font: textFont, textColor: messageTheme.accentTextColor) + + let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 5.0, right: 2.0) + + let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor)) + + var textFrame = CGRect(origin: CGPoint(x: -textInsets.left + textLeftInset, y: -textInsets.top + 5.0 + topOffset), size: textLayout.size) + var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: textFrame.height - textInsets.top - textInsets.bottom)) + + textFrame = textFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top - 5.0 + UIScreenPixel) + textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top) + + var suggestedBoundingWidth: CGFloat + suggestedBoundingWidth = textFrameWithoutInsets.width + suggestedBoundingWidth += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right + textLeftInset + 28.0 + + let iconImage: UIImage? + let iconOffset: CGPoint + if item.message.id.peerId.isReplies { + iconImage = PresentationResourcesChat.chatMessageRepliesIcon(item.presentationData.theme.theme, incoming: incoming) + iconOffset = CGPoint(x: -4.0, y: -4.0) + } else { + iconImage = PresentationResourcesChat.chatMessageCommentsIcon(item.presentationData.theme.theme, incoming: incoming) + iconOffset = CGPoint(x: 0.0, y: -1.0) + } + let arrowImage = PresentationResourcesChat.chatMessageCommentsArrowIcon(item.presentationData.theme.theme, incoming: incoming) + + return (suggestedBoundingWidth, { boundingWidth in + var boundingSize: CGSize + + boundingSize = textFrameWithoutInsets.size + boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right + boundingSize.height = 40.0 + topOffset + + return (boundingSize, { [weak self] animation, synchronousLoad in + if let strongSelf = self { + strongSelf.item = item + + let cachedLayout = strongSelf.textNode.cachedLayout + + if case .System = animation { + if let cachedLayout = cachedLayout { + if !cachedLayout.areLinesEqual(to: textLayout) { + if let textContents = strongSelf.textNode.contents { + let fadeNode = ASDisplayNode() + fadeNode.displaysAsynchronously = false + fadeNode.contents = textContents + fadeNode.frame = strongSelf.textNode.frame + fadeNode.isLayerBacked = true + strongSelf.addSubnode(fadeNode) + fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak fadeNode] _ in + fadeNode?.removeFromSupernode() + }) + strongSelf.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + } + } + } + + strongSelf.textNode.displaysAsynchronously = !item.presentationData.isPreview + let _ = textApply() + + let adjustedTextFrame = textFrame + + strongSelf.textNode.frame = adjustedTextFrame + + if let iconImage = iconImage { + strongSelf.iconNode.image = iconImage + strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: 15.0 + iconOffset.x, y: 6.0 + iconOffset.y + topOffset), size: iconImage.size) + } + + if let arrowImage = arrowImage { + strongSelf.arrowNode.image = arrowImage + strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: boundingWidth - 33.0, y: 6.0 + topOffset), size: arrowImage.size) + } + + strongSelf.iconNode.isHidden = !replyPeers.isEmpty + + let avatarsFrame = CGRect(origin: CGPoint(x: 13.0, y: 3.0 + topOffset), size: CGSize(width: imageSize * 3.0, height: imageSize)) + strongSelf.avatarsNode.frame = avatarsFrame + strongSelf.avatarsNode.updateLayout(size: avatarsFrame.size) + strongSelf.avatarsNode.update(context: item.context, peers: replyPeers, synchronousLoad: synchronousLoad, imageSize: imageSize, imageSpacing: imageSpacing, borderWidth: 2.0 - UIScreenPixel) + + strongSelf.separatorNode.backgroundColor = messageTheme.polls.separator + strongSelf.separatorNode.isHidden = !displaySeparator + strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: layoutConstants.bubble.strokeInsets.left, y: -3.0), size: CGSize(width: boundingWidth - layoutConstants.bubble.strokeInsets.left - layoutConstants.bubble.strokeInsets.right, height: UIScreenPixel)) + + strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: boundingWidth, height: boundingSize.height)) + + strongSelf.buttonNode.isUserInteractionEnabled = item.message.id.namespace == Namespaces.Message.Cloud + strongSelf.buttonNode.alpha = item.message.id.namespace == Namespaces.Message.Cloud ? 1.0 : 0.5 + } + }) + }) + }) + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + + override func animateAdded(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) + } + + override func animateInsertionIntoBubble(_ duration: Double) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + + override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + if self.buttonNode.frame.contains(point) { + return .ignore + } + return .none + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.buttonNode.isUserInteractionEnabled && self.buttonNode.frame.contains(point) { + return self.buttonNode.view + } + return nil + } +} + + + + diff --git a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift index a740794c22..4f63c7c143 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift @@ -146,11 +146,16 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { edited = true } var viewCount: Int? + var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -171,7 +176,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { let statusType: ChatMessageDateAndStatusType? switch position { - case .linear(_, .None): + case .linear(_, .None), .linear(_, .Neighbour(true, _)): if item.message.effectivelyIncoming(item.context.account.peerId) { statusType = .BubbleIncoming } else { @@ -191,7 +196,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift b/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift index 0d6f734525..5086a30fbe 100644 --- a/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift @@ -160,12 +160,15 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { private var reactionNodes: [StatusReactionNode] = [] private var reactionCountNode: TextNode? private var reactionButtonNode: HighlightTrackingButtonNode? + private var repliesIcon: ASImageNode? + private var replyCountNode: TextNode? private var type: ChatMessageDateAndStatusType? private var theme: ChatPresentationThemeData? private var layoutSize: CGSize? var openReactions: (() -> Void)? + var openReplies: (() -> Void)? override init() { self.dateNode = TextNode() @@ -177,7 +180,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { self.addSubnode(self.dateNode) } - func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction]) -> (CGSize, (Bool) -> Void) { + func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction], _ replies: Int) -> (CGSize, (Bool) -> Void) { let dateLayout = TextNode.asyncLayout(self.dateNode) var checkReadNode = self.checkReadNode @@ -187,15 +190,17 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { var currentBackgroundNode = self.backgroundNode var currentImpressionIcon = self.impressionIcon + var currentRepliesIcon = self.repliesIcon let currentType = self.type let currentTheme = self.theme let makeReactionCountLayout = TextNode.asyncLayout(self.reactionCountNode) + let makeReplyCountLayout = TextNode.asyncLayout(self.replyCountNode) let previousLayoutSize = self.layoutSize - return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions in + return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replyCount in let dateColor: UIColor var backgroundImage: UIImage? var outgoingStatus: ChatMessageDateAndStatusOutgoingType? @@ -206,6 +211,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { let clockFrameImage: UIImage? let clockMinImage: UIImage? var impressionImage: UIImage? + var repliesImage: UIImage? let themeUpdated = presentationData.theme != currentTheme || type != currentType @@ -226,6 +232,9 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.incomingDateAndStatusImpressionIcon } + if replyCount != 0 { + repliesImage = graphics.incomingDateAndStatusRepliesIcon + } case let .BubbleOutgoing(status): dateColor = presentationData.theme.theme.chat.message.outgoing.secondaryTextColor outgoingStatus = status @@ -237,6 +246,9 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.outgoingDateAndStatusImpressionIcon } + if replyCount != 0 { + repliesImage = graphics.outgoingDateAndStatusRepliesIcon + } case .ImageIncoming: dateColor = presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor backgroundImage = graphics.dateAndStatusMediaBackground @@ -248,6 +260,9 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.mediaImpressionIcon } + if replyCount != 0 { + repliesImage = graphics.mediaRepliesIcon + } case let .ImageOutgoing(status): dateColor = presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor outgoingStatus = status @@ -260,6 +275,9 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.mediaImpressionIcon } + if replyCount != 0 { + repliesImage = graphics.mediaRepliesIcon + } case .FreeIncoming: let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) dateColor = serviceColor.primaryText @@ -272,6 +290,9 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.freeImpressionIcon } + if replyCount != 0 { + repliesImage = graphics.freeRepliesIcon + } case let .FreeOutgoing(status): let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) dateColor = serviceColor.primaryText @@ -285,6 +306,9 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { if impressionCount != nil { impressionImage = graphics.freeImpressionIcon } + if replyCount != 0 { + repliesImage = graphics.freeRepliesIcon + } } var updatedDateText = dateText @@ -323,6 +347,20 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { currentImpressionIcon = nil } + var repliesIconSize = CGSize() + if let repliesImage = repliesImage { + if currentRepliesIcon == nil { + let iconNode = ASImageNode() + iconNode.isLayerBacked = true + iconNode.displayWithoutProcessing = true + iconNode.displaysAsynchronously = false + currentRepliesIcon = iconNode + } + repliesIconSize = repliesImage.size + } else { + currentRepliesIcon = nil + } + if let outgoingStatus = outgoingStatus { switch outgoingStatus { case .Sending: @@ -433,6 +471,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { let reactionSize: CGFloat = 14.0 var reactionCountLayoutAndApply: (TextNodeLayout, () -> TextNode)? + var replyCountLayoutAndApply: (TextNodeLayout, () -> TextNode)? let reactionSpacing: CGFloat = -4.0 let reactionTrailingSpacing: CGFloat = 4.0 @@ -458,6 +497,22 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { reactionInset += max(10.0, layoutAndApply.0.size.width) + 2.0 reactionCountLayoutAndApply = layoutAndApply } + + if replyCount > 0 { + let countString: String + if replyCount > 1000000 { + countString = "\(replyCount / 1000000)M" + } else if replyCount > 1000 { + countString = "\(replyCount / 1000)K" + } else { + countString = "\(replyCount)" + } + + let layoutAndApply = makeReplyCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0))) + reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0 + replyCountLayoutAndApply = layoutAndApply + } + leftInset += reactionInset let layoutSize = CGSize(width: leftInset + impressionWidth + date.size.width + statusWidth + backgroundInsets.left + backgroundInsets.right, height: date.size.height + backgroundInsets.top + backgroundInsets.bottom) @@ -510,7 +565,6 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { strongSelf.impressionIcon = nil } - strongSelf.dateNode.frame = CGRect(origin: CGPoint(x: leftInset + backgroundInsets.left + impressionWidth, y: backgroundInsets.top + 1.0 + offset), size: date.size) if let clockFrameNode = clockFrameNode { @@ -677,6 +731,60 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { } } + if let currentRepliesIcon = currentRepliesIcon { + currentRepliesIcon.displaysAsynchronously = !presentationData.isPreview + if currentRepliesIcon.image !== repliesImage { + currentRepliesIcon.image = repliesImage + } + if currentRepliesIcon.supernode == nil { + strongSelf.repliesIcon = currentRepliesIcon + strongSelf.addSubnode(currentRepliesIcon) + if animated { + currentRepliesIcon.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + } + currentRepliesIcon.frame = CGRect(origin: CGPoint(x: reactionOffset - 2.0, y: backgroundInsets.top + offset + floor((date.size.height - repliesIconSize.height) / 2.0)), size: repliesIconSize) + reactionOffset += 9.0 + } else if let repliesIcon = strongSelf.repliesIcon { + strongSelf.repliesIcon = nil + if animated { + if let previousLayoutSize = previousLayoutSize { + repliesIcon.frame = repliesIcon.frame.offsetBy(dx: layoutSize.width - previousLayoutSize.width, dy: 0.0) + } + repliesIcon.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak repliesIcon] _ in + repliesIcon?.removeFromSupernode() + }) + } else { + repliesIcon.removeFromSupernode() + } + } + + if let (layout, apply) = replyCountLayoutAndApply { + let node = apply() + if strongSelf.replyCountNode !== node { + strongSelf.replyCountNode?.removeFromSupernode() + strongSelf.addSubnode(node) + strongSelf.replyCountNode = node + if animated { + node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + } + node.frame = CGRect(origin: CGPoint(x: reactionOffset + 4.0, y: backgroundInsets.top + 1.0 + offset), size: layout.size) + reactionOffset += 4.0 + layout.size.width + } else if let replyCountNode = strongSelf.replyCountNode { + strongSelf.replyCountNode = nil + if animated { + if let previousLayoutSize = previousLayoutSize { + replyCountNode.frame = replyCountNode.frame.offsetBy(dx: layoutSize.width - previousLayoutSize.width, dy: 0.0) + } + replyCountNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak replyCountNode] _ in + replyCountNode?.removeFromSupernode() + }) + } else { + replyCountNode.removeFromSupernode() + } + } + /*if !strongSelf.reactionNodes.isEmpty { if strongSelf.reactionButtonNode == nil { let reactionButtonNode = HighlightTrackingButtonNode() @@ -709,17 +817,17 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { } } - static func asyncLayout(_ node: ChatMessageDateAndStatusNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction]) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode) { + static func asyncLayout(_ node: ChatMessageDateAndStatusNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction], _ replies: Int) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode) { let currentLayout = node?.asyncLayout() - return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions in + return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies in let resultNode: ChatMessageDateAndStatusNode let resultSizeAndApply: (CGSize, (Bool) -> Void) if let node = node, let currentLayout = currentLayout { resultNode = node - resultSizeAndApply = currentLayout(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions) + resultSizeAndApply = currentLayout(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies) } else { resultNode = ChatMessageDateAndStatusNode() - resultSizeAndApply = resultNode.asyncLayout()(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions) + resultSizeAndApply = resultNode.asyncLayout()(context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions, replies) } return (resultSizeAndApply.0, { animated in diff --git a/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift index d05a91106a..dc0198b6de 100644 --- a/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift @@ -69,7 +69,7 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { let incoming = item.message.effectivelyIncoming(item.context.account.peerId) let statusType: ChatMessageDateAndStatusType? switch preparePosition { - case .linear(_, .None): + case .linear(_, .None), .linear(_, .Neighbour(true, _)): if incoming { statusType = .BubbleIncoming } else { diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 14cd68d6ef..f6889fe4c6 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -26,7 +26,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { private var selectionNode: ChatMessageSelectionNode? private var deliveryFailedNode: ChatMessageDeliveryFailedNode? - private var shareButtonNode: HighlightableButtonNode? + private var shareButtonNode: ChatMessageShareButton? private var swipeToReplyNode: ChatMessageSwipeToReplyNode? private var swipeToReplyFeedback: HapticFeedback? @@ -178,24 +178,29 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { let avatarInset: CGFloat var hasAvatar = false + let messagePeerId: PeerId switch item.chatLocation { - case let .peer(peerId): - if peerId != item.context.account.peerId { - if peerId.isGroupOrChannel && item.message.author != nil { - var isBroadcastChannel = false - if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { - isBroadcastChannel = true - } - - if !isBroadcastChannel { - hasAvatar = true - } + case let .peer(peerId): + messagePeerId = peerId + case let .replyThread(messageId, _, _): + messagePeerId = messageId.peerId + } + + do { + if messagePeerId != item.context.account.peerId { + if messagePeerId.isGroupOrChannel && item.message.author != nil { + var isBroadcastChannel = false + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { + isBroadcastChannel = true + } + + if !isBroadcastChannel { + hasAvatar = true } - } else if incoming { - hasAvatar = true } - /*case .group: - hasAvatar = true*/ + } else if incoming { + hasAvatar = true + } } if hasAvatar { @@ -332,7 +337,10 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { } if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + if case let .replyThread(replyThreadMessageId, _, _) = item.chatLocation, replyThreadMessageId == replyAttribute.messageId { + } else { + replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + } } else if let _ = attribute as? InlineBotMessageAttribute { } else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty { replyMarkup = attribute @@ -352,28 +360,12 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { var updatedShareButtonBackground: UIImage? - var updatedShareButtonNode: HighlightableButtonNode? + var updatedShareButtonNode: ChatMessageShareButton? if needShareButton { if currentShareButtonNode != nil { updatedShareButtonNode = currentShareButtonNode - if item.presentationData.theme !== currentItem?.presentationData.theme { - let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) - if item.message.id.peerId == item.context.account.peerId { - updatedShareButtonBackground = graphics.chatBubbleNavigateButtonImage - } else { - updatedShareButtonBackground = graphics.chatBubbleShareButtonImage - } - } } else { - let buttonNode = HighlightableButtonNode() - let buttonIcon: UIImage? - let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) - if item.message.id.peerId == item.context.account.peerId { - buttonIcon = graphics.chatBubbleNavigateButtonImage - } else { - buttonIcon = graphics.chatBubbleShareButtonImage - } - buttonNode.setBackgroundImage(buttonIcon, for: [.normal]) + let buttonNode = ChatMessageShareButton() updatedShareButtonNode = buttonNode } } @@ -475,18 +467,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { strongSelf.addSubnode(updatedShareButtonNode) updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside) } - if let updatedShareButtonBackground = updatedShareButtonBackground { - strongSelf.shareButtonNode?.setBackgroundImage(updatedShareButtonBackground, for: [.normal]) - } + let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, message: item.message, account: item.context.account) + updatedShareButtonNode.frame = CGRect(origin: CGPoint(x: videoFrame.maxX - 7.0, y: videoFrame.maxY - 24.0 - buttonSize.height), size: buttonSize) } else if let shareButtonNode = strongSelf.shareButtonNode { shareButtonNode.removeFromSupernode() strongSelf.shareButtonNode = nil } - if let shareButtonNode = strongSelf.shareButtonNode { - shareButtonNode.frame = CGRect(origin: CGPoint(x: videoFrame.maxX - 7.0, y: videoFrame.maxY - 54.0), size: CGSize(width: 29.0, height: 29.0)) - } - if let updatedReplyBackgroundNode = updatedReplyBackgroundNode { if strongSelf.replyBackgroundNode == nil { strongSelf.replyBackgroundNode = updatedReplyBackgroundNode @@ -687,7 +674,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty { item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame) } else { - if let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { + if !item.message.id.peerId.isReplies, let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { if case .member = channel.participationStatus { } else { item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame) @@ -716,7 +703,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { if let item = self.item, let forwardInfo = item.message.forwardInfo { let performAction: () -> Void = { if let sourceMessageId = forwardInfo.sourceMessageId { - if let channel = forwardInfo.author as? TelegramChannel, channel.username == nil { + if !item.message.id.peerId.isReplies, let channel = forwardInfo.author as? TelegramChannel, channel.username == nil { if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { } else if case .member = channel.participationStatus { } else { @@ -752,7 +739,16 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { @objc func shareButtonPressed() { if let item = self.item { - if item.content.firstMessage.id.peerId == item.context.account.peerId { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { + for attribute in item.message.attributes { + if let _ = attribute as? ReplyThreadMessageAttribute { + item.controllerInteraction.openMessageReplies(item.message.id) + return + } + } + } + + if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { for attribute in item.content.firstMessage.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, attribute.messageId) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index c2cf930192..2054b91a02 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -13,6 +13,7 @@ import PhotoResources import TelegramStringFormatting import RadialStatusNode import SemanticStatusNode +import FileMediaResourceStatus private struct FetchControls { let fetch: () -> Void @@ -259,15 +260,15 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { |> map { resourceStatus, actualFetchStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in return (resourceStatus, actualFetchStatus) } - updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: context, file: file, message: message, isRecentActions: isRecentActions) + updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: false) } else { updatedStatusSignal = messageFileMediaResourceStatus(context: context, file: file, message: message, isRecentActions: isRecentActions) |> map { resourceStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in return (resourceStatus, nil) } - updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: context, file: file, message: message, isRecentActions: isRecentActions) + updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: false) } - updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions) + updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: false) } var statusSize: CGSize? @@ -293,11 +294,16 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { edited = true } var viewCount: Int? + var dateReplies = 0 for attribute in message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -316,7 +322,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { let dateText = stringForMessageTimestampStatus(accountPeerId: context.account.peerId, message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings, reactionCount: dateReactionCount) - let (size, apply) = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, constrainedSize, dateReactions) + let (size, apply) = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, constrainedSize, dateReactions, dateReplies) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index 87c3688d60..eafec61d64 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -12,6 +12,7 @@ import AccountContext import RadialStatusNode import PhotoResources import TelegramUniversalVideoContent +import FileMediaResourceStatus struct ChatMessageInstantVideoItemLayoutResult { let contentSize: CGSize @@ -250,11 +251,16 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } let sentViaBot = false var viewCount: Int? = nil + var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -279,7 +285,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } else { maxDateAndStatusWidth = width - videoFrame.midX - 85.0 } - let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), dateReactions) + let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) var contentSize = imageSize var dateAndStatusOverflow = false @@ -624,7 +630,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } playbackStatusNode.frame = videoFrame.insetBy(dx: 1.5, dy: 1.5) - let status = messageFileMediaPlaybackStatus(context: item.context, file: file, message: item.message, isRecentActions: item.associatedData.isRecentActions) + let status = messageFileMediaPlaybackStatus(context: item.context, file: file, message: item.message, isRecentActions: item.associatedData.isRecentActions, isGlobalSearch: false) playbackStatusNode.status = status self.durationNode?.status = status |> map(Optional.init) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift index 2b00d06405..b99d2a3502 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift @@ -21,6 +21,7 @@ import AnimatedStickerNode import TelegramAnimatedStickerNode import LocalMediaResources import WallpaperResources +import ChatMessageInteractiveMediaBadge private struct FetchControls { let fetch: (Bool) -> Void diff --git a/submodules/TelegramUI/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Sources/ChatMessageItem.swift index ad139684c6..bb3ffe9407 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItem.swift @@ -119,23 +119,27 @@ private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs } } - if lhs.id.peerId == accountPeerId { + if lhs.id.peerId.isRepliesOrSavedMessages(accountPeerId: accountPeerId) { if let forwardInfo = lhs.forwardInfo { lhsEffectiveAuthor = forwardInfo.author } } - if rhs.id.peerId == accountPeerId { + if rhs.id.peerId.isRepliesOrSavedMessages(accountPeerId: accountPeerId) { if let forwardInfo = rhs.forwardInfo { rhsEffectiveAuthor = forwardInfo.author } } var sameAuthor = false - if lhsEffectiveAuthor?.id == rhsEffectiveAuthor?.id { + if lhsEffectiveAuthor?.id == rhsEffectiveAuthor?.id && lhs.effectivelyIncoming(accountPeerId) == rhs.effectivelyIncoming(accountPeerId) { sameAuthor = true } if abs(lhs.timestamp - rhs.timestamp) < Int32(10 * 60) && sameAuthor { + if let channel = lhs.peers[lhs.id.peerId] as? TelegramChannel, case .group = channel.info, lhsEffectiveAuthor?.id == channel.id, !lhs.effectivelyIncoming(accountPeerId) { + return .none + } + var upperStyle: Int32 = ChatMessageMerge.fullyMerged.rawValue var lowerStyle: Int32 = ChatMessageMerge.fullyMerged.rawValue for media in lhs.media { @@ -176,6 +180,8 @@ func chatItemsHaveCommonDateHeader(_ lhs: ListViewItem, _ rhs: ListViewItem?) - lhsHeader = nil } else if let lhs = lhs as? ChatUnreadItem { lhsHeader = lhs.header + } else if let lhs = lhs as? ChatReplyCountItem { + lhsHeader = lhs.header } else { lhsHeader = nil } @@ -187,6 +193,8 @@ func chatItemsHaveCommonDateHeader(_ lhs: ListViewItem, _ rhs: ListViewItem?) - rhsHeader = nil } else if let rhs = rhs as? ChatUnreadItem { rhsHeader = rhs.header + } else if let rhs = rhs as? ChatReplyCountItem { + rhsHeader = rhs.header } else { rhsHeader = nil } @@ -220,51 +228,6 @@ enum ChatMessageMerge: Int32 { } } -public final class ChatMessageItemAssociatedData: Equatable { - let automaticDownloadPeerType: MediaAutoDownloadPeerType - let automaticDownloadNetworkType: MediaAutoDownloadNetworkType - let isRecentActions: Bool - let isScheduledMessages: Bool - let contactsPeerIds: Set - let animatedEmojiStickers: [String: [StickerPackItem]] - let forcedResourceStatus: FileMediaResourceStatus? - - init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, isScheduledMessages: Bool = false, contactsPeerIds: Set = Set(), animatedEmojiStickers: [String: [StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil) { - self.automaticDownloadPeerType = automaticDownloadPeerType - self.automaticDownloadNetworkType = automaticDownloadNetworkType - self.isRecentActions = isRecentActions - self.isScheduledMessages = isScheduledMessages - self.contactsPeerIds = contactsPeerIds - self.animatedEmojiStickers = animatedEmojiStickers - self.forcedResourceStatus = forcedResourceStatus - } - - public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool { - if lhs.automaticDownloadPeerType != rhs.automaticDownloadPeerType { - return false - } - if lhs.automaticDownloadNetworkType != rhs.automaticDownloadNetworkType { - return false - } - if lhs.isRecentActions != rhs.isRecentActions { - return false - } - if lhs.isScheduledMessages != rhs.isScheduledMessages { - return false - } - if lhs.contactsPeerIds != rhs.contactsPeerIds { - return false - } - if lhs.animatedEmojiStickers != rhs.animatedEmojiStickers { - return false - } - if lhs.forcedResourceStatus != rhs.forcedResourceStatus { - return false - } - return true - } -} - public final class ChatMessageItem: ListViewItem, CustomStringConvertible { let presentationData: ChatPresentationData let context: AccountContext @@ -313,29 +276,34 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { var effectiveAuthor: Peer? let displayAuthorInfo: Bool + let messagePeerId: PeerId switch chatLocation { - case let .peer(peerId): - if peerId == context.account.peerId { - if let forwardInfo = content.firstMessage.forwardInfo { - effectiveAuthor = forwardInfo.author - if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { - effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: Int32(clamping: authorSignature.persistentHashValue)), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: UserInfoFlags()) - } + case let .peer(peerId): + messagePeerId = peerId + case let .replyThread(messageId, _, _): + messagePeerId = messageId.peerId + } + + do { + let peerId = messagePeerId + if peerId.isRepliesOrSavedMessages(accountPeerId: context.account.peerId) { + if let forwardInfo = content.firstMessage.forwardInfo { + effectiveAuthor = forwardInfo.author + if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { + effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: Int32(clamping: authorSignature.persistentHashValue)), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: UserInfoFlags()) } - displayAuthorInfo = incoming && effectiveAuthor != nil - } else { - effectiveAuthor = content.firstMessage.author - for attribute in content.firstMessage.attributes { - if let attribute = attribute as? SourceReferenceMessageAttribute { - effectiveAuthor = content.firstMessage.peers[attribute.messageId.peerId] - break - } - } - displayAuthorInfo = incoming && peerId.isGroupOrChannel && effectiveAuthor != nil } - /*case .group: + displayAuthorInfo = incoming && effectiveAuthor != nil + } else { effectiveAuthor = content.firstMessage.author - displayAuthorInfo = incoming && effectiveAuthor != nil*/ + for attribute in content.firstMessage.attributes { + if let attribute = attribute as? SourceReferenceMessageAttribute { + effectiveAuthor = content.firstMessage.peers[attribute.messageId.peerId] + break + } + } + displayAuthorInfo = incoming && peerId.isGroupOrChannel && effectiveAuthor != nil + } } self.effectiveAuthorId = effectiveAuthor?.id @@ -479,6 +447,10 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { if bottom.header.id != self.header.id { dateAtBottom = true } + } else if let bottom = bottom as? ChatReplyCountItem { + if bottom.header.id != self.header.id { + dateAtBottom = true + } } else if let _ = bottom as? ChatHoleItem { dateAtBottom = true } else { diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Sources/ChatMessageItemView.swift index f114d2af30..d0a8422e62 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItemView.swift @@ -9,6 +9,7 @@ import AccountContext import LocalizedPeerData import ContextUI import ChatListUI +import TelegramPresentationData struct ChatMessageItemWidthFill { var compactInset: CGFloat diff --git a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift index 192883b28f..6440354556 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift @@ -157,7 +157,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { if activeLiveBroadcastingTimeout != nil || selectedMedia?.venue != nil { var relativePosition = position if case let .linear(top, _) = position { - relativePosition = .linear(top: top, bottom: ChatMessageBubbleRelativePosition.Neighbour) + relativePosition = .linear(top: top, bottom: .Neighbour(false, .freeform)) } imageCorners = chatMessageBubbleImageContentCorners(relativeContentPosition: relativePosition, normalRadius: layoutConstants.image.defaultCornerRadius, mergedRadius: layoutConstants.image.mergedCornerRadius, mergedWithAnotherContentRadius: layoutConstants.image.contentMergedCornerRadius, layoutConstants: layoutConstants, chatPresentationData: item.presentationData) @@ -176,11 +176,16 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { edited = true } var viewCount: Int? + var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -207,7 +212,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { let statusType: ChatMessageDateAndStatusType? switch position { - case .linear(_, .None): + case .linear(_, .None), .linear(_, .Neighbour(true, _)): if selectedMedia?.venue != nil || activeLiveBroadcastingTimeout != nil { if item.message.effectivelyIncoming(item.context.account.peerId) { statusType = .BubbleIncoming @@ -241,7 +246,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift index 577556c523..ebaf889c65 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift @@ -174,6 +174,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { edited = true } var viewCount: Int? + var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { if case .mosaic = preparePosition { @@ -182,6 +183,10 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -202,7 +207,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { let statusType: ChatMessageDateAndStatusType? switch position { - case .linear(_, .None): + case .linear(_, .None), .linear(_, .Neighbour(true, _)): if item.message.effectivelyIncoming(item.context.account.peerId) { statusType = .ImageIncoming } else { @@ -226,7 +231,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: imageSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude), dateReactions) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: imageSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift index bb6149c63f..721e616c3d 100644 --- a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift @@ -1024,11 +1024,16 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { edited = true } var viewCount: Int? + var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -1049,7 +1054,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { let statusType: ChatMessageDateAndStatusType? switch position { - case .linear(_, .None): + case .linear(_, .None), .linear(_, .Neighbour(true, _)): if incoming { statusType = .BubbleIncoming } else { @@ -1069,7 +1074,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies) statusSize = size statusApply = apply } @@ -1509,10 +1514,10 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { timerTransition.updateAlpha(node: strongSelf.solutionButtonNode, alpha: 0.0) } - let avatarsFrame = CGRect(origin: CGPoint(x: typeFrame.maxX + 6.0, y: typeFrame.minY + floor((typeFrame.height - mergedImageSize) / 2.0)), size: CGSize(width: mergedImageSize + mergedImageSpacing * 2.0, height: mergedImageSize)) + let avatarsFrame = CGRect(origin: CGPoint(x: typeFrame.maxX + 6.0, y: typeFrame.minY + floor((typeFrame.height - defaultMergedImageSize) / 2.0)), size: CGSize(width: defaultMergedImageSize + defaultMergedImageSpacing * 2.0, height: defaultMergedImageSize)) strongSelf.avatarsNode.frame = avatarsFrame strongSelf.avatarsNode.updateLayout(size: avatarsFrame.size) - strongSelf.avatarsNode.update(context: item.context, peers: avatarPeers, synchronousLoad: synchronousLoad) + strongSelf.avatarsNode.update(context: item.context, peers: avatarPeers, synchronousLoad: synchronousLoad, imageSize: defaultMergedImageSize, imageSpacing: defaultMergedImageSpacing, borderWidth: defaultBorderWidth) strongSelf.avatarsNode.isHidden = isBotChat let alphaTransition: ContainedViewLayoutTransition if animation.isAnimated { @@ -1787,23 +1792,33 @@ private extension PeerAvatarReference { private final class MergedAvatarsNodeArguments: NSObject { let peers: [PeerAvatarReference] let images: [PeerId: UIImage] + let imageSize: CGFloat + let imageSpacing: CGFloat + let borderWidth: CGFloat - init(peers: [PeerAvatarReference], images: [PeerId: UIImage]) { + init(peers: [PeerAvatarReference], images: [PeerId: UIImage], imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat) { self.peers = peers self.images = images + self.imageSize = imageSize + self.imageSpacing = imageSpacing + self.borderWidth = borderWidth } } -private let mergedImageSize: CGFloat = 16.0 -private let mergedImageSpacing: CGFloat = 15.0 +private let defaultMergedImageSize: CGFloat = 16.0 +private let defaultMergedImageSpacing: CGFloat = 15.0 +private let defaultBorderWidth: CGFloat = 1.0 private let avatarFont = avatarPlaceholderFont(size: 8.0) -private final class MergedAvatarsNode: ASDisplayNode { +final class MergedAvatarsNode: ASDisplayNode { private var peers: [PeerAvatarReference] = [] private var images: [PeerId: UIImage] = [:] private var disposables: [PeerId: Disposable] = [:] private let buttonNode: HighlightTrackingButtonNode + private var imageSize: CGFloat = defaultMergedImageSize + private var imageSpacing: CGFloat = defaultMergedImageSpacing + private var borderWidthValue: CGFloat = defaultBorderWidth var pressed: (() -> Void)? @@ -1832,10 +1847,13 @@ private final class MergedAvatarsNode: ASDisplayNode { self.buttonNode.frame = CGRect(origin: CGPoint(), size: size) } - func update(context: AccountContext, peers: [Peer], synchronousLoad: Bool) { + func update(context: AccountContext, peers: [Peer], synchronousLoad: Bool, imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat) { + self.imageSize = imageSize + self.imageSpacing = imageSpacing + self.borderWidthValue = borderWidth var filteredPeers = peers.map(PeerAvatarReference.init) if filteredPeers.count > 3 { - let _ = filteredPeers.dropLast(filteredPeers.count - 3) + filteredPeers = filteredPeers.dropLast(filteredPeers.count - 3) } if filteredPeers != self.peers { self.peers = filteredPeers @@ -1870,7 +1888,7 @@ private final class MergedAvatarsNode: ASDisplayNode { switch peer { case let .image(peerReference, representation): if self.disposables[peer.peerId] == nil { - if let signal = peerAvatarImage(account: context.account, peerReference: peerReference, authorOfMessage: nil, representation: representation, displayDimensions: CGSize(width: mergedImageSize, height: mergedImageSize), synchronousLoad: synchronousLoad) { + if let signal = peerAvatarImage(account: context.account, peerReference: peerReference, authorOfMessage: nil, representation: representation, displayDimensions: CGSize(width: imageSize, height: imageSize), synchronousLoad: synchronousLoad) { let disposable = (signal |> deliverOnMainQueue).start(next: { [weak self] imageVersions in guard let strongSelf = self else { @@ -1894,7 +1912,7 @@ private final class MergedAvatarsNode: ASDisplayNode { } override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol { - return MergedAvatarsNodeArguments(peers: self.peers, images: self.images) + return MergedAvatarsNodeArguments(peers: self.peers, images: self.images, imageSize: self.imageSize, imageSpacing: self.imageSpacing, borderWidth: self.borderWidthValue) } @objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { @@ -1912,13 +1930,16 @@ private final class MergedAvatarsNode: ASDisplayNode { return } - context.setBlendMode(.copy) + let mergedImageSize = parameters.imageSize + let mergedImageSpacing = parameters.imageSpacing var currentX = mergedImageSize + mergedImageSpacing * CGFloat(parameters.peers.count - 1) - mergedImageSize for i in (0 ..< parameters.peers.count).reversed() { let imageRect = CGRect(origin: CGPoint(x: currentX, y: 0.0), size: CGSize(width: mergedImageSize, height: mergedImageSize)) + context.setBlendMode(.copy) context.setFillColor(UIColor.clear.cgColor) - context.fillEllipse(in: imageRect.insetBy(dx: -1.0, dy: -1.0)) + context.fillEllipse(in: imageRect.insetBy(dx: -parameters.borderWidth, dy: -parameters.borderWidth)) + context.setBlendMode(.normal) context.saveGState() switch parameters.peers[i] { @@ -1926,7 +1947,7 @@ private final class MergedAvatarsNode: ASDisplayNode { context.translateBy(x: currentX, y: 0.0) drawPeerAvatarLetters(context: context, size: CGSize(width: mergedImageSize, height: mergedImageSize), font: avatarFont, letters: letters, peerId: peerId) context.translateBy(x: -currentX, y: 0.0) - case let .image(reference): + case .image: if let image = parameters.images[parameters.peers[i].peerId] { context.translateBy(x: imageRect.midX, y: imageRect.midY) context.scaleBy(x: 1.0, y: -1.0) diff --git a/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift index c7ec00047c..b10af32878 100644 --- a/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift @@ -53,6 +53,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { } var viewCount: Int? var rawText = "" + var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden @@ -60,6 +61,10 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { viewCount = attribute.count } else if let attribute = attribute as? RestrictedContentMessageAttribute { rawText = attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) ?? "" + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -80,7 +85,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { let statusType: ChatMessageDateAndStatusType? switch position { - case .linear(_, .None): + case .linear(_, .None), .linear(_, .Neighbour(true, _)): if incoming { statusType = .BubbleIncoming } else { @@ -100,7 +105,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode { var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index 3d4d47aeba..06979ee0ad 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -28,7 +28,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { private var selectionNode: ChatMessageSelectionNode? private var deliveryFailedNode: ChatMessageDeliveryFailedNode? - private var shareButtonNode: HighlightableButtonNode? + private var shareButtonNode: ChatMessageShareButton? var telegramFile: TelegramMediaFile? private let fetchDisposable = MetaDisposable() @@ -249,8 +249,21 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } else if incoming { hasAvatar = true } - /*case .group: - hasAvatar = true*/ + case let .replyThread(messageId, _, _): + if messageId.peerId != item.context.account.peerId { + if messageId.peerId.isGroupOrChannel && item.message.author != nil { + var isBroadcastChannel = false + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { + isBroadcastChannel = true + } + + if !isBroadcastChannel { + hasAvatar = true + } + } + } else if incoming { + hasAvatar = true + } } if hasAvatar { @@ -337,11 +350,16 @@ class ChatMessageStickerItemNode: ChatMessageItemView { var edited = false var viewCount: Int? = nil + var dateReplies = 0 for attribute in item.message.attributes { if let _ = attribute as? EditedMessageAttribute, isEmoji { edited = true } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -360,7 +378,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular, reactionCount: dateReactionCount) - let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions) + let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies) var viaBotApply: (TextNodeLayout, () -> TextNode)? var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)? @@ -393,13 +411,16 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { - replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + if case let .replyThread(replyThreadMessageId, _, _) = item.chatLocation, replyThreadMessageId == replyAttribute.messageId { + } else { + replyInfoApply = makeReplyInfoLayout(item.presentationData, item.presentationData.strings, item.context, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + } } else if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), !attribute.rows.isEmpty { replyMarkup = attribute } } - if item.message.id.peerId != item.context.account.peerId { + if item.message.id.peerId != item.context.account.peerId && !item.message.id.peerId.isReplies{ for attribute in item.message.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { if let sourcePeer = item.message.peers[attribute.messageId.peerId] { @@ -423,30 +444,12 @@ class ChatMessageStickerItemNode: ChatMessageItemView { replyBackgroundImage = graphics.chatFreeformContentAdditionalInfoBackgroundImage } - var updatedShareButtonBackground: UIImage? - - var updatedShareButtonNode: HighlightableButtonNode? + var updatedShareButtonNode: ChatMessageShareButton? if needShareButton { if currentShareButtonNode != nil { updatedShareButtonNode = currentShareButtonNode - if item.presentationData.theme !== currentItem?.presentationData.theme { - let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) - if item.message.id.peerId == item.context.account.peerId { - updatedShareButtonBackground = graphics.chatBubbleNavigateButtonImage - } else { - updatedShareButtonBackground = graphics.chatBubbleShareButtonImage - } - } } else { - let buttonNode = HighlightableButtonNode() - let buttonIcon: UIImage? - let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) - if item.message.id.peerId == item.context.account.peerId { - buttonIcon = graphics.chatBubbleNavigateButtonImage - } else { - buttonIcon = graphics.chatBubbleShareButtonImage - } - buttonNode.setBackgroundImage(buttonIcon, for: [.normal]) + let buttonNode = ChatMessageShareButton() updatedShareButtonNode = buttonNode } } @@ -512,20 +515,15 @@ class ChatMessageStickerItemNode: ChatMessageItemView { strongSelf.addSubnode(updatedShareButtonNode) updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside) } - if let updatedShareButtonBackground = updatedShareButtonBackground { - strongSelf.shareButtonNode?.setBackgroundImage(updatedShareButtonBackground, for: [.normal]) - } - } else if let shareButtonNode = strongSelf.shareButtonNode { - shareButtonNode.removeFromSupernode() - strongSelf.shareButtonNode = nil - } - - if let shareButtonNode = strongSelf.shareButtonNode { - var shareButtonFrame = CGRect(origin: CGPoint(x: updatedImageFrame.maxX + 8.0, y: updatedImageFrame.maxY - 30.0 - 10.0), size: CGSize(width: 29.0, height: 29.0)) + let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, message: item.message, account: item.context.account) + var shareButtonFrame = CGRect(origin: CGPoint(x: updatedImageFrame.maxX + 6.0, y: updatedImageFrame.maxY - 10.0 - buttonSize.height - 4.0), size: buttonSize) if isEmoji && incoming { shareButtonFrame.origin.x = dateAndStatusFrame.maxX + 8.0 } - transition.updateFrame(node: shareButtonNode, frame: shareButtonFrame) + transition.updateFrame(node: updatedShareButtonNode, frame: shareButtonFrame) + } else if let shareButtonNode = strongSelf.shareButtonNode { + shareButtonNode.removeFromSupernode() + strongSelf.shareButtonNode = nil } if let updatedReplyBackgroundNode = updatedReplyBackgroundNode { @@ -705,7 +703,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty { item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame) } else { - if let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { + if !item.message.id.peerId.isReplies, let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { if case .member = channel.participationStatus { } else { item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame) @@ -774,7 +772,16 @@ class ChatMessageStickerItemNode: ChatMessageItemView { @objc func shareButtonPressed() { if let item = self.item { - if item.content.firstMessage.id.peerId == item.context.account.peerId { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { + for attribute in item.message.attributes { + if let _ = attribute as? ReplyThreadMessageAttribute { + item.controllerInteraction.openMessageReplies(item.message.id) + return + } + } + } + + if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { for attribute in item.content.firstMessage.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, attribute.messageId) diff --git a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift index b36dc9ba26..f21468cb52 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift @@ -109,11 +109,16 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { edited = true } var viewCount: Int? + var dateReplies = 0 for attribute in item.message.attributes { if let attribute = attribute as? EditedMessageAttribute { edited = !attribute.isHidden } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } } } @@ -133,28 +138,38 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, reactionCount: dateReactionCount) let statusType: ChatMessageDateAndStatusType? + var displayStatus = false switch position { - case .linear(_, .None): - if incoming { - statusType = .BubbleIncoming + case let .linear(_, neighbor): + if case .None = neighbor { + displayStatus = true + } else if case .Neighbour(true, _) = neighbor { + displayStatus = true + } + default: + break + } + if displayStatus { + if incoming { + statusType = .BubbleIncoming + } else { + if message.flags.contains(.Failed) { + statusType = .BubbleOutgoing(.Failed) + } else if (message.flags.isSending && !message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil { + statusType = .BubbleOutgoing(.Sending) } else { - if message.flags.contains(.Failed) { - statusType = .BubbleOutgoing(.Failed) - } else if (message.flags.isSending && !message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil { - statusType = .BubbleOutgoing(.Sending) - } else { - statusType = .BubbleOutgoing(.Sent(read: item.read)) - } + statusType = .BubbleOutgoing(.Sent(read: item.read)) } - default: - statusType = nil + } + } else { + statusType = nil } var statusSize: CGSize? var statusApply: ((Bool) -> Void)? if let statusType = statusType { - let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions) + let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies) statusSize = size statusApply = apply } diff --git a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift index 4ad043b1a8..bb141ab5c2 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -12,83 +12,7 @@ import AccountContext import WebsiteType import InstantPageUI import UrlHandling - -enum InstantPageType { - case generic - case album -} - -func instantPageType(of webpage: TelegramMediaWebpageLoadedContent) -> InstantPageType { - if let type = webpage.type, type == "telegram_album" { - return .album - } - - switch websiteType(of: webpage.websiteName) { - case .instagram, .twitter: - return .album - default: - return .generic - } -} - -func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage, galleryMedia: Media) -> [InstantPageGalleryEntry] { - var result: [InstantPageGalleryEntry] = [] - var counter: Int = 0 - - for block in page.blocks { - result.append(contentsOf: instantPageBlockMedia(pageId: webpageId, block: block, media: page.media, counter: &counter)) - } - - var found = false - for item in result { - if item.media.media.id == galleryMedia.id { - found = true - break - } - } - - if !found { - 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, credit: item.credit, location: InstantPageGalleryEntryLocation(position: Int32(i), totalCount: Int32(result.count))) - } - return result -} - -private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, media: [MediaId: Media], counter: inout Int) -> [InstantPageGalleryEntry] { - 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, 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, _, _): - if let m = media[id] { - 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 .collage(items, _): - var result: [InstantPageGalleryEntry] = [] - for item in items { - result.append(contentsOf: instantPageBlockMedia(pageId: pageId, block: item, media: media, counter: &counter)) - } - return result - case let .slideshow(items, _): - var result: [InstantPageGalleryEntry] = [] - for item in items { - result.append(contentsOf: instantPageBlockMedia(pageId: pageId, block: item, media: media, counter: &counter)) - } - return result - default: - break - } - return [] -} +import GalleryData private let titleFont: UIFont = Font.semibold(15.0) diff --git a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift index 40598db1b3..81eed2ea55 100644 --- a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift @@ -72,7 +72,7 @@ final class ChatPanelInterfaceInteraction { let openSearchResults: () -> Void let openCalendarSearch: () -> Void let toggleMembersSearch: (Bool) -> Void - let navigateToMessage: (MessageId) -> Void + let navigateToMessage: (MessageId, Bool) -> Void let navigateToChat: (PeerId) -> Void let navigateToProfile: (PeerId) -> Void let openPeerInfo: () -> Void @@ -120,6 +120,7 @@ final class ChatPanelInterfaceInteraction { let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void let openPeersNearby: () -> Void let unarchivePeer: () -> Void + let viewReplies: (MessageId?, ChatReplyThreadMessage) -> Void let statuses: ChatPanelInterfaceInteractionStatuses? init( @@ -145,7 +146,7 @@ final class ChatPanelInterfaceInteraction { navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, - navigateToMessage: @escaping (MessageId) -> Void, + navigateToMessage: @escaping (MessageId, Bool) -> Void, navigateToChat: @escaping (PeerId) -> Void, navigateToProfile: @escaping (PeerId) -> Void, openPeerInfo: @escaping () -> Void, @@ -193,6 +194,7 @@ final class ChatPanelInterfaceInteraction { openPeersNearby: @escaping () -> Void, displaySearchResultsTooltip: @escaping (ASDisplayNode, CGRect) -> Void, unarchivePeer: @escaping () -> Void, + viewReplies: @escaping (MessageId?, ChatReplyThreadMessage) -> Void, statuses: ChatPanelInterfaceInteractionStatuses? ) { self.setupReplyMessage = setupReplyMessage @@ -265,6 +267,7 @@ final class ChatPanelInterfaceInteraction { self.openPeersNearby = openPeersNearby self.displaySearchResultsTooltip = displaySearchResultsTooltip self.unarchivePeer = unarchivePeer + self.viewReplies = viewReplies self.statuses = statuses } } diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 960a17e718..d400751579 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -28,6 +28,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { private var currentMessage: Message? private var previousMediaReference: AnyMediaReference? + private var isReplyThread: Bool = false + private let fetchDisposable = MetaDisposable() private let queue = Queue() @@ -115,6 +117,16 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.separatorNode.backgroundColor = interfaceState.theme.chat.historyNavigation.strokeColor } + let isReplyThread: Bool + if case .replyThread = interfaceState.chatLocation { + isReplyThread = true + } else { + isReplyThread = false + } + self.isReplyThread = isReplyThread + + self.closeButton.isHidden = isReplyThread + var messageUpdated = false if let currentMessage = self.currentMessage, let pinnedMessage = interfaceState.pinnedMessage { if currentMessage.id != pinnedMessage.id || currentMessage.stableVersion != pinnedMessage.stableVersion { @@ -129,7 +141,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.currentMessage = interfaceState.pinnedMessage if let currentMessage = currentMessage, let currentLayout = self.currentLayout { - self.enqueueTransition(width: currentLayout.0, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil) + self.enqueueTransition(width: currentLayout.0, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil, isReplyThread: isReplyThread) } } @@ -148,14 +160,14 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.currentLayout = (width, leftInset, rightInset) if let currentMessage = self.currentMessage { - self.enqueueTransition(width: width, leftInset: leftInset, rightInset: rightInset, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: interfaceState.accountPeerId, firstTime: true) + self.enqueueTransition(width: width, leftInset: leftInset, rightInset: rightInset, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: interfaceState.accountPeerId, firstTime: true, isReplyThread: isReplyThread) } } return panelHeight } - private func enqueueTransition(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, message: Message, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId, firstTime: Bool) { + private func enqueueTransition(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, message: Message, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId, firstTime: Bool, isReplyThread: Bool) { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTextLayout = TextNode.asyncLayout(self.textNode) let imageNodeLayout = self.imageNode.asyncLayout() @@ -204,6 +216,14 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { } } + if isReplyThread { + if let author = message.effectiveAuthor { + titleString = author.displayTitle(strings: strings, displayOrder: nameDisplayOrder) + } else { + titleString = "" + } + } + var applyImage: (() -> Void)? if let imageDimensions = imageDimensions { let boundingSize = CGSize(width: 35.0, height: 35.0) @@ -278,7 +298,15 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { @objc func tapped() { if let interfaceInteraction = self.interfaceInteraction, let message = self.currentMessage { - interfaceInteraction.navigateToMessage(message.id) + if self.isReplyThread { + if let sourceReference = message.sourceReference { + interfaceInteraction.navigateToMessage(sourceReference.messageId, true) + } else { + interfaceInteraction.navigateToMessage(message.id, false) + } + } else { + interfaceInteraction.navigateToMessage(message.id, false) + } } } diff --git a/submodules/TelegramUI/Sources/ChatPresentationData.swift b/submodules/TelegramUI/Sources/ChatPresentationData.swift deleted file mode 100644 index 253882e904..0000000000 --- a/submodules/TelegramUI/Sources/ChatPresentationData.swift +++ /dev/null @@ -1,65 +0,0 @@ -import Foundation -import UIKit -import Display -import TelegramCore -import SyncCore -import TelegramPresentationData -import TelegramUIPreferences - -public final class ChatPresentationThemeData: Equatable { - public let theme: PresentationTheme - public let wallpaper: TelegramWallpaper - - public init(theme: PresentationTheme, wallpaper: TelegramWallpaper) { - self.theme = theme - self.wallpaper = wallpaper - } - - public static func ==(lhs: ChatPresentationThemeData, rhs: ChatPresentationThemeData) -> Bool { - return lhs.theme === rhs.theme && lhs.wallpaper == rhs.wallpaper - } -} - -public final class ChatPresentationData { - let theme: ChatPresentationThemeData - let fontSize: PresentationFontSize - let strings: PresentationStrings - let dateTimeFormat: PresentationDateTimeFormat - let nameDisplayOrder: PresentationPersonNameOrder - let disableAnimations: Bool - let largeEmoji: Bool - let chatBubbleCorners: PresentationChatBubbleCorners - let animatedEmojiScale: CGFloat - let isPreview: Bool - - let messageFont: UIFont - let messageEmojiFont: UIFont - let messageBoldFont: UIFont - let messageItalicFont: UIFont - let messageBoldItalicFont: UIFont - let messageFixedFont: UIFont - let messageBlockQuoteFont: UIFont - - init(theme: ChatPresentationThemeData, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool, largeEmoji: Bool, chatBubbleCorners: PresentationChatBubbleCorners, animatedEmojiScale: CGFloat = 1.0, isPreview: Bool = false) { - self.theme = theme - self.fontSize = fontSize - self.strings = strings - self.dateTimeFormat = dateTimeFormat - self.nameDisplayOrder = nameDisplayOrder - self.disableAnimations = disableAnimations - self.chatBubbleCorners = chatBubbleCorners - self.largeEmoji = largeEmoji - self.isPreview = isPreview - - let baseFontSize = fontSize.baseDisplaySize - self.messageFont = Font.regular(baseFontSize) - self.messageEmojiFont = Font.regular(53.0) - self.messageBoldFont = Font.bold(baseFontSize) - self.messageItalicFont = Font.italic(baseFontSize) - self.messageBoldItalicFont = Font.semiboldItalic(baseFontSize) - self.messageFixedFont = Font.monospace(baseFontSize) - self.messageBlockQuoteFont = Font.regular(baseFontSize - 1.0) - - self.animatedEmojiScale = animatedEmojiScale - } -} diff --git a/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift index 5f66288675..1be0b8f25f 100644 --- a/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift @@ -6,6 +6,7 @@ import SyncCore import TelegramPresentationData import TelegramUIPreferences import AccountContext +import ChatInterfaceState enum ChatPresentationInputQueryKind: Int32 { case emoji diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift index 2be30eb2bf..763875bfce 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift @@ -75,7 +75,7 @@ final class ChatRecentActionsController: TelegramBaseController { }, navigateMessageSearch: { _ in }, openCalendarSearch: { }, toggleMembersSearch: { _ in - }, navigateToMessage: { _ in + }, navigateToMessage: { _, _ in }, navigateToChat: { _ in }, navigateToProfile: { _ in }, openPeerInfo: { @@ -125,7 +125,7 @@ final class ChatRecentActionsController: TelegramBaseController { }, openScheduledMessages: { }, openPeersNearby: { }, displaySearchResultsTooltip: { _, _ in - }, unarchivePeer: {}, statuses: nil) + }, unarchivePeer: {}, viewReplies: { _, _ in }, statuses: nil) self.navigationItem.titleView = self.titleView diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index 501b521a41..9f371b3aeb 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -157,7 +157,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { break } } - return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, message: message, standalone: true, reverseMessageGalleryOrder: false, navigationController: navigationController, dismissInput: { + return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: true, reverseMessageGalleryOrder: false, navigationController: navigationController, dismissInput: { //self?.chatDisplayNode.dismissInput() }, present: { c, a in self?.presentController(c, a) @@ -451,6 +451,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in + }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, @@ -815,6 +816,10 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { if let navigationController = strongSelf.getNavigationController() { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: .message(messageId))) } + case let .replyThreadMessage(replyThreadMessageId, isChannelPost, maxReadMessageId, messageId): + if let navigationController = strongSelf.getNavigationController() { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(threadMessageId: replyThreadMessageId, isChannelPost: isChannelPost, maxReadMessageId: maxReadMessageId), subject: .message(messageId))) + } case let .stickerPack(name): let packReference: StickerPackReference = .name(name) strongSelf.presentController(StickerPackScreen(context: strongSelf.context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.getNavigationController()), nil) diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift index 72086329da..e070ab91db 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift @@ -118,7 +118,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { peers[peer.id] = peer let action = TelegramMediaActionType.titleUpdated(title: new) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .changeAbout(prev, new): var peers = SimpleDictionary() @@ -149,14 +149,14 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: let peers = SimpleDictionary() let attributes: [MessageAttribute] = [] - let prevMessage = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prev, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let prevMessage = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prev, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: new, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: new, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()), additionalContent: !prev.isEmpty ? .eventLogPreviousDescription(prevMessage) : nil) } case let .changeUsername(prev, new): @@ -187,7 +187,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) } let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: var previousAttributes: [MessageAttribute] = [] @@ -205,8 +205,8 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { attributes.append(TextEntitiesMessageAttribute(entities: [MessageTextEntity(range: 0 ..< text.count, type: .Italic)])) } - let prevMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prevText, attributes: previousAttributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let prevMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prevText, attributes: previousAttributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) } case let .changePhoto(_, new): @@ -225,7 +225,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.photoUpdated(image: photo) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .toggleInvites(value): var peers = SimpleDictionary() @@ -252,7 +252,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .toggleSignatures(value): var peers = SimpleDictionary() @@ -279,7 +279,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .updatePinned(message): switch self.id.contentIndex { @@ -303,7 +303,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: if let message = message { @@ -321,7 +321,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } else { var peers = SimpleDictionary() @@ -343,7 +343,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } } @@ -388,7 +388,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: var peers = SimpleDictionary() @@ -405,7 +405,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes()), additionalContent: !prev.text.isEmpty || !message.text.isEmpty ? .eventLogPreviousMessage(filterOriginalMessageFlags(prev)) : nil) } case let .deleteMessage(message): @@ -431,7 +431,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: var peers = SimpleDictionary() @@ -455,7 +455,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } case .participantJoin, .participantLeave: @@ -473,7 +473,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } else { action = TelegramMediaActionType.removedMembers(peerIds: [self.entry.event.peerId]) } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .participantInvite(participant): var peers = SimpleDictionary() @@ -490,7 +490,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action: TelegramMediaActionType action = TelegramMediaActionType.addedMembers(peerIds: [participant.peer.id]) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .participantToggleBan(prev, new): var peers = SimpleDictionary() @@ -620,7 +620,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .participantToggleAdmin(prev, new): var peers = SimpleDictionary() @@ -649,7 +649,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } else { var appendedRightsHeader = false - if case let .creator(_, prevRank) = prev.participant, case let .creator(_, newRank) = new.participant, prevRank != newRank { + if case let .creator(_, _, prevRank) = prev.participant, case let .creator(_, _, newRank) = new.participant, prevRank != newRank { appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRankName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), newRank ?? "") : self.presentationData.strings.Channel_AdminLog_MessageRankUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!, newRank ?? ""), generateEntities: { index in var result: [MessageTextEntityType] = [] if index == 0 { @@ -756,7 +756,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .changeStickerPack(_, new): var peers = SimpleDictionary() @@ -785,7 +785,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .togglePreHistoryHidden(value): var peers = SimpleDictionary() @@ -815,7 +815,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .updateDefaultBannedRights(prev, new): var peers = SimpleDictionary() @@ -873,7 +873,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .pollStopped(message): switch self.id.contentIndex { @@ -901,7 +901,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case .content: var peers = SimpleDictionary() @@ -918,7 +918,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } } - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.author, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.author, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes()), additionalContent: nil) } case let .linkedPeerUpdated(previous, updated): @@ -974,7 +974,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) case let .changeGeoLocation(_, updated): var peers = SimpleDictionary() @@ -996,12 +996,12 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let mediaMap = TelegramMediaMap(latitude: updated.latitude, longitude: updated.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: [], media: [mediaMap], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: [], media: [mediaMap], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } else { let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } case let .updateSlowmode(_, newValue): @@ -1032,7 +1032,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) } } diff --git a/submodules/TelegramUI/Sources/ChatReplyCountItem.swift b/submodules/TelegramUI/Sources/ChatReplyCountItem.swift new file mode 100644 index 0000000000..c6208f1e50 --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatReplyCountItem.swift @@ -0,0 +1,176 @@ +import Foundation +import UIKit +import Postbox +import AsyncDisplayKit +import Display +import SwiftSignalKit +import TelegramPresentationData +import AccountContext + +private let titleFont = UIFont.systemFont(ofSize: 13.0) + +class ChatReplyCountItem: ListViewItem { + let index: MessageIndex + let isComments: Bool + let count: Int + let presentationData: ChatPresentationData + let header: ChatMessageDateHeader + + init(index: MessageIndex, isComments: Bool, count: Int, presentationData: ChatPresentationData, context: AccountContext) { + self.index = index + self.isComments = isComments + self.count = count + self.presentationData = presentationData + self.header = ChatMessageDateHeader(timestamp: index.timestamp, scheduled: false, presentationData: presentationData, context: context) + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = ChatReplyCountItemNode() + node.layoutForParams(params, item: self, previousItem: previousItem, nextItem: nextItem) + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in }) + }) + } + } + } + + public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + if let nodeValue = node() as? ChatReplyCountItemNode { + let nodeLayout = nodeValue.asyncLayout() + + async { + let dateAtBottom = !chatItemsHaveCommonDateHeader(self, nextItem) + + let (layout, apply) = nodeLayout(self, params, dateAtBottom) + Queue.mainQueue().async { + completion(layout, { _ in + apply() + }) + } + } + } else { + assertionFailure() + } + } + } +} + +class ChatReplyCountItemNode: ListViewItemNode { + var item: ChatReplyCountItem? + let backgroundNode: ASImageNode + let labelNode: TextNode + + private var theme: ChatPresentationThemeData? + + private let layoutConstants = ChatMessageItemLayoutConstants.default + + init() { + self.backgroundNode = ASImageNode() + self.backgroundNode.isLayerBacked = true + self.backgroundNode.displayWithoutProcessing = true + + self.labelNode = TextNode() + self.labelNode.isUserInteractionEnabled = false + + super.init(layerBacked: false, dynamicBounce: true, rotated: true) + + self.addSubnode(self.backgroundNode) + + self.addSubnode(self.labelNode) + + self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) + + self.scrollPositioningInsets = UIEdgeInsets(top: 5.0, left: 0.0, bottom: 6.0, right: 0.0) + self.canBeUsedAsScrollToItemAnchor = false + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + super.animateInsertion(currentTimestamp, duration: duration, short: short) + + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + override func animateAdded(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + if let item = item as? ChatReplyCountItem { + let dateAtBottom = !chatItemsHaveCommonDateHeader(item, nextItem) + let (layout, apply) = self.asyncLayout()(item, params, dateAtBottom) + apply() + self.contentSize = layout.contentSize + self.insets = layout.insets + } + } + + func asyncLayout() -> (_ item: ChatReplyCountItem, _ params: ListViewItemLayoutParams, _ dateAtBottom: Bool) -> (ListViewItemNodeLayout, () -> Void) { + let labelLayout = TextNode.asyncLayout(self.labelNode) + let layoutConstants = self.layoutConstants + let currentTheme = self.theme + + return { item, params, dateAtBottom in + var updatedBackgroundImage: UIImage? + if currentTheme != item.presentationData.theme { + updatedBackgroundImage = PresentationResourcesChat.chatUnreadBarBackgroundImage(item.presentationData.theme.theme) + } + + let text: String + //TODO:localize + if item.isComments { + if item.count == 0 { + text = "Comments" + } else if item.count == 1 { + text = "1 Comment" + } else { + text = "\(item.count) Comments" + } + } else { + if item.count == 0 { + text = "Replies" + } else if item.count == 1 { + text = "1 Reply" + } else { + text = "\(item.count) Replies" + } + } + + let (size, apply) = labelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: text, font: titleFont, textColor: item.presentationData.theme.theme.chat.serviceMessage.unreadBarTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let backgroundSize = CGSize(width: params.width, height: 25.0) + + return (ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 25.0), insets: UIEdgeInsets(top: 6.0 + (dateAtBottom ? layoutConstants.timestampHeaderHeight : 0.0), left: 0.0, bottom: 5.0, right: 0.0)), { [weak self] in + if let strongSelf = self { + strongSelf.item = item + strongSelf.theme = item.presentationData.theme + + if let updatedBackgroundImage = updatedBackgroundImage { + strongSelf.backgroundNode.image = updatedBackgroundImage + } + + let _ = apply() + + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backgroundSize) + strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - size.size.width) / 2.0), y: floorToScreenPixels((backgroundSize.height - size.size.height) / 2.0)), size: size.size) + } + }) + } + } + + override public func header() -> ListViewItemHeader? { + if let item = self.item { + return item.header + } else { + return nil + } + } + + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { + super.animateRemoved(currentTimestamp, duration: duration) + + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + } +} diff --git a/submodules/TelegramUI/Sources/ChatScheduleTimeControllerNode.swift b/submodules/TelegramUI/Sources/ChatScheduleTimeControllerNode.swift index d3b4346f7f..e9082e6f4d 100644 --- a/submodules/TelegramUI/Sources/ChatScheduleTimeControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatScheduleTimeControllerNode.swift @@ -8,7 +8,6 @@ import SyncCore import TelegramPresentationData import TelegramStringFormatting import AccountContext -import ShareController import SolidRoundedButtonNode import PresentationDataUtils diff --git a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift index 8cbe14652a..2038a12862 100644 --- a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift @@ -189,6 +189,10 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode { canSearchMembers = false } } + if case .replyThread = interfaceState.chatLocation { + self.calendarButton.isHidden = true + canSearchMembers = false + } self.membersButton.isHidden = (!(interfaceState.search?.query.isEmpty ?? true)) || self.displayActivity || !canSearchMembers let resultsEnabled = (resultCount ?? 0) > 0 diff --git a/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift b/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift index 420496db61..e8f3da3c41 100644 --- a/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift @@ -9,6 +9,7 @@ import TelegramPresentationData import SearchBarNode import LocalizedPeerData import SwiftSignalKit +import AccountContext private let searchBarFont = Font.regular(17.0) @@ -31,10 +32,8 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode { self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern) let placeholderText: String switch chatLocation { - case .peer: + case .peer, .replyThread: placeholderText = strings.Conversation_SearchPlaceholder - /*case .group: - placeholderText = "Search this feed"*/ } self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) @@ -55,6 +54,10 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode { self?.interaction.toggleMembersSearch(false) } + self.searchBar.clearTokens = { [weak self] in + self?.interaction.toggleMembersSearch(false) + } + if let statuses = interaction.statuses { self.searchingActivityDisposable = (statuses.searching |> deliverOnMainQueue).start(next: { [weak self] value in @@ -91,23 +94,21 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode { switch search.domain { case .everything: + self.searchBar.tokens = [] self.searchBar.prefixString = nil let placeholderText: String switch self.chatLocation { - case .peer: + case .peer, .replyThread: placeholderText = self.strings.Conversation_SearchPlaceholder - /*case .group: - placeholderText = "Search this feed"*/ } self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) case .members: + self.searchBar.tokens = [] self.searchBar.prefixString = NSAttributedString(string: strings.Conversation_SearchByName_Prefix, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputTextColor) self.searchBar.placeholderString = nil case let .member(peer): - let prefixString = NSMutableAttributedString() - prefixString.append(NSAttributedString(string: self.strings.Conversation_SearchByName_Prefix, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputTextColor)) - prefixString.append(NSAttributedString(string: "\(peer.compactDisplayTitle) ", font: searchBarFont, textColor: theme.rootController.navigationSearchBar.accentColor)) - self.searchBar.prefixString = prefixString + self.searchBar.tokens = [SearchBarToken(id: peer.id, icon: UIImage(bundleImageName: "Chat List/Search/User"), title: peer.compactDisplayTitle)] + self.searchBar.prefixString = nil self.searchBar.placeholderString = nil } diff --git a/submodules/TelegramUI/Sources/ChatSendMessageActionSheetController.swift b/submodules/TelegramUI/Sources/ChatSendMessageActionSheetController.swift index 4b420cf10f..0123da2cd2 100644 --- a/submodules/TelegramUI/Sources/ChatSendMessageActionSheetController.swift +++ b/submodules/TelegramUI/Sources/ChatSendMessageActionSheetController.swift @@ -69,9 +69,11 @@ final class ChatSendMessageActionSheetController: ViewController { var reminders = false var isSecret = false + var canSchedule = false if case let .peer(peerId) = self.interfaceState.chatLocation { reminders = peerId == context.account.peerId isSecret = peerId.namespace == Namespaces.Peer.SecretChat + canSchedule = !isSecret } self.displayNode = ChatSendMessageActionSheetControllerNode(context: self.context, reminders: reminders, gesture: gesture, sendButtonFrame: self.sendButtonFrame, textInputNode: self.textInputNode, forwardedCount: forwardedCount, send: { [weak self] in @@ -80,7 +82,7 @@ final class ChatSendMessageActionSheetController: ViewController { }, sendSilently: { [weak self] in self?.controllerInteraction?.sendCurrentMessage(true) self?.dismiss(cancel: false) - }, schedule: isSecret ? nil : { [weak self] in + }, schedule: !canSchedule ? nil : { [weak self] in self?.controllerInteraction?.scheduleCurrentMessage() self?.dismiss(cancel: false) }, cancel: { [weak self] in diff --git a/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift b/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift index fa8a5881a7..24283d889e 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputMediaRecordingButton.swift @@ -8,15 +8,11 @@ import SwiftSignalKit import TelegramPresentationData import LegacyComponents import AccountContext +import ChatInterfaceState private let offsetThreshold: CGFloat = 10.0 private let dismissOffsetThreshold: CGFloat = 70.0 -enum ChatTextInputMediaRecordingButtonMode: Int32 { - case audio = 0 - case video = 1 -} - private func findTargetView(_ view: UIView, point: CGPoint) -> UIView? { if view.bounds.contains(point) && view.tag == 0x01f2bca { return view diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 5a21dcb5f2..8988126ecb 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -790,6 +790,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } else { placeholder = interfaceState.strings.Conversation_InputTextBroadcastPlaceholder } + } else if let channel = peer as? TelegramChannel, case .group = channel.info, channel.hasPermission(.canBeAnonymous) { + placeholder = interfaceState.strings.Conversation_InputTextAnonymousPlaceholder + } else if case let .replyThread(_, isChannelPost, _) = interfaceState.chatLocation { + //TODO:localize + if isChannelPost { + placeholder = "Comment" + } else { + placeholder = "Reply" + } } else { placeholder = interfaceState.strings.Conversation_InputTextPlaceholder } diff --git a/submodules/TelegramUI/Sources/ChatTitleView.swift b/submodules/TelegramUI/Sources/ChatTitleView.swift index 8228544ff7..dd226e10fe 100644 --- a/submodules/TelegramUI/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Sources/ChatTitleView.swift @@ -18,7 +18,13 @@ import PhoneNumberFormat import ChatTitleActivityNode enum ChatTitleContent { + enum ReplyThreadType { + case replies + case comments + } + case peer(peerView: PeerView, onlineMemberCount: Int32?, isScheduledMessages: Bool) + case replyThread(type: ReplyThreadType, text: String) case group([Peer]) case custom(String) } @@ -101,7 +107,12 @@ final class ChatTitleView: UIView, NavigationBarTitleView { var isEnabled = true switch titleContent { case let .peer(peerView, _, isScheduledMessages): - if isScheduledMessages { + if peerView.peerId.isReplies { + //TODO:localize + let typeText: String = "Replies" + string = NSAttributedString(string: typeText, font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor) + isEnabled = false + } else if isScheduledMessages { if peerView.peerId == self.account.peerId { string = NSAttributedString(string: self.strings.ScheduledMessages_RemindersTitle, font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor) } else { @@ -130,6 +141,22 @@ final class ChatTitleView: UIView, NavigationBarTitleView { } } } + case let .replyThread(type, text): + //TODO:localize + let typeText: String + if !text.isEmpty { + typeText = text + } else { + switch type { + case .comments: + typeText = "Comments" + case .replies: + typeText = "Replies" + } + } + + string = NSAttributedString(string: typeText, font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor) + isEnabled = false case .group: string = NSAttributedString(string: "Feed", font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor) case let .custom(text): @@ -169,6 +196,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView { self.setNeedsLayout() } self.isUserInteractionEnabled = isEnabled + self.button.isUserInteractionEnabled = isEnabled self.updateStatus() } } @@ -180,7 +208,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView { switch titleContent { case let .peer(peerView, _, isScheduledMessages): if let peer = peerViewMainPeer(peerView) { - if peer.id == self.account.peerId || isScheduledMessages { + if peer.id == self.account.peerId || isScheduledMessages || peer.id.isReplies { inputActivitiesAllowed = false } } @@ -269,7 +297,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView { case let .peer(peerView, onlineMemberCount, isScheduledMessages): if let peer = peerViewMainPeer(peerView) { let servicePeer = isServicePeer(peer) - if peer.id == self.account.peerId || isScheduledMessages { + if peer.id == self.account.peerId || isScheduledMessages || peer.id.isReplies { let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor) state = .info(string, .generic) } else if let user = peer as? TelegramUser { @@ -601,6 +629,9 @@ final class ChatTitleView: UIView, NavigationBarTitleView { } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !self.isUserInteractionEnabled { + return nil + } if self.button.frame.contains(point) { return self.button.view } diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift index 700919df2b..2d18b17a73 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift @@ -18,6 +18,8 @@ import CounterContollerTitleView private func peerTokenTitle(accountPeerId: PeerId, peer: Peer, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) -> String { if peer.id == accountPeerId { return strings.DialogList_SavedMessages + } else if peer.id.isReplies { + return strings.DialogList_Replies } else { return peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder) } diff --git a/submodules/TelegramUI/Sources/DeclareEncodables.swift b/submodules/TelegramUI/Sources/DeclareEncodables.swift index 01c3ef9546..c469aa47f1 100644 --- a/submodules/TelegramUI/Sources/DeclareEncodables.swift +++ b/submodules/TelegramUI/Sources/DeclareEncodables.swift @@ -9,7 +9,9 @@ import WebSearchUI import InstantPageCache import SettingsUI import WallpaperResources +import MediaResources import LocationUI +import ChatInterfaceState private var telegramUIDeclaredEncodables: Void = { declareEncodable(InAppNotificationSettings.self, f: { InAppNotificationSettings(decoder: $0) }) diff --git a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift index 31a2758560..5bbb1c44de 100644 --- a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift +++ b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift @@ -144,6 +144,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in + }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, diff --git a/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift index a849ec4517..87c6738995 100644 --- a/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift @@ -347,7 +347,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode { @objc func contentTap(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state, let message = self.currentMessage { - self.interfaceInteraction?.navigateToMessage(message.id) + self.interfaceInteraction?.navigateToMessage(message.id, false) } } } diff --git a/submodules/TelegramUI/Sources/GridMessageItem.swift b/submodules/TelegramUI/Sources/GridMessageItem.swift index 46b058964b..9d60e67869 100644 --- a/submodules/TelegramUI/Sources/GridMessageItem.swift +++ b/submodules/TelegramUI/Sources/GridMessageItem.swift @@ -14,6 +14,7 @@ import RadialStatusNode import PhotoResources import GridMessageSelectionNode import ContextUI +import ChatMessageInteractiveMediaBadge private func mediaForMessage(_ message: Message) -> Media? { for media in message.media { diff --git a/submodules/TelegramUI/Sources/ManageSharedAccountInfo.swift b/submodules/TelegramUI/Sources/ManageSharedAccountInfo.swift index 49ee068a3a..396a494187 100644 --- a/submodules/TelegramUI/Sources/ManageSharedAccountInfo.swift +++ b/submodules/TelegramUI/Sources/ManageSharedAccountInfo.swift @@ -22,7 +22,7 @@ private func accountInfo(account: Account) -> Signal var datacenters: [Int32: AccountDatacenterInfo] = [:] for nId in context.knownDatacenterIds() { if let id = nId as? Int { - if let authInfo = context.authInfoForDatacenter(withId: id), let authKey = authInfo.authKey { + if let authInfo = context.authInfoForDatacenter(withId: id, selector: .persistent), let authKey = authInfo.authKey { let transportScheme = context.chooseTransportSchemeForConnection(toDatacenterId: id, schemes: context.transportSchemesForDatacenter(withId: id, media: true, enforceMedia: false, isProxy: false)) var addressList: [AccountDatacenterAddress] = [] if let transportScheme = transportScheme, let address = transportScheme.address, let host = address.host { diff --git a/submodules/TelegramUI/Sources/MediaManager.swift b/submodules/TelegramUI/Sources/MediaManager.swift index 24ab8e3734..988498fede 100644 --- a/submodules/TelegramUI/Sources/MediaManager.swift +++ b/submodules/TelegramUI/Sources/MediaManager.swift @@ -13,6 +13,7 @@ import TelegramUIPreferences import AccountContext import TelegramUniversalVideoContent import DeviceProximity +import MediaResources enum SharedMediaPlayerGroup: Int { case music = 0 diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 83b2486125..04c124c03a 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -125,6 +125,10 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam if message.id.peerId == peerId { return true } + case let .replyThread(messageId, _, _): + if message.id.peerId == messageId.peerId { + return true + } } } } diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index fed47ab635..4458faf3be 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -21,229 +21,11 @@ import AlertUI import PresentationDataUtils import ShareController import UndoUI - -private enum ChatMessageGalleryControllerData { - case url(String) - case pass(TelegramMediaFile) - case instantPage(InstantPageGalleryController, Int, Media) - case map(TelegramMediaMap) - case stickerPack(StickerPackReference) - case audio(TelegramMediaFile) - case document(TelegramMediaFile, Bool) - case gallery(Signal) - case secretGallery(SecretMediaPreviewController) - case chatAvatars(AvatarGalleryController, Media) - case theme(TelegramMediaFile) - case other(Media) -} - -private func chatMessageGalleryControllerData(context: AccountContext, message: Message, navigationController: NavigationController?, standalone: Bool, reverseMessageGalleryOrder: Bool, mode: ChatControllerInteractionOpenMessageMode, synchronousLoad: Bool, actionInteraction: GalleryControllerActionInteraction?) -> ChatMessageGalleryControllerData? { - var galleryMedia: Media? - var otherMedia: Media? - var instantPageMedia: (TelegramMediaWebpage, [InstantPageGalleryEntry])? - for media in message.media { - if let action = media as? TelegramMediaAction { - switch action.action { - case let .photoUpdated(image): - if let peer = messageMainPeer(message), let image = image { - let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer, message.timestamp, nil, message.id, image.immediateThumbnailData, "action")]) - let galleryController = AvatarGalleryController(context: context, peer: peer, sourceCorners: .roundRect(15.5), remoteEntries: promise, skipInitial: true, replaceRootController: { controller, ready in - - }) - return .chatAvatars(galleryController, image) - } - default: - break - } - } else if let file = media as? TelegramMediaFile { - galleryMedia = file - } else if let image = media as? TelegramMediaImage { - galleryMedia = image - } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { - if let file = content.file { - galleryMedia = file - } else if let image = content.image { - if case .link = mode { - } else if ["photo", "document", "video", "gif", "telegram_album"].contains(content.type) { - galleryMedia = image - } - } - - if let instantPage = content.instantPage, let galleryMedia = galleryMedia { - switch instantPageType(of: content) { - case .album: - let medias = instantPageGalleryMedia(webpageId: webpage.webpageId, page: instantPage, galleryMedia: galleryMedia) - if medias.count > 1 { - instantPageMedia = (webpage, medias) - } - default: - break - } - } - } else if let mapMedia = media as? TelegramMediaMap { - galleryMedia = mapMedia - } else if let contactMedia = media as? TelegramMediaContact { - otherMedia = contactMedia - } - } - - var stream = false - var autoplayingVideo = false - var landscape = false - var timecode: Double? = nil - - switch mode { - case .stream: - stream = true - case .automaticPlayback: - autoplayingVideo = true - case .landscape: - autoplayingVideo = true - landscape = true - case let .timecode(time): - timecode = time - default: - break - } - - if let (webPage, instantPageMedia) = instantPageMedia, let galleryMedia = galleryMedia { - var centralIndex: Int = 0 - for i in 0 ..< instantPageMedia.count { - if instantPageMedia[i].media.media.id == galleryMedia.id { - centralIndex = i - break - } - } - - let gallery = InstantPageGalleryController(context: context, webPage: webPage, message: message, entries: instantPageMedia, centralIndex: centralIndex, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, replaceRootController: { [weak navigationController] controller, ready in - if let navigationController = navigationController { - navigationController.replaceTopController(controller, animated: false, ready: ready) - } - }, baseNavigationController: navigationController) - return .instantPage(gallery, centralIndex, galleryMedia) - } else if let galleryMedia = galleryMedia { - if let mapMedia = galleryMedia as? TelegramMediaMap { - return .map(mapMedia) - } else if let file = galleryMedia as? TelegramMediaFile, (file.isSticker || file.isAnimatedSticker) { - for attribute in file.attributes { - if case let .Sticker(_, reference, _) = attribute { - if let reference = reference { - return .stickerPack(reference) - } - break - } - } - } else if let file = galleryMedia as? TelegramMediaFile, file.isAnimatedSticker { - return nil - } else if let file = galleryMedia as? TelegramMediaFile, file.isMusic || file.isVoice || file.isInstantVideo { - return .audio(file) - } else if let file = galleryMedia as? TelegramMediaFile, file.mimeType == "application/vnd.apple.pkpass" || (file.fileName != nil && file.fileName!.lowercased().hasSuffix(".pkpass")) { - return .pass(file) - } else { - if let file = galleryMedia as? TelegramMediaFile { - if let fileName = file.fileName { - let ext = (fileName as NSString).pathExtension.lowercased() - if ext == "tgios-theme" { - return .theme(file) - } else if ext == "wav" || ext == "opus" { - return .audio(file) - } else if ext == "json", let fileSize = file.size, fileSize < 1024 * 1024 { - if let path = context.account.postbox.mediaBox.completedResourcePath(file.resource), let composition = LOTComposition(filePath: path), composition.timeDuration > 0.0 { - let gallery = GalleryController(context: context, source: .peerMessagesAtId(message.id), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in - navigationController?.replaceTopController(controller, animated: false, ready: ready) - }, baseNavigationController: navigationController, actionInteraction: actionInteraction) - return .gallery(.single(gallery)) - } - } - - if ext == "mkv" { - return .document(file, true) - } - } - - if internalDocumentItemSupportsMimeType(file.mimeType, fileName: file.fileName ?? "file") { - let gallery = GalleryController(context: context, source: .peerMessagesAtId(message.id), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in - navigationController?.replaceTopController(controller, animated: false, ready: ready) - }, baseNavigationController: navigationController, actionInteraction: actionInteraction) - return .gallery(.single(gallery)) - } - - if !file.isVideo { - return .document(file, false) - } - } - - if message.containsSecretMedia { - let gallery = SecretMediaPreviewController(context: context, messageId: message.id) - return .secretGallery(gallery) - } else { - let startTimecode: Signal - if let timecode = timecode { - startTimecode = .single(timecode) - } else { - startTimecode = mediaPlaybackStoredState(postbox: context.account.postbox, messageId: message.id) - |> map { state in - return state?.timestamp - } - } - - return .gallery(startTimecode - |> deliverOnMainQueue - |> map { timecode in - let gallery = GalleryController(context: context, source: standalone ? .standaloneMessage(message) : .peerMessagesAtId(message.id), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in - navigationController?.replaceTopController(controller, animated: false, ready: ready) - }, baseNavigationController: navigationController, actionInteraction: actionInteraction) - gallery.temporaryDoNotWaitForReady = autoplayingVideo - return gallery - }) - } - } - } - if let otherMedia = otherMedia { - return .other(otherMedia) - } else { - return nil - } -} - -enum ChatMessagePreviewControllerData { - case instantPage(InstantPageGalleryController, Int, Media) - case gallery(GalleryController) -} - -func chatMessagePreviewControllerData(context: AccountContext, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> ChatMessagePreviewControllerData? { - if let mediaData = chatMessageGalleryControllerData(context: context, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, synchronousLoad: true, actionInteraction: nil) { - switch mediaData { - case let .gallery(gallery): - break - case let .instantPage(gallery, centralIndex, galleryMedia): - return .instantPage(gallery, centralIndex, galleryMedia) - default: - break - } - } - return nil -} - -func chatMediaListPreviewControllerData(context: AccountContext, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> Signal { - if let mediaData = chatMessageGalleryControllerData(context: context, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, synchronousLoad: true, actionInteraction: nil) { - switch mediaData { - case let .gallery(gallery): - return gallery - |> map { gallery in - return .gallery(gallery) - } - case let .instantPage(gallery, centralIndex, galleryMedia): - return .single(.instantPage(gallery, centralIndex, galleryMedia)) - default: - break - } - } - return .single(nil) -} +import WebsiteType +import GalleryData func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { - if let mediaData = chatMessageGalleryControllerData(context: params.context, message: params.message, navigationController: params.navigationController, standalone: params.standalone, reverseMessageGalleryOrder: params.reverseMessageGalleryOrder, mode: params.mode, synchronousLoad: false, actionInteraction: params.actionInteraction) { + if let mediaData = chatMessageGalleryControllerData(context: params.context, chatLocation: params.chatLocation, chatLocationContextHolder: params.chatLocationContextHolder, message: params.message, navigationController: params.navigationController, standalone: params.standalone, reverseMessageGalleryOrder: params.reverseMessageGalleryOrder, mode: params.mode, source: params.gallerySource, synchronousLoad: false, actionInteraction: params.actionInteraction) { switch mediaData { case let .url(url): params.openUrl(url) @@ -351,15 +133,19 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { control = .seek(time) } if (file.isVoice || file.isInstantVideo) && params.message.tags.contains(.voiceOrInstantVideo) { - if params.standalone { + if let playlistLocation = params.playlistLocation { + location = playlistLocation + } else if params.standalone { location = .recentActions(params.message) } else { location = .messages(peerId: params.message.id.peerId, tagMask: .voiceOrInstantVideo, at: params.message.id) } playerType = .voice } else if file.isMusic && params.message.tags.contains(.music) { - if params.standalone { - location = .recentActions(params.message) + if let playlistLocation = params.playlistLocation { + location = playlistLocation + } else if params.standalone { + location = .recentActions(params.message) } else { location = .messages(peerId: params.message.id.peerId, tagMask: .music, at: params.message.id) } @@ -455,52 +241,9 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { } func openChatInstantPage(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?, navigationController: NavigationController) { - for media in message.media { - if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { - if let _ = content.instantPage { - var textUrl: String? - if let pageUrl = URL(string: content.url) { - inner: for attribute in message.attributes { - if let attribute = attribute as? TextEntitiesMessageAttribute { - for entity in attribute.entities { - switch entity.type { - case let .TextUrl(url): - if let parsedUrl = URL(string: url) { - if pageUrl.scheme == parsedUrl.scheme && pageUrl.host == parsedUrl.host && pageUrl.path == parsedUrl.path { - textUrl = url - } - } - case .Url: - let nsText = message.text as NSString - var entityRange = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) - if entityRange.location + entityRange.length > nsText.length { - entityRange.location = max(0, nsText.length - entityRange.length) - entityRange.length = nsText.length - entityRange.location - } - let url = nsText.substring(with: entityRange) - if let parsedUrl = URL(string: url) { - if pageUrl.scheme == parsedUrl.scheme && pageUrl.host == parsedUrl.host && pageUrl.path == parsedUrl.path { - textUrl = url - } - } - default: - break - } - } - break inner - } - } - } - var anchor: String? - if let textUrl = textUrl, let anchorRange = textUrl.range(of: "#") { - anchor = String(textUrl[anchorRange.upperBound...]) - } - - let pageController = InstantPageController(context: context, webPage: webpage, sourcePeerType: sourcePeerType ?? .channel, anchor: anchor) - navigationController.pushViewController(pageController) - } - break - } + if let (webpage, anchor) = instantPageAndAnchor(message: message) { + let pageController = InstantPageController(context: context, webPage: webpage, sourcePeerType: sourcePeerType ?? .channel, anchor: anchor) + navigationController.pushViewController(pageController) } } diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 22df999139..fe62d3d07b 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -20,6 +20,7 @@ import LanguageLinkPreviewUI import SettingsUI import UrlHandling import ShareController +import ChatInterfaceState private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer { if case .default = navigation { @@ -89,6 +90,10 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur navigationController?.pushViewController(controller) case let .channelMessage(peerId, messageId): openPeer(peerId, .chat(textInputState: nil, subject: .message(messageId), peekData: nil)) + case let .replyThreadMessage(replyThreadMessageId, isChannelPost, maxReadMessageId, messageId): + if let navigationController = navigationController { + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .replyThread(threadMessageId: replyThreadMessageId, isChannelPost: isChannelPost, maxReadMessageId: maxReadMessageId), subject: .message(messageId))) + } case let .stickerPack(name): dismissInput() if false { diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift index d1f106392e..9581fbc8ff 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift @@ -15,6 +15,7 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer let type: MediaManagerPlayerType let initialMessageId: MessageId let initialOrder: MusicPlaybackSettingsOrder + let isGlobalSearch: Bool private weak var parentNavigationController: NavigationController? @@ -26,12 +27,13 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer private var accountInUseDisposable: Disposable? - init(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, parentNavigationController: NavigationController?) { + init(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, isGlobalSearch: Bool = false, parentNavigationController: NavigationController?) { self.context = context self.peerId = peerId self.type = type self.initialMessageId = initialMessageId self.initialOrder = initialOrder + self.isGlobalSearch = isGlobalSearch self.parentNavigationController = parentNavigationController super.init(navigationBarPresentationData: nil) @@ -52,7 +54,7 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer } override public func loadDisplayNode() { - self.displayNode = OverlayAudioPlayerControllerNode(context: self.context, peerId: self.peerId, type: self.type, initialMessageId: self.initialMessageId, initialOrder: self.initialOrder, requestDismiss: { [weak self] in + self.displayNode = OverlayAudioPlayerControllerNode(context: self.context, peerId: self.peerId, type: self.type, initialMessageId: self.initialMessageId, initialOrder: self.initialOrder, isGlobalSearch: self.isGlobalSearch, requestDismiss: { [weak self] in self?.dismiss() }, requestShare: { [weak self] messageId in if let strongSelf = self { diff --git a/submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift similarity index 94% rename from submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift rename to submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index dbdaa4786e..4d091f74d2 100644 --- a/submodules/TelegramUI/Sources/OverlayPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -20,6 +20,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu private let type: MediaManagerPlayerType private let requestDismiss: () -> Void private let requestShare: (MessageId) -> Void + private let isGlobalSearch: Bool private let controllerInteraction: ChatControllerInteraction @@ -40,13 +41,14 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu private var presentationDataDisposable: Disposable? private let replacementHistoryNodeReadyDisposable = MetaDisposable() - init(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, requestDismiss: @escaping () -> Void, requestShare: @escaping (MessageId) -> Void) { + init(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, isGlobalSearch: Bool, requestDismiss: @escaping () -> Void, requestShare: @escaping (MessageId) -> Void) { self.context = context self.peerId = peerId self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.type = type self.requestDismiss = requestDismiss self.requestShare = requestShare + self.isGlobalSearch = isGlobalSearch if case .regular = initialOrder { self.currentIsReversed = false @@ -131,6 +133,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in + }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false)) @@ -159,7 +162,9 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu tagMask = .voiceOrInstantVideo } - self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: tagMask, subject: .message(initialMessageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: currentIsReversed, displayHeaders: .none)) + let chatLocationContextHolder = Atomic(value: nil) + + self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(initialMessageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: isGlobalSearch)) super.init() @@ -237,7 +242,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu openMessageImpl = { [weak self] id in if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.historyNode.messageInCurrentHistoryView(id) { - return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in })) + return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in })) } return false } @@ -489,7 +494,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu tagMask = .voiceOrInstantVideo } - let historyNode = ChatHistoryListNode(context: self.context, chatLocation: .peer(self.peerId), tagMask: tagMask, subject: .message(messageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none)) + let chatLocationContextHolder = Atomic(value: nil) + let historyNode = ChatHistoryListNode(context: self.context, chatLocation: .peer(self.peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: .message(messageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch)) historyNode.preloadPages = true historyNode.stackFromBottom = true historyNode.updateFloatingHeaderOffset = { [weak self] offset, _ in diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift index 414c34cc5c..8bc01d9a22 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -13,6 +13,7 @@ import TelegramUIPreferences import UniversalMediaPlayer import TelegramBaseController import OverlayStatusController +import ListMessageItem private final class PassthroughContainerNode: ASDisplayNode { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { @@ -70,7 +71,8 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { self.selectedMessages = chatControllerInteraction.selectionState.flatMap { $0.selectedIds } self.selectedMessagesPromise.set(.single(self.selectedMessages)) - self.listNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: tagMask, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(search: false, reversed: false, displayHeaders: .allButLast)) + let chatLocationContextHolder = Atomic(value: nil) + self.listNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(search: false, reversed: false, displayHeaders: .allButLast, hintLinks: tagMask == .webPage, isGlobalSearch: false)) self.listNode.defaultToSynchronousTransactionWhileScrolling = true if tagMask == .music { @@ -267,7 +269,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { } if let id = state.id as? PeerMessagesMediaPlaylistItemId { if type == .music { - let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), account: account, chatLocation: .peer(id.messageId.peerId), tagMask: MessageTags.music) + let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), context: strongSelf.context, chatLocation: .peer(id.messageId.peerId), chatLocationContextHolder: Atomic(value: nil), tagMask: MessageTags.music) var cancelImpl: (() -> Void)? let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } @@ -304,7 +306,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { } else { controllerContext = strongSelf.context.sharedContext.makeTempAccountContext(account: account) } - let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, peerId: id.messageId.peerId, type: type, initialMessageId: id.messageId, initialOrder: order, parentNavigationController: strongSelf.chatControllerInteraction.navigationController()) + let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, peerId: id.messageId.peerId, type: type, initialMessageId: id.messageId, initialOrder: order, isGlobalSearch: false, parentNavigationController: strongSelf.chatControllerInteraction.navigationController()) strongSelf.view.window?.endEditing(true) strongSelf.chatControllerInteraction.presentController(controller, nil) } else if index.1 { diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift index a779b60417..5a905bda94 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift @@ -12,6 +12,8 @@ import RadialStatusNode import TelegramStringFormatting import GridMessageSelectionNode import UniversalMediaPlayer +import ListMessageItem +import ChatMessageInteractiveMediaBadge private final class FrameSequenceThumbnailNode: ASDisplayNode { private let context: AccountContext diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index 83ec96e376..7f4aa1e359 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -638,7 +638,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen } var discussionPeer: Peer? - if let linkedDiscussionPeerId = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { + if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { discussionPeer = peer } @@ -767,7 +767,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen } var discussionPeer: Peer? - if let linkedDiscussionPeerId = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { + if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { discussionPeer = peer } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 149defb74d..8ff6a0bbde 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -2851,6 +2851,8 @@ final class PeerInfoHeaderNode: ASDisplayNode { if let peer = peer { if peer.id == self.context.account.peerId && !self.isSettings { titleString = NSAttributedString(string: presentationData.strings.Conversation_SavedMessages, font: Font.medium(24.0), textColor: presentationData.theme.list.itemPrimaryTextColor) + } else if peer.id == self.context.account.peerId && !self.isSettings { + titleString = NSAttributedString(string: presentationData.strings.DialogList_Replies, font: Font.medium(24.0), textColor: presentationData.theme.list.itemPrimaryTextColor) } else { titleString = NSAttributedString(string: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.medium(24.0), textColor: presentationData.theme.list.itemPrimaryTextColor) } @@ -3299,14 +3301,14 @@ final class PeerInfoHeaderNode: ASDisplayNode { if buttonKeys.count > 3 { if self.isOpenedFromChat { switch buttonKey { - case .message, .search, .videoCall: + case .message, .search, .mute: hiddenWhileExpanded = true default: hiddenWhileExpanded = false } } else { switch buttonKey { - case .mute, .search, .videoCall: + case .mute, .search, .mute: hiddenWhileExpanded = true default: hiddenWhileExpanded = false diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index b8f58c38a2..67ccc1a56d 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -49,6 +49,9 @@ import LegacyMediaPickerUI import TelegramNotices import SaveToCameraRoll import PeerInfoUI +import ListMessageItem +import GalleryData +import ChatInterfaceState protocol PeerInfoScreenItem: class { var id: AnyHashable { get } @@ -377,7 +380,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, navigateMessageSearch: { _ in }, openCalendarSearch: { }, toggleMembersSearch: { _ in - }, navigateToMessage: { _ in + }, navigateToMessage: { _, _ in }, navigateToChat: { _ in }, navigateToProfile: { _ in }, openPeerInfo: { @@ -427,7 +430,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, openScheduledMessages: { }, openPeersNearby: { }, displaySearchResultsTooltip: { _, _ in - }, unarchivePeer: {}, statuses: nil) + }, unarchivePeer: {}, viewReplies: { _, _ in }, statuses: nil) self.selectionPanel.interfaceInteraction = interfaceInteraction @@ -1681,7 +1684,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD return } - let _ = (chatMediaListPreviewControllerData(context: strongSelf.context, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongSelf.controller?.navigationController as? NavigationController) + let _ = (chatMediaListPreviewControllerData(context: strongSelf.context, chatLocation: .peer(message.id.peerId), chatLocationContextHolder: Atomic(value: nil), message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongSelf.controller?.navigationController as? NavigationController) |> deliverOnMainQueue).start(next: { previewData in guard let strongSelf = self else { gesture?.cancel() @@ -1953,6 +1956,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in + }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, @@ -2713,7 +2717,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } self.view.endEditing(true) - return self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, message: galleryMessage, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { [weak self] in + return self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, chatLocation: nil, chatLocationContextHolder: nil, message: galleryMessage, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { [weak self] in self?.view.endEditing(true) }, present: { [weak self] c, a in self?.controller?.present(c, in: .window(.root), with: a, blockInteraction: true) @@ -2822,7 +2826,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD }) } case .discussion: - if let cachedData = self.data?.cachedData as? CachedChannelData, let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId { + if let cachedData = self.data?.cachedData as? CachedChannelData, case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId { if let navigationController = controller.navigationController as? NavigationController { self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(linkedDiscussionPeerId))) } @@ -4868,7 +4872,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } }) } - }, exceptionsList: .single(settings.notificationExceptions), archivedStickerPacks: .single(settings.archivedStickerPacks), privacySettings: .single(settings.privacySettings), hasWallet: .single(false), activeSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.0 }, webSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.2 }), cancel: { [weak self] in + }, resolvedFaqUrl: self.cachedFaq.get(), exceptionsList: .single(settings.notificationExceptions), archivedStickerPacks: .single(settings.archivedStickerPacks), privacySettings: .single(settings.privacySettings), hasWallet: .single(false), activeSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.0 }, webSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.2 }), cancel: { [weak self] in self?.deactivateSearch() }) } diff --git a/submodules/TelegramUI/Sources/PeerMediaCollectionController.swift b/submodules/TelegramUI/Sources/PeerMediaCollectionController.swift deleted file mode 100644 index 4fdb8667ec..0000000000 --- a/submodules/TelegramUI/Sources/PeerMediaCollectionController.swift +++ /dev/null @@ -1,971 +0,0 @@ -import Foundation -import UIKit -import Postbox -import SwiftSignalKit -import Display -import AsyncDisplayKit -import TelegramCore -import SyncCore -import SafariServices -import TelegramPresentationData -import TelegramUIPreferences -import TelegramBaseController -import OverlayStatusController -import AccountContext -import ShareController -import OpenInExternalAppUI -import PeerInfoUI -import ContextUI -import PresentationDataUtils -import LocalizedPeerData - -public class PeerMediaCollectionController: TelegramBaseController { - private var validLayout: ContainerViewLayout? - - private let context: AccountContext - private let peerId: PeerId - private let messageId: MessageId? - - private let peerDisposable = MetaDisposable() - private let navigationActionDisposable = MetaDisposable() - - private let messageIndexDisposable = MetaDisposable() - - private let _peerReady = Promise() - private var didSetPeerReady = false - private let peer = Promise(nil) - - private var interfaceState: PeerMediaCollectionInterfaceState - - private var rightNavigationButton: PeerMediaCollectionNavigationButton? - - private let galleryHiddenMesageAndMediaDisposable = MetaDisposable() - private var presentationDataDisposable:Disposable? - - private var controllerInteraction: ChatControllerInteraction? - private var interfaceInteraction: ChatPanelInterfaceInteraction? - - private let messageContextDisposable = MetaDisposable() - private var shareStatusDisposable: MetaDisposable? - - private var presentationData: PresentationData - - private var resolveUrlDisposable: MetaDisposable? - - public init(context: AccountContext, peerId: PeerId, messageId: MessageId? = nil) { - self.context = context - self.peerId = peerId - self.messageId = messageId - - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - self.interfaceState = PeerMediaCollectionInterfaceState(theme: self.presentationData.theme, strings: self.presentationData.strings) - - super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme).withUpdatedSeparatorColor(self.presentationData.theme.rootController.navigationBar.backgroundColor), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)), mediaAccessoryPanelVisibility: .specific(size: .compact), locationBroadcastPanelSource: .none) - - self.navigationPresentation = .modalInLargeLayout - - self.title = self.presentationData.strings.SharedMedia_TitleAll - - self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - - self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - - self.ready.set(.never()) - - self.scrollToTop = { [weak self] in - if let strongSelf = self, strongSelf.isNodeLoaded { - strongSelf.mediaCollectionDisplayNode.historyNode.scrollToEndOfHistory() - } - } - - self.presentationDataDisposable = (context.sharedContext.presentationData - |> deliverOnMainQueue).start(next: { [weak self] presentationData in - if let strongSelf = self { - let previousTheme = strongSelf.presentationData.theme - let previousStrings = strongSelf.presentationData.strings - let previousChatWallpaper = strongSelf.presentationData.chatWallpaper - - strongSelf.presentationData = presentationData - - if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings || presentationData.chatWallpaper != previousChatWallpaper { - strongSelf.themeAndStringsUpdated() - } - } - }) - - let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message, mode in - if let strongSelf = self, strongSelf.isNodeLoaded, let galleryMessage = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id) { - guard let navigationController = strongSelf.navigationController as? NavigationController else { - return false - } - strongSelf.mediaCollectionDisplayNode.view.endEditing(true) - return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, message: galleryMessage.message, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { - self?.mediaCollectionDisplayNode.view.endEditing(true) - }, present: { c, a in - self?.present(c, in: .window(.root), with: a, blockInteraction: true) - }, transitionNode: { messageId, media in - if let strongSelf = self { - return strongSelf.mediaCollectionDisplayNode.transitionNodeForGallery(messageId: messageId, media: media) - } - return nil - }, addToTransitionSurface: { view in - if let strongSelf = self { - var belowSubview: UIView? - if let historyNode = strongSelf.mediaCollectionDisplayNode.historyNode as? ChatHistoryGridNode { - if let lowestSectionNode = historyNode.lowestSectionNode() { - belowSubview = lowestSectionNode.view - } - } - strongSelf.mediaCollectionDisplayNode.historyNode - if let belowSubview = belowSubview { - strongSelf.mediaCollectionDisplayNode.historyNode.view.insertSubview(view, belowSubview: belowSubview) - } else { - strongSelf.mediaCollectionDisplayNode.historyNode.view.addSubview(view) - } - } - }, openUrl: { url in - self?.openUrl(url) - }, openPeer: { peer, navigation in - self?.controllerInteraction?.openPeer(peer.id, navigation, nil) - }, callPeer: { peerId, isVideo in - self?.controllerInteraction?.callPeer(peerId, isVideo) - }, enqueueMessage: { _ in - }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in })) - } - return false - }, openPeer: { [weak self] id, navigation, _ in - if let strongSelf = self, let id = id, let navigationController = strongSelf.navigationController as? NavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id))) - } - }, openPeerMention: { _ in - }, openMessageContextMenu: { [weak self] message, _, _, _, _ in - guard let strongSelf = self else { - return - } - (chatAvailableMessageActionsImpl(postbox: strongSelf.context.account.postbox, accountPeerId: strongSelf.context.account.peerId, messageIds: [message.id]) - |> deliverOnMainQueue).start(next: { actions in - var messageIds = Set() - messageIds.insert(message.id) - - if let strongSelf = self, strongSelf.isNodeLoaded { - if let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message { - let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) - var items: [ActionSheetButtonItem] = [] - - items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.SharedMedia_ViewInChat, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), subject: .message(message.id))) - } - })) - items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_ContextMenuForward, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.forwardMessages(messageIds) - } - })) - if actions.options.contains(.deleteLocally) || actions.options.contains(.deleteGlobally) { - items.append( ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_ContextMenuDelete, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.deleteMessages(messageIds) - } - })) - } - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - strongSelf.mediaCollectionDisplayNode.view.endEditing(true) - strongSelf.present(actionSheet, in: .window(.root)) - } - } - }) - }, openMessageContextActions: { [weak self] message, node, rect, gesture in - guard let strongSelf = self else { - gesture?.cancel() - return - } - - let _ = (chatMediaListPreviewControllerData(context: strongSelf.context, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongSelf.navigationController as? NavigationController) - |> deliverOnMainQueue).start(next: { previewData in - guard let strongSelf = self else { - gesture?.cancel() - return - } - if let previewData = previewData { - let context = strongSelf.context - let strings = strongSelf.presentationData.strings - let items = chatAvailableMessageActionsImpl(postbox: strongSelf.context.account.postbox, accountPeerId: strongSelf.context.account.peerId, messageIds: [message.id]) - |> map { actions -> [ContextMenuItem] in - var items: [ContextMenuItem] = [] - - items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { - if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), subject: .message(message.id))) - } - }) - }))) - - items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { c, f in - c.dismiss(completion: { - if let strongSelf = self { - strongSelf.forwardMessages([message.id]) - } - }) - }))) - - if actions.options.contains(.deleteLocally) || actions.options.contains(.deleteGlobally) { - items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { c, f in - c.setItems(context.account.postbox.transaction { transaction -> [ContextMenuItem] in - var items: [ContextMenuItem] = [] - let messageIds = [message.id] - - if let peer = transaction.getPeer(message.id.peerId) { - var personalPeerName: String? - var isChannel = false - if let user = peer as? TelegramUser { - personalPeerName = user.compactDisplayTitle - } else if let channel = peer as? TelegramChannel, case .broadcast = channel.info { - isChannel = true - } - - if actions.options.contains(.deleteGlobally) { - let globalTitle: String - if isChannel { - globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe - } else if let personalPeerName = personalPeerName { - globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).0 - } else { - globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone - } - items.append(.action(ContextMenuActionItem(text: globalTitle, textColor: .destructive, icon: { _ in nil }, action: { c, f in - c.dismiss(completion: { - if let strongSelf = self { - strongSelf.updateInterfaceState(animated: true, { $0.withoutSelectionState() }) - let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forEveryone).start() - } - }) - }))) - } - - if actions.options.contains(.deleteLocally) { - var localOptionText = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe - if strongSelf.context.account.peerId == strongSelf.peerId { - if messageIds.count == 1 { - localOptionText = strongSelf.presentationData.strings.Conversation_Moderate_Delete - } else { - localOptionText = strongSelf.presentationData.strings.Conversation_DeleteManyMessages - } - } - items.append(.action(ContextMenuActionItem(text: localOptionText, textColor: .destructive, icon: { _ in nil }, action: { c, f in - c.dismiss(completion: { - if let strongSelf = self { - strongSelf.updateInterfaceState(animated: true, { $0.withoutSelectionState() }) - let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forLocalPeer).start() - } - }) - }))) - } - } - - return items - }) - }))) - } - - return items - } - - switch previewData { - case let .gallery(gallery): - gallery.setHintWillBePresentedInPreviewingContext(true) - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: items, reactionItems: [], gesture: gesture) - strongSelf.presentInGlobalOverlay(contextController) - case .instantPage: - break - } - } - }) - }, navigateToMessage: { [weak self] fromId, id in - if let strongSelf = self, strongSelf.isNodeLoaded { - if id.peerId == strongSelf.peerId { - var fromIndex: MessageIndex? - - if let message = strongSelf.mediaCollectionDisplayNode.historyNode.messageInCurrentHistoryView(fromId) { - fromIndex = message.index - } - - /*if let fromIndex = fromIndex { - if let message = strongSelf.mediaCollectionDisplayNode.historyNode.messageInCurrentHistoryView(id) { - strongSelf.mediaCollectionDisplayNode.historyNode.scrollToMessage(from: fromIndex, to: MessageIndex(message)) - } else { - strongSelf.messageIndexDisposable.set((strongSelf.account.postbox.messageIndexAtId(id) |> deliverOnMainQueue).start(next: { [weak strongSelf] index in - if let strongSelf = strongSelf, let index = index { - strongSelf.mediaCollectionDisplayNode.historyNode.scrollToMessage(from: fromIndex, to: index) - } - })) - } - }*/ - } else { - (strongSelf.navigationController as? NavigationController)?.pushViewController(ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(id.peerId), subject: .message(id))) - } - } - }, tapMessage: nil, clickThroughMessage: { [weak self] in - self?.view.endEditing(true) - }, toggleMessagesSelection: { [weak self] ids, value in - if let strongSelf = self, strongSelf.isNodeLoaded { - strongSelf.updateInterfaceState(animated: true, { $0.withToggledSelectedMessages(ids, value: value) }) - } - }, sendCurrentMessage: { _ in - }, sendMessage: { _ in - }, sendSticker: { _, _, _, _ in - return false - }, sendGif: { _, _, _ in - return false - }, sendBotContextResultAsGif: { _, _, _, _ in - return false - }, requestMessageActionCallback: { _, _, _, _ in - }, requestMessageActionUrlAuth: { _, _, _ in - }, activateSwitchInline: { _, _ in - }, openUrl: { [weak self] url, _, external, _ in - self?.openUrl(url, external: external ?? false) - }, shareCurrentLocation: { - }, shareAccountContact: { - }, sendBotCommand: { _, _ in - }, openInstantPage: { [weak self] message, associatedData in - if let strongSelf = self, strongSelf.isNodeLoaded, let navigationController = strongSelf.navigationController as? NavigationController, let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message { - openChatInstantPage(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController) - } - }, openWallpaper: { [weak self] message in - if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message { - openChatWallpaper(context: strongSelf.context, message: message, present: { [weak self] c, a in - self?.present(c, in: .window(.root), with: a, blockInteraction: true) - }) - } - }, openTheme: { _ in - }, openHashtag: { _, _ in - }, updateInputState: { _ in - }, updateInputMode: { _ in - }, openMessageShareMenu: { _ in - }, presentController: { _, _ in - }, navigationController: { - return nil - }, chatControllerNode: { - return nil - }, reactionContainerNode: { - return nil - }, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in - }, longTap: { [weak self] content, _ in - if let strongSelf = self { - strongSelf.view.endEditing(true) - switch content { - case let .url(url): - let canOpenIn = availableOpenInOptions(context: strongSelf.context, item: .url(url: url)).count > 1 - let openText = canOpenIn ? strongSelf.presentationData.strings.Conversation_FileOpenIn : strongSelf.presentationData.strings.Conversation_LinkDialogOpen - let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) - actionSheet.setItemGroups([ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: url), - ActionSheetButtonItem(title: openText, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - if canOpenIn { - let actionSheet = OpenInActionSheetController(context: strongSelf.context, item: .url(url: url), openUrl: { [weak self] url in - if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { - strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: strongSelf.presentationData, navigationController: navigationController, dismissInput: { - }) - } - }) - strongSelf.present(actionSheet, in: .window(.root)) - } else { - strongSelf.context.sharedContext.applicationBindings.openUrl(url) - } - } - }), - ActionSheetButtonItem(title: strongSelf.presentationData.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - UIPasteboard.general.string = url - }), - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let link = URL(string: url) { - let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil) - } - }) - ]), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - strongSelf.present(actionSheet, in: .window(.root)) - default: - break - } - } - }, openCheckoutOrReceipt: { _ in - }, openSearch: { [weak self] in - self?.activateSearch() - }, setupReply: { _ in - }, canSetupReply: { _ in - return .none - }, navigateToFirstDateMessage: { _ in - }, requestRedeliveryOfFailedMessages: { _ in - }, addContact: { _ in - }, rateCall: { _, _, _ in - }, requestSelectMessagePollOptions: { _, _ in - }, requestOpenMessagePollResults: { _, _ in - }, openAppStorePage: { - }, displayMessageTooltip: { _, _, _, _ in - }, seekToTimecode: { _, _, _ in - }, scheduleCurrentMessage: { - }, sendScheduledMessagesNow: { _ in - }, editScheduledMessagesTime: { _ in - }, performTextSelectionAction: { _, _, _ in - }, updateMessageLike: { _, _ in - }, openMessageReactions: { _ in - }, displaySwipeToReplyHint: { - }, dismissReplyMarkupMessage: { _ in - }, openMessagePollResults: { _, _ in - }, openPollCreation: { _ in - }, displayPollSolution: { _, _ in - }, displayPsa: { _, _ in - }, displayDiceTooltip: { _ in - }, animateDiceSuccess: { - }, greetingStickerNode: { - return nil - }, openPeerContextMenu: { _, _, _, _ in - }, requestMessageUpdate: { _ in - }, cancelInteractiveKeyboardGestures: { - }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, - pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false)) - - self.controllerInteraction = controllerInteraction - - self.interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { _, _ in - }, setupEditMessage: { _, _ in - }, beginMessageSelection: { _, _ in - }, deleteSelectedMessages: { [weak self] in - if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds { - strongSelf.deleteMessages(messageIds) - } - }, reportSelectedMessages: { [weak self] in - if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty { - strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in - self?.present(c, in: .window(.root), with: a) - }, push: { c in - self?.push(c) - }, completion: { _ in }), in: .window(.root)) - } - }, reportMessages: { _, _ in - }, deleteMessages: { _, _, f in - f(.default) - }, forwardSelectedMessages: { [weak self] in - if let strongSelf = self { - if let forwardMessageIdsSet = strongSelf.interfaceState.selectionState?.selectedIds { - strongSelf.forwardMessages(forwardMessageIdsSet) - } - } - }, forwardCurrentForwardMessages: { - }, forwardMessages: { _ in - }, shareSelectedMessages: { [weak self] in - if let strongSelf = self, let selectedIds = strongSelf.interfaceState.selectionState?.selectedIds, !selectedIds.isEmpty { - let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Message] in - var messages: [Message] = [] - for id in selectedIds { - if let message = transaction.getMessage(id) { - messages.append(message) - } - } - return messages - } |> deliverOnMainQueue).start(next: { messages in - if let strongSelf = self, !messages.isEmpty { - strongSelf.updateInterfaceState(animated: true, { - $0.withoutSelectionState() - }) - - let shareController = ShareController(context: strongSelf.context, subject: .messages(messages.sorted(by: { lhs, rhs in - return lhs.index < rhs.index - })), externalShare: true, immediateExternalShare: true) - strongSelf.present(shareController, in: .window(.root)) - } - }) - } - }, updateTextInputStateAndMode: { _ in - }, updateInputModeAndDismissedButtonKeyboardMessageId: { _ in - }, openStickers: { - }, editMessage: { - }, beginMessageSearch: { _, _ in - }, dismissMessageSearch: { - }, updateMessageSearch: { _ in - }, openSearchResults: { - }, navigateMessageSearch: { _ in - }, openCalendarSearch: { - }, toggleMembersSearch: { _ in - }, navigateToMessage: { _ in - }, navigateToChat: { _ in - }, navigateToProfile: { _ in - }, openPeerInfo: { - }, togglePeerNotifications: { - }, sendContextResult: { _, _, _, _ in - return false - }, sendBotCommand: { _, _ in - }, sendBotStart: { _ in - }, botSwitchChatWithPayload: { _, _ in - }, beginMediaRecording: { _ in - }, finishMediaRecording: { _ in - }, stopMediaRecording: { - }, lockMediaRecording: { - }, deleteRecordedMedia: { - }, sendRecordedMedia: { - }, displayRestrictedInfo: { _, _ in - }, displayVideoUnmuteTip: { _ in - }, switchMediaRecordingMode: { - }, setupMessageAutoremoveTimeout: { - }, sendSticker: { _, _, _ in - return false - }, unblockPeer: { - }, pinMessage: { _ in - }, unpinMessage: { - }, shareAccountContact: { - }, reportPeer: { - }, presentPeerContact: { - }, dismissReportPeer: { - }, deleteChat: { - }, beginCall: { _ in - }, toggleMessageStickerStarred: { _ in - }, presentController: { _, _ in - }, getNavigationController: { - return nil - }, presentGlobalOverlayController: { _, _ in - }, navigateFeed: { - }, openGrouping: { - }, toggleSilentPost: { - }, requestUnvoteInMessage: { _ in - }, requestStopPollInMessage: { _ in - }, updateInputLanguage: { _ in - }, unarchiveChat: { - }, openLinkEditing: { - }, reportPeerIrrelevantGeoLocation: { - }, displaySlowmodeTooltip: { _, _ in - }, displaySendMessageOptions: { _, _ in - }, openScheduledMessages: { - }, openPeersNearby: { - }, displaySearchResultsTooltip: { _, _ in - }, unarchivePeer: {}, statuses: nil) - - self.updateInterfaceState(animated: false, { return $0 }) - - self.peer.set(context.account.postbox.peerView(id: peerId) |> map { $0.peers[$0.peerId] }) - - self.peerDisposable.set((self.peer.get() - |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self { - strongSelf.updateInterfaceState(animated: false, { return $0.withUpdatedPeer(peer) }) - if !strongSelf.didSetPeerReady { - strongSelf.didSetPeerReady = true - strongSelf._peerReady.set(.single(true)) - } - } - })) - } - - private func themeAndStringsUpdated() { - self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) - // self.chatTitleView?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) - self.updateInterfaceState(animated: false, { state in - var state = state - state = state.updatedTheme(self.presentationData.theme) - state = state.updatedStrings(self.presentationData.strings) - return state - }) - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.messageIndexDisposable.dispose() - self.navigationActionDisposable.dispose() - self.galleryHiddenMesageAndMediaDisposable.dispose() - self.messageContextDisposable.dispose() - self.resolveUrlDisposable?.dispose() - self.presentationDataDisposable?.dispose() - } - - var mediaCollectionDisplayNode: PeerMediaCollectionControllerNode { - get { - return super.displayNode as! PeerMediaCollectionControllerNode - } - } - - override public func loadDisplayNode() { - self.displayNode = PeerMediaCollectionControllerNode(context: self.context, peerId: self.peerId, messageId: self.messageId, controllerInteraction: self.controllerInteraction!, interfaceInteraction: self.interfaceInteraction!, navigationBar: self.navigationBar, requestDeactivateSearch: { [weak self] in - self?.deactivateSearch() - }) - - let mediaManager = self.context.sharedContext.mediaManager - self.galleryHiddenMesageAndMediaDisposable.set(mediaManager.galleryHiddenMediaManager.hiddenIds().start(next: { [weak self] ids in - if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { - var messageIdAndMedia: [MessageId: [Media]] = [:] - - for id in ids { - if case let .chat(accountId, messageId, media) = id, accountId == strongSelf.context.account.id { - messageIdAndMedia[messageId] = [media] - } - } - - //if controllerInteraction.hiddenMedia != messageIdAndMedia { - controllerInteraction.hiddenMedia = messageIdAndMedia - - strongSelf.mediaCollectionDisplayNode.historyNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? GridMessageItemNode { - itemNode.updateHiddenMedia() - } else if let itemNode = itemNode as? ListMessageNode { - itemNode.updateHiddenMedia() - } - } - //} - } - })) - - self.ready.set(combineLatest(self.mediaCollectionDisplayNode.historyNode.historyState.get(), self._peerReady.get()) |> map { $1 }) - - self.mediaCollectionDisplayNode.requestLayout = { [weak self] transition in - self?.requestLayout(transition: transition) - } - - self.mediaCollectionDisplayNode.requestUpdateMediaCollectionInterfaceState = { [weak self] animated, f in - self?.updateInterfaceState(animated: animated, f) - } - - self.displayNodeDidLoad() - } - - override public func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - } - - override public func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - self.mediaCollectionDisplayNode.historyNode.preloadPages = true - } - - override public func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - - self.mediaCollectionDisplayNode.clearHighlightAnimated(true) - } - - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - - self.validLayout = layout - - self.mediaCollectionDisplayNode.containerLayoutUpdated(layout, navigationBarHeightAndPrimaryHeight: (self.navigationHeight, self.primaryNavigationHeight), transition: transition, listViewTransaction: { updateSizeAndInsets in - self.mediaCollectionDisplayNode.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets) - }) - } - - func updateInterfaceState(animated: Bool = true, _ f: (PeerMediaCollectionInterfaceState) -> PeerMediaCollectionInterfaceState) { - let updatedInterfaceState = f(self.interfaceState) - - if self.isNodeLoaded { - self.mediaCollectionDisplayNode.updateMediaCollectionInterfaceState(updatedInterfaceState, animated: animated) - } - self.interfaceState = updatedInterfaceState - - if let button = rightNavigationButtonForPeerMediaCollectionInterfaceState(updatedInterfaceState, currentButton: self.rightNavigationButton, target: self, selector: #selector(self.rightNavigationButtonAction)) { - if self.rightNavigationButton != button { - self.navigationItem.setRightBarButton(button.buttonItem, animated: true) - } - self.rightNavigationButton = button - } else if let _ = self.rightNavigationButton { - self.navigationItem.setRightBarButton(nil, animated: true) - self.rightNavigationButton = nil - } - - if let controllerInteraction = self.controllerInteraction { - if updatedInterfaceState.selectionState != controllerInteraction.selectionState { - let animated = animated || controllerInteraction.selectionState == nil || updatedInterfaceState.selectionState == nil - controllerInteraction.selectionState = updatedInterfaceState.selectionState - self.mediaCollectionDisplayNode.historyNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView { - itemNode.updateSelectionState(animated: animated) - } else if let itemNode = itemNode as? GridMessageItemNode { - itemNode.updateSelectionState(animated: animated) - } - } - - self.mediaCollectionDisplayNode.selectedMessages = updatedInterfaceState.selectionState?.selectedIds - view.disablesInteractiveTransitionGestureRecognizer = updatedInterfaceState.selectionState != nil && self.mediaCollectionDisplayNode.historyNode is ChatHistoryGridNode - } - } - } - - @objc func rightNavigationButtonAction() { - if let button = self.rightNavigationButton { - self.navigationButtonAction(button.action) - } - } - - private func navigationButtonAction(_ action: PeerMediaCollectionNavigationButtonAction) { - switch action { - case .cancelMessageSelection: - self.updateInterfaceState(animated: true, { $0.withoutSelectionState() }) - case .beginMessageSelection: - self.updateInterfaceState(animated: true, { $0.withSelectionState() }) - } - } - - private func activateSearch() { - if self.displayNavigationBar { - if let scrollToTop = self.scrollToTop { - scrollToTop() - } - self.mediaCollectionDisplayNode.activateSearch() - self.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring)) - } - } - - private func deactivateSearch() { - if !self.displayNavigationBar { - self.setDisplayNavigationBar(true, transition: .animated(duration: 0.5, curve: .spring)) - self.mediaCollectionDisplayNode.deactivateSearch() - } - } - - private func openUrl(_ url: String, external: Bool = false) { - let disposable: MetaDisposable - if let current = self.resolveUrlDisposable { - disposable = current - } else { - disposable = MetaDisposable() - self.resolveUrlDisposable = disposable - } - - let resolvedUrl: Signal - if external { - resolvedUrl = .single(.externalUrl(url)) - } else { - resolvedUrl = self.context.sharedContext.resolveUrl(account: self.context.account, url: url) - } - - disposable.set((resolvedUrl |> deliverOnMainQueue).start(next: { [weak self] result in - if let strongSelf = self { - strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.navigationController as? NavigationController, openPeer: { peerId, navigation in - if let strongSelf = self { - switch navigation { - case let .chat(_, subject, peekData): - if let navigationController = strongSelf.navigationController as? NavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject, keepStack: .always, peekData: peekData)) - } - case .info: - strongSelf.navigationActionDisposable.set((strongSelf.context.account.postbox.loadedPeerWithId(peerId) - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) { - (strongSelf.navigationController as? NavigationController)?.pushViewController(infoController) - } - } - })) - case let .withBotStartPayload(startPayload): - if let navigationController = strongSelf.navigationController as? NavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), botStart: startPayload)) - } - default: - break - } - } - }, sendFile: nil, - sendSticker: nil, - present: { c, a in - self?.present(c, in: .window(.root), with: a) - }, dismissInput: { - self?.view.endEditing(true) - }, contentContext: nil) - } - })) - } - - func forwardMessages(_ messageIds: Set) { - let currentMessages = (self.mediaCollectionDisplayNode.searchDisplayController?.contentNode as? ChatHistorySearchContainerNode)?.currentMessages - let _ = (self.context.account.postbox.transaction { transaction -> Void in - for id in messageIds { - if transaction.getMessage(id) == nil { - if let message = currentMessages?[id] { - storeMessageFromSearch(transaction: transaction, message: message) - } - } - } - } - |> deliverOnMainQueue).start(completed: { [weak self] in - guard let strongSelf = self else { - return - } - let forwardMessageIds = Array(messageIds).sorted() - - let controller = strongSelf.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: strongSelf.context, filter: [.onlyWriteable, .excludeDisabled])) - controller.peerSelected = { [weak controller] peerId in - if let strongSelf = self, let _ = controller { - if peerId == strongSelf.context.account.peerId { - strongSelf.updateInterfaceState(animated: false, { $0.withoutSelectionState() }) - - let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in - return .forward(source: id, grouping: .auto, attributes: []) - }) - |> deliverOnMainQueue).start(next: { [weak self] messageIds in - if let strongSelf = self { - let signals: [Signal] = messageIds.compactMap({ id -> Signal? in - guard let id = id else { - return nil - } - return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id) - |> mapToSignal { status, _ -> Signal in - if status != nil { - return .never() - } else { - return .single(true) - } - } - |> take(1) - }) - if strongSelf.shareStatusDisposable == nil { - strongSelf.shareStatusDisposable = MetaDisposable() - } - strongSelf.shareStatusDisposable?.set((combineLatest(signals) - |> deliverOnMainQueue).start(completed: { - guard let strongSelf = self else { - return - } - strongSelf.present(OverlayStatusController(theme: strongSelf.presentationData.theme, type: .success), in: .window(.root)) - })) - } - }) - if let strongController = controller { - strongController.dismiss() - } - } else { - let _ = (strongSelf.context.account.postbox.transaction({ transaction -> Void in - transaction.updatePeerChatInterfaceState(peerId, update: { currentState in - if let currentState = currentState as? ChatInterfaceState { - return currentState.withUpdatedForwardMessageIds(forwardMessageIds) - } else { - return ChatInterfaceState().withUpdatedForwardMessageIds(forwardMessageIds) - } - }) - }) |> deliverOnMainQueue).start(completed: { - if let strongSelf = self { - strongSelf.updateInterfaceState(animated: false, { $0.withoutSelectionState() }) - - let ready = Promise() - strongSelf.messageContextDisposable.set((ready.get() |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { _ in - if let strongController = controller { - strongController.dismiss() - } - })) - - (strongSelf.navigationController as? NavigationController)?.replaceTopController(ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(peerId)), animated: false, ready: ready) - } - }) - } - } - } - (strongSelf.navigationController as? NavigationController)?.pushViewController(controller) - }) - } - - func deleteMessages(_ messageIds: Set) { - if !messageIds.isEmpty { - self.messageContextDisposable.set((combineLatest(self.context.sharedContext.chatAvailableMessageActions(postbox: self.context.account.postbox, accountPeerId: self.context.account.peerId, messageIds: messageIds), self.peer.get() |> take(1)) |> deliverOnMainQueue).start(next: { [weak self] actions, peer in - if let strongSelf = self, let peer = peer, !actions.options.isEmpty { - let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) - var items: [ActionSheetItem] = [] - var personalPeerName: String? - var isChannel = false - if let user = peer as? TelegramUser { - personalPeerName = user.compactDisplayTitle - } else if let channel = peer as? TelegramChannel, case .broadcast = channel.info { - isChannel = true - } - - if actions.options.contains(.deleteGlobally) { - let globalTitle: String - if isChannel { - globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe - } else if let personalPeerName = personalPeerName { - globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).0 - } else { - globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone - } - items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.updateInterfaceState(animated: true, { $0.withoutSelectionState() }) - let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forEveryone).start() - } - })) - } - if actions.options.contains(.deleteLocally) { - var localOptionText = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe - if strongSelf.context.account.peerId == strongSelf.peerId { - if messageIds.count == 1 { - localOptionText = strongSelf.presentationData.strings.Conversation_Moderate_Delete - } else { - localOptionText = strongSelf.presentationData.strings.Conversation_DeleteManyMessages - } - } - items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.updateInterfaceState(animated: true, { $0.withoutSelectionState() }) - let _ = deleteMessagesInteractively(account: strongSelf.context.account, messageIds: Array(messageIds), type: .forLocalPeer).start() - } - })) - } - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - strongSelf.present(actionSheet, in: .window(.root)) - } - })) - } - } -} - -private final class ContextControllerContentSourceImpl: ContextControllerContentSource { - let controller: ViewController - weak var sourceNode: ASDisplayNode? - - let navigationController: NavigationController? = nil - - let passthroughTouches: Bool = false - - init(controller: ViewController, sourceNode: ASDisplayNode?) { - self.controller = controller - self.sourceNode = sourceNode - } - - func transitionInfo() -> ContextControllerTakeControllerInfo? { - let sourceNode = self.sourceNode - return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in - if let sourceNode = sourceNode { - return (sourceNode, sourceNode.bounds) - } else { - return nil - } - }) - } - - func animatedIn() { - self.controller.didAppearInContextPreview() - } -} diff --git a/submodules/TelegramUI/Sources/PeerMediaCollectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerMediaCollectionControllerNode.swift deleted file mode 100644 index ac5bd47f2e..0000000000 --- a/submodules/TelegramUI/Sources/PeerMediaCollectionControllerNode.swift +++ /dev/null @@ -1,546 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Postbox -import SwiftSignalKit -import Display -import TelegramCore -import SyncCore -import TelegramPresentationData -import AccountContext -import SearchBarNode -import SearchUI -import ChatListSearchItemNode - -struct PeerMediaCollectionMessageForGallery { - let message: Message - let fromSearchResults: Bool -} - -private func historyNodeImplForMode(_ mode: PeerMediaCollectionMode, context: AccountContext, theme: PresentationTheme, peerId: PeerId, messageId: MessageId?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal?, NoError>) -> ChatHistoryNode & ASDisplayNode { - switch mode { - case .photoOrVideo: - let node = ChatHistoryGridNode(context: context, peerId: peerId, messageId: messageId, tagMask: .photoOrVideo, controllerInteraction: controllerInteraction) - node.showVerticalScrollIndicator = true - if theme.list.plainBackgroundColor.argb == 0xffffffff { - node.indicatorStyle = .default - } else { - node.indicatorStyle = .white - } - return node - case .file: - let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .file, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false, displayHeaders: .all)) - node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor - node.didEndScrolling = { [weak node] in - guard let node = node else { - return - } - fixSearchableListNodeScrolling(node) - } - node.preloadPages = true - return node - case .music: - let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .music, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false, displayHeaders: .all)) - node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor - node.didEndScrolling = { [weak node] in - guard let node = node else { - return - } - fixSearchableListNodeScrolling(node) - } - node.preloadPages = true - return node - case .webpage: - let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .webPage, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false, displayHeaders: .all)) - node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor - node.didEndScrolling = { [weak node] in - guard let node = node else { - return - } - fixSearchableListNodeScrolling(node) - } - node.preloadPages = true - return node - } -} - -private func updateLoadNodeState(_ node: PeerMediaCollectionEmptyNode, _ loadState: ChatHistoryNodeLoadState?) { - if let loadState = loadState { - switch loadState { - case .messages: - node.isHidden = true - node.isLoading = false - case .empty: - node.isHidden = false - node.isLoading = false - case .loading: - node.isHidden = false - node.isLoading = true - } - } else { - node.isHidden = false - node.isLoading = true - } -} - -private func tagMaskForMode(_ mode: PeerMediaCollectionMode) -> MessageTags { - switch mode { - case .photoOrVideo: - return .photoOrVideo - case .file: - return .file - case .music: - return .music - case .webpage: - return .webPage - } -} - -class PeerMediaCollectionControllerNode: ASDisplayNode { - private let context: AccountContext - private let peerId: PeerId - private let controllerInteraction: ChatControllerInteraction - private let interfaceInteraction: ChatPanelInterfaceInteraction - private let navigationBar: NavigationBar? - - private let sectionsNode: PeerMediaCollectionSectionsNode - - private(set) var historyNode: ChatHistoryNode & ASDisplayNode - private var historyEmptyNode: PeerMediaCollectionEmptyNode - - private(set) var searchDisplayController: SearchDisplayController? - - private let candidateHistoryNodeReadyDisposable = MetaDisposable() - private var candidateHistoryNode: (ASDisplayNode, PeerMediaCollectionMode)? - - private var containerLayout: (ContainerViewLayout, CGFloat)? - - var requestLayout: (ContainedViewLayoutTransition) -> Void = { _ in } - var requestUpdateMediaCollectionInterfaceState: (Bool, (PeerMediaCollectionInterfaceState) -> PeerMediaCollectionInterfaceState) -> Void = { _, _ in } - let requestDeactivateSearch: () -> Void - - private var mediaCollectionInterfaceState: PeerMediaCollectionInterfaceState - - private let selectedMessagesPromise = Promise?>(nil) - var selectedMessages: Set? { - didSet { - if self.selectedMessages != oldValue { - self.selectedMessagesPromise.set(.single(self.selectedMessages)) - } - } - } - private var selectionPanel: ChatMessageSelectionInputPanelNode? - private var selectionPanelSeparatorNode: ASDisplayNode? - private var selectionPanelBackgroundNode: ASDisplayNode? - - private var chatPresentationInterfaceState: ChatPresentationInterfaceState - - private var presentationData: PresentationData - - init(context: AccountContext, peerId: PeerId, messageId: MessageId?, controllerInteraction: ChatControllerInteraction, interfaceInteraction: ChatPanelInterfaceInteraction, navigationBar: NavigationBar?, requestDeactivateSearch: @escaping () -> Void) { - self.context = context - self.peerId = peerId - self.controllerInteraction = controllerInteraction - self.interfaceInteraction = interfaceInteraction - self.navigationBar = navigationBar - - self.requestDeactivateSearch = requestDeactivateSearch - - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - self.mediaCollectionInterfaceState = PeerMediaCollectionInterfaceState(theme: self.presentationData.theme, strings: self.presentationData.strings) - - self.sectionsNode = PeerMediaCollectionSectionsNode(theme: self.presentationData.theme, strings: self.presentationData.strings) - - self.historyNode = historyNodeImplForMode(self.mediaCollectionInterfaceState.mode, context: context, theme: self.presentationData.theme, peerId: peerId, messageId: messageId, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) - self.historyEmptyNode = PeerMediaCollectionEmptyNode(mode: self.mediaCollectionInterfaceState.mode, theme: self.presentationData.theme, strings: self.presentationData.strings) - self.historyEmptyNode.isHidden = true - - self.chatPresentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: .defaultValue, fontSize: self.presentationData.listsFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: .standard(previewing: false), chatLocation: .peer(self.peerId), isScheduledMessages: false, peerNearbyData: nil) - - super.init() - - self.setViewBlock({ - return UITracingLayerView() - }) - - self.historyNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor - self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor - - self.addSubnode(self.historyNode) - self.addSubnode(self.historyEmptyNode) - if let navigationBar = navigationBar { - self.addSubnode(navigationBar) - } - if let navigationBar = self.navigationBar { - self.insertSubnode(self.sectionsNode, aboveSubnode: navigationBar) - } else { - self.addSubnode(self.sectionsNode) - } - - self.sectionsNode.indexUpdated = { [weak self] index in - if let strongSelf = self { - let mode: PeerMediaCollectionMode - switch index { - case 0: - mode = .photoOrVideo - case 1: - mode = .file - case 2: - mode = .webpage - case 3: - mode = .music - default: - mode = .photoOrVideo - } - strongSelf.requestUpdateMediaCollectionInterfaceState(true, { $0.withMode(mode) }) - } - } - - updateLoadNodeState(self.historyEmptyNode, self.historyNode.loadState) - self.historyNode.setLoadStateUpdated { [weak self] loadState, _ in - if let strongSelf = self { - updateLoadNodeState(strongSelf.historyEmptyNode, loadState) - } - } - } - - deinit { - self.candidateHistoryNodeReadyDisposable.dispose() - } - - func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeightAndPrimaryHeight: (CGFloat, CGFloat), transition: ContainedViewLayoutTransition, listViewTransaction: (ListViewUpdateSizeAndInsets) -> Void) { - let navigationBarHeight = navigationBarHeightAndPrimaryHeight.0 - let primaryNavigationBarHeight = navigationBarHeightAndPrimaryHeight.1 - let navigationBarHeightDelta = (navigationBarHeight - primaryNavigationBarHeight) - - self.containerLayout = (layout, navigationBarHeight) - - var vanillaInsets = layout.insets(options: []) - vanillaInsets.top += navigationBarHeight - - var additionalInset: CGFloat = 0.0 - - if (navigationBarHeight - (layout.statusBarHeight ?? 0.0)).isLessThanOrEqualTo(44.0) { - } else { - additionalInset += 10.0 - } - - if let searchDisplayController = self.searchDisplayController { - searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) - if !searchDisplayController.isDeactivating { - vanillaInsets.top += (layout.statusBarHeight ?? 0.0) - navigationBarHeightDelta - } - } - - let sectionsHeight = self.sectionsNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalInset: additionalInset, transition: transition, interfaceState: self.mediaCollectionInterfaceState) - var sectionOffset: CGFloat = 0.0 - if primaryNavigationBarHeight.isZero { - sectionOffset = -sectionsHeight - navigationBarHeightDelta - } else { - //layout.statusBarHeight ?? 0.0 - //if navigationBarHeightAndPrimaryHeight.0 > navigationBarHeightAndPrimaryHeight.1 { - // sectionOffset += 1.0 - //}// - } - transition.updateFrame(node: self.sectionsNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight + sectionOffset), size: CGSize(width: layout.size.width, height: sectionsHeight))) - - var insets = vanillaInsets - if !primaryNavigationBarHeight.isZero { - insets.top += sectionsHeight - } - - if let inputHeight = layout.inputHeight { - insets.bottom += inputHeight - } - - if let selectionState = self.mediaCollectionInterfaceState.selectionState { - let interfaceState = self.chatPresentationInterfaceState.updatedPeer({ _ in self.mediaCollectionInterfaceState.peer.flatMap(RenderedPeer.init) }) - - if let selectionPanel = self.selectionPanel { - selectionPanel.selectedMessages = selectionState.selectedIds - let panelHeight = selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: 0.0, isSecondary: false, transition: transition, interfaceState: interfaceState, metrics: layout.metrics) - transition.updateFrame(node: selectionPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight))) - if let selectionPanelSeparatorNode = self.selectionPanelSeparatorNode { - transition.updateFrame(node: selectionPanelSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - } - if let selectionPanelBackgroundNode = self.selectionPanelBackgroundNode { - transition.updateFrame(node: selectionPanelBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: insets.bottom + panelHeight))) - } - } else { - let selectionPanelBackgroundNode = ASDisplayNode() - selectionPanelBackgroundNode.isLayerBacked = true - selectionPanelBackgroundNode.backgroundColor = self.mediaCollectionInterfaceState.theme.chat.inputPanel.panelBackgroundColor - self.addSubnode(selectionPanelBackgroundNode) - self.selectionPanelBackgroundNode = selectionPanelBackgroundNode - - let selectionPanel = ChatMessageSelectionInputPanelNode(theme: self.chatPresentationInterfaceState.theme, strings: self.chatPresentationInterfaceState.strings, peerMedia: true) - selectionPanel.context = self.context - selectionPanel.backgroundColor = self.presentationData.theme.chat.inputPanel.panelBackgroundColor - selectionPanel.interfaceInteraction = self.interfaceInteraction - selectionPanel.selectedMessages = selectionState.selectedIds - let panelHeight = selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: 0.0, isSecondary: false, transition: .immediate, interfaceState: interfaceState, metrics: layout.metrics) - self.selectionPanel = selectionPanel - self.addSubnode(selectionPanel) - - let selectionPanelSeparatorNode = ASDisplayNode() - selectionPanelSeparatorNode.isLayerBacked = true - selectionPanelSeparatorNode.backgroundColor = self.mediaCollectionInterfaceState.theme.chat.inputPanel.panelSeparatorColor - self.addSubnode(selectionPanelSeparatorNode) - self.selectionPanelSeparatorNode = selectionPanelSeparatorNode - - selectionPanel.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: panelHeight)) - selectionPanelBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: 0.0)) - selectionPanelSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: UIScreenPixel)) - transition.updateFrame(node: selectionPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight))) - transition.updateFrame(node: selectionPanelBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: insets.bottom + panelHeight))) - transition.updateFrame(node: selectionPanelSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - } - } else if let selectionPanel = self.selectionPanel { - self.selectionPanel = nil - transition.updateFrame(node: selectionPanel, frame: selectionPanel.frame.offsetBy(dx: 0.0, dy: selectionPanel.bounds.size.height + insets.bottom), completion: { [weak selectionPanel] _ in - selectionPanel?.removeFromSupernode() - }) - if let selectionPanelSeparatorNode = self.selectionPanelSeparatorNode { - transition.updateFrame(node: selectionPanelSeparatorNode, frame: selectionPanelSeparatorNode.frame.offsetBy(dx: 0.0, dy: selectionPanel.bounds.size.height + insets.bottom), completion: { [weak selectionPanelSeparatorNode] _ in - selectionPanelSeparatorNode?.removeFromSupernode() - }) - } - if let selectionPanelBackgroundNode = self.selectionPanelBackgroundNode { - transition.updateFrame(node: selectionPanelBackgroundNode, frame: selectionPanelBackgroundNode.frame.offsetBy(dx: 0.0, dy: selectionPanel.bounds.size.height + insets.bottom), completion: { [weak selectionPanelSeparatorNode] _ in - selectionPanelSeparatorNode?.removeFromSupernode() - }) - } - } - - let previousBounds = self.historyNode.bounds - self.historyNode.bounds = CGRect(x: previousBounds.origin.x, y: previousBounds.origin.y, width: layout.size.width, height: layout.size.height) - self.historyNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) - - self.historyNode.backgroundColor = self.mediaCollectionInterfaceState.theme.list.plainBackgroundColor - self.backgroundColor = self.mediaCollectionInterfaceState.theme.list.plainBackgroundColor - - self.historyEmptyNode.updateLayout(size: layout.size, insets: vanillaInsets, transition: transition, interfaceState: mediaCollectionInterfaceState) - transition.updateFrame(node: self.historyEmptyNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - - var additionalBottomInset: CGFloat = 0.0 - if let selectionPanel = self.selectionPanel { - additionalBottomInset = selectionPanel.bounds.size.height - } - - let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) - listViewTransaction(ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: insets.top, left: - insets.right + layout.safeInsets.right, bottom: insets.bottom + additionalBottomInset, right: insets.left + layout.safeInsets.right), duration: duration, curve: curve)) - - if let (candidateHistoryNode, _) = self.candidateHistoryNode { - let previousBounds = candidateHistoryNode.bounds - candidateHistoryNode.bounds = CGRect(x: previousBounds.origin.x, y: previousBounds.origin.y, width: layout.size.width, height: layout.size.height) - candidateHistoryNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) - - (candidateHistoryNode as! ChatHistoryNode).updateLayout(transition: transition, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: insets.top, left: - insets.right + layout.safeInsets.right, bottom: insets.bottom + additionalBottomInset, right: insets.left + layout.safeInsets.left), duration: duration, curve: curve)) - } - } - - func activateSearch() { - guard let (containerLayout, navigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar else { - return - } - - var maybePlaceholderNode: SearchBarPlaceholderNode? - if let listNode = historyNode as? ListView { - listNode.forEachItemNode { node in - if let node = node as? ChatListSearchItemNode { - maybePlaceholderNode = node.searchBarNode - } - } - } - - if let _ = self.searchDisplayController { - return - } - - if let placeholderNode = maybePlaceholderNode { - self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, contentNode: ChatHistorySearchContainerNode(context: self.context, peerId: self.peerId, tagMask: tagMaskForMode(self.mediaCollectionInterfaceState.mode), interfaceInteraction: self.controllerInteraction), cancel: { [weak self] in - self?.requestDeactivateSearch() - }) - - self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) - self.searchDisplayController?.activate(insertSubnode: { [weak self] subnode, isSearchBar in - if let strongSelf = self { - strongSelf.insertSubnode(subnode, belowSubnode: navigationBar) - } - }, placeholder: placeholderNode) - } - - } - - func deactivateSearch() { - if let searchDisplayController = self.searchDisplayController { - self.searchDisplayController = nil - var maybePlaceholderNode: SearchBarPlaceholderNode? - if let listNode = self.historyNode as? ListView { - listNode.forEachItemNode { node in - if let node = node as? ChatListSearchItemNode { - maybePlaceholderNode = node.searchBarNode - } - } - } - - searchDisplayController.deactivate(placeholder: maybePlaceholderNode) - } - } - - func updateMediaCollectionInterfaceState(_ mediaCollectionInterfaceState: PeerMediaCollectionInterfaceState, animated: Bool) { - if self.mediaCollectionInterfaceState != mediaCollectionInterfaceState { - if self.mediaCollectionInterfaceState.mode != mediaCollectionInterfaceState.mode { - let previousMode = self.mediaCollectionInterfaceState.mode - if let containerLayout = self.containerLayout, self.candidateHistoryNode == nil || self.candidateHistoryNode!.1 != mediaCollectionInterfaceState.mode { - let node = historyNodeImplForMode(mediaCollectionInterfaceState.mode, context: self.context, theme: self.presentationData.theme, peerId: self.peerId, messageId: nil, controllerInteraction: self.controllerInteraction, selectedMessages: self.selectedMessagesPromise.get()) - node.backgroundColor = mediaCollectionInterfaceState.theme.list.plainBackgroundColor - self.candidateHistoryNode = (node, mediaCollectionInterfaceState.mode) - - var vanillaInsets = containerLayout.0.insets(options: []) - vanillaInsets.top += containerLayout.1 - - if let searchDisplayController = self.searchDisplayController { - if !searchDisplayController.isDeactivating { - vanillaInsets.top += containerLayout.0.statusBarHeight ?? 0.0 - } - } - - var insets = vanillaInsets - - if !containerLayout.1.isZero { - insets.top += self.sectionsNode.bounds.size.height - } - - if let inputHeight = containerLayout.0.inputHeight { - insets.bottom += inputHeight - } - - let previousBounds = node.bounds - node.bounds = CGRect(x: previousBounds.origin.x, y: previousBounds.origin.y, width: containerLayout.0.size.width, height: containerLayout.0.size.height) - node.position = CGPoint(x: containerLayout.0.size.width / 2.0, y: containerLayout.0.size.height / 2.0) - - var additionalBottomInset: CGFloat = 0.0 - if let selectionPanel = self.selectionPanel { - additionalBottomInset = selectionPanel.bounds.size.height - } - - node.updateLayout(transition: .immediate, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: containerLayout.0.size, insets: UIEdgeInsets(top: insets.top, left: insets.right + containerLayout.0.safeInsets.right, bottom: insets.bottom + additionalBottomInset, right: insets.left + containerLayout.0.safeInsets.left), duration: 0.0, curve: .Default(duration: nil))) - - let historyEmptyNode = PeerMediaCollectionEmptyNode(mode: mediaCollectionInterfaceState.mode, theme: self.mediaCollectionInterfaceState.theme, strings: self.mediaCollectionInterfaceState.strings) - historyEmptyNode.isHidden = true - historyEmptyNode.updateLayout(size: containerLayout.0.size, insets: vanillaInsets, transition: .immediate, interfaceState: self.mediaCollectionInterfaceState) - historyEmptyNode.frame = CGRect(origin: CGPoint(), size: containerLayout.0.size) - - self.candidateHistoryNodeReadyDisposable.set((node.historyState.get() - |> deliverOnMainQueue).start(next: { [weak self, weak node] _ in - if let strongSelf = self, let strongNode = node, strongNode == strongSelf.candidateHistoryNode?.0 { - strongSelf.candidateHistoryNode = nil - strongSelf.insertSubnode(strongNode, belowSubnode: strongSelf.historyNode) - strongSelf.insertSubnode(historyEmptyNode, aboveSubnode: strongNode) - - let previousNode = strongSelf.historyNode - let previousEmptyNode = strongSelf.historyEmptyNode - strongSelf.historyNode = strongNode - strongSelf.historyEmptyNode = historyEmptyNode - updateLoadNodeState(strongSelf.historyEmptyNode, strongSelf.historyNode.loadState) - strongSelf.historyNode.setLoadStateUpdated { loadState, _ in - if let strongSelf = self { - updateLoadNodeState(strongSelf.historyEmptyNode, loadState) - } - } - - let directionMultiplier: CGFloat - if previousMode.rawValue < mediaCollectionInterfaceState.mode.rawValue { - directionMultiplier = 1.0 - } else { - directionMultiplier = -1.0 - } - - previousNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -directionMultiplier * strongSelf.bounds.width, y: 0.0), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak previousNode] _ in - previousNode?.removeFromSupernode() - }) - previousEmptyNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -directionMultiplier * strongSelf.bounds.width, y: 0.0), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak previousEmptyNode] _ in - previousEmptyNode?.removeFromSupernode() - }) - strongSelf.historyNode.layer.animatePosition(from: CGPoint(x: directionMultiplier * strongSelf.bounds.width, y: 0.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - strongSelf.historyEmptyNode.layer.animatePosition(from: CGPoint(x: directionMultiplier * strongSelf.bounds.width, y: 0.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - } - })) - } - } - - self.mediaCollectionInterfaceState = mediaCollectionInterfaceState - - self.requestLayout(animated ? .animated(duration: 0.4, curve: .spring) : .immediate) - } - } - - func updateHiddenMedia() { - self.historyNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView { - itemNode.updateHiddenMedia() - } else if let itemNode = itemNode as? ListMessageNode { - itemNode.updateHiddenMedia() - } else if let itemNode = itemNode as? GridMessageItemNode { - itemNode.updateHiddenMedia() - } - } - - if let searchContentNode = self.searchDisplayController?.contentNode as? ChatHistorySearchContainerNode { - searchContentNode.updateHiddenMedia() - } - } - - func messageForGallery(_ id: MessageId) -> PeerMediaCollectionMessageForGallery? { - if let message = self.historyNode.messageInCurrentHistoryView(id) { - return PeerMediaCollectionMessageForGallery(message: message, fromSearchResults: false) - } - - if let searchContentNode = self.searchDisplayController?.contentNode as? ChatHistorySearchContainerNode { - if let message = searchContentNode.messageForGallery(id) { - return PeerMediaCollectionMessageForGallery(message: message, fromSearchResults: true) - } - } - - return nil - } - - func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { - if let searchContentNode = self.searchDisplayController?.contentNode as? ChatHistorySearchContainerNode { - if let transitionNode = searchContentNode.transitionNodeForGallery(messageId: messageId, media: media) { - return transitionNode - } - } - - var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? - self.historyNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView { - if let result = itemNode.transitionNode(id: messageId, media: media) { - transitionNode = result - } - } else if let itemNode = itemNode as? ListMessageNode { - if let result = itemNode.transitionNode(id: messageId, media: media) { - transitionNode = result - } - } else if let itemNode = itemNode as? GridMessageItemNode { - if let result = itemNode.transitionNode(id: messageId, media: media) { - transitionNode = result - } - } - } - if let transitionNode = transitionNode { - return transitionNode - } - - return nil - } - - func clearHighlightAnimated(_ animated: Bool) { - if let listView = self.historyNode as? ListView { - listView.clearHighlightAnimated(animated) - } - } -} diff --git a/submodules/TelegramUI/Sources/PeerMediaCollectionInterfaceState.swift b/submodules/TelegramUI/Sources/PeerMediaCollectionInterfaceState.swift index c0dea9dd1a..5ef8ed2ef9 100644 --- a/submodules/TelegramUI/Sources/PeerMediaCollectionInterfaceState.swift +++ b/submodules/TelegramUI/Sources/PeerMediaCollectionInterfaceState.swift @@ -1,6 +1,7 @@ import Foundation import Postbox import TelegramPresentationData +import ChatInterfaceState enum PeerMediaCollectionMode: Int32 { case photoOrVideo diff --git a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift index b8be00a33d..6d072820b2 100644 --- a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift +++ b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift @@ -140,6 +140,26 @@ private enum NavigatedMessageFromViewPosition { case exact } +private func aroundMessagesFromMessages(_ messages: [Message], centralIndex: MessageIndex) -> [Message] { + guard let index = messages.firstIndex(where: { $0.index.id == centralIndex.id }) else { + return [] + } + var result: [Message] = [] + if index != 0 { + for i in (0 ..< index).reversed() { + result.append(messages[i]) + break + } + } + if index != messages.count - 1 { + for i in index + 1 ..< messages.count { + result.append(messages[i]) + break + } + } + return result +} + private func aroundMessagesFromView(view: MessageHistoryView, centralIndex: MessageIndex) -> [Message] { guard let index = view.entries.firstIndex(where: { $0.index.id == centralIndex.id }) else { return [] @@ -160,6 +180,45 @@ private func aroundMessagesFromView(view: MessageHistoryView, centralIndex: Mess return result } +private func navigatedMessageFromMessages(_ messages: [Message], anchorIndex: MessageIndex, position: NavigatedMessageFromViewPosition) -> (message: Message, around: [Message], exact: Bool)? { + var index = 0 + for message in messages { + if message.index.id == anchorIndex.id { + switch position { + case .exact: + return (message, aroundMessagesFromMessages(messages, centralIndex: message.index), true) + case .later: + if index + 1 < messages.count { + let message = messages[index + 1] + return (message, aroundMessagesFromMessages(messages, centralIndex: messages[index + 1].index), true) + } else { + return nil + } + case .earlier: + if index != 0 { + let message = messages[index - 1] + return (message, aroundMessagesFromMessages(messages, centralIndex: messages[index - 1].index), true) + } else { + return nil + } + } + } + index += 1 + } + if !messages.isEmpty { + switch position { + case .later, .exact: + let message = messages[messages.count - 1] + return (message, aroundMessagesFromMessages(messages, centralIndex: messages[messages.count - 1].index), false) + case .earlier: + let message = messages[0] + return (message, aroundMessagesFromMessages(messages, centralIndex: messages[0].index), false) + } + } else { + return nil + } +} + private func navigatedMessageFromView(_ view: MessageHistoryView, anchorIndex: MessageIndex, position: NavigatedMessageFromViewPosition) -> (message: Message, around: [Message], exact: Bool)? { var index = 0 for entry in view.entries { @@ -199,94 +258,6 @@ private func navigatedMessageFromView(_ view: MessageHistoryView, anchorIndex: M } } -enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation { - case messages(peerId: PeerId, tagMask: MessageTags, at: MessageId) - case singleMessage(MessageId) - case recentActions(Message) - - var playlistId: PeerMessagesMediaPlaylistId { - switch self { - case let .messages(peerId, _, _): - return .peer(peerId) - case let .singleMessage(id): - return .peer(id.peerId) - case let .recentActions(message): - return .recentActions(message.id.peerId) - } - } - - var messageId: MessageId? { - switch self { - case let .messages(_, _, messageId), let .singleMessage(messageId): - return messageId - default: - return nil - } - } - - func isEqual(to: SharedMediaPlaylistLocation) -> Bool { - if let to = to as? PeerMessagesPlaylistLocation { - return self == to - } else { - return false - } - } - - static func ==(lhs: PeerMessagesPlaylistLocation, rhs: PeerMessagesPlaylistLocation) -> Bool { - switch lhs { - case let .messages(peerId, tagMask, at): - if case .messages(peerId, tagMask, at) = rhs { - return true - } else { - return false - } - case let .singleMessage(messageId): - if case .singleMessage(messageId) = rhs { - return true - } else { - return false - } - case let .recentActions(lhsMessage): - if case let .recentActions(rhsMessage) = rhs, lhsMessage.id == rhsMessage.id { - return true - } else { - return false - } - } - } -} - -enum PeerMessagesMediaPlaylistId: Equatable, SharedMediaPlaylistId { - case peer(PeerId) - case recentActions(PeerId) - - func isEqual(to: SharedMediaPlaylistId) -> Bool { - if let to = to as? PeerMessagesMediaPlaylistId { - return self == to - } - return false - } -} - -func peerMessageMediaPlayerType(_ message: Message) -> MediaManagerPlayerType? { - if let file = extractFileMedia(message) { - if file.isVoice || file.isInstantVideo { - return .voice - } else if file.isMusic { - return .music - } - } - return nil -} - -func peerMessagesMediaPlaylistAndItemId(_ message: Message, isRecentActions: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? { - if isRecentActions { - return (PeerMessagesMediaPlaylistId.recentActions(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id)) - } else { - return (PeerMessagesMediaPlaylistId.peer(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id)) - } -} - private struct PlaybackStack { var ids: [MessageId] = [] var set: Set = [] @@ -375,9 +346,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { self.messagesLocation = location switch self.messagesLocation { - case let .messages(_, _, messageId): - self.loadItem(anchor: .messageId(messageId), navigation: .later) - case let .singleMessage(messageId): + case let .messages(_, _, messageId), let .singleMessage(messageId), let .custom(_, messageId, _): self.loadItem(anchor: .messageId(messageId), navigation: .later) case let .recentActions(message): self.loadingItem = false @@ -430,7 +399,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { self.currentItem = nil self.updateState() } else { - self.loadItem(anchor: .index(currentItem.current.index), navigation: navigation) + self.loadItem(anchor: .index(currentItem.current.index), navigation: navigation) } } } @@ -506,59 +475,78 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { switch anchor { case let .messageId(messageId): - if case let .messages(peerId, tagMask, _) = self.messagesLocation { - let historySignal = self.postbox.messageAtId(messageId) - |> take(1) - |> mapToSignal { message -> Signal<(Message, [Message])?, NoError> in - guard let message = message else { - return .single(nil) - } - - return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .index(message.index), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: []) - |> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in - if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: message.index, position: .exact) { - return .single((message, aroundMessages)) - } else { - return .single((message, [])) + switch self.messagesLocation { + case let .messages(peerId, tagMask, _): + let historySignal = self.postbox.messageAtId(messageId) + |> take(1) + |> mapToSignal { message -> Signal<(Message, [Message])?, NoError> in + guard let message = message else { + return .single(nil) } - } - } - |> take(1) - |> deliverOnMainQueue - self.navigationDisposable.set(historySignal.start(next: { [weak self] messageAndAroundMessages in - if let strongSelf = self { - assert(strongSelf.loadingItem) - strongSelf.loadingItem = false - if let (message, aroundMessages) = messageAndAroundMessages { - strongSelf.playbackStack.clear() - strongSelf.playbackStack.push(message.id) - strongSelf.currentItem = (message, aroundMessages) - strongSelf.playedToEnd = false - } else { - strongSelf.playedToEnd = true + return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .index(message.index), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: []) + |> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in + if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: message.index, position: .exact) { + return .single((message, aroundMessages)) + } else { + return .single((message, [])) + } } - strongSelf.updateState() } - })) - } else { - self.navigationDisposable.set((self.postbox.messageAtId(messageId) - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] message in - if let strongSelf = self { - assert(strongSelf.loadingItem) - - strongSelf.loadingItem = false - if let message = message { - strongSelf.playbackStack.clear() - strongSelf.playbackStack.push(message.id) - strongSelf.currentItem = (message, []) - } else { - strongSelf.currentItem = nil + |> take(1) + |> deliverOnMainQueue + self.navigationDisposable.set(historySignal.start(next: { [weak self] messageAndAroundMessages in + if let strongSelf = self { + assert(strongSelf.loadingItem) + + strongSelf.loadingItem = false + if let (message, aroundMessages) = messageAndAroundMessages { + strongSelf.playbackStack.clear() + strongSelf.playbackStack.push(message.id) + strongSelf.currentItem = (message, aroundMessages) + strongSelf.playedToEnd = false + } else { + strongSelf.playedToEnd = true + } + strongSelf.updateState() } - strongSelf.updateState() - } - })) + })) + case let .custom(messages, at, _): + self.navigationDisposable.set((messages + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] messages in + if let strongSelf = self { + assert(strongSelf.loadingItem) + + strongSelf.loadingItem = false + if let message = messages.0.first(where: { $0.id == at }) { + strongSelf.playbackStack.clear() + strongSelf.playbackStack.push(message.id) + strongSelf.currentItem = (message, []) + } else { + strongSelf.currentItem = nil + } + strongSelf.updateState() + } + })) + default: + self.navigationDisposable.set((self.postbox.messageAtId(messageId) + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] message in + if let strongSelf = self { + assert(strongSelf.loadingItem) + + strongSelf.loadingItem = false + if let message = message { + strongSelf.playbackStack.clear() + strongSelf.playbackStack.push(message.id) + strongSelf.currentItem = (message, []) + } else { + strongSelf.currentItem = nil + } + strongSelf.updateState() + } + })) } case let .index(index): switch self.messagesLocation { @@ -679,6 +667,107 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { self.loadingItem = false self.currentItem = (message, []) self.updateState() + case let .custom(messages, _, loadMore): + let inputIndex: Signal + let looping = self.looping + switch self.order { + case .regular, .reversed: + inputIndex = .single(index) + case .random: + var playbackStack = self.playbackStack + inputIndex = messages + |> map { messages, _, _ -> MessageIndex in + if case let .random(previous) = navigation, previous { + let _ = playbackStack.pop() + while true { + if let id = playbackStack.pop() { + if let message = messages.first(where: { $0.id == id }) { + return message.index + } + } else { + break + } + } + } + return messages.randomElement()?.index ?? index + } + } + let historySignal = inputIndex + |> mapToSignal { inputIndex -> Signal<(Message, [Message])?, NoError> in + return messages + |> mapToSignal { messages, _, loadMore -> Signal<(Message, [Message])?, NoError> in + let position: NavigatedMessageFromViewPosition + switch navigation { + case .later: + position = .later + case .earlier: + position = .earlier + case .random: + position = .exact + } + + if let (message, aroundMessages, exact) = navigatedMessageFromMessages(messages, anchorIndex: inputIndex, position: position) { + switch navigation { + case .random: + return .single((message, [])) + default: + if exact { + return .single((message, aroundMessages)) + } + } + } + + if case .all = looping { + let viewIndex: HistoryViewInputAnchor + if case .earlier = navigation { + viewIndex = .upperBound + } else { + viewIndex = .lowerBound + } + return .single(nil) +// return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: viewIndex, count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: []) +// |> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in +// let position: NavigatedMessageFromViewPosition +// switch navigation { +// case .later, .random: +// position = .earlier +// case .earlier: +// position = .later +// } +// if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: MessageIndex.absoluteLowerBound(), position: position) { +// return .single((message, aroundMessages)) +// } else { +// return .single(nil) +// } +// } + } else { + return .single(nil) + } + + return .complete() + } + } + |> take(1) + |> deliverOnMainQueue + self.navigationDisposable.set(historySignal.start(next: { [weak self] messageAndAroundMessages in + if let strongSelf = self { + assert(strongSelf.loadingItem) + + strongSelf.loadingItem = false + if let (message, aroundMessages) = messageAndAroundMessages { + if case let .random(previous) = navigation, previous { + strongSelf.playbackStack.resetToId(message.id) + } else { + strongSelf.playbackStack.push(message.id) + } + strongSelf.currentItem = (message, aroundMessages) + strongSelf.playedToEnd = false + } else { + strongSelf.playedToEnd = true + } + strongSelf.updateState() + } + })) } } } diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index b8cc8236fd..553a51b2a3 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -223,8 +223,9 @@ final class PeerSelectionControllerNode: ASDisplayNode { if let requestOpenMessageFromSearch = self?.requestOpenMessageFromSearch { requestOpenMessageFromSearch(peer, messageId) } - }, addContact: nil, peerContextAction: nil, present: { _ in - }), cancel: { [weak self] in + }, addContact: nil, peerContextAction: nil, present: { _, _ in + }, presentInGlobalOverlay: { _, _ in + }, navigationController: nil), cancel: { [weak self] in if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() } diff --git a/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift b/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift index 8d0a8134ae..1efca4fc26 100644 --- a/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift +++ b/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift @@ -5,6 +5,7 @@ import TelegramCore import SyncCore import Display import MergeLists +import AccountContext func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toView: ChatHistoryView, reason: ChatHistoryViewTransitionReason, reverse: Bool, chatLocation: ChatLocation, controllerInteraction: ChatControllerInteraction, scrollPosition: ChatHistoryViewScrollPosition?, initialData: InitialMessageHistoryData?, keyboardButtonsMessage: Message?, cachedData: CachedPeerData?, cachedDataMessages: [MessageId: Message]?, readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?, flashIndicators: Bool, updatedMessageSelection: Bool) -> ChatHistoryViewTransition { let mergeResult: (deleteIndices: [Int], indicesAndItems: [(Int, ChatHistoryEntry, Int?)], updateIndices: [(Int, ChatHistoryEntry, Int)]) diff --git a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift index 9fd3dc2846..14d0be64c1 100644 --- a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift @@ -256,7 +256,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { - self.interfaceInteraction?.navigateToMessage(self.messageId) + self.interfaceInteraction?.navigateToMessage(self.messageId, false) } } } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 5e6fdc18cf..682fe934b9 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -915,8 +915,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.navigateToChatImpl(accountId, peerId, messageId) } - public func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError> { - let historyView = preloadedChatHistoryViewForLocation(location, account: account, chatLocation: chatLocation, fixedCombinedReadStates: nil, tagMask: tagMask, additionalData: []) + public func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError> { + let historyView = preloadedChatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: tagMask, additionalData: []) return historyView |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in switch historyView { @@ -936,8 +936,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { }) } - public func makeOverlayAudioPlayerController(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController { - return OverlayAudioPlayerControllerImpl(context: context, peerId: peerId, type: type, initialMessageId: initialMessageId, initialOrder: initialOrder, parentNavigationController: parentNavigationController) + public func makeOverlayAudioPlayerController(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, isGlobalSearch: Bool, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController { + return OverlayAudioPlayerControllerImpl(context: context, peerId: peerId, type: type, initialMessageId: initialMessageId, initialOrder: initialOrder, isGlobalSearch: isGlobalSearch, parentNavigationController: parentNavigationController) } public func makeTempAccountContext(account: Account) -> AccountContext { @@ -1097,7 +1097,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { } public func makePeerSharedMediaController(context: AccountContext, peerId: PeerId) -> ViewController? { - return peerSharedMediaControllerImpl(context: context, peerId: peerId) + return nil } public func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?) -> ViewController { @@ -1194,6 +1194,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, greetingStickerNode: { return nil }, openPeerContextMenu: { _, _, _, _ in + }, openMessageReplies: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, diff --git a/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift b/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift index 9c3afbb6e3..ecb2de7a00 100644 --- a/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift +++ b/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift @@ -8,6 +8,7 @@ import OpenInExternalAppUI import MusicAlbumArtResources import LocalMediaResources import LocationResources +import ChatInterfaceState public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerChatInputState: { interfaceState, inputState -> PeerChatInterfaceState? in if interfaceState == nil { diff --git a/submodules/TelegramUI/Sources/TextLinkHandling.swift b/submodules/TelegramUI/Sources/TextLinkHandling.swift index f25dd539ed..afab3cbc48 100644 --- a/submodules/TelegramUI/Sources/TextLinkHandling.swift +++ b/submodules/TelegramUI/Sources/TextLinkHandling.swift @@ -15,6 +15,7 @@ import HashtagSearchUI import StickerPackPreviewUI import JoinLinkPreviewUI import PresentationDataUtils +import UrlWhitelist func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) { let presentImpl: (ViewController, Any?) -> Void = { controllerToPresent, _ in @@ -58,6 +59,10 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate if let navigationController = controller.navigationController as? NavigationController { context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), subject: .message(messageId))) } + case let .replyThreadMessage(replyThreadMessageId, isChannelPost, maxReadMessageId, messageId): + if let navigationController = controller.navigationController as? NavigationController { + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .replyThread(threadMessageId: replyThreadMessageId, isChannelPost: isChannelPost, maxReadMessageId: maxReadMessageId), subject: .message(messageId))) + } case let .stickerPack(name): let packReference: StickerPackReference = .name(name) controller.present(StickerPackScreen(context: context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root)) diff --git a/submodules/TemporaryCachedPeerDataManager/Sources/ChannelMemberCategoryListContext.swift b/submodules/TemporaryCachedPeerDataManager/Sources/ChannelMemberCategoryListContext.swift index a21ed2c762..21045ada0f 100644 --- a/submodules/TemporaryCachedPeerDataManager/Sources/ChannelMemberCategoryListContext.swift +++ b/submodules/TemporaryCachedPeerDataManager/Sources/ChannelMemberCategoryListContext.swift @@ -93,7 +93,7 @@ private func isParticipantMember(_ participant: ChannelParticipant, infoIsMember private extension CachedChannelAdminRank { init(participant: ChannelParticipant) { switch participant { - case let .creator(_, rank): + case let .creator(_, _, rank): if let rank = rank { self = .custom(rank) } else { diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 743e955e10..92d99b604a 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -13,11 +13,13 @@ import WalletUrl #endif private let baseTelegramMePaths = ["telegram.me", "t.me", "telegram.dog"] +private let baseTelegraPhPaths = ["telegra.ph/", "te.legra.ph/", "graph.org/", "t.me/iv?"] public enum ParsedInternalPeerUrlParameter { case botStart(String) case groupBotStart(String) case channelMessage(Int32) + case replyThread(Int32, Int32) } public enum ParsedInternalUrl { @@ -243,7 +245,24 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { return nil } } else if let value = Int(pathComponents[1]) { - return .peerName(peerName, .channelMessage(Int32(value))) + var commentId: Int32? + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value { + if queryItem.name == "comment" { + if let intValue = Int32(value) { + commentId = intValue + break + } + } + } + } + } + if let commentId = commentId { + return .peerName(peerName, .replyThread(Int32(value), commentId)) + } else { + return .peerName(peerName, .channelMessage(Int32(value))) + } } else { return nil } @@ -269,26 +288,35 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig } } } - |> map { peer -> ResolvedUrl? in + |> mapToSignal { peer -> Signal in if let peer = peer { if let parameter = parameter { switch parameter { case let .botStart(payload): - return .botStart(peerId: peer.id, payload: payload) + return .single(.botStart(peerId: peer.id, payload: payload)) case let .groupBotStart(payload): - return .groupBotStart(peerId: peer.id, payload: payload) + return .single(.groupBotStart(peerId: peer.id, payload: payload)) case let .channelMessage(id): - return .channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id)) + return .single(.channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id))) + case let .replyThread(id, replyId): + let replyThreadMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id) + return fetchChannelReplyThreadMessage(account: account, messageId: replyThreadMessageId) + |> map { result -> ResolvedUrl? in + guard let result = result else { + return .channelMessage(peerId: peer.id, messageId: replyThreadMessageId) + } + return .replyThreadMessage(replyThreadMessageId: result.messageId, isChannelPost: true, maxReadMessageId: result.maxReadMessageId, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId)) + } } } else { if let peer = peer as? TelegramUser, peer.botInfo == nil { - return .peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil)) + return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil))) } else { - return .peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil)) + return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil))) } } } else { - return .peer(nil, .info) + return .single(.peer(nil, .info)) } } case let .peerId(peerId): @@ -448,7 +476,6 @@ public func resolveUrlImpl(account: Account, url: String) -> Signal Bool { } return false } + +public func parseUrl(url: String, wasConcealed: Bool) -> (string: String, concealed: Bool) { + var parsedUrlValue: URL? + if url.hasPrefix("tel:") { + return (url, false) + } else if let parsed = URL(string: url) { + parsedUrlValue = parsed + } else if let parsed = URL(string: "https://" + url) { + parsedUrlValue = parsed + } else if let encoded = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let parsed = URL(string: encoded) { + parsedUrlValue = parsed + } + let host = parsedUrlValue?.host ?? url + + let rawHost = (host as NSString).removingPercentEncoding ?? host + var latin = CharacterSet() + latin.insert(charactersIn: "A"..."Z") + latin.insert(charactersIn: "a"..."z") + latin.insert(charactersIn: "0"..."9") + var punctuation = CharacterSet() + punctuation.insert(charactersIn: ".-/+_") + var hasLatin = false + var hasNonLatin = false + for c in rawHost { + if c.unicodeScalars.allSatisfy(punctuation.contains) { + } else if c.unicodeScalars.allSatisfy(latin.contains) { + hasLatin = true + } else { + hasNonLatin = true + } + } + var concealed = wasConcealed + if hasLatin && hasNonLatin { + concealed = true + } + + var rawDisplayUrl: String + if hasNonLatin { + rawDisplayUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? url + } else { + rawDisplayUrl = url + } + + if let parsedUrlValue = parsedUrlValue, isConcealedUrlWhitelisted(parsedUrlValue) { + concealed = false + } + + let whitelistedSchemes: [String] = [ + "tel", + ] + if let parsedUrlValue = parsedUrlValue, let scheme = parsedUrlValue.scheme, whitelistedSchemes.contains(scheme) { + concealed = false + } + + return (rawDisplayUrl, concealed) +} diff --git a/submodules/WalletUI/Resources/WalletStrings.mapping b/submodules/WalletUI/Resources/WalletStrings.mapping index 1246758922535bca4d24a763b4150cc1ff0b4957..0f39866777c67e74b19a01f779b9a0361509e1f3 100644 GIT binary patch delta 2275 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 (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[6]!, self._r[6]!, [_0]) - } - public var Wallet_Sent_Title: String { return self._s[7]! } - public var Wallet_Receive_ShareUrlInfo: String { return self._s[8]! } - public var Wallet_RestoreFailed_Title: String { return self._s[9]! } - public var Wallet_TransactionInfo_CopyAddress: String { return self._s[11]! } - public var Wallet_Settings_BackupWallet: String { return self._s[12]! } - public var Wallet_Send_NetworkErrorTitle: String { return self._s[13]! } - public var Wallet_Month_ShortJune: String { return self._s[14]! } - public var Wallet_TransactionInfo_StorageFeeInfo: String { return self._s[15]! } - public var Wallet_Created_Title: String { return self._s[16]! } - public func Wallet_Configuration_ApplyErrorTextURLUnreachable(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[17]!, self._r[17]!, [_0]) - } - public var Wallet_Send_SyncInProgress: String { return self._s[18]! } - public var Wallet_Info_YourBalance: String { return self._s[19]! } - public var Wallet_Configuration_ApplyErrorTextURLInvalidData: String { return self._s[20]! } - public var Wallet_TransactionInfo_CommentHeader: String { return self._s[21]! } - public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[22]! } - public func Wallet_Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[23]!, self._r[23]!, [_1, _2, _3]) - } - public var Wallet_Settings_ConfigurationInfo: String { return self._s[24]! } - public var Wallet_WordImport_IncorrectText: String { return self._s[25]! } - public var Wallet_Month_GenJanuary: String { return self._s[26]! } - public var Wallet_Send_OwnAddressAlertTitle: String { return self._s[27]! } - public var Wallet_Receive_ShareAddress: String { return self._s[28]! } - public var Wallet_WordImport_Title: String { return self._s[29]! } - public var Wallet_TransactionInfo_Title: String { return self._s[30]! } - public var Wallet_Words_NotDoneText: String { return self._s[32]! } - public var Wallet_RestoreFailed_EnterWords: String { return self._s[33]! } - public var Wallet_WordImport_Text: String { return self._s[34]! } - public var Wallet_RestoreFailed_Text: String { return self._s[36]! } - public var Wallet_TransactionInfo_NoAddress: String { return self._s[37]! } - public var Wallet_Navigation_Back: String { return self._s[38]! } - public var Wallet_Intro_Terms: String { return self._s[39]! } - public func Wallet_Send_Balance(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[40]!, self._r[40]!, [_0]) - } - public func Wallet_Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[41]!, self._r[41]!, [_1, _2, _3]) - } - public var Wallet_TransactionInfo_AddressCopied: String { return self._s[42]! } - public func Wallet_Info_TransactionDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[43]!, self._r[43]!, [_1, _2, _3]) - } - public var Wallet_Send_NetworkErrorText: String { return self._s[44]! } - public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[45]! } - public var Wallet_Intro_ImportExisting: String { return self._s[46]! } - public var Wallet_Receive_CommentInfo: String { return self._s[47]! } - public var Wallet_WordCheck_Continue: String { return self._s[48]! } - public var Wallet_Send_EncryptComment: String { return self._s[49]! } - public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[50]! } - public var Wallet_Completed_Text: String { return self._s[51]! } - public var Wallet_WordCheck_IncorrectHeader: String { return self._s[53]! } - public var Wallet_Configuration_SourceHeader: String { return self._s[54]! } - public var Wallet_TransactionInfo_StorageFeeInfoUrl: String { return self._s[55]! } - public var Wallet_Receive_Title: String { return self._s[56]! } - public var Wallet_Info_WalletCreated: String { return self._s[57]! } - public var Wallet_Navigation_Cancel: String { return self._s[58]! } - public var Wallet_CreateInvoice_Title: String { return self._s[59]! } - public func Wallet_WordCheck_Text(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[60]!, self._r[60]!, [_1, _2, _3]) - } - public var Wallet_TransactionInfo_SenderHeader: String { return self._s[61]! } - public func Wallet_Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[62]!, self._r[62]!, [_1, _2, _3]) - } - public var Wallet_Month_GenAugust: String { return self._s[63]! } - public var Wallet_Info_UnknownTransaction: String { return self._s[64]! } - public var Wallet_Receive_CreateInvoice: String { return self._s[65]! } - public var Wallet_Month_GenSeptember: String { return self._s[66]! } - public var Wallet_Month_GenJuly: String { return self._s[67]! } - public var Wallet_Receive_AddressHeader: String { return self._s[68]! } - public var Wallet_Send_AmountText: String { return self._s[69]! } - public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[70]! } - public func Wallet_Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[71]!, self._r[71]!, [_1, _2, _3]) - } public func Wallet_Updated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[72]!, self._r[72]!, [_0]) + return formatWithArgumentRanges(self._s[0]!, self._r[0]!, [_0]) } - public var Wallet_Configuration_Title: String { return self._s[74]! } - public var Wallet_Configuration_BlockchainIdHeader: String { return self._s[75]! } - public var Wallet_Words_Title: String { return self._s[76]! } - public var Wallet_Month_ShortMay: String { return self._s[77]! } - public var Wallet_WordCheck_Title: String { return self._s[78]! } - public var Wallet_Words_NotDoneResponse: String { return self._s[79]! } - public var Wallet_Configuration_SourceURL: String { return self._s[80]! } - public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[81]! } - public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[82]! } - public func Wallet_Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + public var Wallet_Receive_AddressHeader: String { return self._s[2]! } + public var Wallet_Navigation_Cancel: String { return self._s[3]! } + public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[4]! } + public var Wallet_Month_GenNovember: String { return self._s[5]! } + public var Wallet_Month_GenApril: String { return self._s[6]! } + public var Wallet_Weekday_Today: String { return self._s[7]! } + public var Wallet_Info_ReceiveGrams: String { return self._s[9]! } + public var Wallet_TransactionInfo_Title: String { return self._s[10]! } + public var Wallet_Receive_CommentHeader: String { return self._s[11]! } + public func Wallet_Sent_Text(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[12]!, self._r[12]!, [_0]) + } + public var Wallet_Info_WalletCreated: String { return self._s[14]! } + public var Wallet_Month_GenJanuary: String { return self._s[15]! } + public var Wallet_Send_NetworkErrorTitle: String { return self._s[16]! } + public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[17]! } + public var Wallet_WordCheck_Continue: String { return self._s[18]! } + public func Wallet_Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[19]!, self._r[19]!, [_1, _2, _3]) + } + public var Wallet_Created_ExportErrorText: String { return self._s[20]! } + public var Wallet_Info_RefreshErrorText: String { return self._s[21]! } + public var Wallet_Month_GenSeptember: String { return self._s[22]! } + public var Wallet_Month_GenDecember: String { return self._s[23]! } + public var Wallet_Sent_Title: String { return self._s[24]! } + public func Wallet_SecureStorageChanged_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[25]!, self._r[25]!, [_0]) + } + public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[26]! } + public var Wallet_Receive_CopyAddress: String { return self._s[27]! } + public var Wallet_TransactionInfo_SenderHeader: String { return self._s[28]! } + public var Wallet_Words_NotDoneOk: String { return self._s[29]! } + public var Wallet_Receive_AmountHeader: String { return self._s[30]! } + public var Wallet_Configuration_SourceJSON: String { return self._s[31]! } + public var Wallet_Send_ErrorInvalidAddress: String { return self._s[32]! } + public var Wallet_Receive_ShareUrlInfo: String { return self._s[33]! } + public var Wallet_Words_Text: String { return self._s[34]! } + public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[35]! } + public var Wallet_Send_AddressInfo: String { return self._s[36]! } + public var Wallet_Month_ShortJuly: String { return self._s[37]! } + public var Wallet_AccessDenied_Settings: String { return self._s[38]! } + public var Wallet_WordImport_IncorrectTitle: String { return self._s[39]! } + public var Wallet_Completed_Text: String { return self._s[40]! } + public var Wallet_Info_TransactionFrom: String { return self._s[41]! } + public func Wallet_Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[42]!, self._r[42]!, [_1, _2, _3]) + } + public var Wallet_Configuration_ApplyErrorTextJSONInvalidData: String { return self._s[43]! } + public var Wallet_SecureStorageReset_PasscodeText: String { return self._s[44]! } + public var Wallet_Month_ShortNovember: String { return self._s[46]! } + public var Wallet_Configuration_BlockchainNameChangedText: String { return self._s[47]! } + public var Wallet_Configuration_ApplyErrorTextURLInvalid: String { return self._s[48]! } + public var Wallet_Send_UninitializedText: String { return self._s[49]! } + public var Wallet_WordImport_Title: String { return self._s[50]! } + public func Wallet_Info_TransactionDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[51]!, self._r[51]!, [_1, _2, _3]) + } + public var Wallet_Send_AddressHeader: String { return self._s[52]! } + public var Wallet_Send_Title: String { return self._s[53]! } + public var Wallet_Send_SendAnyway: String { return self._s[54]! } + public func Wallet_Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[55]!, self._r[55]!, [_1, _2, _3]) + } + public var Wallet_UnknownError: String { return self._s[56]! } + public var Wallet_Month_ShortApril: String { return self._s[57]! } + public var Wallet_Settings_ConfigurationInfo: String { return self._s[58]! } + public var Wallet_Qr_ScanCode: String { return self._s[59]! } + public var Wallet_Info_Address: String { return self._s[60]! } + public func Wallet_Configuration_ApplyErrorTextURLUnreachable(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[61]!, self._r[61]!, [_0]) + } + public var Wallet_Month_ShortMay: String { return self._s[62]! } + public var Wallet_Send_OwnAddressAlertTitle: String { return self._s[63]! } + public var Wallet_TransactionInfo_OtherFeeInfoUrl: String { return self._s[64]! } + public var Wallet_WordCheck_IncorrectText: String { return self._s[65]! } + public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[66]! } + public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[67]! } + public var Wallet_TransactionInfo_StorageFeeInfoUrl: String { return self._s[68]! } + public var Wallet_Navigation_Back: String { return self._s[69]! } + public var Wallet_Send_Confirmation: String { return self._s[70]! } + public var Wallet_Configuration_SourceURL: String { return self._s[71]! } + public var Wallet_Intro_CreateErrorTitle: String { return self._s[72]! } + public var Wallet_Month_GenJuly: String { return self._s[73]! } + public var Wallet_Words_NotDoneTitle: String { return self._s[74]! } + public var Wallet_TransactionInfo_SendGrams: String { return self._s[75]! } + public func Wallet_SecureStorageReset_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[76]!, self._r[76]!, [_0]) + } + public var Wallet_Send_UninitializedTitle: String { return self._s[77]! } + public var Wallet_Created_Title: String { return self._s[78]! } + public var Wallet_Settings_Title: String { return self._s[79]! } + public var Wallet_Completed_ViewWallet: String { return self._s[80]! } + public var Wallet_Send_SyncInProgress: String { return self._s[81]! } + public var Wallet_Configuration_SourceHeader: String { return self._s[82]! } + public func Wallet_Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[83]!, self._r[83]!, [_1, _2, _3]) } - public var Wallet_Info_Address: String { return self._s[84]! } - public var Wallet_Intro_CreateWallet: String { return self._s[85]! } - public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[86]! } - public func Wallet_SecureStorageReset_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[87]!, self._r[87]!, [_0]) + public var Wallet_Info_Updating: String { return self._s[84]! } + public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[85]! } + public var Wallet_Month_ShortMarch: String { return self._s[86]! } + public var Wallet_Send_Send: String { return self._s[87]! } + public var Wallet_Send_TransactionInProgress: String { return self._s[88]! } + public var Wallet_Month_ShortJanuary: String { return self._s[89]! } + public var Wallet_Navigation_Done: String { return self._s[90]! } + public var Wallet_Words_NotDoneText: String { return self._s[91]! } + public var Wallet_Month_GenMay: String { return self._s[92]! } + public var Wallet_TransactionInfo_AddressCopied: String { return self._s[93]! } + public var Wallet_Month_GenMarch: String { return self._s[94]! } + public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[95]! } + public var Wallet_RestoreFailed_CreateWallet: String { return self._s[96]! } + public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[97]! } + public var Wallet_Receive_AmountText: String { return self._s[98]! } + public var Wallet_Receive_ShareAddress: String { return self._s[99]! } + public var Wallet_Receive_CommentInfo: String { return self._s[100]! } + public var Wallet_Intro_Text: String { return self._s[101]! } + public var Wallet_WordImport_IncorrectText: String { return self._s[102]! } + public var Wallet_Month_GenFebruary: String { return self._s[104]! } + public var Wallet_Send_NetworkErrorText: String { return self._s[105]! } + public var Wallet_Created_Proceed: String { return self._s[106]! } + public var Wallet_Info_UnknownTransaction: String { return self._s[107]! } + public func Wallet_Send_Balance(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[108]!, self._r[108]!, [_0]) } - public var Wallet_Send_SendAnyway: String { return self._s[88]! } - public var Wallet_UnknownError: String { return self._s[89]! } - public var Wallet_Configuration_ApplyErrorTextURLInvalid: String { return self._s[90]! } - public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[91]! } - public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[93]! } - public var Wallet_Configuration_SourceInfo: String { return self._s[94]! } - public var Wallet_Words_NotDoneOk: String { return self._s[95]! } - public var Wallet_Intro_Title: String { return self._s[96]! } - public var Wallet_Info_Receive: String { return self._s[97]! } - public var Wallet_Completed_ViewWallet: String { return self._s[98]! } - public var Wallet_Month_ShortJuly: String { return self._s[99]! } - public var Wallet_Month_ShortApril: String { return self._s[100]! } - public func Wallet_Info_TransactionDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[101]!, self._r[101]!, [_1, _2]) - } - public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[102]! } - public func Wallet_Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[103]!, self._r[103]!, [_1, _2, _3]) - } - public var Wallet_Send_UninitializedText: String { return self._s[105]! } - public func Wallet_Sent_Text(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[106]!, self._r[106]!, [_0]) - } - public var Wallet_Month_GenNovember: String { return self._s[107]! } + public var Wallet_SecureStorageReset_Title: String { return self._s[110]! } + public var Wallet_Configuration_Title: String { return self._s[111]! } + public var Wallet_Send_ErrorDecryptionFailed: String { return self._s[112]! } + public var Wallet_CreateInvoice_Title: String { return self._s[113]! } + public var Wallet_Info_Receive: String { return self._s[114]! } + public var Wallet_Sending_Text: String { return self._s[115]! } + public var Wallet_Intro_NotNow: String { return self._s[116]! } + public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[117]! } + public var Wallet_TransactionInfo_CommentHeader: String { return self._s[118]! } + public var Wallet_Intro_CreateErrorText: String { return self._s[119]! } + public var Wallet_Weekday_Yesterday: String { return self._s[120]! } + public var Wallet_Configuration_ApplyErrorTitle: String { return self._s[121]! } + public var Wallet_Info_Send: String { return self._s[122]! } public func Wallet_Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[108]!, self._r[108]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[123]!, self._r[123]!, [_1, _2, _3]) } - public var Wallet_Month_GenApril: String { return self._s[109]! } - public var Wallet_Month_ShortMarch: String { return self._s[110]! } - public var Wallet_Month_GenFebruary: String { return self._s[111]! } - public var Wallet_Qr_ScanCode: String { return self._s[112]! } - public var Wallet_Receive_AddressCopied: String { return self._s[113]! } - public var Wallet_Send_UninitializedTitle: String { return self._s[114]! } - public var Wallet_AccessDenied_Title: String { return self._s[115]! } - public var Wallet_AccessDenied_Settings: String { return self._s[116]! } - public var Wallet_Send_Send: String { return self._s[117]! } - public var Wallet_Info_RefreshErrorTitle: String { return self._s[118]! } - public var Wallet_Month_GenJune: String { return self._s[119]! } - public var Wallet_Send_AddressHeader: String { return self._s[120]! } - public var Wallet_SecureStorageReset_BiometryTouchId: String { return self._s[121]! } - public var Wallet_Send_Confirmation: String { return self._s[122]! } - public var Wallet_Completed_Title: String { return self._s[123]! } - public var Wallet_Alert_OK: String { return self._s[124]! } - public var Wallet_Settings_DeleteWallet: String { return self._s[125]! } - public var Wallet_SecureStorageReset_PasscodeText: String { return self._s[126]! } - public var Wallet_Month_ShortSeptember: String { return self._s[127]! } - public var Wallet_Info_TransactionTo: String { return self._s[128]! } - public var Wallet_Send_ConfirmationConfirm: String { return self._s[129]! } - public var Wallet_TransactionInfo_OtherFeeInfo: String { return self._s[130]! } - public var Wallet_Receive_AmountText: String { return self._s[131]! } - public var Wallet_Receive_CopyAddress: String { return self._s[132]! } - public var Wallet_Intro_Text: String { return self._s[134]! } - public var Wallet_Configuration_Apply: String { return self._s[135]! } - public func Wallet_SecureStorageChanged_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[136]!, self._r[136]!, [_0]) + public var Wallet_Intro_CreateWallet: String { return self._s[124]! } + public var Wallet_Sending_Title: String { return self._s[125]! } + public var Wallet_Updated_JustNow: String { return self._s[126]! } + public func Wallet_Info_TransactionBlockchainFee(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[127]!, self._r[127]!, [_0]) } - public func Wallet_Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + public var Wallet_Month_ShortDecember: String { return self._s[128]! } + public var Wallet_Info_YourBalance: String { return self._s[129]! } + public var Wallet_Configuration_BlockchainNameChangedTitle: String { return self._s[130]! } + public var Wallet_AccessDenied_Title: String { return self._s[131]! } + public var Wallet_Words_Title: String { return self._s[132]! } + public var Wallet_Configuration_BlockchainNameChangedProceed: String { return self._s[133]! } + public func Wallet_Info_TransactionDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[134]!, self._r[134]!, [_1, _2]) + } + public var Wallet_Words_NotDoneResponse: String { return self._s[135]! } + public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[136]! } + public func Wallet_WordCheck_Text(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[137]!, self._r[137]!, [_1, _2, _3]) } - public var Wallet_RestoreFailed_CreateWallet: String { return self._s[138]! } - public var Wallet_Weekday_Yesterday: String { return self._s[139]! } - public var Wallet_Receive_AmountHeader: String { return self._s[140]! } - public var Wallet_TransactionInfo_OtherFeeInfoUrl: String { return self._s[141]! } - public var Wallet_Month_ShortFebruary: String { return self._s[142]! } - public var Wallet_Configuration_SourceJSON: String { return self._s[143]! } - public var Wallet_Alert_Cancel: String { return self._s[144]! } - public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[145]! } - public var Wallet_Configuration_ApplyErrorTextJSONInvalidData: String { return self._s[146]! } - public var Wallet_Info_TransactionFrom: String { return self._s[147]! } - public var Wallet_Send_ErrorDecryptionFailed: String { return self._s[148]! } - public var Wallet_Send_OwnAddressAlertText: String { return self._s[149]! } - public var Wallet_Words_NotDoneTitle: String { return self._s[150]! } - public var Wallet_Month_ShortOctober: String { return self._s[151]! } - public var Wallet_Month_GenMay: String { return self._s[152]! } - public var Wallet_Intro_CreateErrorTitle: String { return self._s[153]! } - public var Wallet_SecureStorageReset_BiometryFaceId: String { return self._s[154]! } - public var Wallet_Month_ShortJanuary: String { return self._s[155]! } - public var Wallet_Month_GenMarch: String { return self._s[156]! } - public var Wallet_AccessDenied_Camera: String { return self._s[157]! } - public var Wallet_Sending_Text: String { return self._s[158]! } - public var Wallet_Month_GenOctober: String { return self._s[159]! } - public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[160]! } - public var Wallet_ContextMenuCopy: String { return self._s[161]! } - public func Wallet_Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[162]!, self._r[162]!, [_1, _2, _3]) + public var Wallet_SecureStorageReset_BiometryTouchId: String { return self._s[138]! } + public var Wallet_RestoreFailed_Title: String { return self._s[140]! } + public var Wallet_Alert_OK: String { return self._s[141]! } + public var Wallet_Navigation_Close: String { return self._s[142]! } + public var Wallet_Configuration_BlockchainIdHeader: String { return self._s[143]! } + public var Wallet_Send_AddressText: String { return self._s[144]! } + public var Wallet_Configuration_BlockchainIdInfo: String { return self._s[145]! } + public func Wallet_Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[146]!, self._r[146]!, [_1, _2, _3]) } - public var Wallet_Info_Updating: String { return self._s[164]! } - public var Wallet_Created_ExportErrorTitle: String { return self._s[165]! } - public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[166]! } - public var Wallet_Sending_Title: String { return self._s[167]! } - public var Wallet_Navigation_Done: String { return self._s[168]! } - public var Wallet_Configuration_BlockchainIdInfo: String { return self._s[169]! } - public var Wallet_Configuration_BlockchainNameChangedTitle: String { return self._s[170]! } - public var Wallet_Settings_Title: String { return self._s[171]! } - public func Wallet_Receive_ShareInvoiceUrlInfo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[172]!, self._r[172]!, [_0]) - } - public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[173]! } - public var Wallet_Weekday_Today: String { return self._s[175]! } - public var Wallet_Month_ShortDecember: String { return self._s[176]! } - public var Wallet_Words_Text: String { return self._s[177]! } - public var Wallet_Configuration_BlockchainNameChangedProceed: String { return self._s[178]! } - public var Wallet_WordCheck_ViewWords: String { return self._s[179]! } - public var Wallet_Send_AddressInfo: String { return self._s[180]! } - public func Wallet_Updated_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[181]!, self._r[181]!, [_0]) - } - public var Wallet_Intro_NotNow: String { return self._s[182]! } - public var Wallet_Send_OwnAddressAlertProceed: String { return self._s[183]! } - public var Wallet_Navigation_Close: String { return self._s[184]! } - public var Wallet_Month_GenDecember: String { return self._s[186]! } - public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[187]! } - public var Wallet_WordImport_IncorrectTitle: String { return self._s[188]! } - public var Wallet_Send_AddressText: String { return self._s[189]! } - public var Wallet_Receive_AmountInfo: String { return self._s[190]! } - public func Wallet_Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[191]!, self._r[191]!, [_1, _2, _3]) - } - public var Wallet_Month_ShortAugust: String { return self._s[192]! } - public var Wallet_Qr_Title: String { return self._s[193]! } - public var Wallet_Settings_Configuration: String { return self._s[194]! } - public var Wallet_WordCheck_TryAgain: String { return self._s[195]! } - public var Wallet_Info_TransactionPendingHeader: String { return self._s[196]! } - public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[197]! } - public var Wallet_Configuration_ApplyErrorTitle: String { return self._s[198]! } - public var Wallet_Send_TransactionInProgress: String { return self._s[199]! } - public var Wallet_Created_Text: String { return self._s[200]! } - public var Wallet_Created_Proceed: String { return self._s[201]! } - public var Wallet_Words_Done: String { return self._s[202]! } - public var Wallet_WordImport_Continue: String { return self._s[203]! } - public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[204]! } - public var Wallet_WordImport_CanNotRemember: String { return self._s[205]! } - public func Wallet_Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[206]!, self._r[206]!, [_1, _2, _3]) + public func Wallet_Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[147]!, self._r[147]!, [_1, _2, _3]) } + public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[148]! } + public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[149]! } + public var Wallet_TransactionInfo_CopyAddress: String { return self._s[150]! } + public var Wallet_Settings_BackupWallet: String { return self._s[151]! } + public var Wallet_Configuration_ApplyErrorTextURLInvalidData: String { return self._s[152]! } + public var Wallet_RestoreFailed_EnterWords: String { return self._s[153]! } + public var Wallet_Created_Text: String { return self._s[154]! } + public var Wallet_Month_ShortJune: String { return self._s[155]! } + public var Wallet_Send_AmountText: String { return self._s[156]! } + public var Wallet_Intro_Title: String { return self._s[157]! } + public var Wallet_Month_GenAugust: String { return self._s[158]! } + public var Wallet_Qr_Title: String { return self._s[159]! } + public var Wallet_Month_GenJune: String { return self._s[160]! } + public var Wallet_Configuration_Apply: String { return self._s[162]! } public func Wallet_Send_ConfirmationText(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[207]!, self._r[207]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[163]!, self._r[163]!, [_1, _2, _3]) } - public var Wallet_Created_ExportErrorText: String { return self._s[209]! } + public var Wallet_ContextMenuCopy: String { return self._s[164]! } + public var Wallet_SecureStorageReset_BiometryFaceId: String { return self._s[165]! } + public var Wallet_WordCheck_TryAgain: String { return self._s[166]! } + public var Wallet_Settings_DeleteWalletInfo: String { return self._s[167]! } + public var Wallet_Month_ShortOctober: String { return self._s[168]! } + public var Wallet_Configuration_SourceInfo: String { return self._s[169]! } + public var Wallet_TransactionInfo_NoAddress: String { return self._s[170]! } + public var Wallet_WordCheck_IncorrectHeader: String { return self._s[171]! } + public var Wallet_Completed_Title: String { return self._s[172]! } + public func Wallet_Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[173]!, self._r[173]!, [_1, _2, _3]) + } + public var Wallet_WordImport_Text: String { return self._s[174]! } + public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[175]! } public func Wallet_Updated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[177]!, self._r[177]!, [_0]) + } + public var Wallet_Send_OwnAddressAlertProceed: String { return self._s[178]! } + public var Wallet_RestoreFailed_Text: String { return self._s[179]! } + public var Wallet_Settings_DeleteWallet: String { return self._s[180]! } + public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[181]! } + public var Wallet_Settings_Configuration: String { return self._s[182]! } + public var Wallet_Sent_ViewWallet: String { return self._s[183]! } + public var Wallet_WordImport_Continue: String { return self._s[184]! } + public var Wallet_WordCheck_ViewWords: String { return self._s[185]! } + public var Wallet_Words_Done: String { return self._s[186]! } + public func Wallet_Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[187]!, self._r[187]!, [_1, _2, _3]) + } + public var Wallet_TransactionInfo_StorageFeeInfo: String { return self._s[188]! } + public func Wallet_Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[190]!, self._r[190]!, [_1, _2, _3]) + } + public var Wallet_Info_TransactionTo: String { return self._s[191]! } + public var Wallet_AccessDenied_Camera: String { return self._s[192]! } + public var Wallet_Info_RefreshErrorTitle: String { return self._s[193]! } + public var Wallet_Month_ShortAugust: String { return self._s[194]! } + public var Wallet_TransactionInfo_OtherFeeInfo: String { return self._s[195]! } + public var Wallet_Receive_AmountInfo: String { return self._s[196]! } + public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[197]! } + public var Wallet_Receive_CreateInvoice: String { return self._s[198]! } + public var Wallet_Receive_Title: String { return self._s[200]! } + public var Wallet_Configuration_BlockchainIdPlaceholder: String { return self._s[201]! } + public var Wallet_Month_ShortFebruary: String { return self._s[202]! } + public func Wallet_Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[203]!, self._r[203]!, [_1, _2, _3]) + } + public var Wallet_Receive_AddressCopied: String { return self._s[204]! } + public var Wallet_Month_GenOctober: String { return self._s[205]! } + public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[206]! } + public var Wallet_Send_EncryptComment: String { return self._s[207]! } + public var Wallet_WordCheck_Title: String { return self._s[208]! } + public var Wallet_Alert_Cancel: String { return self._s[209]! } + public func Wallet_Updated_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[210]!, self._r[210]!, [_0]) } - public var Wallet_Settings_DeleteWalletInfo: String { return self._s[211]! } - public var Wallet_Intro_CreateErrorText: String { return self._s[212]! } - public var Wallet_Sent_ViewWallet: String { return self._s[213]! } - public var Wallet_Send_ErrorInvalidAddress: String { return self._s[214]! } - public var Wallet_Configuration_BlockchainNameChangedText: String { return self._s[215]! } - public func Wallet_Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[216]!, self._r[216]!, [_1, _2, _3]) + public var Wallet_Intro_Terms: String { return self._s[211]! } + public func Wallet_Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[212]!, self._r[212]!, [_1, _2, _3]) } - public var Wallet_Send_Title: String { return self._s[217]! } - public var Wallet_Info_RefreshErrorText: String { return self._s[218]! } - public var Wallet_SecureStorageReset_Title: String { return self._s[219]! } - public var Wallet_Receive_CommentHeader: String { return self._s[220]! } - public var Wallet_Info_ReceiveGrams: String { return self._s[221]! } - public func Wallet_Updated_HoursAgo(_ value: Int32) -> String { + public var Wallet_Intro_ImportExisting: String { return self._s[213]! } + public var Wallet_WordImport_CanNotRemember: String { return self._s[214]! } + public var Wallet_Month_ShortSeptember: String { return self._s[215]! } + public var Wallet_Send_OwnAddressAlertText: String { return self._s[216]! } + public func Wallet_Receive_ShareInvoiceUrlInfo(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(self._s[217]!, self._r[217]!, [_0]) + } + public var Wallet_Send_ConfirmationConfirm: String { return self._s[218]! } + public var Wallet_Created_ExportErrorTitle: String { return self._s[219]! } + public var Wallet_Info_TransactionPendingHeader: String { return self._s[220]! } + public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue) } - public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String { + public func Wallet_Updated_HoursAgo(_ value: Int32) -> String { let form = getPluralizationForm(self.lc, value) let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator) return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue) diff --git a/submodules/WebsiteType/BUCK b/submodules/WebsiteType/BUCK index cc609e752b..7c8ecfbdd2 100644 --- a/submodules/WebsiteType/BUCK +++ b/submodules/WebsiteType/BUCK @@ -5,6 +5,9 @@ static_library( srcs = glob([ "Sources/**/*.swift", ]), + deps = [ + "//submodules/SyncCore:SyncCore#shared", + ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", ], diff --git a/submodules/WebsiteType/BUILD b/submodules/WebsiteType/BUILD index ba2a658eb1..dd3c2d8c69 100644 --- a/submodules/WebsiteType/BUILD +++ b/submodules/WebsiteType/BUILD @@ -7,6 +7,7 @@ swift_library( "Sources/**/*.swift", ]), deps = [ + "//submodules/SyncCore:SyncCore", ], visibility = [ "//visibility:public", diff --git a/submodules/WebsiteType/Sources/WebsiteType.swift b/submodules/WebsiteType/Sources/WebsiteType.swift index ce214994cb..53b6950108 100644 --- a/submodules/WebsiteType/Sources/WebsiteType.swift +++ b/submodules/WebsiteType/Sources/WebsiteType.swift @@ -1,4 +1,5 @@ import Foundation +import SyncCore public enum WebsiteType { case generic @@ -16,3 +17,21 @@ public func websiteType(of websiteName: String?) -> WebsiteType { } return .generic } + +public enum InstantPageType { + case generic + case album +} + +public func instantPageType(of webpage: TelegramMediaWebpageLoadedContent) -> InstantPageType { + if let type = webpage.type, type == "telegram_album" { + return .album + } + + switch websiteType(of: webpage.websiteName) { + case .instagram, .twitter: + return .album + default: + return .generic + } +}