diff --git a/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/Contents.json b/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/Contents.json new file mode 100644 index 0000000000..f9b52ce6f6 --- /dev/null +++ b/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "StickersGroupCross@2x-1.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "StickersGroupCross@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/StickersGroupCross@2x-1.png b/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/StickersGroupCross@2x-1.png new file mode 100644 index 0000000000..3c45e3b85a Binary files /dev/null and b/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/StickersGroupCross@2x-1.png differ diff --git a/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/StickersGroupCross@3x.png b/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/StickersGroupCross@3x.png new file mode 100644 index 0000000000..44dfdfabb3 Binary files /dev/null and b/Images.xcassets/Chat/Input/Media/GridDismissIcon.imageset/StickersGroupCross@3x.png differ diff --git a/Images.xcassets/Chat/Input/Media/SettingsIcon.imageset/StickerKeyboardSettingsIcon@2x.png b/Images.xcassets/Chat/Input/Media/SettingsIcon.imageset/StickerKeyboardSettingsIcon@2x.png index b51f1d9428..d0dc89b9ff 100644 Binary files a/Images.xcassets/Chat/Input/Media/SettingsIcon.imageset/StickerKeyboardSettingsIcon@2x.png and b/Images.xcassets/Chat/Input/Media/SettingsIcon.imageset/StickerKeyboardSettingsIcon@2x.png differ diff --git a/Images.xcassets/Chat/Input/Media/SettingsIcon.imageset/StickerKeyboardSettingsIcon@3x.png b/Images.xcassets/Chat/Input/Media/SettingsIcon.imageset/StickerKeyboardSettingsIcon@3x.png index 997cc0bba9..54b04299e5 100644 Binary files a/Images.xcassets/Chat/Input/Media/SettingsIcon.imageset/StickerKeyboardSettingsIcon@3x.png and b/Images.xcassets/Chat/Input/Media/SettingsIcon.imageset/StickerKeyboardSettingsIcon@3x.png differ diff --git a/Images.xcassets/Chat/Intro/ChannelIntro.imageset/ChannelIntro@2x.png b/Images.xcassets/Chat/Intro/ChannelIntro.imageset/ChannelIntro@2x.png new file mode 100644 index 0000000000..e2cd97b8a7 Binary files /dev/null and b/Images.xcassets/Chat/Intro/ChannelIntro.imageset/ChannelIntro@2x.png differ diff --git a/Images.xcassets/Chat/Intro/ChannelIntro.imageset/Contents.json b/Images.xcassets/Chat/Intro/ChannelIntro.imageset/Contents.json new file mode 100644 index 0000000000..1955c423c3 --- /dev/null +++ b/Images.xcassets/Chat/Intro/ChannelIntro.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ChannelIntro@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Images.xcassets/Chat/Intro/Contents.json b/Images.xcassets/Chat/Intro/Contents.json new file mode 100644 index 0000000000..38f0c81fc2 --- /dev/null +++ b/Images.xcassets/Chat/Intro/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "provides-namespace" : true + } +} \ No newline at end of file diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index 1c52dc6bb2..8e9c226ae1 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -162,8 +162,12 @@ D057C5452004235000990762 /* mute.json in Resources */ = {isa = PBXBuildFile; fileRef = D057C5422004226C00990762 /* mute.json */; }; D0642EFC1F3E1E7B00792790 /* ChatHistoryNavigationButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0642EFB1F3E1E7B00792790 /* ChatHistoryNavigationButtons.swift */; }; D064EF871F69A06F00AC0398 /* MessageContentKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = D064EF861F69A06F00AC0398 /* MessageContentKind.swift */; }; + D067B4A5211C911C00796039 /* LegacyChannelIntroController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D067B4A4211C911C00796039 /* LegacyChannelIntroController.swift */; }; + D067B4AA211C916300796039 /* TGChannelIntroController.h in Headers */ = {isa = PBXBuildFile; fileRef = D067B4A6211C916200796039 /* TGChannelIntroController.h */; }; + D067B4AD211C916300796039 /* TGChannelIntroController.m in Sources */ = {isa = PBXBuildFile; fileRef = D067B4A9211C916200796039 /* TGChannelIntroController.m */; }; D0684A041F6C3AD50059F570 /* ChatListTypingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0684A031F6C3AD50059F570 /* ChatListTypingNode.swift */; }; D06887F01F72DEE6000AB936 /* ShareInputFieldNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06887EF1F72DEE6000AB936 /* ShareInputFieldNode.swift */; }; + D069F5D0212700B90000565A /* StickerPanePeerSpecificSetupGridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D069F5CF212700B90000565A /* StickerPanePeerSpecificSetupGridItem.swift */; }; D06BB8821F58994B0084FC30 /* LegacyInstantVideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BB8811F58994B0084FC30 /* LegacyInstantVideoController.swift */; }; D06BEC771F62F68B0035A545 /* OverlayUniversalVideoNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BEC761F62F68B0035A545 /* OverlayUniversalVideoNode.swift */; }; D06BEC8A1F6597A80035A545 /* OverlayVideoDecoration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BEC891F6597A80035A545 /* OverlayVideoDecoration.swift */; }; @@ -173,8 +177,15 @@ D06D37A92077DDF3009219B6 /* AutodownloadMediaCategoryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06D37A82077DDF3009219B6 /* AutodownloadMediaCategoryController.swift */; }; D06D37B22077E77F009219B6 /* AutodownloadSizeLimitItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06D37B12077E77F009219B6 /* AutodownloadSizeLimitItem.swift */; }; D06E0F8E1F79ABFB003CF3DD /* ChatLoadingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06E0F8D1F79ABFB003CF3DD /* ChatLoadingNode.swift */; }; + D06E4C312134910400088087 /* ChatListEmptyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06E4C302134910400088087 /* ChatListEmptyNode.swift */; }; + D06E4C332134A59700088087 /* ThemeAccentColorActionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06E4C322134A59700088087 /* ThemeAccentColorActionSheet.swift */; }; + D06E4C352134AE3C00088087 /* ThemeAutoNightSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06E4C342134AE3C00088087 /* ThemeAutoNightSettingsController.swift */; }; D06ECFCB20B8448E00C576C2 /* ContactSynchronizationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06ECFCA20B8448E00C576C2 /* ContactSynchronizationSettings.swift */; }; D06F1EA41F6C0A5D00FE8B74 /* ChatHistorySearchContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06F1EA31F6C0A5D00FE8B74 /* ChatHistorySearchContainerNode.swift */; }; + D06F31E12135829B001A0F12 /* EDSunriseSet.m in Sources */ = {isa = PBXBuildFile; fileRef = D06F31DF2135829A001A0F12 /* EDSunriseSet.m */; }; + D06F31E22135829B001A0F12 /* EDSunriseSet.h in Headers */ = {isa = PBXBuildFile; fileRef = D06F31E02135829A001A0F12 /* EDSunriseSet.h */; }; + D06F31E4213597FF001A0F12 /* ThemeAutoNightTimeSelectionActionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06F31E3213597FF001A0F12 /* ThemeAutoNightTimeSelectionActionSheet.swift */; }; + D06F31E62135A41C001A0F12 /* ThemeSettingsBrightnessItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06F31E52135A41C001A0F12 /* ThemeSettingsBrightnessItem.swift */; }; D073D2DB1FB61DA9009E1DA2 /* CallListSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D073D2DA1FB61DA9009E1DA2 /* CallListSettings.swift */; }; D0754D1E1EEDDF6200884F6E /* ChatMessageAttachedContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0754D1D1EEDDF6200884F6E /* ChatMessageAttachedContentNode.swift */; }; D0754D201EEDEBA000884F6E /* ChatMessageGameBubbleContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0754D1F1EEDEBA000884F6E /* ChatMessageGameBubbleContentNode.swift */; }; @@ -202,6 +213,7 @@ D08984EE2114964700918162 /* GroupPreHistorySetupController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08984ED2114964700918162 /* GroupPreHistorySetupController.swift */; }; D08984F02114AE0C00918162 /* DataPrivacySettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08984EF2114AE0C00918162 /* DataPrivacySettingsController.swift */; }; D089F78A1F4E0C14000E934D /* InstantPagePresentationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D089F7891F4E0C14000E934D /* InstantPagePresentationSettings.swift */; }; + D08A10BB211DF7A80077488B /* StickerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08A10BA211DF7A80077488B /* StickerSettings.swift */; }; D08BDF641FA37BEA009D08E1 /* ChatRecordingPreviewInputPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08BDF631FA37BEA009D08E1 /* ChatRecordingPreviewInputPanelNode.swift */; }; D08BDF661FA8CB10009D08E1 /* EditSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08BDF651FA8CB10009D08E1 /* EditSettingsController.swift */; }; D08D7E79209FA2930005D80C /* SecureIdValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08D7E78209FA2930005D80C /* SecureIdValues.swift */; }; @@ -252,6 +264,7 @@ D0AD02E81FFFDE5F00C1DCFF /* ChatMessageLiveLocationTimerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AD02E71FFFDE5F00C1DCFF /* ChatMessageLiveLocationTimerNode.swift */; }; D0AD02EA1FFFEBEF00C1DCFF /* ChatMessageLiveLocationTextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AD02E91FFFEBEF00C1DCFF /* ChatMessageLiveLocationTextNode.swift */; }; D0AD02EC20000D0100C1DCFF /* ChatMessageLiveLocationPositionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AD02EB20000D0100C1DCFF /* ChatMessageLiveLocationPositionNode.swift */; }; + D0ADF966212E05A300310BBC /* TonePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ADF965212E05A300310BBC /* TonePlayer.swift */; }; D0AEAE252080D6830013176E /* StickerPaneSearchContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AEAE242080D6830013176E /* StickerPaneSearchContainerNode.swift */; }; D0AEAE272080D6970013176E /* StickerPaneSearchBarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AEAE262080D6970013176E /* StickerPaneSearchBarNode.swift */; }; D0AEAE292080FD660013176E /* StickerPaneSearchGlobaltem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AEAE282080FD660013176E /* StickerPaneSearchGlobaltem.swift */; }; @@ -261,7 +274,6 @@ D0AFCC791F4C8D2C000720C6 /* InstantPageSlideshowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AFCC781F4C8D2C000720C6 /* InstantPageSlideshowItem.swift */; }; D0AFCC7B1F4C8D39000720C6 /* InstantPageSlideshowItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AFCC7A1F4C8D39000720C6 /* InstantPageSlideshowItemNode.swift */; }; D0B2F76220506E2A00D3BFB9 /* MediaInputSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B2F76120506E2A00D3BFB9 /* MediaInputSettings.swift */; }; - D0B2F7642052739100D3BFB9 /* CreateContactController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B2F7632052739100D3BFB9 /* CreateContactController.swift */; }; D0B2F76820528E3D00D3BFB9 /* UserInfoEditingPhoneActionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B2F76720528E3D00D3BFB9 /* UserInfoEditingPhoneActionItem.swift */; }; D0B2F76A2052920D00D3BFB9 /* UserInfoEditingPhoneItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B2F7692052920D00D3BFB9 /* UserInfoEditingPhoneItem.swift */; }; D0B2F76C2052A7D600D3BFB9 /* SinglePhoneInputNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B2F76B2052A7D600D3BFB9 /* SinglePhoneInputNode.swift */; }; @@ -1368,10 +1380,14 @@ D0613FD41E6064D200202CDB /* ConvertToSupergroupController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConvertToSupergroupController.swift; sourceTree = ""; }; D0642EFB1F3E1E7B00792790 /* ChatHistoryNavigationButtons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryNavigationButtons.swift; sourceTree = ""; }; D064EF861F69A06F00AC0398 /* MessageContentKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContentKind.swift; sourceTree = ""; }; + D067B4A4211C911C00796039 /* LegacyChannelIntroController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyChannelIntroController.swift; sourceTree = ""; }; + D067B4A6211C916200796039 /* TGChannelIntroController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGChannelIntroController.h; sourceTree = ""; }; + D067B4A9211C916200796039 /* TGChannelIntroController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGChannelIntroController.m; sourceTree = ""; }; D0684A031F6C3AD50059F570 /* ChatListTypingNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListTypingNode.swift; sourceTree = ""; }; D06879541DB8F1FC00424BBD /* CachedResourceRepresentations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedResourceRepresentations.swift; sourceTree = ""; }; D06879561DB8F22200424BBD /* FetchCachedRepresentations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchCachedRepresentations.swift; sourceTree = ""; }; D06887EF1F72DEE6000AB936 /* ShareInputFieldNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareInputFieldNode.swift; sourceTree = ""; }; + D069F5CF212700B90000565A /* StickerPanePeerSpecificSetupGridItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPanePeerSpecificSetupGridItem.swift; sourceTree = ""; }; D06BB8811F58994B0084FC30 /* LegacyInstantVideoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyInstantVideoController.swift; sourceTree = ""; }; D06BEC761F62F68B0035A545 /* OverlayUniversalVideoNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayUniversalVideoNode.swift; sourceTree = ""; }; D06BEC891F6597A80035A545 /* OverlayVideoDecoration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayVideoDecoration.swift; sourceTree = ""; }; @@ -1382,8 +1398,15 @@ D06D37B12077E77F009219B6 /* AutodownloadSizeLimitItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutodownloadSizeLimitItem.swift; sourceTree = ""; }; D06E0F8D1F79ABFB003CF3DD /* ChatLoadingNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatLoadingNode.swift; sourceTree = ""; }; D06E4AC31E84806300627D1D /* FetchPhotoLibraryImageResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchPhotoLibraryImageResource.swift; sourceTree = ""; }; + D06E4C302134910400088087 /* ChatListEmptyNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListEmptyNode.swift; sourceTree = ""; }; + D06E4C322134A59700088087 /* ThemeAccentColorActionSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeAccentColorActionSheet.swift; sourceTree = ""; }; + D06E4C342134AE3C00088087 /* ThemeAutoNightSettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeAutoNightSettingsController.swift; sourceTree = ""; }; D06ECFCA20B8448E00C576C2 /* ContactSynchronizationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSynchronizationSettings.swift; sourceTree = ""; }; D06F1EA31F6C0A5D00FE8B74 /* ChatHistorySearchContainerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHistorySearchContainerNode.swift; sourceTree = ""; }; + D06F31DF2135829A001A0F12 /* EDSunriseSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EDSunriseSet.m; sourceTree = ""; }; + D06F31E02135829A001A0F12 /* EDSunriseSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EDSunriseSet.h; sourceTree = ""; }; + D06F31E3213597FF001A0F12 /* ThemeAutoNightTimeSelectionActionSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeAutoNightTimeSelectionActionSheet.swift; sourceTree = ""; }; + D06F31E52135A41C001A0F12 /* ThemeSettingsBrightnessItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSettingsBrightnessItem.swift; sourceTree = ""; }; D06FFBA71EAFAC4F00CB53D4 /* PresentationThemeEssentialGraphics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentationThemeEssentialGraphics.swift; sourceTree = ""; }; D06FFBA91EAFAD2500CB53D4 /* PresentationResourcesChat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentationResourcesChat.swift; sourceTree = ""; }; D0736F241DF4D0E500F2C02A /* TelegramController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramController.swift; sourceTree = ""; }; @@ -1448,6 +1471,7 @@ D08984ED2114964700918162 /* GroupPreHistorySetupController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupPreHistorySetupController.swift; sourceTree = ""; }; D08984EF2114AE0C00918162 /* DataPrivacySettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataPrivacySettingsController.swift; sourceTree = ""; }; D089F7891F4E0C14000E934D /* InstantPagePresentationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPagePresentationSettings.swift; sourceTree = ""; }; + D08A10BA211DF7A80077488B /* StickerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerSettings.swift; sourceTree = ""; }; D08BDF631FA37BEA009D08E1 /* ChatRecordingPreviewInputPanelNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRecordingPreviewInputPanelNode.swift; sourceTree = ""; }; D08BDF651FA8CB10009D08E1 /* EditSettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSettingsController.swift; sourceTree = ""; }; D08C367E1DB66A820064C744 /* ChatMediaInputPanelEntries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMediaInputPanelEntries.swift; sourceTree = ""; }; @@ -1526,6 +1550,7 @@ D0AD02E71FFFDE5F00C1DCFF /* ChatMessageLiveLocationTimerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageLiveLocationTimerNode.swift; sourceTree = ""; }; D0AD02E91FFFEBEF00C1DCFF /* ChatMessageLiveLocationTextNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageLiveLocationTextNode.swift; sourceTree = ""; }; D0AD02EB20000D0100C1DCFF /* ChatMessageLiveLocationPositionNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageLiveLocationPositionNode.swift; sourceTree = ""; }; + D0ADF965212E05A300310BBC /* TonePlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TonePlayer.swift; sourceTree = ""; }; D0AEAE242080D6830013176E /* StickerPaneSearchContainerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPaneSearchContainerNode.swift; sourceTree = ""; }; D0AEAE262080D6970013176E /* StickerPaneSearchBarNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPaneSearchBarNode.swift; sourceTree = ""; }; D0AEAE282080FD660013176E /* StickerPaneSearchGlobaltem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPaneSearchGlobaltem.swift; sourceTree = ""; }; @@ -1535,7 +1560,6 @@ D0AFCC781F4C8D2C000720C6 /* InstantPageSlideshowItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageSlideshowItem.swift; sourceTree = ""; }; D0AFCC7A1F4C8D39000720C6 /* InstantPageSlideshowItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageSlideshowItemNode.swift; sourceTree = ""; }; D0B2F76120506E2A00D3BFB9 /* MediaInputSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInputSettings.swift; sourceTree = ""; }; - D0B2F7632052739100D3BFB9 /* CreateContactController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateContactController.swift; sourceTree = ""; }; D0B2F76720528E3D00D3BFB9 /* UserInfoEditingPhoneActionItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoEditingPhoneActionItem.swift; sourceTree = ""; }; D0B2F7692052920D00D3BFB9 /* UserInfoEditingPhoneItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoEditingPhoneItem.swift; sourceTree = ""; }; D0B2F76B2052A7D600D3BFB9 /* SinglePhoneInputNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SinglePhoneInputNode.swift; sourceTree = ""; }; @@ -2232,6 +2256,7 @@ D0AEAE242080D6830013176E /* StickerPaneSearchContainerNode.swift */, D0AEAE282080FD660013176E /* StickerPaneSearchGlobaltem.swift */, D02B2B9720810DA00062476B /* StickerPaneSearchStickerItem.swift */, + D069F5CF212700B90000565A /* StickerPanePeerSpecificSetupGridItem.swift */, ); name = Media; sourceTree = ""; @@ -2355,6 +2380,8 @@ D04614352005093B00EC0EF2 /* Device Location */ = { isa = PBXGroup; children = ( + D06F31E02135829A001A0F12 /* EDSunriseSet.h */, + D06F31DF2135829A001A0F12 /* EDSunriseSet.m */, D04614362005094E00EC0EF2 /* DeviceLocationManager.swift */, ); name = "Device Location"; @@ -2610,6 +2637,10 @@ D0B37C5B1F8D22AE004252DF /* ThemeSettingsController.swift */, D0B37C5D1F8D26A8004252DF /* ThemeSettingsChatPreviewItem.swift */, D0B37C5F1F8D286E004252DF /* ThemeSettingsFontSizeItem.swift */, + D06E4C322134A59700088087 /* ThemeAccentColorActionSheet.swift */, + D06E4C342134AE3C00088087 /* ThemeAutoNightSettingsController.swift */, + D06F31E3213597FF001A0F12 /* ThemeAutoNightTimeSelectionActionSheet.swift */, + D06F31E52135A41C001A0F12 /* ThemeSettingsBrightnessItem.swift */, ); name = Themes; sourceTree = ""; @@ -2627,6 +2658,16 @@ name = Resources; sourceTree = ""; }; + D067B4AE211C916D00796039 /* Channel Intro */ = { + isa = PBXGroup; + children = ( + D067B4A4211C911C00796039 /* LegacyChannelIntroController.swift */, + D067B4A6211C916200796039 /* TGChannelIntroController.h */, + D067B4A9211C916200796039 /* TGChannelIntroController.m */, + ); + name = "Channel Intro"; + sourceTree = ""; + }; D0736F261DF4D2F300F2C02A /* Telegram Controller */ = { isa = PBXGroup; children = ( @@ -2687,6 +2728,7 @@ isa = PBXGroup; children = ( D04BB2C61E48797500650E93 /* RMIntro */, + D067B4AE211C916D00796039 /* Channel Intro */, D075518A1DDA4D7D0073E051 /* LegacyController.swift */, D075518C1DDA4E0B0073E051 /* LegacyControllerNode.swift */, D07551921DDA540F0073E051 /* TelegramInitializeLegacyComponents.swift */, @@ -2796,6 +2838,7 @@ D08D7E8320A0F6020005D80C /* ExperimentalUISettings.swift */, D048B33A203C777500038D05 /* RenderedTotalUnreadCount.swift */, D06ECFCA20B8448E00C576C2 /* ContactSynchronizationSettings.swift */, + D08A10BA211DF7A80077488B /* StickerSettings.swift */, ); name = Settings; sourceTree = ""; @@ -3626,6 +3669,7 @@ D0F69E9D1D6B8E240046BCD6 /* Resources */, D0177B831DFB095000A5083A /* FileMediaResourceStatus.swift */, D0FA08BF20483F9600DD23FC /* ExtractVideoData.swift */, + D0ADF965212E05A300310BBC /* TonePlayer.swift */, ); name = Media; sourceTree = ""; @@ -3785,6 +3829,7 @@ D01749611E11DB240057C89A /* NetworkStatusTitleView.swift */, D0575AEA1E9FD579006F2541 /* ChatListTitleLockView.swift */, D07E413A208A432100FCA8F0 /* ChatListTitleProxyNode.swift */, + D06E4C302134910400088087 /* ChatListEmptyNode.swift */, D0F69E051D6B8A8B0046BCD6 /* Search */, ); name = "Chat List"; @@ -3994,7 +4039,6 @@ D0F69E6D1D6B8C340046BCD6 /* ContactsController.swift */, D0F69E6E1D6B8C340046BCD6 /* ContactsControllerNode.swift */, D0F69E701D6B8C340046BCD6 /* ContactsSearchContainerNode.swift */, - D0B2F7632052739100D3BFB9 /* CreateContactController.swift */, D0B2F76720528E3D00D3BFB9 /* UserInfoEditingPhoneActionItem.swift */, D0B2F7692052920D00D3BFB9 /* UserInfoEditingPhoneItem.swift */, D0B2F76D2052B59F00D3BFB9 /* InviteContactsController.swift */, @@ -4268,11 +4312,13 @@ D0208AD91FA34017001F0D5F /* DeviceProximityManager.h in Headers */, D0E9BAC61F05738600F079A4 /* STPAPIClient.h in Headers */, D00ADFD91EBA2E9D00873D2E /* OngoingCallThreadLocalContext.h in Headers */, + D06F31E22135829B001A0F12 /* EDSunriseSet.h in Headers */, D0E9BA531F0559DA00F079A4 /* STPImageLibrary+Private.h in Headers */, D0E9BA601F055A4300F079A4 /* STPDelegateProxy.h in Headers */, D0E9BADF1F0574D800F079A4 /* STPDispatchFunctions.h in Headers */, D0E9BACB1F05738600F079A4 /* STPAPIPostRequest.h in Headers */, D0E9BA561F055A0B00F079A4 /* STPFormTextField.h in Headers */, + D067B4AA211C916300796039 /* TGChannelIntroController.h in Headers */, D0E9BABE1F05735F00F079A4 /* STPPaymentConfiguration+Private.h in Headers */, D0E9BACA1F05738600F079A4 /* STPAPIClient+Private.h in Headers */, D0E9BA251F05578900F079A4 /* STPCardBrand.h in Headers */, @@ -4454,6 +4500,7 @@ D0EC6CB21EB9F58800EBF1C3 /* rngs.c in Sources */, D083491C209361DC008CFD52 /* AvatarGalleryItemFooterContentNode.swift in Sources */, D0EC6CB31EB9F58800EBF1C3 /* shader.c in Sources */, + D06E4C352134AE3C00088087 /* ThemeAutoNightSettingsController.swift in Sources */, D0F0AAE21EC20EF8005EE2A5 /* CallControllerStatusNode.swift in Sources */, D0EC6CB41EB9F58800EBF1C3 /* timing.c in Sources */, D0EC6CB51EB9F58800EBF1C3 /* platform_log.c in Sources */, @@ -4577,6 +4624,7 @@ D0EC6CF21EB9F58800EBF1C3 /* VoiceCallSettings.swift in Sources */, D0F8C397201774A200236FC5 /* FeedGroupingController.swift in Sources */, D0EC6CF31EB9F58800EBF1C3 /* PresentationThemeSettings.swift in Sources */, + D067B4AD211C916300796039 /* TGChannelIntroController.m in Sources */, D0BE303220601FFC00FBE6D8 /* LocationBroadcastActionSheetItem.swift in Sources */, D0EC6CF41EB9F58800EBF1C3 /* ManagedMediaId.swift in Sources */, D0CFBB971FD8B0F700B65C0D /* ChatBubbleInstantVideoDecoration.swift in Sources */, @@ -4607,6 +4655,7 @@ D0EC6D031EB9F58800EBF1C3 /* opus_header.c in Sources */, D093D7DF2062F3F000BC3599 /* SecureIdDocumentFormController.swift in Sources */, D0E9BA371F05585000F079A4 /* STPPhoneNumberValidator.m in Sources */, + D069F5D0212700B90000565A /* StickerPanePeerSpecificSetupGridItem.swift in Sources */, D0EC6D041EB9F58800EBF1C3 /* opusenc.m in Sources */, D0185E8A208A01AF005E1A6C /* ProxySettingsActionItem.swift in Sources */, D0EC6D051EB9F58800EBF1C3 /* picture.c in Sources */, @@ -4657,6 +4706,7 @@ D056CD781FF2A6EE00880D28 /* ChatMessageSwipeToReplyNode.swift in Sources */, D0CE67941F7DB45100FFB557 /* ChatMessageContactBubbleContentNode.swift in Sources */, D0943AFE1FDAE454001522CC /* ChatMultipleAvatarsNavigationNode.swift in Sources */, + D0ADF966212E05A300310BBC /* TonePlayer.swift in Sources */, D007019E2029EFDD006B9E34 /* ICloudResources.swift in Sources */, D0EC6D221EB9F58800EBF1C3 /* PhotoResources.swift in Sources */, D048EA871F4F296400188713 /* InstantPageSettingsFontSizeItemNode.swift in Sources */, @@ -4803,7 +4853,6 @@ D0EC6D761EB9F58800EBF1C3 /* ChatListController.swift in Sources */, D0EC6D771EB9F58800EBF1C3 /* ChatListControllerNode.swift in Sources */, D0EC6D781EB9F58800EBF1C3 /* NetworkStatusTitleView.swift in Sources */, - D0B2F7642052739100D3BFB9 /* CreateContactController.swift in Sources */, D048EA8D1F4F299A00188713 /* InstantPageSettingsSwitchItemNode.swift in Sources */, D0E9B9F41F018A6700F079A4 /* BotCheckoutPaymentMethodSheet.swift in Sources */, D0F6800A1EE750EE000E5906 /* ChannelBannedMemberController.swift in Sources */, @@ -4828,6 +4877,7 @@ D0E9BA231F05577700F079A4 /* STPCard.m in Sources */, D0EC6D841EB9F58800EBF1C3 /* ChatHistoryEntry.swift in Sources */, D0EC6D851EB9F58800EBF1C3 /* ChatHistoryLocation.swift in Sources */, + D06F31E12135829B001A0F12 /* EDSunriseSet.m in Sources */, D0EC6D861EB9F58800EBF1C3 /* ChatAvatarNavigationNode.swift in Sources */, D0EC6D871EB9F58800EBF1C3 /* ChatTitleView.swift in Sources */, D04614372005094E00EC0EF2 /* DeviceLocationManager.swift in Sources */, @@ -4931,6 +4981,7 @@ D0EC6DB51EB9F58900EBF1C3 /* ReplyAccessoryPanelNode.swift in Sources */, D0EC6DB61EB9F58900EBF1C3 /* ForwardAccessoryPanelNode.swift in Sources */, D0EC6DB71EB9F58900EBF1C3 /* EditAccessoryPanelNode.swift in Sources */, + D06E4C312134910400088087 /* ChatListEmptyNode.swift in Sources */, D0EC6DB81EB9F58900EBF1C3 /* WebpagePreviewAccessoryPanelNode.swift in Sources */, D0EC6DB91EB9F58900EBF1C3 /* ChatInputNode.swift in Sources */, D0EC6DBA1EB9F58900EBF1C3 /* ChatMediaInputNode.swift in Sources */, @@ -4944,6 +4995,7 @@ D0EC6DBE1EB9F58900EBF1C3 /* ChatMediaInputGridEntries.swift in Sources */, D0EC6DBF1EB9F58900EBF1C3 /* ChatMediaInputMetaSectionItemNode.swift in Sources */, D0EC6DC01EB9F58900EBF1C3 /* ChatMediaInputRecentGifsItem.swift in Sources */, + D06F31E4213597FF001A0F12 /* ThemeAutoNightTimeSelectionActionSheet.swift in Sources */, D0477D211F61A47600412B44 /* UniversalVideoContentManager.swift in Sources */, D0EC6DC11EB9F58900EBF1C3 /* ChatMediaInputTrendingItem.swift in Sources */, D0BCC3D620404CD8008126C2 /* ChatMessageActionSheetControllerNode.swift in Sources */, @@ -5033,6 +5085,7 @@ D0EC6DF31EB9F58900EBF1C3 /* ShareControllerPeerGridItem.swift in Sources */, D0EC6DF41EB9F58900EBF1C3 /* ShareActionButtonNode.swift in Sources */, D0EC6DF51EB9F58900EBF1C3 /* PeerMediaCollectionController.swift in Sources */, + D06E4C332134A59700088087 /* ThemeAccentColorActionSheet.swift in Sources */, D0EC6DF61EB9F58900EBF1C3 /* PeerMediaCollectionControllerNode.swift in Sources */, D0477D1D1F617E8900412B44 /* NativeVideoContent.swift in Sources */, D0EC6DF81EB9F58900EBF1C3 /* PeerMediaCollectionInterfaceState.swift in Sources */, @@ -5051,6 +5104,7 @@ D0EC6E001EB9F58900EBF1C3 /* GalleryItemNode.swift in Sources */, D048EA8B1F4F298A00188713 /* InstantPageSettingsThemeItemNode.swift in Sources */, D0EC6E011EB9F58900EBF1C3 /* GalleryPagerNode.swift in Sources */, + D06F31E62135A41C001A0F12 /* ThemeSettingsBrightnessItem.swift in Sources */, D0EC6E021EB9F58900EBF1C3 /* GalleryFooterNode.swift in Sources */, D0EC6E031EB9F58900EBF1C3 /* GalleryFooterContentNode.swift in Sources */, D0E9BA0A1F0457DD00F079A4 /* BotCheckoutWebInteractionController.swift in Sources */, @@ -5086,6 +5140,7 @@ D0AD02EA1FFFEBEF00C1DCFF /* ChatMessageLiveLocationTextNode.swift in Sources */, D0EC6E141EB9F58900EBF1C3 /* InstantPageMedia.swift in Sources */, D0EC6E151EB9F58900EBF1C3 /* InstantPageLinkSelectionView.swift in Sources */, + D08A10BB211DF7A80077488B /* StickerSettings.swift in Sources */, D0EC6E161EB9F58900EBF1C3 /* InstantPageLayoutSpacings.swift in Sources */, D0EC6E171EB9F58900EBF1C3 /* InstantPageTextStyleStack.swift in Sources */, D0EC6E181EB9F58900EBF1C3 /* InstantPageTextItem.swift in Sources */, @@ -5163,6 +5218,7 @@ D0EC6E451EB9F58900EBF1C3 /* ItemListMultilineTextItem.swift in Sources */, D02F4AE91FCF370B004DFBAE /* ChatMessageInteractiveMediaBadge.swift in Sources */, D0EC6E461EB9F58900EBF1C3 /* ItemListLoadingIndicatorEmptyStateItem.swift in Sources */, + D067B4A5211C911C00796039 /* LegacyChannelIntroController.swift in Sources */, D01A21AF1F39EA2E00DDA104 /* InstantPageTheme.swift in Sources */, D0EC6E471EB9F58900EBF1C3 /* ItemListTextEmptyStateItem.swift in Sources */, D0E412C62069B60600BEE4A2 /* FormControllerHeaderItem.swift in Sources */, @@ -5603,6 +5659,124 @@ }; name = "Release Hockeyapp Internal"; }; + D0ADF948212B3B0000310BBC /* Debug AppStore LLC */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_SWIFT_FLAGS = ""; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Debug AppStore LLC"; + }; + D0ADF949212B3B0000310BBC /* Debug AppStore LLC */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + DEVELOPMENT_TEAM = X834Q8SBVP; + HEADERMAP_USES_VFS = YES; + INFOPLIST_FILE = TelegramUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies -driver-show-incremental"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.TelegramUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + }; + name = "Debug AppStore LLC"; + }; + D0ADF94A212B3B0000310BBC /* Debug AppStore LLC */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + COPY_PHASE_STRIP = NO; + DEVELOPMENT_TEAM = X834Q8SBVP; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + HEADERMAP_USES_VFS = YES; + HEADER_SEARCH_PATHS = "third-party/ogg"; + INFOPLIST_FILE = "$(SRCROOT)/TelegramUI/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/third-party/opus/lib", + "$(PROJECT_DIR)/third-party/libwebp/lib", + "$(PROJECT_DIR)/third-party/FFmpeg-iOS/lib", + ); + OTHER_CFLAGS = ( + "-DTGVOIP_USE_CUSTOM_CRYPTO", + "-DWEBRTC_APM_DEBUG_DUMP=0", + "-DWEBRTC_POSIX", + "-DMINIMAL_ASDK=1", + ); + OTHER_LDFLAGS = "-ObjC"; + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TelegramUI; + PRODUCT_NAME = TelegramUI; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_VERSION = 4.0; + USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; + }; + name = "Debug AppStore LLC"; + }; D0EC6E9E1EB9F79800EBF1C3 /* Debug Hockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { @@ -5876,6 +6050,7 @@ buildConfigurations = ( D0EC6E9E1EB9F79800EBF1C3 /* Debug Hockeyapp */, D079FD281F06BEF70038FADE /* Debug AppStore */, + D0ADF94A212B3B0000310BBC /* Debug AppStore LLC */, D0EC6E9F1EB9F79800EBF1C3 /* Release Hockeyapp */, D0924FF01FE52C29003F693F /* Release Hockeyapp Internal */, D0EC6EA01EB9F79800EBF1C3 /* Release AppStore */, @@ -5888,6 +6063,7 @@ buildConfigurations = ( D0FC40911D5B8E7500261D9D /* Debug Hockeyapp */, D079FD261F06BEF70038FADE /* Debug AppStore */, + D0ADF948212B3B0000310BBC /* Debug AppStore LLC */, D0FC40921D5B8E7500261D9D /* Release Hockeyapp */, D0924FEE1FE52C29003F693F /* Release Hockeyapp Internal */, D0400EDB1D5B900A007931CE /* Release AppStore */, @@ -5900,6 +6076,7 @@ buildConfigurations = ( D0FC40971D5B8E7500261D9D /* Debug Hockeyapp */, D079FD271F06BEF70038FADE /* Debug AppStore */, + D0ADF949212B3B0000310BBC /* Debug AppStore LLC */, D0FC40981D5B8E7500261D9D /* Release Hockeyapp */, D0924FEF1FE52C29003F693F /* Release Hockeyapp Internal */, D0400EDD1D5B900A007931CE /* Release AppStore */, diff --git a/TelegramUI/ArhivedStickerPacksController.swift b/TelegramUI/ArhivedStickerPacksController.swift index afa7ffc71a..e61af6e165 100644 --- a/TelegramUI/ArhivedStickerPacksController.swift +++ b/TelegramUI/ArhivedStickerPacksController.swift @@ -9,12 +9,14 @@ private final class ArchivedStickerPacksControllerArguments { let openStickerPack: (StickerPackCollectionInfo) -> Void let setPackIdWithRevealedOptions: (ItemCollectionId?, ItemCollectionId?) -> Void + let addPack: (StickerPackCollectionInfo) -> Void let removePack: (StickerPackCollectionInfo) -> Void - init(account: Account, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, removePack: @escaping (StickerPackCollectionInfo) -> Void) { + init(account: Account, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, addPack: @escaping (StickerPackCollectionInfo) -> Void, removePack: @escaping (StickerPackCollectionInfo) -> Void) { self.account = account self.openStickerPack = openStickerPack self.setPackIdWithRevealedOptions = setPackIdWithRevealedOptions + self.addPack = addPack self.removePack = removePack } } @@ -139,11 +141,12 @@ private enum ArchivedStickerPacksEntry: ItemListNodeEntry { case let .info(theme, text): return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) case let .pack(_, theme, strings, info, topItem, count, enabled, editing): - return ItemListStickerPackItem(theme: theme, strings: strings, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: .none, editing: editing, enabled: enabled, sectionId: self.section, action: { + return ItemListStickerPackItem(theme: theme, strings: strings, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: .installation(installed: false), editing: editing, enabled: enabled, sectionId: self.section, action: { arguments.openStickerPack(info) }, setPackIdWithRevealedOptions: { current, previous in arguments.setPackIdWithRevealedOptions(current, previous) }, addPack: { + arguments.addPack(info) }, removePack: { arguments.removePack(info) }) @@ -236,7 +239,7 @@ public func archivedStickerPacksController(account: Account) -> ViewController { actionsDisposable.add(removePackDisposables) let stickerPacks = Promise<[ArchivedStickerPackItem]?>() - stickerPacks.set(.single(nil) |> then(archivedStickerPacks(account: account) |> map { Optional($0) })) + stickerPacks.set(.single(nil) |> then(archivedStickerPacks(account: account) |> map(Optional.init))) let installedStickerPacks = Promise() installedStickerPacks.set(account.postbox.combinedView(keys: [.itemCollectionIds(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) @@ -253,6 +256,60 @@ public func archivedStickerPacksController(account: Account) -> ViewController { return state } } + }, addPack: { info in + var add = false + updateState { state in + var removingPackIds = state.removingPackIds + if !removingPackIds.contains(info.id) { + removingPackIds.insert(info.id) + add = true + } + return state.withUpdatedRemovingPackIds(removingPackIds) + } + if !add { + return + } + let _ = (loadedStickerPack(postbox: account.postbox, network: account.network, reference: .id(id: info.id.id, accessHash: info.accessHash)) + |> mapToSignal { result -> Signal in + switch result { + case let .result(info, items, installed): + if installed { + return .complete() + } else { + return addStickerPackInteractively(postbox: account.postbox, info: info, items: items) + } + case .fetching: + break + case .none: + break + } + return .complete() + } + |> deliverOnMainQueue).start(completed: { + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .success), nil) + + let applyPacks: Signal = stickerPacks.get() + |> filter { $0 != nil } + |> take(1) + |> deliverOnMainQueue + |> mapToSignal { packs -> Signal in + if let packs = packs { + var updatedPacks = packs + for i in 0 ..< updatedPacks.count { + if updatedPacks[i].info.id == info.id { + updatedPacks.remove(at: i) + break + } + } + stickerPacks.set(.single(updatedPacks)) + } + + return .complete() + } + + let _ = applyPacks.start() + }) }, removePack: { info in var remove = false updateState { state in diff --git a/TelegramUI/AuthorizationSequenceAwaitingAccountResetController.swift b/TelegramUI/AuthorizationSequenceAwaitingAccountResetController.swift index 0ddf0d4df3..b9852fc7c7 100644 --- a/TelegramUI/AuthorizationSequenceAwaitingAccountResetController.swift +++ b/TelegramUI/AuthorizationSequenceAwaitingAccountResetController.swift @@ -33,6 +33,8 @@ final class AuthorizationSequenceAwaitingAccountResetController: ViewController super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: AuthorizationSequenceController.navigationBarTheme(theme), strings: NavigationBarStrings(presentationStrings: strings))) + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + self.statusBar.statusBarStyle = theme.statusBarStyle self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: " ", style: .plain, target: self, action: nil) diff --git a/TelegramUI/AuthorizationSequenceCodeEntryController.swift b/TelegramUI/AuthorizationSequenceCodeEntryController.swift index da528b781a..45f6e59f25 100644 --- a/TelegramUI/AuthorizationSequenceCodeEntryController.swift +++ b/TelegramUI/AuthorizationSequenceCodeEntryController.swift @@ -40,6 +40,8 @@ final class AuthorizationSequenceCodeEntryController: ViewController { super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: AuthorizationSequenceController.navigationBarTheme(theme), strings: NavigationBarStrings(presentationStrings: strings))) + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + self.hasActiveInput = true self.statusBar.statusBarStyle = theme.statusBarStyle @@ -125,7 +127,7 @@ final class AuthorizationSequenceCodeEntryController: ViewController { if let termsOfService = self.termsOfService { var acceptImpl: (() -> Void)? var declineImpl: (() -> Void)? - let controller = TermsOfServiceController(theme: defaultDarkPresentationTheme, strings: self.strings, text: termsOfService.text, entities: termsOfService.entities, ageConfirmation: termsOfService.ageConfirmation, signingUp: true, accept: { + let controller = TermsOfServiceController(theme: TermsOfServiceControllerTheme(authTheme: self.theme), strings: self.strings, text: termsOfService.text, entities: termsOfService.entities, ageConfirmation: termsOfService.ageConfirmation, signingUp: true, accept: { acceptImpl?() }, decline: { declineImpl?() @@ -142,7 +144,9 @@ final class AuthorizationSequenceCodeEntryController: ViewController { declineImpl = { [weak self, weak controller] in controller?.dismiss() self?.reset?() + self?.controllerNode.activateInput() } + self.view.endEditing(true) self.present(controller, in: .window(.root)) } else { self.loginWithCode?(code) diff --git a/TelegramUI/AuthorizationSequenceCodeEntryControllerNode.swift b/TelegramUI/AuthorizationSequenceCodeEntryControllerNode.swift index 74f9eac621..bcc7659698 100644 --- a/TelegramUI/AuthorizationSequenceCodeEntryControllerNode.swift +++ b/TelegramUI/AuthorizationSequenceCodeEntryControllerNode.swift @@ -4,39 +4,39 @@ import Display import TelegramCore import SwiftSignalKit -func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType, strings: PresentationStrings, theme: AuthorizationTheme) -> NSAttributedString { +func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType, strings: PresentationStrings, primaryColor: UIColor, accentColor: UIColor) -> NSAttributedString { switch type { case .sms: - return NSAttributedString(string: strings.Login_CodeSentSms, font: Font.regular(16.0), textColor: theme.primaryColor, paragraphAlignment: .center) + return NSAttributedString(string: strings.Login_CodeSentSms, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center) case .otherSession: - let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: theme.primaryColor) - let bold = MarkdownAttributeSet(font: Font.semibold(16.0), textColor: theme.primaryColor) + let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: primaryColor) + let bold = MarkdownAttributeSet(font: Font.semibold(16.0), textColor: primaryColor) return parseMarkdownIntoAttributedString(strings.Login_CodeSentInternal, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil }), textAlignment: .center) case .call, .flashCall: - return NSAttributedString(string: strings.ChangePhoneNumberCode_Called, font: Font.regular(16.0), textColor: theme.primaryColor, paragraphAlignment: .center) + return NSAttributedString(string: strings.ChangePhoneNumberCode_Called, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center) } } -func authorizationNextOptionText(_ type: AuthorizationCodeNextType?, timeout: Int32?, strings: PresentationStrings, theme: AuthorizationTheme) -> (NSAttributedString, Bool) { +func authorizationNextOptionText(_ type: AuthorizationCodeNextType?, timeout: Int32?, strings: PresentationStrings, primaryColor: UIColor, accentColor: UIColor) -> (NSAttributedString, Bool) { if let type = type, let timeout = timeout { let minutes = timeout / 60 let seconds = timeout % 60 switch type { case .sms: if timeout <= 0 { - return (NSAttributedString(string: strings.Login_CodeSentSms, font: Font.regular(16.0), textColor: theme.primaryColor, paragraphAlignment: .center), false) + return (NSAttributedString(string: strings.Login_CodeSentSms, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center), false) } else { - return (NSAttributedString(string: strings.Login_SmsRequestState1(Int(minutes), Int(seconds)).0, font: Font.regular(16.0), textColor: theme.primaryColor, paragraphAlignment: .center), false) + return (NSAttributedString(string: strings.Login_SmsRequestState1(Int(minutes), Int(seconds)).0, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center), false) } case .call, .flashCall: if timeout <= 0 { - return (NSAttributedString(string: strings.ChangePhoneNumberCode_Called, font: Font.regular(16.0), textColor: theme.primaryColor, paragraphAlignment: .center), false) + return (NSAttributedString(string: strings.ChangePhoneNumberCode_Called, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center), false) } else { - return (NSAttributedString(string: String(format: strings.ChangePhoneNumberCode_CallTimer(String(format: "%d:%.2d", minutes, seconds)).0, minutes, seconds), font: Font.regular(16.0), textColor: theme.primaryColor, paragraphAlignment: .center), false) + return (NSAttributedString(string: String(format: strings.ChangePhoneNumberCode_CallTimer(String(format: "%d:%.2d", minutes, seconds)).0, minutes, seconds), font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center), false) } } } else { - return (NSAttributedString(string: strings.Login_HaveNotReceivedCodeInternal, font: Font.regular(16.0), textColor: theme.accentColor, paragraphAlignment: .center), true) + return (NSAttributedString(string: strings.Login_HaveNotReceivedCodeInternal, font: Font.regular(16.0), textColor: accentColor, paragraphAlignment: .center), true) } } @@ -124,7 +124,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.nextOptionNode = HighlightableButtonNode() self.nextOptionNode.displaysAsynchronously = false - let (nextOptionText, nextOptionActive) = authorizationNextOptionText(AuthorizationCodeNextType.call, timeout: 60, strings: self.strings, theme: self.theme) + let (nextOptionText, nextOptionActive) = authorizationNextOptionText(AuthorizationCodeNextType.call, timeout: 60, strings: self.strings, primaryColor: self.theme.primaryColor, accentColor: self.theme.accentColor) self.nextOptionNode.setAttributedTitle(nextOptionText, for: []) self.nextOptionNode.isUserInteractionEnabled = nextOptionActive @@ -177,14 +177,14 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.codeType = codeType self.phoneNumber = number - self.currentOptionNode.attributedText = authorizationCurrentOptionText(codeType, strings: self.strings, theme: self.theme) + self.currentOptionNode.attributedText = authorizationCurrentOptionText(codeType, strings: self.strings, primaryColor: self.theme.primaryColor, accentColor: self.theme.accentColor) if let timeout = timeout { self.currentTimeoutTime = timeout let disposable = ((Signal.single(1) |> delay(1.0, queue: Queue.mainQueue())) |> restart).start(next: { [weak self] _ in if let strongSelf = self { if let currentTimeoutTime = strongSelf.currentTimeoutTime, currentTimeoutTime > 0 { strongSelf.currentTimeoutTime = currentTimeoutTime - 1 - let (nextOptionText, nextOptionActive) = authorizationNextOptionText(nextType, timeout:strongSelf.currentTimeoutTime, strings: strongSelf.strings, theme: strongSelf.theme) + let (nextOptionText, nextOptionActive) = authorizationNextOptionText(nextType, timeout:strongSelf.currentTimeoutTime, strings: strongSelf.strings, primaryColor: strongSelf.theme.primaryColor, accentColor: strongSelf.theme.accentColor) strongSelf.nextOptionNode.setAttributedTitle(nextOptionText, for: []) strongSelf.nextOptionNode.isUserInteractionEnabled = nextOptionActive @@ -202,7 +202,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.currentTimeoutTime = nil self.countdownDisposable.set(nil) } - let (nextOptionText, nextOptionActive) = authorizationNextOptionText(nextType, timeout: self.currentTimeoutTime, strings: self.strings, theme: self.theme) + let (nextOptionText, nextOptionActive) = authorizationNextOptionText(nextType, timeout: self.currentTimeoutTime, strings: self.strings, primaryColor: self.theme.primaryColor, accentColor: self.theme.accentColor) self.nextOptionNode.setAttributedTitle(nextOptionText, for: []) self.nextOptionNode.isUserInteractionEnabled = nextOptionActive } diff --git a/TelegramUI/AuthorizationSequenceController.swift b/TelegramUI/AuthorizationSequenceController.swift index f939acb3a1..fac23a3398 100644 --- a/TelegramUI/AuthorizationSequenceController.swift +++ b/TelegramUI/AuthorizationSequenceController.swift @@ -29,12 +29,13 @@ public final class AuthorizationSequenceController: NavigationController { self.apiHash = apiHash self.strings = strings self.openUrl = openUrl - self.theme = defaultAuthorizationTheme + self.theme = defaultLightAuthorizationTheme super.init(mode: .single, theme: NavigationControllerTheme(navigationBar: AuthorizationSequenceController.navigationBarTheme(theme), emptyAreaColor: .black, emptyDetailIcon: nil)) - self.stateDisposable = (account.postbox.stateView() |> deliverOnMainQueue).start(next: { [weak self] view in - self?.updateState(state: view.state ?? UnauthorizedAccountState(masterDatacenterId: account.masterDatacenterId, contents: .empty)) + self.stateDisposable = (account.postbox.stateView() + |> deliverOnMainQueue).start(next: { [weak self] view in + self?.updateState(state: view.state ?? UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty)) }) } @@ -66,6 +67,7 @@ public final class AuthorizationSequenceController: NavigationController { strongSelf.strings = strings } let masterDatacenterId = strongSelf.account.masterDatacenterId + let isTestingEnvironment = strongSelf.account.testingEnvironment var countryId: String? = nil let networkInfo = CTTelephonyNetworkInfo() @@ -90,7 +92,7 @@ public final class AuthorizationSequenceController: NavigationController { } let _ = (strongSelf.account.postbox.transaction { transaction -> Void in - transaction.setState(UnauthorizedAccountState(masterDatacenterId: masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))) + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: isTestingEnvironment, masterDatacenterId: masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))) }).start() } } @@ -241,7 +243,7 @@ public final class AuthorizationSequenceController: NavigationController { if let strongSelf = self { let account = strongSelf.account let _ = (strongSelf.account.postbox.transaction { transaction -> Void in - transaction.setState(UnauthorizedAccountState(masterDatacenterId: account.masterDatacenterId, contents: .empty)) + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty)) }).start() } } @@ -300,7 +302,7 @@ public final class AuthorizationSequenceController: NavigationController { case let .email(pattern): let _ = (strongSelf.account.postbox.transaction { transaction -> Void in if let state = transaction.getState() as? UnauthorizedAccountState, case let .passwordEntry(hint, number, code) = state.contents { - transaction.setState(UnauthorizedAccountState(masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .passwordRecovery(hint: hint, number: number, code: code, emailPattern: pattern))) + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .passwordRecovery(hint: hint, number: number, code: code, emailPattern: pattern))) } }).start() case .none: @@ -390,7 +392,7 @@ public final class AuthorizationSequenceController: NavigationController { let account = strongSelf.account let _ = (strongSelf.account.postbox.transaction { transaction -> Void in if let state = transaction.getState() as? UnauthorizedAccountState, case let .passwordRecovery(hint, number, code, _) = state.contents { - transaction.setState(UnauthorizedAccountState(masterDatacenterId: account.masterDatacenterId, contents: .passwordEntry(hint: hint, number: number, code: code))) + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .passwordEntry(hint: hint, number: number, code: code))) } }).start() } @@ -444,7 +446,7 @@ public final class AuthorizationSequenceController: NavigationController { if let strongSelf = self { let account = strongSelf.account let _ = (strongSelf.account.postbox.transaction { transaction -> Void in - transaction.setState(UnauthorizedAccountState(masterDatacenterId: account.masterDatacenterId, contents: .empty)) + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty)) }).start() } } @@ -466,11 +468,12 @@ public final class AuthorizationSequenceController: NavigationController { controller = currentController } else { controller = AuthorizationSequenceSignUpController(strings: self.strings, theme: self.theme) - controller.signUpWithName = { [weak self, weak controller] firstName, lastName in + controller.signUpWithName = { [weak self, weak controller] firstName, lastName, avatarData in if let strongSelf = self { controller?.inProgress = true - strongSelf.actionDisposable.set((signUpWithName(account: strongSelf.account, firstName: firstName, lastName: lastName) |> deliverOnMainQueue).start(error: { error in + strongSelf.actionDisposable.set((signUpWithName(account: strongSelf.account, firstName: firstName, lastName: lastName, avatarData: avatarData) + |> deliverOnMainQueue).start(error: { error in Queue.mainQueue().async { if let strongSelf = self, let controller = controller { controller.inProgress = false diff --git a/TelegramUI/AuthorizationSequencePasswordEntryController.swift b/TelegramUI/AuthorizationSequencePasswordEntryController.swift index 8c457e9eb7..10c3999803 100644 --- a/TelegramUI/AuthorizationSequencePasswordEntryController.swift +++ b/TelegramUI/AuthorizationSequencePasswordEntryController.swift @@ -45,6 +45,8 @@ final class AuthorizationSequencePasswordEntryController: ViewController { super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: AuthorizationSequenceController.navigationBarTheme(theme), strings: NavigationBarStrings(presentationStrings: strings))) + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + self.hasActiveInput = true self.statusBar.statusBarStyle = theme.statusBarStyle diff --git a/TelegramUI/AuthorizationSequencePasswordRecoveryController.swift b/TelegramUI/AuthorizationSequencePasswordRecoveryController.swift index 5d8bf475b1..bbb4cd4450 100644 --- a/TelegramUI/AuthorizationSequencePasswordRecoveryController.swift +++ b/TelegramUI/AuthorizationSequencePasswordRecoveryController.swift @@ -35,6 +35,8 @@ final class AuthorizationSequencePasswordRecoveryController: ViewController { super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: AuthorizationSequenceController.navigationBarTheme(theme), strings: NavigationBarStrings(presentationStrings: strings))) + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + self.hasActiveInput = true self.statusBar.statusBarStyle = theme.statusBarStyle diff --git a/TelegramUI/AuthorizationSequencePhoneEntryController.swift b/TelegramUI/AuthorizationSequencePhoneEntryController.swift index cc54aafd46..68c11e338b 100644 --- a/TelegramUI/AuthorizationSequencePhoneEntryController.swift +++ b/TelegramUI/AuthorizationSequencePhoneEntryController.swift @@ -41,6 +41,8 @@ final class AuthorizationSequencePhoneEntryController: ViewController { super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: AuthorizationSequenceController.navigationBarTheme(theme), strings: NavigationBarStrings(presentationStrings: strings))) + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + self.hasActiveInput = true self.statusBar.statusBarStyle = theme.statusBarStyle diff --git a/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift b/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift index 1ccdb923f9..18afd282d4 100644 --- a/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift +++ b/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift @@ -210,7 +210,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { self.termsOfServiceNode.displaysAsynchronously = false let termsOfServiceAttributes = MarkdownAttributeSet(font: Font.regular(16.0), textColor: self.theme.primaryColor) - let termsOfServiceLinkAttributes = MarkdownAttributeSet(font: Font.regular(16.0), textColor: self.theme.accentColor, additionalAttributes: [NSAttributedStringKey.underlineStyle.rawValue: NSUnderlineStyle.styleSingle.rawValue as NSNumber, TelegramTextAttributes.Url: ""]) + let termsOfServiceLinkAttributes = MarkdownAttributeSet(font: Font.regular(16.0), textColor: self.theme.accentColor, additionalAttributes: [NSAttributedStringKey.underlineStyle.rawValue: NSUnderlineStyle.styleSingle.rawValue as NSNumber, TelegramTextAttributes.URL: ""]) let termsString = parseMarkdownIntoAttributedString(self.strings.Login_TermsOfServiceLabel.replacingOccurrences(of: "]", with: "]()"), attributes: MarkdownAttributes(body: termsOfServiceAttributes, bold: termsOfServiceAttributes, link: termsOfServiceLinkAttributes, linkAttribute: { _ in return nil @@ -240,14 +240,14 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { } self.termsOfServiceNode.highlightAttributeAction = { attributes in - if let _ = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] { - return NSAttributedStringKey(rawValue: TelegramTextAttributes.Url) + if let _ = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedStringKey(rawValue: TelegramTextAttributes.URL) } else { return nil } } self.termsOfServiceNode.tapAttributeAction = { [weak self] attributes in - if let _ = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] as? String { + if let _ = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { } } self.termsOfServiceNode.linkHighlightColor = theme.accentColor.withAlphaComponent(0.5) diff --git a/TelegramUI/AuthorizationSequenceSignUpController.swift b/TelegramUI/AuthorizationSequenceSignUpController.swift index 537f5bbf9d..4c85b69bfe 100644 --- a/TelegramUI/AuthorizationSequenceSignUpController.swift +++ b/TelegramUI/AuthorizationSequenceSignUpController.swift @@ -1,6 +1,9 @@ import Foundation import Display import AsyncDisplayKit +import SwiftSignalKit + +import LegacyComponents final class AuthorizationSequenceSignUpController: ViewController { private var controllerNode: AuthorizationSequenceSignUpControllerNode { @@ -11,7 +14,7 @@ final class AuthorizationSequenceSignUpController: ViewController { private let theme: AuthorizationTheme var initialName: (String, String) = ("", "") - var signUpWithName: ((String, String) -> Void)? + var signUpWithName: ((String, String, Data?) -> Void)? private let hapticFeedback = HapticFeedback() @@ -21,7 +24,7 @@ final class AuthorizationSequenceSignUpController: ViewController { let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.theme.accentColor)) self.navigationItem.rightBarButtonItem = item } else { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(self.nextPressed)) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed)) } self.controllerNode.inProgress = self.inProgress } @@ -33,6 +36,8 @@ final class AuthorizationSequenceSignUpController: ViewController { super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: AuthorizationSequenceController.navigationBarTheme(theme), strings: NavigationBarStrings(presentationStrings: strings))) + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + self.statusBar.statusBarStyle = self.theme.statusBarStyle self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed)) @@ -43,7 +48,46 @@ final class AuthorizationSequenceSignUpController: ViewController { } override public func loadDisplayNode() { - self.displayNode = AuthorizationSequenceSignUpControllerNode(theme: self.theme, strings: self.strings) + let currentAvatarMixin = Atomic(value: nil) + + self.displayNode = AuthorizationSequenceSignUpControllerNode(theme: self.theme, strings: self.strings, addPhoto: { [weak self] in + guard let strongSelf = self else { + return + } + let legacyController = LegacyController(presentation: .custom, theme: defaultPresentationTheme) + legacyController.statusBar.statusBarStyle = .Ignore + + let emptyController = LegacyEmptyController(context: legacyController.context)! + let navigationController = makeLegacyNavigationController(rootController: emptyController) + navigationController.setNavigationBarHidden(true, animated: false) + navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0) + + legacyController.bind(controller: navigationController) + + strongSelf.view.endEditing(true) + strongSelf.present(legacyController, in: .window(.root)) + + let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: false, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false)! + let _ = currentAvatarMixin.swap(mixin) + mixin.didFinishWithImage = { image in + guard let strongSelf = self, let image = image else { + return + } + strongSelf.controllerNode.currentPhoto = image + } + /*mixin.didFinishWithDelete = { + }*/ + mixin.didDismiss = { [weak legacyController] in + let _ = currentAvatarMixin.swap(nil) + legacyController?.dismiss() + } + let menuController = mixin.present() + if let menuController = menuController { + menuController.customRemoveFromParentViewController = { [weak legacyController] in + legacyController?.dismiss() + } + } + }) self.displayNodeDidLoad() self.controllerNode.signUpWithName = { [weak self] _, _ in @@ -79,7 +123,10 @@ final class AuthorizationSequenceSignUpController: ViewController { self.controllerNode.animateError() } else { let name = self.controllerNode.currentName - self.signUpWithName?(name.0, name.1) + + self.signUpWithName?(name.0, name.1, self.controllerNode.currentPhoto.flatMap({ image in + return compressImageToJPEG(image, quality: 0.7) + })) } } } diff --git a/TelegramUI/AuthorizationSequenceSignUpControllerNode.swift b/TelegramUI/AuthorizationSequenceSignUpControllerNode.swift index 56eb37063c..6dc3acdf57 100644 --- a/TelegramUI/AuthorizationSequenceSignUpControllerNode.swift +++ b/TelegramUI/AuthorizationSequenceSignUpControllerNode.swift @@ -2,9 +2,23 @@ import Foundation import AsyncDisplayKit import Display +private func roundCorners(diameter: CGFloat) -> UIImage { + UIGraphicsBeginImageContextWithOptions(CGSize(width: diameter, height: diameter), false, 0.0) + let context = UIGraphicsGetCurrentContext()! + context.setBlendMode(.copy) + context.setFillColor(UIColor.black.cgColor) + context.fill(CGRect(origin: CGPoint(), size: CGSize(width: diameter, height: diameter))) + context.setFillColor(UIColor.clear.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: diameter, height: diameter))) + let image = UIGraphicsGetImageFromCurrentImageContext()!.stretchableImage(withLeftCapWidth: Int(diameter / 2.0), topCapHeight: Int(diameter / 2.0)) + UIGraphicsEndImageContext() + return image +} + final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFieldDelegate { private let theme: AuthorizationTheme private let strings: PresentationStrings + private let addPhoto: () -> Void private let titleNode: ASTextNode private let currentOptionNode: ASTextNode @@ -13,6 +27,7 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel private let lastNameField: TextFieldNode private let firstSeparatorNode: ASDisplayNode private let lastSeparatorNode: ASDisplayNode + private let currentPhotoNode: ASImageNode private let addPhotoButton: HighlightableButtonNode private var layoutArguments: (ContainerViewLayout, CGFloat)? @@ -21,6 +36,22 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel return (self.firstNameField.textField.text ?? "", self.lastNameField.textField.text ?? "") } + var currentPhoto: UIImage? = nil { + didSet { + if let currentPhoto = self.currentPhoto { + self.currentPhotoNode.image = generateImage(CGSize(width: 110.0, height: 110.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setBlendMode(.copy) + context.draw(currentPhoto.cgImage!, in: CGRect(origin: CGPoint(), size: size)) + context.setBlendMode(.destinationOut) + context.draw(roundCorners(diameter: size.width).cgImage!, in: CGRect(origin: CGPoint(), size: size)) + }) + } else { + self.currentPhotoNode.image = nil + } + } + } + var signUpWithName: ((String, String) -> Void)? var requestNextOption: (() -> Void)? @@ -31,9 +62,10 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel } } - init(theme: AuthorizationTheme, strings: PresentationStrings) { + init(theme: AuthorizationTheme, strings: PresentationStrings, addPhoto: @escaping () -> Void) { self.theme = theme self.strings = strings + self.addPhoto = addPhoto self.titleNode = ASTextNode() self.titleNode.isLayerBacked = true @@ -43,7 +75,7 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel self.currentOptionNode = ASTextNode() self.currentOptionNode.isLayerBacked = true self.currentOptionNode.displaysAsynchronously = false - self.currentOptionNode.attributedText = NSAttributedString(string: "Enter your name and add a profile picture", font: Font.regular(16.0), textColor: theme.primaryColor, paragraphAlignment: .center) + self.currentOptionNode.attributedText = NSAttributedString(string: self.strings.Login_InfoHelp, font: Font.regular(16.0), textColor: theme.primaryColor, paragraphAlignment: .center) self.firstSeparatorNode = ASDisplayNode() self.firstSeparatorNode.isLayerBacked = true @@ -67,10 +99,18 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel self.lastNameField.textField.returnKeyType = .done self.lastNameField.textField.attributedPlaceholder = NSAttributedString(string: strings.UserInfo_LastNamePlaceholder, font: self.lastNameField.textField.font, textColor: self.theme.textPlaceholderColor) + self.currentPhotoNode = ASImageNode() + self.currentPhotoNode.isUserInteractionEnabled = false + self.currentPhotoNode.displaysAsynchronously = false + self.currentPhotoNode.displayWithoutProcessing = true + self.addPhotoButton = HighlightableButtonNode() self.addPhotoButton.setAttributedTitle(NSAttributedString(string: "\(self.strings.Login_InfoAvatarAdd)\n\(self.strings.Login_InfoAvatarPhoto)", font: Font.regular(16.0), textColor: self.theme.textPlaceholderColor, paragraphAlignment: .center), for: .normal) self.addPhotoButton.setBackgroundImage(generateCircleImage(diameter: 110.0, lineWidth: 1.0, color: self.theme.textPlaceholderColor), for: .normal) + self.addPhotoButton.addSubnode(self.currentPhotoNode) + self.addPhotoButton.allowsGroupOpacity = true + super.init() self.setViewBlock({ @@ -90,6 +130,22 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel self.addSubnode(self.currentOptionNode) self.addSubnode(self.addPhotoButton) + /*self.addPhotoButton.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.addPhotoButton.layer.removeAnimation(forKey: "opacity") + strongSelf.addPhotoButton.alpha = 0.4 + strongSelf.currentPhotoNode.layer.removeAnimation(forKey: "opacity") + strongSelf.currentPhotoNode.alpha = 0.4 + } else { + strongSelf.addPhotoButton.alpha = 1.0 + strongSelf.addPhotoButton.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.currentPhotoNode.alpha = 1.0 + strongSelf.currentPhotoNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + }*/ + self.addPhotoButton.addTarget(self, action: #selector(self.addPhotoPressed), forControlEvents: .touchUpInside) } @@ -152,6 +208,7 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel let addPhotoButtonFrame = CGRect(origin: CGPoint(x: 10.0, y: navigationHeight + 10.0), size: CGSize(width: 110.0, height: 110.0)) transition.updateFrame(node: self.addPhotoButton, frame: addPhotoButtonFrame) + self.currentPhotoNode.frame = CGRect(origin: CGPoint(), size: addPhotoButtonFrame.size) let firstFieldFrame = CGRect(origin: CGPoint(x: leftInset, y: navigationHeight + 3.0), size: CGSize(width: layout.size.width - leftInset, height: fieldHeight)) transition.updateFrame(node: self.firstNameField, frame: firstFieldFrame) @@ -202,7 +259,7 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel return false } - @objc func addPhotoPressed() { - + @objc private func addPhotoPressed() { + self.addPhoto() } } diff --git a/TelegramUI/AuthorizationSequenceSplashController.swift b/TelegramUI/AuthorizationSequenceSplashController.swift index 5547313724..937aa933af 100644 --- a/TelegramUI/AuthorizationSequenceSplashController.swift +++ b/TelegramUI/AuthorizationSequenceSplashController.swift @@ -63,6 +63,8 @@ final class AuthorizationSequenceSplashController: ViewController { super.init(navigationBarPresentationData: nil) + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + self.statusBar.statusBarStyle = theme.statusBarStyle self.controller.startMessaging = { [weak self] in diff --git a/TelegramUI/AuthorizationTheme.swift b/TelegramUI/AuthorizationTheme.swift index a49ba9025b..062f91d97d 100644 --- a/TelegramUI/AuthorizationTheme.swift +++ b/TelegramUI/AuthorizationTheme.swift @@ -21,8 +21,9 @@ public final class AuthorizationTheme { let disclosureControlColor: UIColor let textPlaceholderColor: UIColor let alertBackgroundColor: UIColor + let listBackgroundColor: UIColor - init(statusBarStyle: StatusBarStyle, navigationBarBackgroundColor: UIColor, navigationBarTextColor: UIColor, navigationBarSeparatorColor: UIColor, searchBarBackgroundColor: UIColor, searchBarFillColor: UIColor, searchBarPlaceholderColor: UIColor, searchBarTextColor: UIColor, keyboardAppearance: UIKeyboardAppearance, backgroundColor: UIColor, primaryColor: UIColor, separatorColor: UIColor, itemHighlightedBackgroundColor: UIColor, accentColor: UIColor, destructiveColor: UIColor, disclosureControlColor: UIColor, textPlaceholderColor: UIColor, alertBackgroundColor: UIColor) { + init(statusBarStyle: StatusBarStyle, navigationBarBackgroundColor: UIColor, navigationBarTextColor: UIColor, navigationBarSeparatorColor: UIColor, searchBarBackgroundColor: UIColor, searchBarFillColor: UIColor, searchBarPlaceholderColor: UIColor, searchBarTextColor: UIColor, keyboardAppearance: UIKeyboardAppearance, backgroundColor: UIColor, primaryColor: UIColor, separatorColor: UIColor, itemHighlightedBackgroundColor: UIColor, accentColor: UIColor, destructiveColor: UIColor, disclosureControlColor: UIColor, textPlaceholderColor: UIColor, alertBackgroundColor: UIColor, listBackgroundColor: UIColor) { self.statusBarStyle = statusBarStyle self.navigationBarBackgroundColor = navigationBarBackgroundColor self.navigationBarTextColor = navigationBarTextColor @@ -41,6 +42,7 @@ public final class AuthorizationTheme { self.disclosureControlColor = disclosureControlColor self.textPlaceholderColor = textPlaceholderColor self.alertBackgroundColor = alertBackgroundColor + self.listBackgroundColor = listBackgroundColor } } @@ -56,13 +58,14 @@ let defaultLightAuthorizationTheme = AuthorizationTheme( keyboardAppearance: .default, backgroundColor: .white, primaryColor: .black, - separatorColor: .lightGray, - itemHighlightedBackgroundColor: .gray, - accentColor: .blue, - destructiveColor: .red, - disclosureControlColor: .lightGray, - textPlaceholderColor: .lightGray, - alertBackgroundColor: .white + separatorColor: UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0), + itemHighlightedBackgroundColor: UIColor(rgb: 0xd9d9d9), + accentColor: UIColor(rgb: 0x007ee5), + destructiveColor: UIColor(rgb: 0xff3b30), + disclosureControlColor: UIColor(rgb: 0xbab9be), + textPlaceholderColor: UIColor(rgb: 0x8e8e93), + alertBackgroundColor: .white, + listBackgroundColor: UIColor(rgb: 0xefeff4) ) let defaultAuthorizationTheme = AuthorizationTheme( @@ -83,6 +86,7 @@ let defaultAuthorizationTheme = AuthorizationTheme( destructiveColor: UIColor(rgb: 0xFF736B), disclosureControlColor: UIColor(rgb: 0x717171), textPlaceholderColor: UIColor(rgb: 0x4d4d4d), - alertBackgroundColor: UIColor(rgb: 0x1c1c1c) + alertBackgroundColor: UIColor(rgb: 0x1c1c1c), + listBackgroundColor: UIColor(rgb: 0xefeff4) ) diff --git a/TelegramUI/AutomaticMediaDownloadSettings.swift b/TelegramUI/AutomaticMediaDownloadSettings.swift index 8e294b9522..206262e736 100644 --- a/TelegramUI/AutomaticMediaDownloadSettings.swift +++ b/TelegramUI/AutomaticMediaDownloadSettings.swift @@ -174,20 +174,9 @@ public enum AutomaticMediaDownloadPeerType { case channel } -private func tempPeerTypeForPeer(_ peer: Peer) -> AutomaticMediaDownloadPeerType { - if let _ = peer as? TelegramUser { - return .contact - } else if let _ = peer as? TelegramSecretChat { - return .contact - } else if let channel = peer as? TelegramChannel { - if case .broadcast = channel.info { - return .channel - } else { - return .group - } - } else { - return .channel - } +public enum AutomaticDownloadNetworkType { + case wifi + case cellular } private func categoriesForPeerType(_ type: AutomaticMediaDownloadPeerType, settings: AutomaticMediaDownloadSettings) -> AutomaticMediaDownloadCategories { @@ -203,8 +192,8 @@ private func categoriesForPeerType(_ type: AutomaticMediaDownloadPeerType, setti } } -private func categoryForPeerAndMedia(settings: AutomaticMediaDownloadSettings, peer: Peer, media: Media) -> (AutomaticMediaDownloadCategory, Int32?)? { - let categories = categoriesForPeerType(tempPeerTypeForPeer(peer), settings: settings) +private func categoryForPeerAndMedia(settings: AutomaticMediaDownloadSettings, peerType: AutomaticMediaDownloadPeerType, media: Media) -> (AutomaticMediaDownloadCategory, Int32?)? { + let categories = categoriesForPeerType(peerType, settings: settings) if media is TelegramMediaImage || media is TelegramMediaWebFile { return (categories.photo, nil) } else if let file = media as? TelegramMediaFile { @@ -236,23 +225,31 @@ private func categoryForPeerAndMedia(settings: AutomaticMediaDownloadSettings, p } } -public func shouldDownloadMediaAutomatically(settings: AutomaticMediaDownloadSettings, peer: Peer?, media: Media) -> Bool { +public func shouldDownloadMediaAutomatically(settings: AutomaticMediaDownloadSettings, peerType: AutomaticMediaDownloadPeerType, networkType: AutomaticDownloadNetworkType, media: Media) -> Bool { if !settings.masterEnabled { return false } - guard let peer = peer else { - return false - } if let file = media as? TelegramMediaFile, file.isSticker { return true } - if let (category, size) = categoryForPeerAndMedia(settings: settings, peer: peer, media: media) { - if let size = size { - return category.cellular && size <= category.sizeLimit - } else if category.sizeLimit == Int32.max { - return category.cellular - } else { - return false + if let (category, size) = categoryForPeerAndMedia(settings: settings, peerType: peerType, media: media) { + switch networkType { + case .cellular: + if let size = size { + return category.cellular && size <= category.sizeLimit + } else if category.sizeLimit == Int32.max { + return category.cellular + } else { + return false + } + case .wifi: + if let size = size { + return category.wifi && size <= category.sizeLimit + } else if category.sizeLimit == Int32.max { + return category.wifi + } else { + return false + } } } else { return false diff --git a/TelegramUI/AvatarGalleryController.swift b/TelegramUI/AvatarGalleryController.swift index 1cac731df0..5342e21a02 100644 --- a/TelegramUI/AvatarGalleryController.swift +++ b/TelegramUI/AvatarGalleryController.swift @@ -37,7 +37,7 @@ enum AvatarGalleryEntry: Equatable { return false } case let .image(lhsImage, lhsIndexData): - if case let .image(rhsImage, rhsIndexData) = rhs, lhsImage.isEqual(rhsImage), lhsIndexData == rhsIndexData { + if case let .image(rhsImage, rhsIndexData) = rhs, lhsImage.isEqual(to: rhsImage), lhsIndexData == rhsIndexData { return true } else { return false diff --git a/TelegramUI/BlockedPeersController.swift b/TelegramUI/BlockedPeersController.swift index 3bbcccad98..c71d3bdea2 100644 --- a/TelegramUI/BlockedPeersController.swift +++ b/TelegramUI/BlockedPeersController.swift @@ -288,7 +288,7 @@ public func blockedPeersController(account: Account) -> ViewController { } }) - let peersSignal: Signal<[Peer]?, NoError> = .single(nil) |> then(requestBlockedPeers(account: account) |> map { Optional($0) }) + let peersSignal: Signal<[Peer]?, NoError> = .single(nil) |> then(requestBlockedPeers(account: account) |> map(Optional.init)) peersPromise.set(peersSignal) diff --git a/TelegramUI/BotCheckoutControllerNode.swift b/TelegramUI/BotCheckoutControllerNode.swift index 98e326b231..98c332d4ab 100644 --- a/TelegramUI/BotCheckoutControllerNode.swift +++ b/TelegramUI/BotCheckoutControllerNode.swift @@ -77,7 +77,7 @@ enum BotCheckoutEntry: ItemListNodeEntry { if lhsTheme !== rhsTheme { return false } - if !lhsInvoice.isEqual(rhsInvoice) { + if !lhsInvoice.isEqual(to: rhsInvoice) { return false } if lhsName != rhsName { diff --git a/TelegramUI/BotCheckoutHeaderItem.swift b/TelegramUI/BotCheckoutHeaderItem.swift index 47d00533b7..10da97155f 100644 --- a/TelegramUI/BotCheckoutHeaderItem.swift +++ b/TelegramUI/BotCheckoutHeaderItem.swift @@ -126,7 +126,7 @@ class BotCheckoutHeaderItemNode: ListViewItemNode { let previousPhoto = currentItem?.invoice.photo var imageUpdated = false if let previousPhoto = previousPhoto, let photo = item.invoice.photo { - if !previousPhoto.isEqual(photo) { + if !previousPhoto.isEqual(to: photo) { imageUpdated = true } } else if (previousPhoto != nil) != (item.invoice.photo != nil) { diff --git a/TelegramUI/BotReceiptControllerNode.swift b/TelegramUI/BotReceiptControllerNode.swift index 6f812749b6..38d8eca532 100644 --- a/TelegramUI/BotReceiptControllerNode.swift +++ b/TelegramUI/BotReceiptControllerNode.swift @@ -68,7 +68,7 @@ enum BotReceiptEntry: ItemListNodeEntry { if lhsTheme !== rhsTheme { return false } - if !lhsInvoice.isEqual(rhsInvoice) { + if !lhsInvoice.isEqual(to: rhsInvoice) { return false } if lhsName != rhsName { diff --git a/TelegramUI/CallController.swift b/TelegramUI/CallController.swift index 70b24cbf8d..085416285e 100644 --- a/TelegramUI/CallController.swift +++ b/TelegramUI/CallController.swift @@ -29,8 +29,8 @@ public final class CallController: ViewController { private var callMutedDisposable: Disposable? private var isMuted = false - private var speakerModeDisposable: Disposable? - private var speakerMode = false + private var audioOutputStateDisposable: Disposable? + private var audioOutputState: ([AudioSessionOutput], AudioSessionOutput?)? public init(account: Account, call: PresentationCall) { self.account = account @@ -43,13 +43,15 @@ public final class CallController: ViewController { self.statusBar.statusBarStyle = .White self.statusBar.ignoreInCall = true - self.supportedOrientations = .portrait + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) - self.disposable = (call.state |> deliverOnMainQueue).start(next: { [weak self] callState in + self.disposable = (call.state + |> deliverOnMainQueue).start(next: { [weak self] callState in self?.callStateUpdated(callState) }) - self.callMutedDisposable = (call.isMuted |> deliverOnMainQueue).start(next: { [weak self] value in + self.callMutedDisposable = (call.isMuted + |> deliverOnMainQueue).start(next: { [weak self] value in if let strongSelf = self { strongSelf.isMuted = value if strongSelf.isNodeLoaded { @@ -58,11 +60,12 @@ public final class CallController: ViewController { } }) - self.speakerModeDisposable = (call.speakerMode |> deliverOnMainQueue).start(next: { [weak self] value in + self.audioOutputStateDisposable = (call.audioOutputState + |> deliverOnMainQueue).start(next: { [weak self] state in if let strongSelf = self { - strongSelf.speakerMode = value + strongSelf.audioOutputState = state if strongSelf.isNodeLoaded { - strongSelf.controllerNode.speakerMode = value + strongSelf.controllerNode.updateAudioOutputs(availableOutputs: state.0, currentOutput: state.1) } } }) @@ -76,7 +79,7 @@ public final class CallController: ViewController { self.peerDisposable?.dispose() self.disposable?.dispose() self.callMutedDisposable?.dispose() - self.speakerModeDisposable?.dispose() + self.audioOutputStateDisposable?.dispose() } private func callStateUpdated(_ callState: PresentationCallState) { @@ -93,8 +96,53 @@ public final class CallController: ViewController { self?.call.toggleIsMuted() } - self.controllerNode.toggleSpeaker = { [weak self] in - self?.call.toggleSpeaker() + self.controllerNode.setCurrentAudioOutput = { [weak self] output in + self?.call.setCurrentAudioOutput(output) + } + + self.controllerNode.beginAudioOuputSelection = { [weak self] in + guard let strongSelf = self, let (availableOutputs, currentOutput) = strongSelf.audioOutputState else { + return + } + guard availableOutputs.count >= 2 else { + return + } + if availableOutputs.count == 2 { + for output in availableOutputs { + if output != currentOutput { + strongSelf.call.setCurrentAudioOutput(output) + break + } + } + } else { + let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) + var items: [ActionSheetItem] = [] + for output in availableOutputs { + let title: String + switch output { + case .builtin: + title = UIDevice.current.model + case .speaker: + title = strongSelf.presentationData.strings.Call_AudioRouteSpeaker + case .headphones: + title = strongSelf.presentationData.strings.Call_AudioRouteHeadphones + case let .port(port): + title = port.name + } + items.append(ActionSheetButtonItem(title: title, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + self?.call.setCurrentAudioOutput(output) + })) + } + + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ]) + ]) + strongSelf.present(actionSheet, in: .window(.root)) + } } self.controllerNode.acceptCall = { [weak self] in @@ -126,7 +174,10 @@ public final class CallController: ViewController { }) self.controllerNode.isMuted = self.isMuted - self.controllerNode.speakerMode = self.speakerMode + + if let audioOutputState = self.audioOutputState { + self.controllerNode.updateAudioOutputs(availableOutputs: audioOutputState.0, currentOutput: audioOutputState.1) + } } override public func viewDidAppear(_ animated: Bool) { @@ -145,7 +196,7 @@ public final class CallController: ViewController { self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition) } - override open func dismiss(completion: (() -> Void)? = nil) { + override public func dismiss(completion: (() -> Void)? = nil) { self.controllerNode.animateOut(completion: { [weak self] in self?.animatedAppearance = false self?.presentingViewController?.dismiss(animated: false, completion: nil) diff --git a/TelegramUI/CallControllerButton.swift b/TelegramUI/CallControllerButton.swift index ead09af6d6..ba0dcd09e7 100644 --- a/TelegramUI/CallControllerButton.swift +++ b/TelegramUI/CallControllerButton.swift @@ -73,14 +73,18 @@ private let invertedFill = UIColor(white: 1.0, alpha: 1.0) private let labelFont = Font.regular(14.5) final class CallControllerButtonNode: HighlightTrackingButtonNode { - private let regularImage: UIImage? - private let highlightedImage: UIImage? - private let filledImage: UIImage? + private var type: CallControllerButtonType + + private var regularImage: UIImage? + private var highlightedImage: UIImage? + private var filledImage: UIImage? private let backgroundNode: ASImageNode private let labelNode: ASTextNode? init(type: CallControllerButtonType, label: String?) { + self.type = type + self.backgroundNode = ASImageNode() self.backgroundNode.isLayerBacked = true self.backgroundNode.displayWithoutProcessing = false @@ -175,6 +179,43 @@ final class CallControllerButtonNode: HighlightTrackingButtonNode { } } + func updateType(_ type: CallControllerButtonType) { + if self.type == type { + return + } + self.type = type + var regularImage: UIImage? + var highlightedImage: UIImage? + var filledImage: UIImage? + + switch type { + case .mute: + regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallMuteButton"), strokeColor: emptyStroke, fillColor: .clear) + highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallMuteButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill) + filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallMuteButton"), strokeColor: nil, fillColor: invertedFill, knockout: true) + case .accept: + regularImage = generateFilledButtonImage(color: UIColor(rgb: 0x74db58), icon: UIImage(bundleImageName: "Call/CallPhoneButton"), angle: CGFloat.pi * 3.0 / 4.0) + highlightedImage = generateFilledButtonImage(color: UIColor(rgb: 0x74db58), icon: UIImage(bundleImageName: "Call/CallPhoneButton"), angle: CGFloat.pi * 3.0 / 4.0) + case .end: + regularImage = generateFilledButtonImage(color: UIColor(rgb: 0xd92326), icon: UIImage(bundleImageName: "Call/CallPhoneButton")) + highlightedImage = generateFilledButtonImage(color: UIColor(rgb: 0xd92326), icon: UIImage(bundleImageName: "Call/CallPhoneButton")) + case .speaker: + regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallSpeakerButton"), strokeColor: emptyStroke, fillColor: .clear) + highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallSpeakerButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill) + filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallSpeakerButton"), strokeColor: nil, fillColor: invertedFill, knockout: true) + case .bluetooth: + regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: .clear) + highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill) + filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: nil, fillColor: invertedFill, knockout: true) + } + + self.regularImage = regularImage + self.highlightedImage = highlightedImage + self.filledImage = filledImage + + self.updateState(highlighted: self.isHighlighted, selected: self.isSelected) + } + func animateRollTransition() { self.backgroundNode.layer.animate(from: 0.0 as NSNumber, to: (-CGFloat.pi * 5 / 4) as NSNumber, keyPath: "transform.rotation.z", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.3, removeOnCompletion: false) self.labelNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) diff --git a/TelegramUI/CallControllerButtonsNode.swift b/TelegramUI/CallControllerButtonsNode.swift index 0ce05bea09..9f3068878b 100644 --- a/TelegramUI/CallControllerButtonsNode.swift +++ b/TelegramUI/CallControllerButtonsNode.swift @@ -2,32 +2,19 @@ import Foundation import Display import AsyncDisplayKit import SwiftSignalKit +import MediaPlayer enum CallControllerButtonsSpeakerMode { - case bluetooth + case none + case builtin case speaker + case headphones + case bluetooth } enum CallControllerButtonsMode: Equatable { case active(CallControllerButtonsSpeakerMode) case incoming - - static func ==(lhs: CallControllerButtonsMode, rhs: CallControllerButtonsMode) -> Bool { - switch lhs { - case let .active(mode): - if case .active(mode) = rhs { - return true - } else { - return false - } - case .incoming: - if case .incoming = rhs { - return true - } else { - return false - } - } - } } final class CallControllerButtonsNode: ASDisplayNode { @@ -48,12 +35,6 @@ final class CallControllerButtonsNode: ASDisplayNode { } } - var speakerMode = false { - didSet { - self.speakerButton.isSelected = self.speakerMode - } - } - var accept: (() -> Void)? var mute: (() -> Void)? var end: (() -> Void)? @@ -141,7 +122,7 @@ final class CallControllerButtonsNode: ASDisplayNode { for button in [self.muteButton, self.endButton, self.speakerButton] { button.alpha = 0.0 } - case .active: + case let .active(speakerMode): for button in [self.muteButton, self.speakerButton] { if animated && button.alpha.isZero { button.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) @@ -179,6 +160,19 @@ final class CallControllerButtonsNode: ASDisplayNode { if self.acceptButton.alpha.isZero && !animatingAcceptButton { self.acceptButton.alpha = 0.0 } + + self.speakerButton.isSelected = speakerMode == .speaker + self.speakerButton.isHidden = speakerMode == .none + let speakerButtonType: CallControllerButtonType + switch speakerMode { + case .none, .builtin, .speaker: + speakerButtonType = .speaker + case .headphones: + speakerButtonType = .bluetooth + case .bluetooth: + speakerButtonType = .bluetooth + } + self.speakerButton.updateType(speakerButtonType) } } diff --git a/TelegramUI/CallControllerNode.swift b/TelegramUI/CallControllerNode.swift index eabf855f78..108a8fc14b 100644 --- a/TelegramUI/CallControllerNode.swift +++ b/TelegramUI/CallControllerNode.swift @@ -36,14 +36,12 @@ final class CallControllerNode: ASDisplayNode { } } - var speakerMode: Bool = false { - didSet { - self.buttonsNode.speakerMode = self.speakerMode - } - } + private var audioOutputState: ([AudioSessionOutput], currentOutput: AudioSessionOutput?)? + private var callState: PresentationCallState? var toggleMute: (() -> Void)? - var toggleSpeaker: (() -> Void)? + var setCurrentAudioOutput: ((AudioSessionOutput) -> Void)? + var beginAudioOuputSelection: (() -> Void)? var acceptCall: (() -> Void)? var endCall: (() -> Void)? var back: (() -> Void)? @@ -113,7 +111,7 @@ final class CallControllerNode: ASDisplayNode { } self.buttonsNode.speaker = { [weak self] in - self?.toggleSpeaker?() + self?.beginAudioOuputSelection?() } self.buttonsNode.end = { [weak self] in @@ -158,7 +156,16 @@ final class CallControllerNode: ASDisplayNode { } } + func updateAudioOutputs(availableOutputs: [AudioSessionOutput], currentOutput: AudioSessionOutput?) { + if self.audioOutputState?.0 != availableOutputs || self.audioOutputState?.1 != currentOutput { + self.audioOutputState = (availableOutputs, currentOutput) + self.updateButtonsMode() + } + } + func updateCallState(_ callState: PresentationCallState) { + self.callState = callState + let statusValue: CallControllerStatusValue switch callState { case .waiting, .connecting: @@ -233,11 +240,35 @@ final class CallControllerNode: ASDisplayNode { } self.statusNode.status = statusValue + self.updateButtonsMode() + } + + private func updateButtonsMode() { + guard let callState = self.callState else { + return + } + switch callState { case .ringing: self.buttonsNode.updateMode(.incoming) default: - self.buttonsNode.updateMode(.active(.speaker)) + var mode: CallControllerButtonsSpeakerMode = .none + if let (availableOutputs, maybeCurrentOutput) = self.audioOutputState, let currentOutput = maybeCurrentOutput { + switch currentOutput { + case .builtin: + mode = .builtin + case .speaker: + mode = .speaker + case .headphones: + mode = .headphones + case .port: + mode = .bluetooth + } + if availableOutputs.count <= 1 { + mode = .none + } + } + self.buttonsNode.updateMode(.active(mode)) } } diff --git a/TelegramUI/CallListCallItem.swift b/TelegramUI/CallListCallItem.swift index 9be208f5d2..b8548ef307 100644 --- a/TelegramUI/CallListCallItem.swift +++ b/TelegramUI/CallListCallItem.swift @@ -212,7 +212,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { self.typeIconNode.displaysAsynchronously = false self.infoButtonNode = HighlightableButtonNode() - self.infoButtonNode.hitTestSlop = UIEdgeInsets(top: 6.0, left: 6.0, bottom: 6.0, right: 10.0) + self.infoButtonNode.hitTestSlop = UIEdgeInsets(top: -6.0, left: -6.0, bottom: -6.0, right: -10.0) super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) @@ -640,7 +640,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { } } - override func revealOptionSelected(_ option: ItemListRevealOption) { + override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { self.setRevealOptionsOpened(false, animated: true) self.revealOptionsInteractivelyClosed() if let item = self.layoutParams?.0 { diff --git a/TelegramUI/CallListSettings.swift b/TelegramUI/CallListSettings.swift index 2e156d3e5e..fd0d1a7b87 100644 --- a/TelegramUI/CallListSettings.swift +++ b/TelegramUI/CallListSettings.swift @@ -3,7 +3,7 @@ import Postbox import SwiftSignalKit public struct CallListSettings: PreferencesEntry, Equatable { - public let showTab: Bool + public var showTab: Bool public static var defaultSettings: CallListSettings { return CallListSettings(showTab: true) diff --git a/TelegramUI/ChangePhoneNumberCodeController.swift b/TelegramUI/ChangePhoneNumberCodeController.swift index c30073739c..7a8941a2c7 100644 --- a/TelegramUI/ChangePhoneNumberCodeController.swift +++ b/TelegramUI/ChangePhoneNumberCodeController.swift @@ -130,9 +130,9 @@ private func changePhoneNumberCodeControllerEntries(presentationData: Presentati var entries: [ChangePhoneNumberCodeEntry] = [] entries.append(.codeEntry(presentationData.theme, state.codeText)) - var text = authorizationCurrentOptionText(codeData.type, strings: presentationData.strings, theme: defaultLightAuthorizationTheme).string + var text = authorizationCurrentOptionText(codeData.type, strings: presentationData.strings, primaryColor: presentationData.theme.list.itemPrimaryTextColor, accentColor: presentationData.theme.list.itemAccentColor).string if let nextType = codeData.nextType { - text += "\n\n" + authorizationNextOptionText(nextType, timeout: timeout, strings: presentationData.strings, theme: defaultAuthorizationTheme).0.string + text += "\n\n" + authorizationNextOptionText(nextType, timeout: timeout, strings: presentationData.strings, primaryColor: .black, accentColor: .black).0.string } entries.append(.codeInfo(presentationData.theme, text)) diff --git a/TelegramUI/ChangePhoneNumberControllerNode.swift b/TelegramUI/ChangePhoneNumberControllerNode.swift index 2bb333b66b..c1b10d3f26 100644 --- a/TelegramUI/ChangePhoneNumberControllerNode.swift +++ b/TelegramUI/ChangePhoneNumberControllerNode.swift @@ -3,62 +3,68 @@ import AsyncDisplayKit import Display import TelegramCore -private let countryButtonBackground = generateImage(CGSize(width: 45.0, height: 44.0 + 6.0), rotatedContext: { size, context in - let arrowSize: CGFloat = 6.0 - let lineWidth = UIScreenPixel - - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(UIColor.white.cgColor) - context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height - arrowSize))) - context.move(to: CGPoint(x: size.width, y: size.height - arrowSize)) - context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - arrowSize)) - context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize, y: size.height)) - context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize - arrowSize, y: size.height - arrowSize)) - context.closePath() - context.fillPath() - - context.setStrokeColor(UIColor(rgb: 0xbcbbc1).cgColor) - context.setLineWidth(lineWidth) - - context.move(to: CGPoint(x: size.width, y: size.height - arrowSize - lineWidth / 2.0)) - context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - arrowSize - lineWidth / 2.0)) - context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize, y: size.height - lineWidth / 2.0)) - context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize - arrowSize, y: size.height - arrowSize - lineWidth / 2.0)) - context.addLine(to: CGPoint(x: 15.0, y: size.height - arrowSize - lineWidth / 2.0)) - context.strokePath() - - context.move(to: CGPoint(x: 0.0, y: lineWidth / 2.0)) - context.addLine(to: CGPoint(x: size.width, y: lineWidth / 2.0)) - context.strokePath() -})?.stretchableImage(withLeftCapWidth: 46, topCapHeight: 1) +private func generateCountryButtonBackground(color: UIColor, strokeColor: UIColor) -> UIImage? { + return generateImage(CGSize(width: 45.0, height: 44.0 + 6.0), rotatedContext: { size, context in + let arrowSize: CGFloat = 6.0 + let lineWidth = UIScreenPixel + + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.cgColor) + context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height - arrowSize))) + context.move(to: CGPoint(x: size.width, y: size.height - arrowSize)) + context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - arrowSize)) + context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize, y: size.height)) + context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize - arrowSize, y: size.height - arrowSize)) + context.closePath() + context.fillPath() + + context.setStrokeColor(strokeColor.cgColor) + context.setLineWidth(lineWidth) + + context.move(to: CGPoint(x: size.width, y: size.height - arrowSize - lineWidth / 2.0)) + context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - arrowSize - lineWidth / 2.0)) + context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize, y: size.height - lineWidth / 2.0)) + context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize - arrowSize, y: size.height - arrowSize - lineWidth / 2.0)) + context.addLine(to: CGPoint(x: 15.0, y: size.height - arrowSize - lineWidth / 2.0)) + context.strokePath() + + context.move(to: CGPoint(x: 0.0, y: lineWidth / 2.0)) + context.addLine(to: CGPoint(x: size.width, y: lineWidth / 2.0)) + context.strokePath() + })?.stretchableImage(withLeftCapWidth: 46, topCapHeight: 1) +} -private let countryButtonHighlightedBackground = generateImage(CGSize(width: 45.0, height: 44.0 + 6.0), rotatedContext: { size, context in - let arrowSize: CGFloat = 6.0 - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(UIColor(rgb: 0xbcbbc1).cgColor) - context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height - arrowSize))) - context.move(to: CGPoint(x: size.width, y: size.height - arrowSize)) - context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - arrowSize)) - context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize, y: size.height)) - context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize - arrowSize, y: size.height - arrowSize)) - context.closePath() - context.fillPath() -})?.stretchableImage(withLeftCapWidth: 46, topCapHeight: 2) +private func generateCountryButtonHighlightedBackground(color: UIColor) -> UIImage? { + return generateImage(CGSize(width: 45.0, height: 44.0 + 6.0), rotatedContext: { size, context in + let arrowSize: CGFloat = 6.0 + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.cgColor) + context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height - arrowSize))) + context.move(to: CGPoint(x: size.width, y: size.height - arrowSize)) + context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - arrowSize)) + context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize, y: size.height)) + context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize - arrowSize, y: size.height - arrowSize)) + context.closePath() + context.fillPath() + })?.stretchableImage(withLeftCapWidth: 46, topCapHeight: 2) +} -private let phoneInputBackground = generateImage(CGSize(width: 60.0, height: 44.0), rotatedContext: { size, context in - let lineWidth = UIScreenPixel - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(UIColor.white.cgColor) - context.fill(CGRect(origin: CGPoint(), size: size)) - context.setStrokeColor(UIColor(rgb: 0xbcbbc1).cgColor) - context.setLineWidth(lineWidth) - context.move(to: CGPoint(x: 0.0, y: size.height - lineWidth / 2.0)) - context.addLine(to: CGPoint(x: size.width, y: size.height - lineWidth / 2.0)) - context.strokePath() - context.move(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: size.height - lineWidth / 2.0)) - context.addLine(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: 0.0)) - context.strokePath() -})?.stretchableImage(withLeftCapWidth: 61, topCapHeight: 2) +private func generatePhoneInputBackground(color: UIColor, strokeColor: UIColor) -> UIImage? { + return generateImage(CGSize(width: 60.0, height: 44.0), rotatedContext: { size, context in + let lineWidth = UIScreenPixel + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(strokeColor.cgColor) + context.setLineWidth(lineWidth) + context.move(to: CGPoint(x: 0.0, y: size.height - lineWidth / 2.0)) + context.addLine(to: CGPoint(x: size.width, y: size.height - lineWidth / 2.0)) + context.strokePath() + context.move(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: size.height - lineWidth / 2.0)) + context.addLine(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: 0.0)) + context.strokePath() + })?.stretchableImage(withLeftCapWidth: 61, topCapHeight: 2) +} final class ChangePhoneNumberControllerNode: ASDisplayNode { private let titleNode: ASTextNode @@ -105,16 +111,20 @@ final class ChangePhoneNumberControllerNode: ASDisplayNode { self.noticeNode.attributedText = NSAttributedString(string: self.presentationData.strings.ChangePhoneNumberNumber_Help, font: Font.regular(14.0), textColor: self.presentationData.theme.list.freeTextColor) self.countryButton = ASButtonNode() - self.countryButton.setBackgroundImage(countryButtonBackground, for: []) - self.countryButton.setBackgroundImage(countryButtonHighlightedBackground, for: .highlighted) + self.countryButton.setBackgroundImage(generateCountryButtonBackground(color: self.presentationData.theme.list.itemBlocksBackgroundColor, strokeColor: self.presentationData.theme.list.itemBlocksSeparatorColor), for: []) + self.countryButton.setBackgroundImage(generateCountryButtonHighlightedBackground(color: self.presentationData.theme.list.itemHighlightedBackgroundColor), for: .highlighted) self.phoneBackground = ASImageNode() - self.phoneBackground.image = phoneInputBackground + self.phoneBackground.image = generatePhoneInputBackground(color: self.presentationData.theme.list.itemBlocksBackgroundColor, strokeColor: self.presentationData.theme.list.itemBlocksSeparatorColor) self.phoneBackground.displaysAsynchronously = false self.phoneBackground.displayWithoutProcessing = true self.phoneBackground.isLayerBacked = true self.phoneInputNode = PhoneInputNode(fontSize: 17.0) + self.phoneInputNode.countryCodeField.textField.textColor = self.presentationData.theme.list.itemPrimaryTextColor + self.phoneInputNode.countryCodeField.textField.keyboardAppearance = self.presentationData.theme.chatList.searchBarKeyboardColor.keyboardAppearance + self.phoneInputNode.numberField.textField.textColor = self.presentationData.theme.list.itemPrimaryTextColor + self.phoneInputNode.numberField.textField.keyboardAppearance = self.presentationData.theme.chatList.searchBarKeyboardColor.keyboardAppearance super.init() @@ -140,7 +150,7 @@ final class ChangePhoneNumberControllerNode: ASDisplayNode { self.phoneInputNode.countryCodeUpdated = { [weak self] code in if let strongSelf = self { if let code = Int(code), let (coutnryId, countryName) = countryCodeToIdAndName[code] { - strongSelf.countryButton.setTitle(countryName, with: Font.regular(17.0), with: .black, for: []) + strongSelf.countryButton.setTitle(countryName, with: Font.regular(17.0), with: strongSelf.presentationData.theme.list.itemPrimaryTextColor, for: []) } else { strongSelf.countryButton.setTitle(strongSelf.presentationData.strings.Login_CountryCode, with: Font.regular(17.0), with: .black, for: []) } diff --git a/TelegramUI/ChannelAdminsController.swift b/TelegramUI/ChannelAdminsController.swift index fbdf2f159d..af55c32279 100644 --- a/TelegramUI/ChannelAdminsController.swift +++ b/TelegramUI/ChannelAdminsController.swift @@ -507,23 +507,26 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon } return account.postbox.loadedPeerWithId(peerId) - |> mapToSignal { peer -> Signal in - if let peer = peer as? TelegramChannel, case let .group(info) = peer.info { - var updatedValue: Bool? - if value && !info.flags.contains(.everyMemberCanInviteMembers) { - updatedValue = true - } else if !value && info.flags.contains(.everyMemberCanInviteMembers) { - updatedValue = false - } - if let updatedValue = updatedValue { - return updateGroupManagementType(account: account, peerId: peerId, type: updatedValue ? .unrestricted : .restrictedToAdmins) - } else { + |> mapToSignal { peer -> Signal in + if let peer = peer as? TelegramChannel, case let .group(info) = peer.info { + var updatedValue: Bool? + if value && !info.flags.contains(.everyMemberCanInviteMembers) { + updatedValue = true + } else if !value && info.flags.contains(.everyMemberCanInviteMembers) { + updatedValue = false + } + if let updatedValue = updatedValue { + return updateGroupManagementType(account: account, peerId: peerId, type: updatedValue ? .unrestricted : .restrictedToAdmins) + |> `catch` { _ -> Signal in return .complete() } } else { return .complete() } + } else { + return .complete() } + } } updateAdministrationDisposable.set(updateSignal.start()) presentControllerImpl?(actionSheet, nil) diff --git a/TelegramUI/ChannelInfoController.swift b/TelegramUI/ChannelInfoController.swift index ff1dbeffce..9e04b0fff5 100644 --- a/TelegramUI/ChannelInfoController.swift +++ b/TelegramUI/ChannelInfoController.swift @@ -266,7 +266,7 @@ private enum ChannelInfoEntry: ItemListNodeEntry { arguments.aboutLinkAction(action, itemLink) }, tag: ChannelInfoEntryTag.about) case let .addressName(theme, text, value): - return ItemListTextWithLabelItem(theme: theme, label: text, text: "https://t.me/\(value)", enabledEntitiyTypes: [], multiline: false, sectionId: self.section, action: { + return ItemListTextWithLabelItem(theme: theme, label: text, text: "https://t.me/\(value)", textColor: .accent, enabledEntitiyTypes: [], multiline: false, sectionId: self.section, action: { arguments.displayAddressNameContextMenu("https://t.me/\(value)") }, longTapAction: { arguments.displayContextMenu(ChannelInfoEntryTag.link, "https://t.me/\(value)") @@ -594,7 +594,7 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr updateState { $0.withUpdatedUpdatingAvatar(.image(representation)) } - updateAvatarDisposable.set((updatePeerPhoto(account: account, peerId: peerId, photo: uploadedPeerPhoto(account: account, resource: resource)) |> deliverOnMainQueue).start(next: { result in + updateAvatarDisposable.set((updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) |> deliverOnMainQueue).start(next: { result in switch result { case .complete: updateState { @@ -616,7 +616,7 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr return $0.withUpdatedUpdatingAvatar(.none) } } - updateAvatarDisposable.set((updatePeerPhoto(account: account, peerId: peerId, photo: nil) |> deliverOnMainQueue).start(next: { result in + updateAvatarDisposable.set((updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: nil) |> deliverOnMainQueue).start(next: { result in switch result { case .complete: updateState { @@ -693,7 +693,7 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr if let value = value { title = muteForIntervalString(strings: presentationData.strings, value: value) } else { - title = "Default" + title = presentationData.strings.UserInfo_NotificationsDefault } items.append(ActionSheetButtonItem(title: title, action: { dismissAction() diff --git a/TelegramUI/ChannelMembersController.swift b/TelegramUI/ChannelMembersController.swift index 554526d290..bc0adce059 100644 --- a/TelegramUI/ChannelMembersController.swift +++ b/TelegramUI/ChannelMembersController.swift @@ -288,66 +288,66 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo }) confirmationImpl = { [weak contactsController] peerId in return account.postbox.loadedPeerWithId(peerId) - |> deliverOnMainQueue - |> mapToSignal { peer in - let result = ValuePromise() - if let contactsController = contactsController { - let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let alertController = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: "Add \(peer.displayTitle)?", actions: [ - TextAlertAction(type: .genericAction, title: "Cancel", action: { - result.set(false) - }), - TextAlertAction(type: .defaultAction, title: "OK", action: { - result.set(true) - }) - ]) - contactsController.present(alertController, in: .window(.root)) - } - - return result.get() + |> deliverOnMainQueue + |> mapToSignal { peer in + let result = ValuePromise() + if let contactsController = contactsController { + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + let alertController = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.GroupInfo_AddParticipantConfirmation(peer.displayTitle).0, actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + result.set(false) + }), + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { + result.set(true) + }) + ]) + contactsController.present(alertController, in: .window(.root)) + } + + return result.get() } } let addMember = contactsController.result - |> mapError { _ -> AddPeerMemberError in return .generic } - |> deliverOnMainQueue - |> mapToSignal { memberPeer -> Signal in - if let memberPeer = memberPeer, case let .peer(selectedPeer, _) = memberPeer { - let memberId = selectedPeer.id - let applyMembers: Signal = peersPromise.get() - |> filter { $0 != nil } - |> take(1) - |> mapToSignal { peers -> Signal in - return account.postbox.transaction { transaction -> Peer? in - return transaction.getPeer(memberId) - } - |> deliverOnMainQueue - |> mapToSignal { peer -> Signal in - if let peer = peer, let peers = peers { - var updatedPeers = peers - var found = false - for i in 0 ..< updatedPeers.count { - if updatedPeers[i].peer.id == memberId { - found = true - break - } - } - if !found { - let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - updatedPeers.append(RenderedChannelParticipant(participant: ChannelParticipant.member(id: peer.id, invitedAt: timestamp, adminInfo: nil, banInfo: nil), peer: peer, peers: [:])) - peersPromise.set(.single(updatedPeers)) + |> mapError { _ -> AddPeerMemberError in return .generic } + |> deliverOnMainQueue + |> mapToSignal { memberPeer -> Signal in + if let memberPeer = memberPeer, case let .peer(selectedPeer, _) = memberPeer { + let memberId = selectedPeer.id + let applyMembers: Signal = peersPromise.get() + |> filter { $0 != nil } + |> take(1) + |> mapToSignal { peers -> Signal in + return account.postbox.transaction { transaction -> Peer? in + return transaction.getPeer(memberId) + } + |> deliverOnMainQueue + |> mapToSignal { peer -> Signal in + if let peer = peer, let peers = peers { + var updatedPeers = peers + var found = false + for i in 0 ..< updatedPeers.count { + if updatedPeers[i].peer.id == memberId { + found = true + break } } - return .complete() + if !found { + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + updatedPeers.append(RenderedChannelParticipant(participant: ChannelParticipant.member(id: peer.id, invitedAt: timestamp, adminInfo: nil, banInfo: nil), peer: peer, peers: [:])) + peersPromise.set(.single(updatedPeers)) + } } + return .complete() } - |> mapError { _ -> AddPeerMemberError in return .generic } - - return addPeerMember(account: account, peerId: peerId, memberId: memberId) - |> then(applyMembers) - } else { - return .complete() - } + } + |> mapError { _ -> AddPeerMemberError in return .generic } + + return addPeerMember(account: account, peerId: peerId, memberId: memberId) + |> then(applyMembers) + } else { + return .complete() + } } presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) addMembersDisposable.set(addMember.start()) diff --git a/TelegramUI/ChannelVisibilityController.swift b/TelegramUI/ChannelVisibilityController.swift index 590e1fe5df..3aa547062f 100644 --- a/TelegramUI/ChannelVisibilityController.swift +++ b/TelegramUI/ChannelVisibilityController.swift @@ -636,7 +636,7 @@ public func channelVisibilityController(account: Account, peerId: PeerId, mode: peersDisablingAddressNameAssignment.set(.single(nil) |> then(channelAddressNameAssignmentAvailability(account: account, peerId: peerId) |> mapToSignal { result -> Signal<[Peer]?, NoError> in if case .addressNameLimitReached = result { return adminedPublicChannels(account: account) - |> map { Optional($0) } + |> map(Optional.init) } else { return .single([]) } @@ -912,7 +912,38 @@ public func channelVisibilityController(account: Account, peerId: PeerId, mode: } nextImpl = { [weak controller] in if let controller = controller { - (controller.navigationController as? NavigationController)?.replaceAllButRootController(ChatController(account: account, chatLocation: .peer(peerId)), animated: true) + if case .initialSetup = mode { + let selectionController = ContactMultiselectionController(account: account, mode: .channelCreation) + (controller.navigationController as? NavigationController)?.replaceAllButRootController(selectionController, animated: true) + let _ = (selectionController.result + |> deliverOnMainQueue).start(next: { [weak selectionController] peerIds in + guard let selectionController = selectionController, let navigationController = selectionController.navigationController as? NavigationController else { + return + } + let filteredPeerIds = peerIds.compactMap({ peerId -> PeerId? in + if case let .peer(id) = peerId { + return id + } else { + return nil + } + }) + if filteredPeerIds.isEmpty { + navigateToChatController(navigationController: navigationController, chatController: nil, account: account, chatLocation: .peer(peerId), keepStack: .never, animated: true) + } else { + selectionController.displayProgress = true + let _ = (addChannelMembers(account: account, peerId: peerId, memberIds: filteredPeerIds) + |> deliverOnMainQueue).start(completed: { [weak selectionController] in + guard let selectionController = selectionController, let navigationController = selectionController.navigationController as? NavigationController else { + return + } + + navigateToChatController(navigationController: navigationController, chatController: nil, account: account, chatLocation: .peer(peerId), keepStack: .never, animated: true) + }) + } + }) + } else { + (controller.navigationController as? NavigationController)?.replaceAllButRootController(ChatController(account: account, chatLocation: .peer(peerId)), animated: true) + } } } displayPrivateLinkMenuImpl = { [weak controller] text in diff --git a/TelegramUI/ChatBotInfoItem.swift b/TelegramUI/ChatBotInfoItem.swift index 5d041af64e..5c51bcebe1 100644 --- a/TelegramUI/ChatBotInfoItem.swift +++ b/TelegramUI/ChatBotInfoItem.swift @@ -13,10 +13,10 @@ private let messageFixedFont: UIFont = UIFont(name: "Menlo-Regular", size: 16.0) final class ChatBotInfoItem: ListViewItem { fileprivate let text: String fileprivate let controllerInteraction: ChatControllerInteraction - fileprivate let theme: PresentationTheme + fileprivate let theme: ChatPresentationThemeData fileprivate let strings: PresentationStrings - init(text: String, controllerInteraction: ChatControllerInteraction, theme: PresentationTheme, strings: PresentationStrings) { + init(text: String, controllerInteraction: ChatControllerInteraction, theme: ChatPresentationThemeData, strings: PresentationStrings) { self.text = text self.controllerInteraction = controllerInteraction self.theme = theme @@ -73,7 +73,7 @@ final class ChatBotInfoItemNode: ListViewItemNode { var currentTextAndEntities: (String, [MessageTextEntity])? - private var theme: PresentationTheme? + private var theme: ChatPresentationThemeData? init() { self.offsetContainer = ASDisplayNode() @@ -105,8 +105,9 @@ final class ChatBotInfoItemNode: ListViewItemNode { let currentTheme = self.theme return { [weak self] item, params in var updatedBackgroundImage: UIImage? - if currentTheme !== item.theme { - updatedBackgroundImage = PresentationResourcesChat.chatInfoItemBackgroundImage(item.theme) + if currentTheme != item.theme { + let principalGraphics = PresentationResourcesChat.principalGraphics(item.theme.theme, wallpaper: !item.theme.wallpaper.isEmpty) + updatedBackgroundImage = PresentationResourcesChat.chatInfoItemBackgroundImage(item.theme.theme, wallpaper: !item.theme.wallpaper.isEmpty) } var updatedTextAndEntities: (String, [MessageTextEntity]) @@ -120,7 +121,7 @@ final class ChatBotInfoItemNode: ListViewItemNode { updatedTextAndEntities = (item.text, generateTextEntities(item.text, enabledTypes: .all)) } - let attributedText = stringWithAppliedEntities(updatedTextAndEntities.0, entities: updatedTextAndEntities.1, baseColor: item.theme.chat.bubble.infoPrimaryTextColor, linkColor: item.theme.chat.bubble.infoLinkTextColor, baseFont: messageFont, linkFont: messageFont, boldFont: messageBoldFont, italicFont: messageItalicFont, fixedFont: messageFixedFont) + let attributedText = stringWithAppliedEntities(updatedTextAndEntities.0, entities: updatedTextAndEntities.1, baseColor: item.theme.theme.chat.bubble.infoPrimaryTextColor, linkColor: item.theme.theme.chat.bubble.infoLinkTextColor, baseFont: messageFont, linkFont: messageFont, boldFont: messageBoldFont, italicFont: messageItalicFont, fixedFont: messageFixedFont) let horizontalEdgeInset: CGFloat = 10.0 + params.leftInset let horizontalContentInset: CGFloat = 12.0 @@ -174,9 +175,13 @@ final class ChatBotInfoItemNode: ListViewItemNode { func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction { let textNodeFrame = self.textNode.frame - if let (_, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { - if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] as? String { - return .url(url) + if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { + if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { + var concealed = true + if let attributeText = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) { + concealed = !doesUrlMatchText(url: url, text: attributeText) + } + return .url(url: url, concealed: concealed) } else if let peerMention = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention { return .peerMention(peerMention.peerId, peerMention.mention) } else if let peerName = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { @@ -201,10 +206,10 @@ final class ChatBotInfoItemNode: ListViewItemNode { switch tapAction { case .none: break - case let .url(url): + case let .url(url, concealed): foundTapAction = true if let controllerInteraction = self.controllerInteraction { - controllerInteraction.openUrl(url) + controllerInteraction.openUrl(url, concealed) } case let .peerMention(peerId, _): foundTapAction = true diff --git a/TelegramUI/ChatButtonKeyboardInputNode.swift b/TelegramUI/ChatButtonKeyboardInputNode.swift index 1af6b4154e..5a9a7a4398 100644 --- a/TelegramUI/ChatButtonKeyboardInputNode.swift +++ b/TelegramUI/ChatButtonKeyboardInputNode.swift @@ -155,7 +155,7 @@ final class ChatButtonKeyboardInputNode: ChatInputNode { case .text: controllerInteraction.sendMessage(markupButton.title) case let .url(url): - controllerInteraction.openUrl(url) + controllerInteraction.openUrl(url, true) case .requestMap: controllerInteraction.shareCurrentLocation() case .requestPhone: diff --git a/TelegramUI/ChatContextResultPeekContentNode.swift b/TelegramUI/ChatContextResultPeekContentNode.swift index 9fc3094fe2..164d1cbbcf 100644 --- a/TelegramUI/ChatContextResultPeekContentNode.swift +++ b/TelegramUI/ChatContextResultPeekContentNode.swift @@ -202,7 +202,7 @@ private final class ChatContextResultPeekNode: ASDisplayNode, PeekControllerCont var updatedVideoFile = false if let currentVideoFile = currentVideoFile, let videoFileReference = videoFileReference { - if !currentVideoFile.isEqual(videoFileReference.media) { + if !currentVideoFile.isEqual(to: videoFileReference.media) { updatedVideoFile = true } } else if (currentVideoFile != nil) != (videoFileReference != nil) { diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index e681f9caca..aa05cfb169 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -271,7 +271,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin strongSelf.chatDisplayNode.historyNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.chatDisplayNode.historyNode.view) } }, openUrl: { url in - self?.openUrl(url) + self?.openUrl(url, concealed: false) }, openPeer: { peer, navigation in self?.openPeer(peerId: peer.id, navigation: navigation, fromMessage: nil) }, callPeer: { peerId in @@ -471,7 +471,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin strongSelf.chatDisplayNode.dismissInput() strongSelf.present(GameController(account: strongSelf.account, url: url, message: message), in: .window(.root)) } else { - strongSelf.openUrl(url) + strongSelf.openUrl(url, concealed: false) } } } @@ -487,9 +487,9 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin } else { strongSelf.openPeer(peerId: peerId, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), messageId: nil), fromMessage: nil) } - }, openUrl: { [weak self] url in + }, openUrl: { [weak self] url, concealed in if let strongSelf = self { - strongSelf.openUrl(url) + strongSelf.openUrl(url, concealed: concealed) } }, shareCurrentLocation: { [weak self] in if let strongSelf = self { @@ -659,7 +659,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin items.append(ActionSheetButtonItem(title: openText, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() if let strongSelf = self { - strongSelf.openUrl(url) + strongSelf.openUrl(url, concealed: false) } })) items.append(ActionSheetButtonItem(title: canAddToReadingList ? strongSelf.presentationData.strings.ShareMenu_CopyShareLink : strongSelf.presentationData.strings.Conversation_ContextMenuCopy, color: .accent, action: { [weak actionSheet] in @@ -925,7 +925,9 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin } } var renderedPeer: RenderedPeer? + var isContact: Bool = false if let peer = peerView.peers[peerView.peerId] { + isContact = peerView.peerIsContact var peers = SimpleDictionary() peers[peer.id] = peer if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] { @@ -933,7 +935,15 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin } renderedPeer = RenderedPeer(peerId: peer.id, peers: peers) } - strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { return $0.updatedPeer { _ in return renderedPeer }.updatedPeerIsMuted(peerIsMuted) }) + + var animated = false + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, let updated = renderedPeer?.peer as? TelegramSecretChat, peer.embeddedState != updated.embeddedState { + animated = true + } + strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, { + return $0.updatedPeer { _ in return renderedPeer + }.updatedIsContact(isContact).updatedPeerIsMuted(peerIsMuted) + }) if !strongSelf.didSetChatLocationInfoReady { strongSelf.didSetChatLocationInfoReady = true strongSelf._chatLocationInfoReady.set(.single(true)) @@ -1118,10 +1128,11 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin 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 { + if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings || presentationData.chatWallpaper != previousChatWallpaper { strongSelf.themeAndStringsUpdated() } } @@ -1237,6 +1248,14 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + self.chatTitleView?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) + self.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in + var state = state + state = state.updatedTheme(self.presentationData.theme) + state = state.updatedStrings(self.presentationData.strings) + state = state.updatedChatWallpaper(self.presentationData.chatWallpaper) + return state + }) } override public func loadDisplayNode() { @@ -1693,7 +1712,8 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin }, deleteSelectedMessages: { [weak self] in if let strongSelf = self { if let messageIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty { - strongSelf.messageContextDisposable.set((chatAvailableMessageActions(postbox: strongSelf.account.postbox, accountPeerId: strongSelf.account.peerId, messageIds: messageIds) |> deliverOnMainQueue).start(next: { actions in + strongSelf.messageContextDisposable.set((chatAvailableMessageActions(postbox: strongSelf.account.postbox, accountPeerId: strongSelf.account.peerId, messageIds: messageIds) + |> deliverOnMainQueue).start(next: { actions in if let strongSelf = self, !actions.options.isEmpty { if let banAuthor = actions.banAuthor { strongSelf.presentBanMessageOptions(author: banAuthor, messageIds: messageIds, options: actions.options) @@ -2121,32 +2141,11 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin } }) - let rect: CGRect? = strongSelf.chatDisplayNode.frameForInputActionButton() - - let text: String - if let updatedMode = updatedMode, updatedMode == .audio { - text = strongSelf.presentationData.strings.Conversation_HoldForAudio - } else { - text = strongSelf.presentationData.strings.Conversation_HoldForVideo + if let updatedMode = updatedMode, updatedMode == .video { + let _ = ApplicationSpecificNotice.incrementChatMediaMediaRecordingTips(postbox: strongSelf.account.postbox, count: 3).start() } - if let tooltipController = strongSelf.mediaRecordingModeTooltipController { - tooltipController.text = text - } else if let rect = rect { - let tooltipController = TooltipController(text: text) - strongSelf.mediaRecordingModeTooltipController = tooltipController - tooltipController.dismissed = { [weak tooltipController] in - if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRecordingModeTooltipController === tooltipController { - strongSelf.mediaRecordingModeTooltipController = nil - } - } - strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { - if let strongSelf = self { - return (strongSelf.chatDisplayNode, rect) - } - return nil - })) - } + strongSelf.displayMediaRecordingTip() } }, setupMessageAutoremoveTimeout: { [weak self] in if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat { @@ -2261,6 +2260,8 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin } }, reportPeer: { [weak self] in self?.reportPeer() + }, presentPeerContact: { [weak self] in + self?.addPeerContact() }, dismissReportPeer: { [weak self] in self?.dismissReportPeer() }, deleteChat: { [weak self] in @@ -2295,6 +2296,8 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin } }, presentController: { [weak self] controller, arguments in self?.present(controller, in: .window(.root), with: arguments) + }, getNavigationController: { [weak self] in + return self?.navigationController as? NavigationController }, presentGlobalOverlayController: { [weak self] controller, arguments in self?.presentInGlobalOverlay(controller, with: arguments) }, navigateFeed: { [weak self] in @@ -2577,6 +2580,25 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin if case let .peer(peerId) = self.chatLocation { let _ = checkPeerChatServiceActions(postbox: self.account.postbox, peerId: peerId).start() } + + if self.chatDisplayNode.frameForInputActionButton() != nil, self.presentationInterfaceState.interfaceState.mediaRecordingMode == .audio { + let _ = (ApplicationSpecificNotice.getChatMediaMediaRecordingTips(postbox: self.account.postbox) + |> deliverOnMainQueue).start(next: { [weak self] counter in + guard let strongSelf = self else { + return + } + var displayTip = false + if counter == 0 { + displayTip = true + } else if counter < 3 && arc4random_uniform(4) == 1 { + displayTip = true + } + if displayTip { + let _ = ApplicationSpecificNotice.incrementChatMediaMediaRecordingTips(postbox: strongSelf.account.postbox).start() + strongSelf.displayMediaRecordingTip() + } + }) + } } } @@ -2997,7 +3019,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin } private func editMessageMediaWithLegacySignals(_ signals: [Any]) { - guard case let .peer(peerId) = self.chatLocation else { + guard case .peer = self.chatLocation else { return } @@ -3950,7 +3972,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin if let fromMessage = fromMessage { peerSignal = loadedPeerFromMessage(account: self.account, peerId: peerId, messageId: fromMessage.id) } else { - peerSignal = self.account.postbox.loadedPeerWithId(peerId) |> map { Optional($0) } + peerSignal = self.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 { @@ -4106,6 +4128,12 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin } } + private func addPeerContact() { + if let peer = self.presentationInterfaceState.renderedPeer?.peer as? TelegramUser, let phone = peer.phone, !phone.isEmpty, let contactData = DeviceContactExtendedData(peer: peer) { + self.present(addContactOptionsController(account: self.account, peer: peer, contactData: contactData), in: .window(.root)) + } + } + private func dismissReportPeer() { guard case let .peer(peerId) = self.chatLocation else { return @@ -4143,54 +4171,78 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin })) } - private func openUrl(_ url: String) { - let disposable: MetaDisposable - if let current = self.resolveUrlDisposable { - disposable = current - } else { - disposable = MetaDisposable() - self.resolveUrlDisposable = disposable - } - disposable.set((resolveUrl(account: self.account, url: url) |> deliverOnMainQueue).start(next: { [weak self] result in - if let strongSelf = self { + private func openUrl(_ url: String, concealed: Bool) { + let openImpl: () -> Void = { [weak self] in + guard let strongSelf = self else { + return + } + + let disposable: MetaDisposable + if let current = strongSelf.resolveUrlDisposable { + disposable = current + } else { + disposable = MetaDisposable() + strongSelf.resolveUrlDisposable = disposable + } + disposable.set((resolveUrl(account: strongSelf.account, url: url) + |> deliverOnMainQueue).start(next: { [weak self] result in + guard let strongSelf = self else { + return + } openResolvedUrl(result, account: strongSelf.account, navigationController: strongSelf.navigationController as? NavigationController, openPeer: { peerId, navigation in - if let strongSelf = self { - switch navigation { - case let .chat(_, messageId): - if case .peer(peerId) = strongSelf.chatLocation { - if let messageId = messageId { - strongSelf.navigateToMessage(from: nil, to: .id(messageId)) - } - } else if let navigationController = strongSelf.navigationController as? NavigationController { - navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(peerId), messageId: messageId, keepStack: .always) + guard let strongSelf = self else { + return + } + switch navigation { + case let .chat(_, messageId): + if case .peer(peerId) = strongSelf.chatLocation { + if let messageId = messageId { + strongSelf.navigateToMessage(from: nil, to: .id(messageId)) } - case .info: - strongSelf.navigationActionDisposable.set((strongSelf.account.postbox.loadedPeerWithId(peerId) - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self, peer.restrictionText == nil { - if let infoController = peerInfoController(account: strongSelf.account, peer: peer) { - (strongSelf.navigationController as? NavigationController)?.pushViewController(infoController) - } + } else if let navigationController = strongSelf.navigationController as? NavigationController { + navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(peerId), messageId: messageId, keepStack: .always) + } + case .info: + strongSelf.navigationActionDisposable.set((strongSelf.account.postbox.loadedPeerWithId(peerId) + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] peer in + if let strongSelf = self, peer.restrictionText == nil { + if let infoController = peerInfoController(account: strongSelf.account, peer: peer) { + (strongSelf.navigationController as? NavigationController)?.pushViewController(infoController) } - })) - case let .withBotStartPayload(startPayload): - if case .peer(peerId) = strongSelf.chatLocation { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { - $0.updatedBotStartPayload(startPayload.payload) - }) - } else if let navigationController = strongSelf.navigationController as? NavigationController { - navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(peerId), botStart: startPayload) - } - } + } + })) + case let .withBotStartPayload(startPayload): + if case .peer(peerId) = strongSelf.chatLocation { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { + $0.updatedBotStartPayload(startPayload.payload) + }) + } else if let navigationController = strongSelf.navigationController as? NavigationController { + navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(peerId), botStart: startPayload) + } } }, present: { c, a in self?.present(c, in: .window(.root), with: a) }, dismissInput: { self?.chatDisplayNode.dismissInput() }) - } - })) + })) + } + + var parsedUrlValue: URL? + if let parsed = URL(string: url) { + parsedUrlValue = parsed + } else if let encoded = (url as NSString).addingPercentEscapes(using: String.Encoding.utf8.rawValue), let parsed = URL(string: encoded) { + parsedUrlValue = parsed + } + + if concealed, let parsedUrlValue = parsedUrlValue, (parsedUrlValue.scheme == "http" || parsedUrlValue.scheme == "https") { + self.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: self.presentationData.theme), title: nil, text: self.presentationData.strings.Generic_OpenHiddenLinkAlert(url).0, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_No, action: {}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Yes, action: { + openImpl() + })]), in: .window(.root)) + } else { + openImpl() + } } @available(iOSApplicationExtension 9.0, *) @@ -4503,7 +4555,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin var isChannel = false if let user = self.presentationInterfaceState.renderedPeer?.peer as? TelegramUser { personalPeerName = user.compactDisplayTitle - } else if let peer = self.presentationInterfaceState.renderedPeer?.peer as? TelegramUser, let associatedPeerId = peer.associatedPeerId, let user = self.presentationInterfaceState.renderedPeer?.peers[associatedPeerId] as? TelegramUser { + } else if let peer = self.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, let associatedPeerId = peer.associatedPeerId, let user = self.presentationInterfaceState.renderedPeer?.peers[associatedPeerId] as? TelegramUser { personalPeerName = user.compactDisplayTitle } else if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, case .broadcast = channel.info { isChannel = true @@ -4595,4 +4647,35 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin public func beginMessageSearch(_ query: String) { self.interfaceInteraction?.beginMessageSearch(.everything, query) } + + private func displayMediaRecordingTip() { + let rect: CGRect? = self.chatDisplayNode.frameForInputActionButton() + + let updatedMode: ChatTextInputMediaRecordingButtonMode = self.presentationInterfaceState.interfaceState.mediaRecordingMode + + let text: String + if updatedMode == .audio { + text = self.presentationData.strings.Conversation_HoldForAudio + } else { + text = self.presentationData.strings.Conversation_HoldForVideo + } + + if let tooltipController = self.mediaRecordingModeTooltipController { + tooltipController.text = text + } else if let rect = rect { + let tooltipController = TooltipController(text: text) + self.mediaRecordingModeTooltipController = tooltipController + tooltipController.dismissed = { [weak self, weak tooltipController] in + if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRecordingModeTooltipController === tooltipController { + strongSelf.mediaRecordingModeTooltipController = nil + } + } + self.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in + if let strongSelf = self { + return (strongSelf.chatDisplayNode, rect) + } + return nil + })) + } + } } diff --git a/TelegramUI/ChatControllerInteraction.swift b/TelegramUI/ChatControllerInteraction.swift index 3137a0592b..056558924b 100644 --- a/TelegramUI/ChatControllerInteraction.swift +++ b/TelegramUI/ChatControllerInteraction.swift @@ -49,7 +49,7 @@ public final class ChatControllerInteraction { let sendGif: (FileMediaReference) -> Void let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool) -> Void let activateSwitchInline: (PeerId?, String) -> Void - let openUrl: (String) -> Void + let openUrl: (String, Bool) -> Void let shareCurrentLocation: () -> Void let shareAccountContact: () -> Void let sendBotCommand: (MessageId?, String) -> Void @@ -77,7 +77,7 @@ public final class ChatControllerInteraction { var contextHighlightedState: ChatInterfaceHighlightedState? var automaticMediaDownloadSettings: AutomaticMediaDownloadSettings - init(openMessage: @escaping (Message) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings) { + init(openMessage: @escaping (Message) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings) { self.openMessage = openMessage self.openPeer = openPeer self.openPeerMention = openPeerMention diff --git a/TelegramUI/ChatControllerNode.swift b/TelegramUI/ChatControllerNode.swift index 1eabee7121..c65976f92f 100644 --- a/TelegramUI/ChatControllerNode.swift +++ b/TelegramUI/ChatControllerNode.swift @@ -1254,12 +1254,23 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } if self.chatPresentationInterfaceState != chatPresentationInterfaceState { + let themeUpdated = self.chatPresentationInterfaceState.theme !== chatPresentationInterfaceState.theme + + if self.chatPresentationInterfaceState.chatWallpaper != chatPresentationInterfaceState.chatWallpaper { + self.backgroundNode.contents = chatControllerBackgroundImage(wallpaper: chatPresentationInterfaceState.chatWallpaper, postbox: account.postbox)?.cgImage + } + let updatedInputFocus = self.chatPresentationInterfaceStateRequiresInputFocus(self.chatPresentationInterfaceState) != self.chatPresentationInterfaceStateRequiresInputFocus(chatPresentationInterfaceState) let updateInputTextState = self.chatPresentationInterfaceState.interfaceState.effectiveInputState != chatPresentationInterfaceState.interfaceState.effectiveInputState self.chatPresentationInterfaceState = chatPresentationInterfaceState self.navigateButtons.updateTheme(theme: chatPresentationInterfaceState.theme) + if themeUpdated { + self.inputPanelBackgroundNode.backgroundColor = chatPresentationInterfaceState.theme.chat.inputPanel.panelBackgroundColor + self.inputPanelBackgroundSeparatorNode.backgroundColor = self.chatPresentationInterfaceState.theme.chat.inputPanel.panelStrokeColor + } + let keepSendButtonEnabled = chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil || chatPresentationInterfaceState.interfaceState.editMessage != nil var extendedSearchLayout = false loop: for (_, result) in chatPresentationInterfaceState.inputQueryResults { diff --git a/TelegramUI/ChatDocumentGalleryItem.swift b/TelegramUI/ChatDocumentGalleryItem.swift index 42ef4c76d8..9aa0e56b7e 100644 --- a/TelegramUI/ChatDocumentGalleryItem.swift +++ b/TelegramUI/ChatDocumentGalleryItem.swift @@ -197,10 +197,10 @@ class ChatDocumentGalleryItemNode: GalleryItemNode, WKNavigationDelegate { if isActive { actualProgress = max(actualProgress, 0.027) } - strongSelf.statusNode.transitionToState(.progress(color: .white, value: CGFloat(actualProgress), cancelEnabled: true), completion: {}) + strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: CGFloat(actualProgress), cancelEnabled: true), completion: {}) case .Local: if let previousStatus = previousStatus, case .Fetching = previousStatus { - strongSelf.statusNode.transitionToState(.progress(color: .white, value: 1.0, cancelEnabled: true), completion: { + strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: 1.0, cancelEnabled: true), completion: { if let strongSelf = self { strongSelf.statusNode.alpha = 0.0 strongSelf.statusNodeContainer.isUserInteractionEnabled = false diff --git a/TelegramUI/ChatEditInterfaceMessageState.swift b/TelegramUI/ChatEditInterfaceMessageState.swift index 769adcca12..ead7bdfa4b 100644 --- a/TelegramUI/ChatEditInterfaceMessageState.swift +++ b/TelegramUI/ChatEditInterfaceMessageState.swift @@ -21,7 +21,7 @@ final class ChatEditInterfaceMessageState: Equatable { return false } if let lhsMedia = lhs.mediaReference, let rhsMedia = rhs.mediaReference { - if !lhsMedia.media.isEqual(rhsMedia.media) { + if !lhsMedia.media.isEqual(to: rhsMedia.media) { return false } } else if (lhs.mediaReference != nil) != (rhs.mediaReference != nil) { diff --git a/TelegramUI/ChatExternalFileGalleryItem.swift b/TelegramUI/ChatExternalFileGalleryItem.swift index 195075dd0a..82d39129be 100644 --- a/TelegramUI/ChatExternalFileGalleryItem.swift +++ b/TelegramUI/ChatExternalFileGalleryItem.swift @@ -195,10 +195,10 @@ class ChatExternalFileGalleryItemNode: GalleryItemNode { if isActive { actualProgress = max(actualProgress, 0.027) } - strongSelf.statusNode.transitionToState(.progress(color: .white, value: CGFloat(actualProgress), cancelEnabled: true), completion: {}) + strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: CGFloat(actualProgress), cancelEnabled: true), completion: {}) case .Local: if let previousStatus = previousStatus, case .Fetching = previousStatus { - strongSelf.statusNode.transitionToState(.progress(color: .white, value: 1.0, cancelEnabled: true), completion: { + strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: 1.0, cancelEnabled: true), completion: { if let strongSelf = self { strongSelf.statusNode.alpha = 0.0 strongSelf.statusNodeContainer.isUserInteractionEnabled = false diff --git a/TelegramUI/ChatHistoryEntriesForView.swift b/TelegramUI/ChatHistoryEntriesForView.swift index 67d40b4572..aeb0e02078 100644 --- a/TelegramUI/ChatHistoryEntriesForView.swift +++ b/TelegramUI/ChatHistoryEntriesForView.swift @@ -25,7 +25,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, groupBucket.removeAll() } if view.tagMask == nil { - entries.append(.HoleEntry(hole, presentationData.theme, presentationData.strings)) + entries.append(.HoleEntry(hole, presentationData.theme.theme, presentationData.strings)) } case let .MessageEntry(message, read, _, monthLocation): var isAdmin = false @@ -112,7 +112,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, } } if hasMessages { - entries.append(.SearchEntry(presentationData.theme, presentationData.strings)) + entries.append(.SearchEntry(presentationData.theme.theme, presentationData.strings)) } } } diff --git a/TelegramUI/ChatHistoryEntry.swift b/TelegramUI/ChatHistoryEntry.swift index 9731265abb..abb7e74990 100644 --- a/TelegramUI/ChatHistoryEntry.swift +++ b/TelegramUI/ChatHistoryEntry.swift @@ -27,8 +27,8 @@ enum ChatHistoryEntry: Identifiable, Comparable { case HoleEntry(MessageHistoryHole, PresentationTheme, PresentationStrings) case MessageEntry(Message, ChatPresentationData, Bool, MessageHistoryEntryMonthLocation?, ChatHistoryMessageSelection, Bool) case MessageGroupEntry(MessageGroupInfo, [(Message, Bool, ChatHistoryMessageSelection, Bool)], ChatPresentationData) - case UnreadEntry(MessageIndex, PresentationTheme, PresentationStrings) - case ChatInfoEntry(String, PresentationTheme, PresentationStrings) + case UnreadEntry(MessageIndex, ChatPresentationThemeData, PresentationStrings) + case ChatInfoEntry(String, ChatPresentationThemeData, PresentationStrings) case EmptyChatInfoEntry(PresentationTheme, PresentationStrings, MessageTags?) case SearchEntry(PresentationTheme, PresentationStrings) @@ -91,7 +91,7 @@ enum ChatHistoryEntry: Identifiable, Comparable { return false } for i in 0 ..< lhsMessage.media.count { - if !lhsMessage.media[i].isEqual(rhsMessage.media[i]) { + if !lhsMessage.media[i].isEqual(to: rhsMessage.media[i]) { return false } } @@ -148,7 +148,7 @@ enum ChatHistoryEntry: Identifiable, Comparable { return false } for i in 0 ..< lhsMessage.media.count { - if !lhsMessage.media[i].isEqual(rhsMessage.media[i]) { + if !lhsMessage.media[i].isEqual(to: rhsMessage.media[i]) { return false } } @@ -180,7 +180,7 @@ enum ChatHistoryEntry: Identifiable, Comparable { return false } case let .ChatInfoEntry(lhsText, lhsTheme, lhsStrings): - if case let .ChatInfoEntry(rhsText, rhsTheme, rhsStrings) = rhs, lhsText == rhsText, lhsTheme === rhsTheme, lhsStrings === rhsStrings { + if case let .ChatInfoEntry(rhsText, rhsTheme, rhsStrings) = rhs, lhsText == rhsText, lhsTheme == rhsTheme, lhsStrings === rhsStrings { return true } else { return false diff --git a/TelegramUI/ChatHistoryGridNode.swift b/TelegramUI/ChatHistoryGridNode.swift index a701b75090..614409fcac 100644 --- a/TelegramUI/ChatHistoryGridNode.swift +++ b/TelegramUI/ChatHistoryGridNode.swift @@ -133,7 +133,7 @@ private func mappedChatHistoryViewListTransition(account: Account, peerId: PeerI } } - return ChatHistoryGridViewTransition(historyView: transition.historyView, topOffsetWithinMonth: topOffsetWithinMonth, deleteItems: transition.deleteItems.map { $0.index }, insertItems: mappedInsertEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, entries: transition.insertEntries, theme: presentationData.theme, strings: presentationData.strings), updateItems: mappedUpdateEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, entries: transition.updateEntries, theme: presentationData.theme, strings: presentationData.strings), scrollToItem: mappedScrollToItem, stationaryItems: stationaryItems) + return ChatHistoryGridViewTransition(historyView: transition.historyView, topOffsetWithinMonth: topOffsetWithinMonth, deleteItems: transition.deleteItems.map { $0.index }, insertItems: mappedInsertEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, entries: transition.insertEntries, theme: presentationData.theme.theme, strings: presentationData.strings), updateItems: mappedUpdateEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, entries: transition.updateEntries, theme: presentationData.theme.theme, strings: presentationData.strings), scrollToItem: mappedScrollToItem, stationaryItems: stationaryItems) } private func itemSizeForContainerLayout(size: CGSize) -> CGSize { @@ -191,7 +191,7 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode { super.init() - self.chatPresentationDataPromise.set(.single((ChatPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.fontSize, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, timeFormat: self.presentationData.timeFormat)))) + self.chatPresentationDataPromise.set(.single((ChatPresentationData(theme: ChatPresentationThemeData(theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper), fontSize: self.presentationData.fontSize, strings: self.presentationData.strings, timeFormat: self.presentationData.timeFormat)))) self.floatingSections = true @@ -246,7 +246,7 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode { } } - 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)) + 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), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: false)) let previous = previousView.swap(processedView) return preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: false, account: account, chatLocation: .peer(peerId), controllerInteraction: controllerInteraction, scrollPosition: scrollPosition, initialData: nil, keyboardButtonsMessage: nil, cachedData: nil, cachedDataMessages: nil, readStateData: nil) |> map({ mappedChatHistoryViewListTransition(account: account, peerId: peerId, controllerInteraction: controllerInteraction, transition: $0, from: previous, presentationData: chatPresentationData) }) |> runOn(prepareOnMainQueue ? Queue.mainQueue() : messageViewQueue) diff --git a/TelegramUI/ChatHistoryListNode.swift b/TelegramUI/ChatHistoryListNode.swift index 265ebd28d0..453aad28e1 100644 --- a/TelegramUI/ChatHistoryListNode.swift +++ b/TelegramUI/ChatHistoryListNode.swift @@ -60,6 +60,7 @@ enum ChatHistoryViewUpdate { struct ChatHistoryView { let originalView: MessageHistoryView let filteredEntries: [ChatHistoryEntry] + let associatedData: ChatMessageItemAssociatedData } enum ChatHistoryViewTransitionReason { @@ -149,7 +150,7 @@ private func mappedInsertEntries(account: Account, chatLocation: ChatLocation, a case .bubbles: item = ChatMessageItem(presentationData: presentationData, account: account, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, isAdmin: isAdmin)) case let .list(search, _): - item = ListMessageItem(theme: presentationData.theme, strings: presentationData.strings, account: account, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: message, selection: selection, displayHeader: search) + item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, account: account, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: message, selection: selection, displayHeader: search) } return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .MessageGroupEntry(_, messages, presentationData): @@ -159,7 +160,7 @@ private func mappedInsertEntries(account: Account, chatLocation: ChatLocation, a item = ChatMessageItem(presentationData: presentationData, account: account, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages)) case let .list(search, _): assertionFailure() - item = ListMessageItem(theme: presentationData.theme, strings: presentationData.strings, account: account, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: messages[0].0, selection: .none, displayHeader: search) + item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, account: account, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: messages[0].0, selection: .none, displayHeader: search) } return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .HoleEntry(_, theme, strings): @@ -194,7 +195,7 @@ private func mappedUpdateEntries(account: Account, chatLocation: ChatLocation, a case .bubbles: item = ChatMessageItem(presentationData: presentationData, account: account, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, isAdmin: isAdmin)) case let .list(search, _): - item = ListMessageItem(theme: presentationData.theme, strings: presentationData.strings, account: account, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: message, selection: selection, displayHeader: search) + item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, account: account, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: message, selection: selection, displayHeader: search) } return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .MessageGroupEntry(_, messages, presentationData): @@ -204,7 +205,7 @@ private func mappedUpdateEntries(account: Account, chatLocation: ChatLocation, a item = ChatMessageItem(presentationData: presentationData, account: account, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages)) case let .list(search, _): assertionFailure() - item = ListMessageItem(theme: presentationData.theme, strings: presentationData.strings, account: account, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: messages[0].0, selection: .none, displayHeader: search) + item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, account: account, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: messages[0].0, selection: .none, displayHeader: search) } return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .HoleEntry(_, theme, strings): @@ -242,7 +243,7 @@ private final class ChatHistoryTransactionOpaqueState { } } -private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView) -> ChatMessageItemAssociatedData { +private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: AutomaticDownloadNetworkType) -> ChatMessageItemAssociatedData { var automaticMediaDownloadPeerType: AutomaticMediaDownloadPeerType = .channel if case let .peer(peerId) = chatLocation { if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat { @@ -267,7 +268,7 @@ private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHist } } } - let associatedData = ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType) + let associatedData = ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false) return associatedData } @@ -356,7 +357,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.currentPresentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - self.chatPresentationDataPromise = Promise(ChatPresentationData(theme: self.currentPresentationData.theme, fontSize: self.currentPresentationData.fontSize, strings: self.currentPresentationData.strings, wallpaper: self.currentPresentationData.chatWallpaper, timeFormat: self.currentPresentationData.timeFormat)) + self.chatPresentationDataPromise = Promise(ChatPresentationData(theme: ChatPresentationThemeData(theme: self.currentPresentationData.theme, wallpaper: self.currentPresentationData.chatWallpaper), fontSize: self.currentPresentationData.fontSize, strings: self.currentPresentationData.strings, timeFormat: self.currentPresentationData.timeFormat)) super.init() @@ -413,8 +414,19 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } let previousView = Atomic(value: nil) + let automaticDownloadNetworkType = account.networkType + |> map { type -> AutomaticDownloadNetworkType in + switch type { + case .none, .wifi: + return .wifi + case .cellular: + return .cellular + } + } + |> distinctUntilChanged - let historyViewTransition = combineLatest(historyViewUpdate, self.chatPresentationDataPromise.get(), selectedMessages) |> mapToQueue { [weak self] update, chatPresentationData, selectedMessages -> Signal in + let historyViewTransition = combineLatest(historyViewUpdate, self.chatPresentationDataPromise.get(), selectedMessages, automaticDownloadNetworkType) + |> mapToQueue { [weak self] update, chatPresentationData, selectedMessages, networkType -> Signal in let initialData: ChatHistoryCombinedInitialData? switch update { case let .Loading(combinedInitialData): @@ -454,7 +466,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { reverse = reverseValue } - let processedView = ChatHistoryView(originalView: view, filteredEntries: chatHistoryEntriesForView(location: chatLocation, view: view, includeUnreadEntry: mode == .bubbles, includeEmptyEntry: mode == .bubbles && tagMask == nil, includeChatInfoEntry: mode == .bubbles, includeSearchEntry: includeSearchEntry && tagMask != nil, reverse: reverse, groupMessages: mode == .bubbles, selectedMessages: selectedMessages, presentationData: chatPresentationData)) + let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType) + + let processedView = ChatHistoryView(originalView: view, filteredEntries: chatHistoryEntriesForView(location: chatLocation, view: view, includeUnreadEntry: mode == .bubbles, includeEmptyEntry: mode == .bubbles && tagMask == nil, includeChatInfoEntry: mode == .bubbles, includeSearchEntry: includeSearchEntry && tagMask != nil, reverse: reverse, groupMessages: mode == .bubbles, selectedMessages: selectedMessages, presentationData: chatPresentationData), associatedData: associatedData) let previous = previousView.swap(processedView) if scrollPosition == nil, let originalScrollPosition = originalScrollPosition { @@ -495,8 +509,6 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } - let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view) - return preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: reverse, account: account, chatLocation: chatLocation, controllerInteraction: controllerInteraction, scrollPosition: updatedScrollPosition, initialData: initialData?.initialData, keyboardButtonsMessage: view.topTaggedMessages.first, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData) |> map({ mappedChatHistoryViewListTransition(account: account, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, transition: $0) }) |> runOn(prepareOnMainQueue ? Queue.mainQueue() : messageViewQueue) } } @@ -659,16 +671,18 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if let strongSelf = self { let previousTheme = strongSelf.currentPresentationData.theme let previousStrings = strongSelf.currentPresentationData.strings + let previousWallpaper = strongSelf.currentPresentationData.chatWallpaper strongSelf.currentPresentationData = presentationData - if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { + if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings || previousWallpaper != presentationData.chatWallpaper { + let themeData = ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper) strongSelf.forEachItemHeaderNode { itemHeaderNode in if let dateNode = itemHeaderNode as? ChatMessageDateHeaderNode { - dateNode.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings) + dateNode.updateThemeAndStrings(theme: themeData, strings: presentationData.strings) } } - strongSelf.chatPresentationDataPromise.set(.single(ChatPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, wallpaper: presentationData.chatWallpaper, timeFormat: presentationData.timeFormat))) + strongSelf.chatPresentationDataPromise.set(.single(ChatPresentationData(theme: themeData, fontSize: presentationData.fontSize, strings: presentationData.strings, timeFormat: presentationData.timeFormat))) } } }) @@ -1105,26 +1119,40 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { func requestMessageUpdate(_ id: MessageId) { if let historyView = self.historyView { - let associatedData = extractAssociatedData(chatLocation: self.chatLocation, view: historyView.originalView) - - loop: for i in 0 ..< historyView.filteredEntries.count { - switch historyView.filteredEntries[i] { - case let .MessageEntry(message, presentationData, read, _, selection, isAdmin): + var messageItem: ChatMessageItem? + self.forEachItemNode({ itemNode in + if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item { + for message in item.content { if message.id == id { - let index = historyView.filteredEntries.count - 1 - i - let item: ListViewItem - switch self.mode { - case .bubbles: - item = ChatMessageItem(presentationData: presentationData, account: self.account, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, isAdmin: isAdmin)) - case let .list(search, _): - item = ListMessageItem(theme: presentationData.theme, strings: presentationData.strings, account: self.account, chatLocation: self.chatLocation, controllerInteraction: self.controllerInteraction, message: message, selection: selection, displayHeader: search) - } - 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 }) - break loop + messageItem = item + break } - default: - break + } + } + }) + + if let messageItem = messageItem { + let associatedData = messageItem.associatedData + + loop: for i in 0 ..< historyView.filteredEntries.count { + switch historyView.filteredEntries[i] { + case let .MessageEntry(message, presentationData, read, _, selection, isAdmin): + if message.id == id { + let index = historyView.filteredEntries.count - 1 - i + let item: ListViewItem + switch self.mode { + case .bubbles: + item = ChatMessageItem(presentationData: presentationData, account: self.account, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, isAdmin: isAdmin)) + case let .list(search, _): + item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, account: self.account, chatLocation: self.chatLocation, controllerInteraction: self.controllerInteraction, message: message, selection: selection, displayHeader: search) + } + 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 }) + break loop + } + default: + break + } } } } diff --git a/TelegramUI/ChatImageGalleryItem.swift b/TelegramUI/ChatImageGalleryItem.swift index d62cf526ba..2f7f34a06f 100644 --- a/TelegramUI/ChatImageGalleryItem.swift +++ b/TelegramUI/ChatImageGalleryItem.swift @@ -12,13 +12,13 @@ enum ChatMediaGalleryThumbnail: Equatable { static func ==(lhs: ChatMediaGalleryThumbnail, rhs: ChatMediaGalleryThumbnail) -> Bool { switch lhs { case let .image(lhsImage): - if case let .image(rhsImage) = rhs, lhsImage.media.isEqual(rhsImage.media) { + if case let .image(rhsImage) = rhs, lhsImage.media.isEqual(to: rhsImage.media) { return true } else { return false } case let .video(lhsVideo): - if case let .video(rhsVideo) = rhs, lhsVideo.media.isEqual(rhsVideo.media) { + if case let .video(rhsVideo) = rhs, lhsVideo.media.isEqual(to: rhsVideo.media) { return true } else { return false @@ -207,7 +207,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { } fileprivate func setImage(imageReference: ImageMediaReference) { - if self.accountAndMedia == nil || !self.accountAndMedia!.1.media.isEqual(imageReference.media) { + if self.accountAndMedia == nil || !self.accountAndMedia!.1.media.isEqual(to: imageReference.media) { if let largestSize = largestRepresentationForPhoto(imageReference.media) { let displaySize = largestSize.dimensions.fitted(CGSize(width: 1280.0, height: 1280.0)).dividedByScreenScale().integralFloor self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))() @@ -224,7 +224,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { } func setFile(account: Account, fileReference: FileMediaReference) { - if self.accountAndMedia == nil || !self.accountAndMedia!.1.media.isEqual(fileReference.media) { + if self.accountAndMedia == nil || !self.accountAndMedia!.1.media.isEqual(to: fileReference.media) { if var largestSize = fileReference.media.dimensions { var displaySize = largestSize.dividedByScreenScale() if let previewDimensions = largestImageRepresentation(fileReference.media.previewRepresentations)?.dimensions { @@ -266,10 +266,10 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { if isActive { actualProgress = max(actualProgress, 0.027) } - strongSelf.statusNode.transitionToState(.progress(color: .white, value: CGFloat(actualProgress), cancelEnabled: true), completion: {}) + strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: CGFloat(actualProgress), cancelEnabled: true), completion: {}) case .Local: if let previousStatus = previousStatus, case .Fetching = previousStatus { - strongSelf.statusNode.transitionToState(.progress(color: .white, value: 1.0, cancelEnabled: true), completion: { + strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: 1.0, cancelEnabled: true), completion: { if let strongSelf = self { strongSelf.statusNode.alpha = 0.0 strongSelf.statusNodeContainer.isUserInteractionEnabled = false diff --git a/TelegramUI/ChatInterfaceInputContexts.swift b/TelegramUI/ChatInterfaceInputContexts.swift index 334be95314..cc7febec99 100644 --- a/TelegramUI/ChatInterfaceInputContexts.swift +++ b/TelegramUI/ChatInterfaceInputContexts.swift @@ -229,7 +229,17 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte } else { var accessoryItems: [ChatTextInputAccessoryItem] = [] if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat { + var extendedSearchLayout = false + loop: for (_, result) in chatPresentationInterfaceState.inputQueryResults { + if case let .contextRequestResult(peer, _) = result, peer != nil { + extendedSearchLayout = true + break loop + } + } + + if !extendedSearchLayout { accessoryItems.append(.messageAutoremoveTimeout(peer.messageAutoremoveTimeout)) + } } if chatPresentationInterfaceState.interfaceState.composeInputState.inputText.length == 0 { var stickersEnabled = true diff --git a/TelegramUI/ChatInterfaceState.swift b/TelegramUI/ChatInterfaceState.swift index 2a28bbe77f..4addee08f7 100644 --- a/TelegramUI/ChatInterfaceState.swift +++ b/TelegramUI/ChatInterfaceState.swift @@ -299,21 +299,24 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { let closedButtonKeyboardMessageId: MessageId? let processedSetupReplyMessageId: MessageId? let closedPinnedMessageId: MessageId? + let closedPeerSpecificPackSetup: Bool var isEmpty: Bool { - return self.closedButtonKeyboardMessageId == nil && self.processedSetupReplyMessageId == nil && self.closedPinnedMessageId == nil + return self.closedButtonKeyboardMessageId == nil && self.processedSetupReplyMessageId == nil && self.closedPinnedMessageId == nil && self.closedPeerSpecificPackSetup == false } init() { self.closedButtonKeyboardMessageId = nil self.processedSetupReplyMessageId = nil self.closedPinnedMessageId = nil + self.closedPeerSpecificPackSetup = false } - init(closedButtonKeyboardMessageId: MessageId?, processedSetupReplyMessageId: MessageId?, closedPinnedMessageId: MessageId?) { + init(closedButtonKeyboardMessageId: MessageId?, processedSetupReplyMessageId: MessageId?, closedPinnedMessageId: MessageId?, closedPeerSpecificPackSetup: Bool) { self.closedButtonKeyboardMessageId = closedButtonKeyboardMessageId self.processedSetupReplyMessageId = processedSetupReplyMessageId self.closedPinnedMessageId = closedPinnedMessageId + self.closedPeerSpecificPackSetup = closedPeerSpecificPackSetup } init(decoder: PostboxDecoder) { @@ -334,6 +337,8 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { } else { self.closedPinnedMessageId = nil } + + self.closedPeerSpecificPackSetup = decoder.decodeInt32ForKey("cpss", orElse: 0) != 0 } func encode(_ encoder: PostboxEncoder) { @@ -366,22 +371,28 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable { encoder.encodeNil(forKey: "cp.n") encoder.encodeNil(forKey: "cp.i") } + + encoder.encodeInt32(self.closedPeerSpecificPackSetup ? 1 : 0, forKey: "cpss") } static func ==(lhs: ChatInterfaceMessageActionsState, rhs: ChatInterfaceMessageActionsState) -> Bool { - return lhs.closedButtonKeyboardMessageId == rhs.closedButtonKeyboardMessageId && lhs.processedSetupReplyMessageId == rhs.processedSetupReplyMessageId && lhs.closedPinnedMessageId == rhs.closedPinnedMessageId + return lhs.closedButtonKeyboardMessageId == rhs.closedButtonKeyboardMessageId && lhs.processedSetupReplyMessageId == rhs.processedSetupReplyMessageId && lhs.closedPinnedMessageId == rhs.closedPinnedMessageId && lhs.closedPeerSpecificPackSetup == rhs.closedPeerSpecificPackSetup } func withUpdatedClosedButtonKeyboardMessageId(_ closedButtonKeyboardMessageId: MessageId?) -> ChatInterfaceMessageActionsState { - return ChatInterfaceMessageActionsState(closedButtonKeyboardMessageId: closedButtonKeyboardMessageId, processedSetupReplyMessageId: self.processedSetupReplyMessageId, closedPinnedMessageId: self.closedPinnedMessageId) + return ChatInterfaceMessageActionsState(closedButtonKeyboardMessageId: closedButtonKeyboardMessageId, processedSetupReplyMessageId: self.processedSetupReplyMessageId, closedPinnedMessageId: self.closedPinnedMessageId, closedPeerSpecificPackSetup: self.closedPeerSpecificPackSetup) } func withUpdatedProcessedSetupReplyMessageId(_ processedSetupReplyMessageId: MessageId?) -> ChatInterfaceMessageActionsState { - return ChatInterfaceMessageActionsState(closedButtonKeyboardMessageId: self.closedButtonKeyboardMessageId, processedSetupReplyMessageId: processedSetupReplyMessageId, closedPinnedMessageId: self.closedPinnedMessageId) + return ChatInterfaceMessageActionsState(closedButtonKeyboardMessageId: self.closedButtonKeyboardMessageId, processedSetupReplyMessageId: processedSetupReplyMessageId, closedPinnedMessageId: self.closedPinnedMessageId, closedPeerSpecificPackSetup: self.closedPeerSpecificPackSetup) } func withUpdatedClosedPinnedMessageId(_ closedPinnedMessageId: MessageId?) -> ChatInterfaceMessageActionsState { - return ChatInterfaceMessageActionsState(closedButtonKeyboardMessageId: self.closedButtonKeyboardMessageId, processedSetupReplyMessageId: self.processedSetupReplyMessageId, closedPinnedMessageId: closedPinnedMessageId) + return ChatInterfaceMessageActionsState(closedButtonKeyboardMessageId: self.closedButtonKeyboardMessageId, processedSetupReplyMessageId: self.processedSetupReplyMessageId, closedPinnedMessageId: closedPinnedMessageId, closedPeerSpecificPackSetup: self.closedPeerSpecificPackSetup) + } + + func withUpdatedClosedPeerSpecificPackSetup(_ closedPeerSpecificPackSetup: Bool) -> ChatInterfaceMessageActionsState { + return ChatInterfaceMessageActionsState(closedButtonKeyboardMessageId: self.closedButtonKeyboardMessageId, processedSetupReplyMessageId: self.processedSetupReplyMessageId, closedPinnedMessageId: self.closedPinnedMessageId, closedPeerSpecificPackSetup: closedPeerSpecificPackSetup) } } diff --git a/TelegramUI/ChatInterfaceStateContextMenus.swift b/TelegramUI/ChatInterfaceStateContextMenus.swift index 4f7d052e3d..cd27eceb2e 100644 --- a/TelegramUI/ChatInterfaceStateContextMenus.swift +++ b/TelegramUI/ChatInterfaceStateContextMenus.swift @@ -202,59 +202,6 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } - var canEdit = false - if !isAction { - let message = messages[0] - - var hasEditRights = false - if message.id.peerId.namespace == Namespaces.Peer.SecretChat { - hasEditRights = false - } else if let author = message.author, author.id == account.peerId { - hasEditRights = true - } else if message.author?.id == message.id.peerId, let peer = message.peers[message.id.peerId] { - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { - if peer.hasAdminRights(.canEditMessages) { - hasEditRights = true - } - } - } - - if hasEditRights { - var hasUneditableAttributes = false - for attribute in message.attributes { - if let _ = attribute as? InlineBotMessageAttribute { - hasUneditableAttributes = true - break - } - } - if message.forwardInfo != nil { - hasUneditableAttributes = true - } - - for media in message.media { - if let file = media as? TelegramMediaFile { - if file.isSticker || file.isInstantVideo { - hasUneditableAttributes = true - break - } - } else if let _ = media as? TelegramMediaContact { - hasUneditableAttributes = true - break - } else if let _ = media as? TelegramMediaExpiredContent { - hasUneditableAttributes = true - break - } - } - - if !hasUneditableAttributes { - let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - if message.timestamp >= timestamp - 60 * 60 * 24 * 2 || message.id.peerId == account.peerId { - canEdit = true - } - } - } - } - var loadStickerSaveStatusSignal: Signal = .single(nil) if loadStickerSaveStatus != nil { loadStickerSaveStatusSignal = account.postbox.transaction { transaction -> Bool? in @@ -278,8 +225,65 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: |> map(Optional.init) } - dataSignal = combineLatest(loadStickerSaveStatusSignal, loadResourceStatusSignal, chatAvailableMessageActions(postbox: account.postbox, accountPeerId: account.peerId, messageIds: Set(messages.map { $0.id }))) - |> map { stickerSaveStatus, resourceStatus, messageActions -> MessageContextMenuData in + let loadLimits = account.postbox.transaction { transaction -> LimitsConfiguration in + return transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration) as? LimitsConfiguration ?? LimitsConfiguration.defaultValue + } + + dataSignal = combineLatest(loadLimits, loadStickerSaveStatusSignal, loadResourceStatusSignal, chatAvailableMessageActions(postbox: account.postbox, accountPeerId: account.peerId, messageIds: Set(messages.map { $0.id }))) + |> map { limitsConfiguration, stickerSaveStatus, resourceStatus, messageActions -> MessageContextMenuData in + var canEdit = false + if messages[0].id.namespace == Namespaces.Message.Cloud && !isAction { + let message = messages[0] + + var hasEditRights = false + if message.id.peerId.namespace == Namespaces.Peer.SecretChat { + hasEditRights = false + } else if let author = message.author, author.id == account.peerId { + hasEditRights = true + } else if message.author?.id == message.id.peerId, let peer = message.peers[message.id.peerId] { + if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if peer.hasAdminRights(.canEditMessages) { + hasEditRights = true + } + } + } + + if hasEditRights { + var hasUneditableAttributes = false + for attribute in message.attributes { + if let _ = attribute as? InlineBotMessageAttribute { + hasUneditableAttributes = true + break + } + } + if message.forwardInfo != nil { + hasUneditableAttributes = true + } + + for media in message.media { + if let file = media as? TelegramMediaFile { + if file.isSticker || file.isInstantVideo { + hasUneditableAttributes = true + break + } + } else if let _ = media as? TelegramMediaContact { + hasUneditableAttributes = true + break + } else if let _ = media as? TelegramMediaExpiredContent { + hasUneditableAttributes = true + break + } + } + + if !hasUneditableAttributes { + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + if canPerformEditingActions(limits: limitsConfiguration, accountPeerId: account.peerId, message: message) { + canEdit = true + } + } + } + } + return MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, resourceStatus: resourceStatus, messageActions: messageActions) } @@ -358,6 +362,12 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } + if let message = messages.first, message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, let addressName = channel.addressName { + actions.append(.sheet(ChatMessageContextMenuSheetAction(color: .accent, title: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopyLink, action: { + UIPasteboard.general.string = "https://t.me/\(addressName)/\(message.id.id)" + }))) + } + if messages.count == 1 { let message = messages[0] @@ -436,8 +446,23 @@ struct ChatAvailableMessageActions { let banAuthor: Peer? } +private func canPerformEditingActions(limits: LimitsConfiguration, accountPeerId: PeerId, message: Message) -> Bool { + if message.id.peerId == accountPeerId { + return true + } + + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + + if message.timestamp + limits.maxMessageEditingInterval > timestamp { + return true + } else { + return false + } +} + func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messageIds: Set) -> Signal { return postbox.transaction { transaction -> ChatAvailableMessageActions in + let limitsConfiguration: LimitsConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration) as? LimitsConfiguration ?? LimitsConfiguration.defaultValue var optionsMap: [MessageId: ChatAvailableMessageActionOptions] = [:] var banPeer: Peer? var hadBanPeerId = false @@ -500,7 +525,9 @@ func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messag } optionsMap[id]!.insert(.deleteLocally) if !message.flags.contains(.Incoming) { - optionsMap[id]!.insert(.deleteGlobally) + if canPerformEditingActions(limits: limitsConfiguration, accountPeerId: accountPeerId, message: message) { + optionsMap[id]!.insert(.deleteGlobally) + } } } else if let _ = peer as? TelegramSecretChat { optionsMap[id]!.insert(.deleteGlobally) diff --git a/TelegramUI/ChatInterfaceStateContextQueries.swift b/TelegramUI/ChatInterfaceStateContextQueries.swift index 513a9b78ff..dbc5911239 100644 --- a/TelegramUI/ChatInterfaceStateContextQueries.swift +++ b/TelegramUI/ChatInterfaceStateContextQueries.swift @@ -68,7 +68,22 @@ private func updatedContextQueryResultStateForQuery(account: Account, peer: Peer } else { signal = .single({ _ in return .stickers([]) }) } - let stickers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = searchStickers(account: account, query: query.firstEmoji) + let stickers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = account.postbox.transaction { transaction -> StickerSettings in + let stickerSettings: StickerSettings = (transaction.getPreferencesEntry(key: ApplicationSpecificPreferencesKeys.stickerSettings) as? StickerSettings) ?? .defaultSettings + return stickerSettings + } + |> mapToSignal { stickerSettings -> Signal<[FoundStickerItem], NoError> in + let scope: SearchStickersScope + switch stickerSettings.emojiStickerSuggestionMode { + case .none: + scope = [] + case .all: + scope = [.installed, .remote] + case .installed: + scope = [.installed] + } + return searchStickers(account: account, query: query.firstEmoji, scope: scope) + } |> map { stickers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in return { _ in return .stickers(stickers) diff --git a/TelegramUI/ChatListController.swift b/TelegramUI/ChatListController.swift index a9b94eb58a..029c1ada61 100644 --- a/TelegramUI/ChatListController.swift +++ b/TelegramUI/ChatListController.swift @@ -91,7 +91,8 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD self?.chatListDisplayNode.chatListNode.scrollToPosition(.top) } self.scrollToTopWithTabBar = { [weak self] in - self?.chatListDisplayNode.chatListNode.scrollToPosition(.auto) + self?.chatListDisplayNode.chatListNode.scrollToPosition(.top) + //.auto for unread navigation } let hasProxy = account.postbox.preferencesView(keys: [PreferencesKeys.proxySettings]) @@ -429,7 +430,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD } } - self.badgeIconDisposable = (self.chatListDisplayNode.chatListNode.scrollToTopOption + /*self.badgeIconDisposable = (self.chatListDisplayNode.chatListNode.scrollToTopOption |> distinctUntilChanged |> deliverOnMainQueue).start(next: { [weak self] option in guard let strongSelf = self else { @@ -443,7 +444,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD case .unread: strongSelf.tabBarItem.selectedImage = tabImageUnread } - }) + })*/ self.displayNodeDidLoad() } diff --git a/TelegramUI/ChatListControllerNode.swift b/TelegramUI/ChatListControllerNode.swift index 691ab0f2c8..5b0bb4346b 100644 --- a/TelegramUI/ChatListControllerNode.swift +++ b/TelegramUI/ChatListControllerNode.swift @@ -8,6 +8,7 @@ class ChatListControllerNode: ASDisplayNode { private let account: Account private let groupId: PeerGroupId? + private var chatListEmptyNode: ChatListEmptyNode? let chatListNode: ChatListNode var navigationBar: NavigationBar? @@ -35,13 +36,38 @@ class ChatListControllerNode: ASDisplayNode { return UITracingLayerView() }) + self.backgroundColor = theme.chatList.backgroundColor + self.addSubnode(self.chatListNode) + self.chatListNode.isEmptyUpdated = { [weak self] isEmpty in + guard let strongSelf = self else { + return + } + if isEmpty { + if strongSelf.chatListEmptyNode == nil { + let chatListEmptyNode = ChatListEmptyNode(theme: strongSelf.themeAndStrings.0, strings: strongSelf.themeAndStrings.1) + strongSelf.chatListEmptyNode = chatListEmptyNode + strongSelf.insertSubnode(chatListEmptyNode, belowSubnode: strongSelf.chatListNode) + if let (layout, navigationHeight) = strongSelf.containerLayout { + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate) + } + } + } else if let chatListEmptyNode = strongSelf.chatListEmptyNode { + strongSelf.chatListEmptyNode = nil + chatListEmptyNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak chatListEmptyNode] _ in + chatListEmptyNode?.removeFromSupernode() + }) + } + } } func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat) { self.themeAndStrings = (theme, strings, timeFormat) + + self.backgroundColor = theme.chatList.backgroundColor self.chatListNode.updateThemeAndStrings(theme: theme, strings: strings, timeFormat: timeFormat) self.searchDisplayController?.updateThemeAndStrings(theme: theme, strings: strings) + self.chatListEmptyNode?.updateThemeAndStrings(theme: theme, strings: strings) } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { @@ -82,6 +108,12 @@ class ChatListControllerNode: ASDisplayNode { self.chatListNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets) + if let chatListEmptyNode = self.chatListEmptyNode { + let emptySize = CGSize(width: updateSizeAndInsets.size.width, height: updateSizeAndInsets.size.height - updateSizeAndInsets.insets.top - updateSizeAndInsets.insets.bottom) + transition.updateFrame(node: chatListEmptyNode, frame: CGRect(origin: CGPoint(x: 0.0, y: updateSizeAndInsets.insets.top), size: emptySize)) + chatListEmptyNode.updateLayout(size: emptySize, transition: transition) + } + if let searchDisplayController = self.searchDisplayController { searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) } diff --git a/TelegramUI/ChatListEmptyNode.swift b/TelegramUI/ChatListEmptyNode.swift new file mode 100644 index 0000000000..361da7fdd9 --- /dev/null +++ b/TelegramUI/ChatListEmptyNode.swift @@ -0,0 +1,42 @@ +import Foundation +import AsyncDisplayKit +import Display + +final class ChatListEmptyNode: ASDisplayNode { + private let textNode: ImmediateTextNode + + private var validLayout: CGSize? + + init(theme: PresentationTheme, strings: PresentationStrings) { + self.textNode = ImmediateTextNode() + self.textNode.displaysAsynchronously = false + self.textNode.maximumNumberOfLines = 0 + self.textNode.isLayerBacked = true + self.textNode.textAlignment = .center + self.textNode.lineSpacing = 0.1 + + super.init() + + self.addSubnode(self.textNode) + + self.updateThemeAndStrings(theme: theme, strings: strings) + } + + func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + let string = NSMutableAttributedString() + string.append(NSAttributedString(string: strings.DialogList_NoMessagesTitle + "\n", font: Font.medium(17.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center)) + string.append(NSAttributedString(string: strings.DialogList_NoMessagesText, font: Font.regular(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center)) + self.textNode.attributedText = string + + if let size = self.validLayout { + self.updateLayout(size: size, transition: .immediate) + } + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + self.validLayout = size + + let textSize = self.textNode.updateLayout(size) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((size.height - textSize.height) / 2.0)), size: textSize)) + } +} diff --git a/TelegramUI/ChatListItem.swift b/TelegramUI/ChatListItem.swift index 9e03fb97e9..db64d94b15 100644 --- a/TelegramUI/ChatListItem.swift +++ b/TelegramUI/ChatListItem.swift @@ -194,9 +194,9 @@ private func revealOptions(strings: PresentationStrings, theme: PresentationThem private func leftRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isUnread: Bool) -> [ItemListRevealOption] { var options: [ItemListRevealOption] = [] if isUnread { - options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.ChatList_MarkAsRead, icon: readIcon, color: theme.list.itemDisclosureActions.neutral1.fillColor, textColor: theme.list.itemDisclosureActions.neutral1.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.DialogList_Read, icon: readIcon, color: theme.list.itemDisclosureActions.neutral1.fillColor, textColor: theme.list.itemDisclosureActions.neutral1.foregroundColor)) } else { - options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.ChatList_MarkAsUnread, icon: unreadIcon, color: theme.list.itemDisclosureActions.accent.fillColor, textColor: theme.list.itemDisclosureActions.accent.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.DialogList_Unread, icon: unreadIcon, color: theme.list.itemDisclosureActions.accent.fillColor, textColor: theme.list.itemDisclosureActions.accent.foregroundColor)) } return options } @@ -1142,7 +1142,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } } - override func revealOptionSelected(_ option: ItemListRevealOption) { + override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { var close = true if let item = self.item { switch option.key { @@ -1177,7 +1177,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { case RevealOptionKey.ungroup.rawValue: item.interaction.updatePeerGrouping(item.index.messageIndex.id.peerId, false) case RevealOptionKey.toggleMarkedUnread.rawValue: - item.interaction.togglePeerMarkedUnread(item.index.messageIndex.id.peerId) + item.interaction.togglePeerMarkedUnread(item.index.messageIndex.id.peerId, animated) close = false default: break diff --git a/TelegramUI/ChatListNode.swift b/TelegramUI/ChatListNode.swift index 8235d1906f..2bb29d8080 100644 --- a/TelegramUI/ChatListNode.swift +++ b/TelegramUI/ChatListNode.swift @@ -56,11 +56,11 @@ final class ChatListNodeInteraction { let setPeerMuted: (PeerId, Bool) -> Void let deletePeer: (PeerId) -> Void let updatePeerGrouping: (PeerId, Bool) -> Void - let togglePeerMarkedUnread: (PeerId) -> Void + let togglePeerMarkedUnread: (PeerId, Bool) -> Void var highlightedChatLocation: ChatListHighlightedLocation? - init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, messageSelected: @escaping (Message) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId) -> Void) { + init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, messageSelected: @escaping (Message) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void) { self.activateSearch = activateSearch self.peerSelected = peerSelected self.messageSelected = messageSelected @@ -330,6 +330,9 @@ final class ChatListNode: ListView { } } + var isEmptyUpdated: ((Bool) -> Void)? + private var wasEmpty: Bool? + init(account: Account, groupId: PeerGroupId?, controlsHistoryPreload: Bool, mode: ChatListNodeMode, theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat) { self.account = account self.controlsHistoryPreload = controlsHistoryPreload @@ -342,8 +345,6 @@ final class ChatListNode: ListView { super.init() - self.backgroundColor = theme.chatList.backgroundColor - let nodeInteraction = ChatListNodeInteraction(activateSearch: { [weak self] in if let strongSelf = self, let activateSearch = strongSelf.activateSearch { activateSearch() @@ -392,16 +393,24 @@ final class ChatListNode: ListView { self?.deletePeerChat?(peerId) }, updatePeerGrouping: { [weak self] peerId, group in self?.updatePeerGrouping?(peerId, group) - }, togglePeerMarkedUnread: { [weak self, weak account] peerId in + }, togglePeerMarkedUnread: { [weak self, weak account] peerId, animated in guard let account = account else { return } - let _ = (togglePeerUnreadMarkInteractively(postbox: account.postbox, viewTracker: account.viewTracker, peerId: peerId) - |> deliverOnMainQueue).start(completed: { + if false && animated { self?.updateState { return $0.withUpdatedPeerIdWithRevealedOptions(nil) } + } + + let _ = (togglePeerUnreadMarkInteractively(postbox: account.postbox, viewTracker: account.viewTracker, peerId: peerId) + |> deliverOnMainQueue).start(completed: { + if true || animated { + self?.updateState { + return $0.withUpdatedPeerIdWithRevealedOptions(nil) + } + } }) }) @@ -760,7 +769,6 @@ final class ChatListNode: ListView { func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat) { if theme !== self.currentState.presentationData.theme || strings !== self.currentState.presentationData.strings || timeFormat != self.currentState.presentationData.timeFormat { self.theme = theme - self.backgroundColor = theme.chatList.backgroundColor if self.keepTopItemOverscrollBackground != nil { self.keepTopItemOverscrollBackground = ListViewKeepTopItemOverscrollBackground(color: theme.chatList.pinnedItemBackgroundColor, direction: true) @@ -846,6 +854,15 @@ final class ChatListNode: ListView { strongSelf._ready.set(true) } + var isEmpty = false + if transition.chatListView.filteredEntries.count == 1, case .SearchEntry = transition.chatListView.filteredEntries[0] { + isEmpty = true + } + if strongSelf.wasEmpty != isEmpty { + strongSelf.wasEmpty = isEmpty + strongSelf.isEmptyUpdated?(isEmpty) + } + completion() } } diff --git a/TelegramUI/ChatListNodeEntries.swift b/TelegramUI/ChatListNodeEntries.swift index 7b292d591c..089505641c 100644 --- a/TelegramUI/ChatListNodeEntries.swift +++ b/TelegramUI/ChatListNodeEntries.swift @@ -256,7 +256,9 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState, } result.append(.SearchEntry(theme: state.presentationData.theme, text: view.groupId == nil ? state.presentationData.strings.DialogList_SearchLabel : "Search this feed")) } - if result.count == 2, case .SearchEntry = result[1], case .HoleEntry = result[0] { + if result.count >= 2, case .SearchEntry = result[result.count - 1], case .HoleEntry = result[result.count - 2] { + return [] + } else if result.count == 2, case .SearchEntry = result[1], case .HoleEntry = result[0] { return [] } return result diff --git a/TelegramUI/ChatListSearchContainerNode.swift b/TelegramUI/ChatListSearchContainerNode.swift index 587ec0a6d5..8c5bca6947 100644 --- a/TelegramUI/ChatListSearchContainerNode.swift +++ b/TelegramUI/ChatListSearchContainerNode.swift @@ -38,13 +38,13 @@ private enum ChatListRecentEntryStableId: Hashable { private enum ChatListRecentEntry: Comparable, Identifiable { case topPeers([Peer], PresentationTheme, PresentationStrings) - case peer(index: Int, peer: RecentlySearchedPeer, PresentationTheme, PresentationStrings, Bool) + case peer(index: Int, peer: RecentlySearchedPeer, PresentationTheme, PresentationStrings, PresentationTimeFormat, Bool) var stableId: ChatListRecentEntryStableId { switch self { case .topPeers: return .topPeers - case let .peer(_, peer, _, _, _): + case let .peer(_, peer, _, _, _, _): return .peerId(peer.peer.peerId) } } @@ -71,8 +71,8 @@ private enum ChatListRecentEntry: Comparable, Identifiable { } else { return false } - case let .peer(lhsIndex, lhsPeer, lhsTheme, lhsStrings, lhsHasRevealControls): - if case let .peer(rhsIndex, rhsPeer, rhsTheme, rhsStrings, rhsHasRevealControls) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings && lhsHasRevealControls == rhsHasRevealControls { + case let .peer(lhsIndex, lhsPeer, lhsTheme, lhsStrings, lhsTimeFormat, lhsHasRevealControls): + if case let .peer(rhsIndex, rhsPeer, rhsTheme, rhsStrings, rhsTimeFormat, rhsHasRevealControls) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings && lhsTimeFormat == rhsTimeFormat && lhsHasRevealControls == rhsHasRevealControls { return true } else { return false @@ -84,11 +84,11 @@ private enum ChatListRecentEntry: Comparable, Identifiable { switch lhs { case .topPeers: return true - case let .peer(lhsIndex, _, _, _, _): + case let .peer(lhsIndex, _, _, _, _, _): switch rhs { case .topPeers: return false - case let .peer(rhsIndex, _, _, _, _): + case let .peer(rhsIndex, _, _, _, _, _): return lhsIndex <= rhsIndex } } @@ -102,7 +102,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable { }, peerLongTapped: { peer in peerLongTapped(peer) }) - case let .peer(_, peer, theme, strings, hasRevealControls): + case let .peer(_, peer, theme, strings, timeFormat, hasRevealControls): let primaryPeer: Peer var chatPeer: Peer? let maybeChatPeer = peer.peer.peers[peer.peer.peerId]! @@ -147,7 +147,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable { if let _ = user.botInfo { status = .custom(strings.Bot_GenericBotStatus) } else if let presence = peer.presence { - status = .presence(presence) + status = .presence(presence, timeFormat) } else { status = .none } @@ -621,7 +621,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in - }, togglePeerMarkedUnread: { _ in + }, togglePeerMarkedUnread: { _, _ in }) let previousRecentItems = Atomic<[ChatListRecentEntry]?>(value: nil) @@ -655,7 +655,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { } peerIds.insert(peer.id) - entries.append(.peer(index: index, peer: searchedPeer, presentationData.theme, presentationData.strings, state.peerIdWithRevealedOptions == peer.id)) + entries.append(.peer(index: index, peer: searchedPeer, presentationData.theme, presentationData.strings, presentationData.timeFormat, state.peerIdWithRevealedOptions == peer.id)) index += 1 } } diff --git a/TelegramUI/ChatListSearchItemHeader.swift b/TelegramUI/ChatListSearchItemHeader.swift index 26cbb4e276..fcbcffcf62 100644 --- a/TelegramUI/ChatListSearchItemHeader.swift +++ b/TelegramUI/ChatListSearchItemHeader.swift @@ -79,6 +79,11 @@ final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode { self.addSubnode(self.sectionHeaderNode) } + func updateTheme(theme: PresentationTheme) { + self.theme = theme + self.sectionHeaderNode.updateTheme(theme: theme) + } + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { self.sectionHeaderNode.frame = CGRect(origin: CGPoint(), size: size) self.sectionHeaderNode.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset) diff --git a/TelegramUI/ChatListTitleProxyNode.swift b/TelegramUI/ChatListTitleProxyNode.swift index 409f9831eb..66c2072d72 100644 --- a/TelegramUI/ChatListTitleProxyNode.swift +++ b/TelegramUI/ChatListTitleProxyNode.swift @@ -8,22 +8,6 @@ enum ChatTitleProxyStatus { case available } -/* - - - - Created with Sketch. - - - - - - - - - - */ - private func generateIcon(color: UIColor, connected: Bool, off: Bool) -> UIImage? { return generateImage(CGSize(width: 18.0, height: 22.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) diff --git a/TelegramUI/ChatListViewTransition.swift b/TelegramUI/ChatListViewTransition.swift index 32fbf0b096..8540a960b7 100644 --- a/TelegramUI/ChatListViewTransition.swift +++ b/TelegramUI/ChatListViewTransition.swift @@ -181,6 +181,11 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV } } + if let fromView = fromView, fromView.filteredEntries.isEmpty { + options.remove(.AnimateInsertion) + options.remove(.AnimateAlpha) + } + subscriber.putNext(ChatListNodeViewTransition(chatListView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange)) subscriber.putCompletion() diff --git a/TelegramUI/ChatMediaInputGifPane.swift b/TelegramUI/ChatMediaInputGifPane.swift index e066013351..d0f563f9cc 100644 --- a/TelegramUI/ChatMediaInputGifPane.swift +++ b/TelegramUI/ChatMediaInputGifPane.swift @@ -9,16 +9,23 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate { private let account: Account private let controllerInteraction: ChatControllerInteraction + private let paneDidScroll: (ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void + private let fixPaneScroll: (ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void private var multiplexedNode: MultiplexedVideoNode? private let emptyNode: ImmediateTextNode private let disposable = MetaDisposable() private var validLayout: CGSize? + private var didScrollPreviousOffset: CGFloat? - init(account: Account, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ChatControllerInteraction) { + private var didScrollPreviousState: ChatMediaInputPaneScrollState? + + init(account: Account, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ChatControllerInteraction, paneDidScroll: @escaping (ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void, fixPaneScroll: @escaping (ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void) { self.account = account self.controllerInteraction = controllerInteraction + self.paneDidScroll = paneDidScroll + self.fixPaneScroll = fixPaneScroll self.emptyNode = ImmediateTextNode() self.emptyNode.isLayerBacked = true @@ -35,7 +42,7 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate { self.disposable.dispose() } - override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, transition: ContainedViewLayoutTransition) { self.validLayout = size let emptySize = self.emptyNode.updateLayout(size) transition.updateFrame(node: self.emptyNode, frame: CGRect(origin: CGPoint(x: floor(size.width - emptySize.width) / 2.0, y: topInset + floor(size.height - topInset - emptySize.height) / 2.0), size: emptySize)) @@ -94,6 +101,30 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate { multiplexedNode.fileSelected = { [weak self] fileReference in self?.controllerInteraction.sendGif(fileReference) } + + multiplexedNode.didScroll = { [weak self] offset, height in + guard let strongSelf = self else { + return + } + let absoluteOffset = -offset + var delta: CGFloat = 0.0 + if let didScrollPreviousOffset = strongSelf.didScrollPreviousOffset { + delta = absoluteOffset - didScrollPreviousOffset + } + strongSelf.didScrollPreviousOffset = absoluteOffset + let state = ChatMediaInputPaneScrollState(absoluteOffset: absoluteOffset, relativeChange: delta) + strongSelf.didScrollPreviousState = state + strongSelf.paneDidScroll(strongSelf, state, .immediate) + } + + multiplexedNode.didEndScrolling = { [weak self] in + guard let strongSelf = self else { + return + } + if let didScrollPreviousState = strongSelf.didScrollPreviousState { + strongSelf.fixPaneScroll(strongSelf, didScrollPreviousState) + } + } } } } diff --git a/TelegramUI/ChatMediaInputGridEntries.swift b/TelegramUI/ChatMediaInputGridEntries.swift index 85e4de10c1..9c731a5f88 100644 --- a/TelegramUI/ChatMediaInputGridEntries.swift +++ b/TelegramUI/ChatMediaInputGridEntries.swift @@ -5,17 +5,21 @@ import Display enum ChatMediaInputGridEntryStableId: Equatable, Hashable { case search + case peerSpecificSetup case sticker(ItemCollectionId, ItemCollectionItemIndex.Id) } enum ChatMediaInputGridEntryIndex: Equatable, Comparable { case search + case peerSpecificSetup(dismissed: Bool) case collectionIndex(ItemCollectionViewEntryIndex) var stableId: ChatMediaInputGridEntryStableId { switch self { case .search: return .search + case .peerSpecificSetup: + return .peerSpecificSetup case let .collectionIndex(index): return .sticker(index.collectionId, index.itemIndex.id) } @@ -29,10 +33,31 @@ enum ChatMediaInputGridEntryIndex: Equatable, Comparable { } else { return true } + case let .peerSpecificSetup(lhsDismissed): + switch rhs { + case .search, .peerSpecificSetup: + return false + case let .collectionIndex(index): + if lhsDismissed { + return false + } else { + if index.collectionId.id == 0 { + return false + } else { + return true + } + } + } case let .collectionIndex(lhsIndex): switch rhs { case .search: return false + case let .peerSpecificSetup(dismissed): + if dismissed { + return true + } else { + return false + } case let .collectionIndex(rhsIndex): return lhsIndex < rhsIndex } @@ -42,12 +67,15 @@ enum ChatMediaInputGridEntryIndex: Equatable, Comparable { enum ChatMediaInputGridEntry: Equatable, Comparable, Identifiable { case search(theme: PresentationTheme, strings: PresentationStrings) + case peerSpecificSetup(theme: PresentationTheme, strings: PresentationStrings, dismissed: Bool) case sticker(index: ItemCollectionViewEntryIndex, stickerItem: StickerPackItem, stickerPackInfo: StickerPackCollectionInfo?, theme: PresentationTheme) var index: ChatMediaInputGridEntryIndex { switch self { case .search: return .search + case let .peerSpecificSetup(_, _, dismissed): + return .peerSpecificSetup(dismissed: dismissed) case let .sticker(index, _, _, _): return .collectionIndex(index) } @@ -71,6 +99,12 @@ enum ChatMediaInputGridEntry: Equatable, Comparable, Identifiable { } else { return false } + case let .peerSpecificSetup(lhsTheme, lhsStrings, lhsDismissed): + if case let .peerSpecificSetup(rhsTheme, rhsStrings, rhsDismissed) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDismissed == rhsDismissed { + return true + } else { + return false + } case let .sticker(lhsIndex, lhsStickerItem, lhsStickerPackInfo, lhsTheme): if case let .sticker(rhsIndex, rhsStickerItem, rhsStickerPackInfo, rhsTheme) = rhs { if lhsIndex != rhsIndex { @@ -102,6 +136,12 @@ enum ChatMediaInputGridEntry: Equatable, Comparable, Identifiable { return StickerPaneSearchBarPlaceholderItem(theme: theme, strings: strings, activate: { inputNodeInteraction.toggleSearch(true) }) + case let .peerSpecificSetup(theme, strings, dismissed): + return StickerPanePeerSpecificSetupGridItem(theme: theme, strings: strings, setup: { + inputNodeInteraction.openPeerSpecificSettings() + }, dismiss: dismissed ? nil : { + inputNodeInteraction.dismissPeerSpecificSettings() + }) case let .sticker(index, stickerItem, stickerPackInfo, theme): return ChatMediaInputStickerGridItem(account: account, collectionId: index.collectionId, stickerPackInfo: stickerPackInfo, index: index, stickerItem: stickerItem, interfaceInteraction: interfaceInteraction, inputNodeInteraction: inputNodeInteraction, theme: theme, selected: { }) } diff --git a/TelegramUI/ChatMediaInputNode.swift b/TelegramUI/ChatMediaInputNode.swift index 928877b34e..e01bf46470 100644 --- a/TelegramUI/ChatMediaInputNode.swift +++ b/TelegramUI/ChatMediaInputNode.swift @@ -11,6 +11,11 @@ private struct PeerSpecificPackData { let items: [ItemCollectionItem] } +private enum CanInstallPeerSpecificPack { + case none + case available(dismissed: Bool) +} + private struct ChatMediaInputPanelTransition { let deletions: [ListViewDeleteItem] let insertions: [ListViewInsertItem] @@ -46,7 +51,7 @@ private func preparedChatMediaInputGridEntryTransition(account: Account, view: I case .initial: for i in (0 ..< toEntries.count).reversed() { switch toEntries[i] { - case .search: + case .search, .peerSpecificSetup: break case .sticker: scrollToItem = GridNodeScrollToItem(index: i, position: .top, transition: .animated(duration: 0.0, curve: .easeInOut), directionHint: .down, adjustForSection: true, adjustForTopInset: true) @@ -108,7 +113,7 @@ private func preparedChatMediaInputGridEntryTransition(account: Account, view: I var firstIndexInSectionOffset = 0 if !toEntries.isEmpty { switch toEntries[0].index { - case .search: + case .search, .peerSpecificSetup: break case let .collectionIndex(index): firstIndexInSectionOffset = Int(index.itemIndex.index) @@ -149,7 +154,7 @@ private func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers if let recentStickers = recentStickers, !recentStickers.items.isEmpty { var found = false for item in recentStickers.items { - if let item = item.contents as? RecentMediaItem, let file = item.media as? TelegramMediaFile, let mediaId = item.media.id { + if let item = item.contents as? RecentMediaItem, let _ = item.media as? TelegramMediaFile, let mediaId = item.media.id { if !savedStickerIds.contains(mediaId.id) { found = true break @@ -175,7 +180,7 @@ private func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers return entries } -private func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, strings: PresentationStrings, theme: PresentationTheme) -> [ChatMediaInputGridEntry] { +private func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, strings: PresentationStrings, theme: PresentationTheme) -> [ChatMediaInputGridEntry] { var entries: [ChatMediaInputGridEntry] = [] if view.lower == nil { @@ -221,6 +226,10 @@ private func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: } } + if peerSpecificPack == nil, case .available(false) = canInstallPeerSpecificPack { + entries.append(.peerSpecificSetup(theme: theme, strings: strings, dismissed: false)) + } + if let peerSpecificPack = peerSpecificPack { for i in 0 ..< peerSpecificPack.items.count { let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_GroupStickers, shortName: "", hash: 0, count: 0) @@ -239,6 +248,12 @@ private func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: entries.append(.sticker(index: entry.index, stickerItem: item, stickerPackInfo: stickerPackInfos[entry.index.collectionId], theme: theme)) } } + + if view.higher == nil { + if peerSpecificPack == nil, case .available(true) = canInstallPeerSpecificPack { + entries.append(.peerSpecificSetup(theme: theme, strings: strings, dismissed: true)) + } + } return entries } @@ -279,17 +294,19 @@ final class ChatMediaInputNodeInteraction { let openSettings: () -> Void let toggleSearch: (Bool) -> Void let openPeerSpecificSettings: () -> Void + let dismissPeerSpecificSettings: () -> Void var highlightedStickerItemCollectionId: ItemCollectionId? var highlightedItemCollectionId: ItemCollectionId? var previewedStickerPackItem: StickerPreviewPeekItem? var appearanceTransition: CGFloat = 1.0 - init(navigateToCollectionId: @escaping (ItemCollectionId) -> Void, openSettings: @escaping () -> Void, toggleSearch: @escaping (Bool) -> Void, openPeerSpecificSettings: @escaping () -> Void) { + init(navigateToCollectionId: @escaping (ItemCollectionId) -> Void, openSettings: @escaping () -> Void, toggleSearch: @escaping (Bool) -> Void, openPeerSpecificSettings: @escaping () -> Void, dismissPeerSpecificSettings: @escaping () -> Void) { self.navigateToCollectionId = navigateToCollectionId self.openSettings = openSettings self.toggleSearch = toggleSearch self.openPeerSpecificSettings = openPeerSpecificSettings + self.dismissPeerSpecificSettings = dismissPeerSpecificSettings } } @@ -338,6 +355,7 @@ private final class CollectionListContainerNode: ASDisplayNode { final class ChatMediaInputNode: ChatInputNode { private let account: Account + private let peerId: PeerId? private let controllerInteraction: ChatControllerInteraction private let gifPaneIsActiveUpdated: (Bool) -> Void @@ -364,6 +382,7 @@ final class ChatMediaInputNode: ChatInputNode { private let itemCollectionsViewPosition = Promise() private var currentStickerPacksCollectionPosition: StickerPacksCollectionPosition? private var currentView: ItemCollectionsView? + private let dismissedPeerSpecificStickerPack = Promise() private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, ChatPresentationInterfaceState)? private var paneArrangement: ChatMediaInputPaneArrangement @@ -375,6 +394,7 @@ final class ChatMediaInputNode: ChatInputNode { init(account: Account, peerId: PeerId?, controllerInteraction: ChatControllerInteraction, theme: PresentationTheme, strings: PresentationStrings, gifPaneIsActiveUpdated: @escaping (Bool) -> Void) { self.account = account + self.peerId = peerId self.controllerInteraction = controllerInteraction self.theme = theme self.strings = strings @@ -404,7 +424,11 @@ final class ChatMediaInputNode: ChatInputNode { }, fixPaneScroll: { pane, state in fixPaneScrollImpl?(pane, state) }) - self.gifPane = ChatMediaInputGifPane(account: account, theme: theme, strings: strings, controllerInteraction: controllerInteraction) + self.gifPane = ChatMediaInputGifPane(account: account, theme: theme, strings: strings, controllerInteraction: controllerInteraction, paneDidScroll: { pane, state, transition in + paneDidScrollImpl?(pane, state, transition) + }, fixPaneScroll: { pane, state in + fixPaneScrollImpl?(pane, state) + }) var getItemIsPreviewedImpl: ((StickerPackItem) -> Bool)? self.trendingPane = ChatMediaInputTrendingPane(account: account, controllerInteraction: controllerInteraction, getItemIsPreviewed: { item in @@ -482,6 +506,8 @@ final class ChatMediaInputNode: ChatInputNode { } strongSelf.controllerInteraction.presentController(groupStickerPackSetupController(account: account, peerId: peerId, currentPackInfo: info), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }) + }, dismissPeerSpecificSettings: { [weak self] in + self?.dismissPeerSpecificPackSetup() }) getItemIsPreviewedImpl = { [weak self] item in @@ -547,46 +573,62 @@ final class ChatMediaInputNode: ChatInputNode { let previousEntries = Atomic<([ChatMediaInputPanelEntry], [ChatMediaInputGridEntry])>(value: ([], [])) let inputNodeInteraction = self.inputNodeInteraction! - let peerSpecificPack: Signal + let peerSpecificPack: Signal<(PeerSpecificPackData?, CanInstallPeerSpecificPack), NoError> if let peerId = peerId { - peerSpecificPack = combineLatest(peerSpecificStickerPack(postbox: account.postbox, network: account.network, peerId: peerId), account.postbox.multiplePeersView([peerId])) - |> map { packData, peersView -> PeerSpecificPackData? in + self.dismissedPeerSpecificStickerPack.set(account.postbox.transaction { transaction -> Bool in + guard let state = transaction.getPeerChatInterfaceState(peerId) as? ChatInterfaceState else { + return false + } + if state.messageActionsState.closedPeerSpecificPackSetup { + return true + } + + return false + }) + peerSpecificPack = combineLatest(peerSpecificStickerPack(postbox: account.postbox, network: account.network, peerId: peerId), account.postbox.multiplePeersView([peerId]), self.dismissedPeerSpecificStickerPack.get()) + |> map { packData, peersView, dismissedPeerSpecificPack -> (PeerSpecificPackData?, CanInstallPeerSpecificPack) in if let peer = peersView.peers[peerId] { - if let (info, items) = packData { - return PeerSpecificPackData(peer: peer, info: info, items: items) + var canInstall: CanInstallPeerSpecificPack = .none + if packData.canSetup { + canInstall = .available(dismissed: dismissedPeerSpecificPack) + } + if let (info, items) = packData.packInfo { + return (PeerSpecificPackData(peer: peer, info: info, items: items), canInstall) + } else { + return (nil, canInstall) } } - return nil + return (nil, .none) } } else { - peerSpecificPack = .single(nil) + peerSpecificPack = .single((nil, .none)) } let previousView = Atomic(value: nil) let transitions = combineLatest(itemCollectionsView, peerSpecificPack, self.themeAndStringsPromise.get()) - |> map { viewAndUpdate, peerSpecificPack, themeAndStrings -> (ItemCollectionsView, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in - let (view, viewUpdate) = viewAndUpdate - let previous = previousView.swap(view) - var update = viewUpdate - if previous === view { - update = .generic - } - let (theme, strings) = themeAndStrings - - var savedStickers: OrderedItemListView? - var recentStickers: OrderedItemListView? - for orderedView in view.orderedItemListsViews { - if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentStickers { - recentStickers = orderedView - } else if orderedView.collectionId == Namespaces.OrderedItemList.CloudSavedStickers { - savedStickers = orderedView - } - } - let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack, theme: theme) - let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack, strings: strings, theme: theme) - let (previousPanelEntries, previousGridEntries) = previousEntries.swap((panelEntries, gridEntries)) - return (view, preparedChatMediaInputPanelEntryTransition(account: account, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: inputNodeInteraction), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: inputNodeInteraction), previousGridEntries.isEmpty) + |> map { viewAndUpdate, peerSpecificPack, themeAndStrings -> (ItemCollectionsView, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in + let (view, viewUpdate) = viewAndUpdate + let previous = previousView.swap(view) + var update = viewUpdate + if previous === view { + update = .generic } + let (theme, strings) = themeAndStrings + + var savedStickers: OrderedItemListView? + var recentStickers: OrderedItemListView? + for orderedView in view.orderedItemListsViews { + if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentStickers { + recentStickers = orderedView + } else if orderedView.collectionId == Namespaces.OrderedItemList.CloudSavedStickers { + savedStickers = orderedView + } + } + let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, theme: theme) + let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, strings: strings, theme: theme) + let (previousPanelEntries, previousGridEntries) = previousEntries.swap((panelEntries, gridEntries)) + return (view, preparedChatMediaInputPanelEntryTransition(account: account, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: inputNodeInteraction), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: inputNodeInteraction), previousGridEntries.isEmpty) + } self.disposable.set((transitions |> deliverOnMainQueue).start(next: { [weak self] (view, panelTransition, panelFirstTime, gridTransition, gridFirstTime) in @@ -629,8 +671,12 @@ final class ChatMediaInputNode: ChatInputNode { strongSelf.itemCollectionsViewPosition.set(.single(position)) } } else if bottomIndex >= visibleItems.count - 10 && currentView.higher != nil { - let position: StickerPacksCollectionPosition = clipScrollPosition(.scroll(aroundIndex: (bottomItem as! ChatMediaInputStickerGridItem).index)) - if strongSelf.currentStickerPacksCollectionPosition != position { + var position: StickerPacksCollectionPosition? + if let bottomItem = bottomItem as? ChatMediaInputStickerGridItem { + position = clipScrollPosition(.scroll(aroundIndex: bottomItem.index)) + } + + if let position = position, strongSelf.currentStickerPacksCollectionPosition != position { strongSelf.currentStickerPacksCollectionPosition = position strongSelf.itemCollectionsViewPosition.set(.single(position)) } @@ -1002,7 +1048,9 @@ final class ChatMediaInputNode: ChatInputNode { let separatorHeight = UIScreenPixel let panelHeight: CGFloat + var isExpanded: Bool = false if case let .media(_, maybeExpanded) = interfaceState.inputMode, let expanded = maybeExpanded { + isExpanded = true switch expanded { case .content: panelHeight = maximumHeight @@ -1025,7 +1073,7 @@ final class ChatMediaInputNode: ChatInputNode { self?.inputNodeInteraction.toggleSearch(false) }) self.stickerSearchContainerNode = stickerSearchContainerNode - self.addSubnode(stickerSearchContainerNode) + self.insertSubnode(stickerSearchContainerNode, belowSubnode: self.collectionListContainer) stickerSearchContainerNode.frame = containerFrame stickerSearchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, transition: .immediate) var placeholderNode: StickerPaneSearchBarPlaceholderNode? @@ -1121,9 +1169,10 @@ final class ChatMediaInputNode: ChatInputNode { } } - self.gifPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, transition: transition) - self.stickerPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, transition: transition) - self.trendingPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, transition: transition) + self.gifPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, transition: transition) + + self.stickerPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, transition: transition) + self.trendingPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, transition: transition) if self.gifPane.supernode != nil { if !visiblePanes.contains(where: { $0.0 == .gifs }) { @@ -1245,7 +1294,7 @@ final class ChatMediaInputNode: ChatInputNode { if transition.animated { itemTransition = .animated(duration: 0.3, curve: .spring) } - self.stickerPane.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: itemTransition, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.updateFirstIndexInSectionOffset), completion: { _ in }) + self.stickerPane.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: itemTransition, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.updateFirstIndexInSectionOffset, updateOpaqueState: transition.updateOpaqueState), completion: { _ in }) } private func updatePreviewingItem(item: StickerPreviewPeekItem?, animated: Bool) { @@ -1380,4 +1429,20 @@ final class ChatMediaInputNode: ChatInputNode { } return insets } + + private func dismissPeerSpecificPackSetup() { + guard let peerId = self.peerId else { + return + } + self.dismissedPeerSpecificStickerPack.set(.single(true)) + let _ = (self.account.postbox.transaction { transaction -> Void in + transaction.updatePeerChatInterfaceState(peerId, update: { current in + if let current = current as? ChatInterfaceState { + return current.withUpdatedMessageActionsState({ $0.withUpdatedClosedPeerSpecificPackSetup(true) }) + } else { + return current + } + }) + }).start() + } } diff --git a/TelegramUI/ChatMediaInputPane.swift b/TelegramUI/ChatMediaInputPane.swift index c7b2f993a1..6fe5c6cf74 100644 --- a/TelegramUI/ChatMediaInputPane.swift +++ b/TelegramUI/ChatMediaInputPane.swift @@ -10,6 +10,6 @@ struct ChatMediaInputPaneScrollState { class ChatMediaInputPane: ASDisplayNode { var collectionListPanelOffset: CGFloat = 0.0 - func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) { + func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, transition: ContainedViewLayoutTransition) { } } diff --git a/TelegramUI/ChatMediaInputSettingsItem.swift b/TelegramUI/ChatMediaInputSettingsItem.swift index adbb2a9d04..221da92a7a 100644 --- a/TelegramUI/ChatMediaInputSettingsItem.swift +++ b/TelegramUI/ChatMediaInputSettingsItem.swift @@ -70,6 +70,8 @@ final class ChatMediaInputSettingsItemNode: ListViewItemNode { self.buttonNode.frame = CGRect(origin: CGPoint(), size: boundingSize) self.imageNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) + self.imageNode.contentMode = .center + self.imageNode.contentsScale = UIScreenScale super.init(layerBacked: false, dynamicBounce: false) diff --git a/TelegramUI/ChatMediaInputStickerPane.swift b/TelegramUI/ChatMediaInputStickerPane.swift index 3934d0cf3a..309bfb75a8 100644 --- a/TelegramUI/ChatMediaInputStickerPane.swift +++ b/TelegramUI/ChatMediaInputStickerPane.swift @@ -14,6 +14,7 @@ final class ChatMediaInputStickerPaneOpaqueState { } final class ChatMediaInputStickerPane: ChatMediaInputPane { + private var isExpanded: Bool? let gridNode: GridNode private let paneDidScroll: (ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void private let fixPaneScroll: (ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void @@ -22,7 +23,6 @@ final class ChatMediaInputStickerPane: ChatMediaInputPane { init(theme: PresentationTheme, strings: PresentationStrings, paneDidScroll: @escaping (ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void, fixPaneScroll: @escaping (ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void) { self.gridNode = GridNode() - //self.gridNode.initialOffset = 54.0 self.paneDidScroll = paneDidScroll self.fixPaneScroll = fixPaneScroll @@ -31,11 +31,12 @@ final class ChatMediaInputStickerPane: ChatMediaInputPane { self.addSubnode(self.gridNode) self.gridNode.presentationLayoutUpdated = { [weak self] layout, transition in if let strongSelf = self, let opaqueState = strongSelf.gridNode.opaqueState as? ChatMediaInputStickerPaneOpaqueState { - let offset: CGFloat + var offset: CGFloat if opaqueState.hasLower { offset = -(layout.contentOffset.y + 41.0) } else { offset = -(layout.contentOffset.y + 41.0) + offset = min(0.0, offset + 56.0) } var relativeChange: CGFloat = 0.0 if let didScrollPreviousOffset = strongSelf.didScrollPreviousOffset { @@ -57,12 +58,51 @@ final class ChatMediaInputStickerPane: ChatMediaInputPane { self.gridNode.scrollView.alwaysBounceVertical = true } - override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, transition: ContainedViewLayoutTransition) { + var changedIsExpanded = false + if let previousIsExpanded = self.isExpanded { + if previousIsExpanded != isExpanded { + changedIsExpanded = true + } + } + self.isExpanded = isExpanded + let sideInset: CGFloat = 2.0 var itemSide: CGFloat = floor((size.width - sideInset * 2.0) / 5.0) itemSide = min(itemSide, 75.0) let itemSize = CGSize(width: itemSide, height: itemSide) - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset, bottom: bottomInset, right: sideInset), preloadSize: 300.0, type: .fixed(itemSize: itemSize, lineSpacing: 0.0)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + + var scrollToItem: GridNodeScrollToItem? + if changedIsExpanded { + if isExpanded { + var scrollIndex: Int? + for i in 0 ..< self.gridNode.items.count { + if let _ = self.gridNode.items[i] as? StickerPaneSearchBarPlaceholderItem { + scrollIndex = i + break + } + } + if let scrollIndex = scrollIndex { + scrollToItem = GridNodeScrollToItem(index: scrollIndex, position: .top, transition: transition, directionHint: .down, adjustForSection: true, adjustForTopInset: true) + } + } else { + var scrollIndex: Int? + for i in 0 ..< self.gridNode.items.count { + if let _ = self.gridNode.items[i] as? ChatMediaInputStickerGridItem { + scrollIndex = i + break + } + } + if let scrollIndex = scrollIndex { + scrollToItem = GridNodeScrollToItem(index: scrollIndex, position: .top, transition: transition, directionHint: .down, adjustForSection: true, adjustForTopInset: true) + } + } + } + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset, bottom: bottomInset, right: sideInset), preloadSize: 300.0, type: .fixed(itemSize: itemSize, lineSpacing: 0.0)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + + if false, let scrollToItem = scrollToItem { + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + } transition.updateFrame(node: self.gridNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) } diff --git a/TelegramUI/ChatMediaInputTrendingPane.swift b/TelegramUI/ChatMediaInputTrendingPane.swift index 8f7fc656cf..85ffddd323 100644 --- a/TelegramUI/ChatMediaInputTrendingPane.swift +++ b/TelegramUI/ChatMediaInputTrendingPane.swift @@ -192,7 +192,7 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane { }) } - override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, transition: ContainedViewLayoutTransition) { let hadValidLayout = self.validLayout != nil self.validLayout = (size, bottomInset) diff --git a/TelegramUI/ChatMessageActionItemNode.swift b/TelegramUI/ChatMessageActionItemNode.swift index 108cfc9f02..8d65a5e54b 100644 --- a/TelegramUI/ChatMessageActionItemNode.swift +++ b/TelegramUI/ChatMessageActionItemNode.swift @@ -468,7 +468,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center) return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in - let attributedString = attributedServiceMessageString(theme: item.presentationData.theme, strings: item.presentationData.strings, message: item.message, accountPeerId: item.account.peerId) + let attributedString = attributedServiceMessageString(theme: item.presentationData.theme.theme, strings: item.presentationData.strings, message: item.message, accountPeerId: item.account.peerId) let (labelLayout, apply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) @@ -492,7 +492,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { labelRects[i].origin.x = floor((labelLayout.size.width - labelRects[i].width) / 2.0) } - let backgroundApply = backgroundLayout(item.presentationData.theme.chat.serviceMessage.serviceMessageFillColor, labelRects, 10.0, 10.0, 0.0) + let backgroundApply = backgroundLayout(item.presentationData.theme.theme.chat.serviceMessage.serviceMessageFillColor, labelRects, 10.0, 10.0, 0.0) let backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + 4.0) let layoutInsets = UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0) @@ -522,7 +522,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { if let point = point { if let (index, attributes) = self.labelNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY - 10.0)) { let possibleNames: [String] = [ - TelegramTextAttributes.Url, + TelegramTextAttributes.URL, TelegramTextAttributes.PeerMention, TelegramTextAttributes.PeerTextMention, TelegramTextAttributes.BotCommand, @@ -550,7 +550,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { if let current = self.linkHighlightingNode { linkHighlightingNode = current } else { - linkHighlightingNode = LinkHighlightingNode(color: item.presentationData.theme.chat.serviceMessage.serviceMessageLinkHighlightColor) + linkHighlightingNode = LinkHighlightingNode(color: item.presentationData.theme.theme.chat.serviceMessage.serviceMessageLinkHighlightColor) linkHighlightingNode.inset = 2.5 self.linkHighlightingNode = linkHighlightingNode self.insertSubnode(linkHighlightingNode, belowSubnode: self.labelNode) @@ -568,9 +568,13 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction { let textNodeFrame = self.labelNode.frame - if let (_, attributes) = self.labelNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY - 10.0)) { - if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] as? String { - return .url(url) + if let (index, attributes) = self.labelNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY - 10.0)) { + if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { + var concealed = true + if let attributeText = self.labelNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) { + concealed = !doesUrlMatchText(url: url, text: attributeText) + } + return .url(url: url, concealed: concealed) } else if let peerMention = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention { return .peerMention(peerMention.peerId, peerMention.mention) } else if let peerName = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { diff --git a/TelegramUI/ChatMessageAttachedContentNode.swift b/TelegramUI/ChatMessageAttachedContentNode.swift index 9154028b24..d0d0f602c5 100644 --- a/TelegramUI/ChatMessageAttachedContentNode.swift +++ b/TelegramUI/ChatMessageAttachedContentNode.swift @@ -224,7 +224,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { private var account: Account? private var message: Message? private var media: Media? - private var theme: PresentationTheme? + private var theme: ChatPresentationThemeData? var openMedia: (() -> Void)? var activateAction: (() -> Void)? @@ -263,7 +263,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { self.addSubnode(self.statusNode) } - func asyncLayout() -> (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: AutomaticMediaDownloadSettings, _ account: Account, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ title: String?, _ subtitle: String?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ constrainedSize: CGSize) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))) { + func asyncLayout() -> (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: AutomaticMediaDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ account: Account, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ title: String?, _ subtitle: String?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ constrainedSize: CGSize) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))) { let textAsyncLayout = TextNode.asyncLayout(self.textNode) let currentImage = self.media as? TelegramMediaImage let imageLayout = self.inlineImageNode.asyncLayout() @@ -276,7 +276,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { let currentAdditionalImageBadgeNode = self.additionalImageBadgeNode - return { presentationData, automaticDownloadSettings, account, controllerInteraction, message, messageRead, title, subtitle, text, entities, mediaAndFlags, actionIcon, actionTitle, displayLine, layoutConstants, constrainedSize in + return { presentationData, automaticDownloadSettings, associatedData, account, controllerInteraction, message, messageRead, title, subtitle, text, entities, mediaAndFlags, actionIcon, actionTitle, displayLine, layoutConstants, constrainedSize in let incoming = message.effectivelyIncoming(account.peerId) var horizontalInsets = UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 12.0) @@ -338,7 +338,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { let string = NSMutableAttributedString() var notEmpty = false - let bubbleTheme = presentationData.theme.chat.bubble + let bubbleTheme = presentationData.theme.theme.chat.bubble if let title = title, !title.isEmpty { string.append(NSAttributedString(string: title, font: titleFont, textColor: incoming ? bubbleTheme.incomingAccentTextColor : bubbleTheme.outgoingAccentTextColor)) @@ -373,23 +373,23 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { if let (media, flags) = mediaAndFlags { if let file = media as? TelegramMediaFile { if file.isInstantVideo { - let (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(account: account, controllerInteraction: controllerInteraction, message: message, read: messageRead, presentationData: presentationData), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, CGSize(width: 180.0, height: 180.0), .bubble) + let (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(account: account, controllerInteraction: controllerInteraction, message: message, read: messageRead, presentationData: presentationData, associatedData: associatedData), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, CGSize(width: 180.0, height: 180.0), .bubble) initialWidth = videoLayout.contentSize.width + videoLayout.overflowLeft + videoLayout.overflowRight contentInstantVideoSizeAndApply = (videoLayout, apply) } else if file.isVideo { var automaticDownload = false - automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peer: message.peers[message.id.peerId], media: file) - let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme, presentationData.strings, message, file, automaticDownload, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) + automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: file) + let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme.theme, presentationData.strings, message, file, automaticDownload, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right refineContentImageLayout = refineLayout } else if file.isSticker, let _ = file.dimensions { - let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peer: message.peers[message.id.peerId], media: file) - let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme, presentationData.strings, message, file, automaticDownload, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) + let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: file) + let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme.theme, presentationData.strings, message, file, automaticDownload, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right refineContentImageLayout = refineLayout } else { var automaticDownload = false - automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peer: message.peers[message.id.peerId], media: file) + automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: file) let statusType: ChatMessageDateAndStatusType if message.effectivelyIncoming(account.peerId) { @@ -404,13 +404,13 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } } - let (_, refineLayout) = contentFileLayout(account, presentationData, message, file, automaticDownload, message.effectivelyIncoming(account.peerId), statusType, CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)) + let (_, refineLayout) = contentFileLayout(account, presentationData, message, file, automaticDownload, message.effectivelyIncoming(account.peerId), false, statusType, CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)) refineContentFileLayout = refineLayout } } else if let image = media as? TelegramMediaImage { if !flags.contains(.preferMediaInline) { - let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peer: message.peers[message.id.peerId], media: image) - let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme, presentationData.strings, message, image, automaticDownload, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) + let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: image) + let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme.theme, presentationData.strings, message, image, automaticDownload, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right refineContentImageLayout = refineLayout } else if let dimensions = largestImageRepresentation(image.representations)?.dimensions { @@ -421,8 +421,8 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } } } else if let image = media as? TelegramMediaWebFile { - let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peer: message.peers[message.id.peerId], media: image) - let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme, presentationData.strings, message, image, automaticDownload, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) + let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: image) + let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme.theme, presentationData.strings, message, image, automaticDownload, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right refineContentImageLayout = refineLayout } @@ -460,7 +460,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { var skipStandardStatus = false if let count = webpageGalleryMediaCount { - additionalImageBadgeContent = .text(backgroundColor: presentationData.theme.chat.bubble.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.chat.bubble.mediaDateAndStatusTextColor, shape: .corners(2.0), text: NSAttributedString(string: "1 \(presentationData.strings.Common_of) \(count)")) + additionalImageBadgeContent = .text(backgroundColor: presentationData.theme.theme.chat.bubble.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.theme.chat.bubble.mediaDateAndStatusTextColor, shape: .corners(2.0), text: NSAttributedString(string: "1 \(presentationData.strings.Common_of) \(count)")) skipStandardStatus = imageMode } @@ -535,7 +535,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { textFrame = textFrame.offsetBy(dx: insets.left, dy: insets.top) - let lineImage = incoming ? PresentationResourcesChat.chatBubbleVerticalLineIncomingImage(presentationData.theme) : PresentationResourcesChat.chatBubbleVerticalLineOutgoingImage(presentationData.theme) + let lineImage = incoming ? PresentationResourcesChat.chatBubbleVerticalLineIncomingImage(presentationData.theme.theme) : PresentationResourcesChat.chatBubbleVerticalLineOutgoingImage(presentationData.theme.theme) var boundingSize = textFrame.size var lineHeight = textFrame.size.height @@ -591,23 +591,27 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { let titleColor: UIColor let titleHighlightedColor: UIColor if incoming { - buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonIncoming(presentationData.theme)! - buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIncoming(presentationData.theme)! + buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonIncoming(presentationData.theme.theme)! + buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIncoming(presentationData.theme.theme)! if let actionIcon = actionIcon, case .instant = actionIcon { - buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconInstantIncoming(presentationData.theme)! - buttonHighlightedIconImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIconInstantIncoming(presentationData.theme)! + buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconInstantIncoming(presentationData.theme.theme)! + buttonHighlightedIconImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIconInstantIncoming(presentationData.theme.theme, wallpaper: !presentationData.theme.wallpaper.isEmpty)! } - titleColor = presentationData.theme.chat.bubble.incomingAccentTextColor - titleHighlightedColor = presentationData.theme.chat.bubble.incomingFillColor + titleColor = presentationData.theme.theme.chat.bubble.incomingAccentTextColor + + let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: true, wallpaper: !presentationData.theme.wallpaper.isEmpty) + titleHighlightedColor = bubbleColor.fill } else { - buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonOutgoing(presentationData.theme)! - buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonOutgoing(presentationData.theme)! + buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonOutgoing(presentationData.theme.theme)! + buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonOutgoing(presentationData.theme.theme)! if let actionIcon = actionIcon, case .instant = actionIcon { - buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconInstantOutgoing(presentationData.theme)! - buttonHighlightedIconImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIconInstantOutgoing(presentationData.theme)! + buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconInstantOutgoing(presentationData.theme.theme)! + buttonHighlightedIconImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIconInstantOutgoing(presentationData.theme.theme, wallpaper: !presentationData.theme.wallpaper.isEmpty)! } - titleColor = presentationData.theme.chat.bubble.outgoingAccentTextColor - titleHighlightedColor = presentationData.theme.chat.bubble.outgoingFillColor + let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: true, wallpaper: !presentationData.theme.wallpaper.isEmpty) + + titleColor = presentationData.theme.theme.chat.bubble.outgoingAccentTextColor + titleHighlightedColor = bubbleColor.fill } let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, buttonImage, buttonHighlightedImage, buttonIconImage, buttonHighlightedIconImage, actionTitle, titleColor, titleHighlightedColor) boundingSize.width = max(buttonWidth, boundingSize.width) @@ -861,7 +865,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { if let media = media { var found = false for m in media { - if currentMedia.isEqual(m) { + if currentMedia.isEqual(to: m) { found = true break } @@ -878,11 +882,11 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } func transitionNode(media: Media) -> (ASDisplayNode, () -> UIView?)? { - if let contentImageNode = self.contentImageNode, let image = self.media as? TelegramMediaImage, image.isEqual(media) { + if let contentImageNode = self.contentImageNode, let image = self.media as? TelegramMediaImage, image.isEqual(to: media) { return (contentImageNode, { [weak contentImageNode] in return contentImageNode?.view.snapshotContentTree(unhide: true) }) - } else if let contentImageNode = self.contentImageNode, let file = self.media as? TelegramMediaFile, file.isEqual(media) { + } else if let contentImageNode = self.contentImageNode, let file = self.media as? TelegramMediaFile, file.isEqual(to: media) { return (contentImageNode, { [weak contentImageNode] in return contentImageNode?.view.snapshotContentTree(unhide: true) }) @@ -899,9 +903,13 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction { let textNodeFrame = self.textNode.frame - if let (_, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { - if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] as? String { - return .url(url) + if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { + if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { + var concealed = true + if let attributeText = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) { + concealed = !doesUrlMatchText(url: url, text: attributeText) + } + return .url(url: url, concealed: concealed) } else if let peerMention = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention { return .peerMention(peerMention.peerId, peerMention.mention) } else if let peerName = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { @@ -925,7 +933,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { let textNodeFrame = self.textNode.frame if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { let possibleNames: [String] = [ - TelegramTextAttributes.Url, + TelegramTextAttributes.URL, TelegramTextAttributes.PeerMention, TelegramTextAttributes.PeerTextMention, TelegramTextAttributes.BotCommand, @@ -945,7 +953,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { if let current = self.linkHighlightingNode { linkHighlightingNode = current } else { - linkHighlightingNode = LinkHighlightingNode(color: message.effectivelyIncoming(account.peerId) ? theme.chat.bubble.incomingLinkHighlightColor : theme.chat.bubble.outgoingLinkHighlightColor) + linkHighlightingNode = LinkHighlightingNode(color: message.effectivelyIncoming(account.peerId) ? theme.theme.chat.bubble.incomingLinkHighlightColor : theme.theme.chat.bubble.outgoingLinkHighlightColor) self.linkHighlightingNode = linkHighlightingNode self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode) } diff --git a/TelegramUI/ChatMessageBackground.swift b/TelegramUI/ChatMessageBackground.swift index 621238350b..9234128af2 100644 --- a/TelegramUI/ChatMessageBackground.swift +++ b/TelegramUI/ChatMessageBackground.swift @@ -2,14 +2,14 @@ import Foundation import AsyncDisplayKit import Display -enum ChatMessageBackgroundMergeType { - case None, Side, Top, Bottom, Both +enum ChatMessageBackgroundMergeType: Equatable { + case None, Side, Top(side: Bool), Bottom, Both init(top: Bool, bottom: Bool, side: Bool) { if top && bottom { self = .Both } else if top { - self = .Top + self = .Top(side: side) } else if bottom { if side { self = .Side @@ -85,8 +85,12 @@ class ChatMessageBackground: ASImageNode { switch mergeType { case .None: image = highlighted ? graphics.chatMessageBackgroundIncomingHighlightedImage : graphics.chatMessageBackgroundIncomingImage - case .Top: - image = highlighted ? graphics.chatMessageBackgroundIncomingMergedTopHighlightedImage : graphics.chatMessageBackgroundIncomingMergedTopImage + case let .Top(side): + if side { + image = highlighted ? graphics.chatMessageBackgroundIncomingMergedTopSideHighlightedImage : graphics.chatMessageBackgroundIncomingMergedTopSideImage + } else { + image = highlighted ? graphics.chatMessageBackgroundIncomingMergedTopHighlightedImage : graphics.chatMessageBackgroundIncomingMergedTopImage + } case .Bottom: image = highlighted ? graphics.chatMessageBackgroundIncomingMergedBottomHighlightedImage : graphics.chatMessageBackgroundIncomingMergedBottomImage case .Both: @@ -98,8 +102,12 @@ class ChatMessageBackground: ASImageNode { switch mergeType { case .None: image = highlighted ? graphics.chatMessageBackgroundOutgoingHighlightedImage : graphics.chatMessageBackgroundOutgoingImage - case .Top: - image = highlighted ? graphics.chatMessageBackgroundOutgoingMergedTopHighlightedImage : graphics.chatMessageBackgroundOutgoingMergedTopImage + case let .Top(side): + if side { + image = highlighted ? graphics.chatMessageBackgroundOutgoingMergedTopSideHighlightedImage : graphics.chatMessageBackgroundOutgoingMergedTopSideImage + } else { + image = highlighted ? graphics.chatMessageBackgroundOutgoingMergedTopHighlightedImage : graphics.chatMessageBackgroundOutgoingMergedTopImage + } case .Bottom: image = highlighted ? graphics.chatMessageBackgroundOutgoingMergedBottomHighlightedImage : graphics.chatMessageBackgroundOutgoingMergedBottomImage case .Both: diff --git a/TelegramUI/ChatMessageBubbleContentNode.swift b/TelegramUI/ChatMessageBubbleContentNode.swift index ea5b24b43b..20ca90f13d 100644 --- a/TelegramUI/ChatMessageBubbleContentNode.swift +++ b/TelegramUI/ChatMessageBubbleContentNode.swift @@ -64,7 +64,7 @@ enum ChatMessageBubblePreparePosition { enum ChatMessageBubbleContentTapAction { case none - case url(String) + case url(url: String, concealed: Bool) case textMention(String) case peerMention(PeerId, String) case botCommand(String) @@ -81,13 +81,15 @@ final class ChatMessageBubbleContentItem { let message: Message let read: Bool let presentationData: ChatPresentationData + let associatedData: ChatMessageItemAssociatedData - init(account: Account, controllerInteraction: ChatControllerInteraction, message: Message, read: Bool, presentationData: ChatPresentationData) { + init(account: Account, controllerInteraction: ChatControllerInteraction, message: Message, read: Bool, presentationData: ChatPresentationData, associatedData: ChatMessageItemAssociatedData) { self.account = account self.controllerInteraction = controllerInteraction self.message = message self.read = read self.presentationData = presentationData + self.associatedData = associatedData } } diff --git a/TelegramUI/ChatMessageBubbleImages.swift b/TelegramUI/ChatMessageBubbleImages.swift index d7d6d56883..158d8526e6 100644 --- a/TelegramUI/ChatMessageBubbleImages.swift +++ b/TelegramUI/ChatMessageBubbleImages.swift @@ -3,7 +3,7 @@ import Display enum MessageBubbleImageNeighbors { case none - case top + case top(side: Bool) case bottom case both case side @@ -55,16 +55,20 @@ func messageBubbleImage(incoming: Bool, fillColor: UIColor, strokeColor: UIColor context.fillPath() case .side: context.strokeEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 35.0, height: 35.0))) - //let _ = try? drawSvgPath(context, path: "M35,17.5 C35,7.83501688 27.1671082,0 17.5,0 L17.5,0 C7.83501688,0 0,7.83289181 0,17.5 L0,29.0031815 C0,32.3151329 2.6882755,35 5.99681848,35 L17.5,35 C27.1649831,35 35,27.1671082 35,17.5 L35,17.5 L35,17.5 ") context.strokePath() context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 35.0, height: 35.0))) - /*let _ = try? drawSvgPath(context, path: "M35,17.5 C35,7.83501688 27.1671082,0 17.5,0 L17.5,0 C7.83501688,0 0,7.83289181 0,17.5 L0,29.0031815 C0,32.3151329 2.6882755,35 5.99681848,35 L17.5,35 C27.1649831,35 35,27.1671082 35,17.5 L35,17.5 L35,17.5 ") - context.fillPath()*/ - case .top: - let _ = try? drawSvgPath(context, path: "M35,17.5 C35,7.83501688 27.1671082,0 17.5,0 L17.5,0 C7.83501688,0 0,7.83289181 0,17.5 L0,29.0031815 C0,32.3151329 2.6882755,35 5.99681848,35 L17.5,35 C27.1649831,35 35,27.1671082 35,17.5 L35,17.5 L35,17.5 ") - context.strokePath() - let _ = try? drawSvgPath(context, path: "M35,17.5 C35,7.83501688 27.1671082,0 17.5,0 L17.5,0 C7.83501688,0 0,7.83289181 0,17.5 L0,29.0031815 C0,32.3151329 2.6882755,35 5.99681848,35 L17.5,35 C27.1649831,35 35,27.1671082 35,17.5 L35,17.5 L35,17.5 ") - context.fillPath() + case let .top(side): + if side { + let _ = try? drawSvgPath(context, path: "M17.5,0 L17.5,0 C27.1649831,-1.7754286e-15 35,7.83501688 35,17.5 L35,29 C35,32.3137085 32.3137085,35 29,35 L6,35 C2.6862915,35 4.05812251e-16,32.3137085 0,29 L0,17.5 C-1.18361906e-15,7.83501688 7.83501688,1.7754286e-15 17.5,0 ") + context.strokePath() + let _ = try? drawSvgPath(context, path: "M17.5,0 L17.5,0 C27.1649831,-1.7754286e-15 35,7.83501688 35,17.5 L35,29 C35,32.3137085 32.3137085,35 29,35 L6,35 C2.6862915,35 4.05812251e-16,32.3137085 0,29 L0,17.5 C-1.18361906e-15,7.83501688 7.83501688,1.7754286e-15 17.5,0 ") + context.fillPath() + } else { + let _ = try? drawSvgPath(context, path: "M35,17.5 C35,7.83501688 27.1671082,0 17.5,0 L17.5,0 C7.83501688,0 0,7.83289181 0,17.5 L0,29.0031815 C0,32.3151329 2.6882755,35 5.99681848,35 L17.5,35 C27.1649831,35 35,27.1671082 35,17.5 L35,17.5 L35,17.5 ") + context.strokePath() + let _ = try? drawSvgPath(context, path: "M35,17.5 C35,7.83501688 27.1671082,0 17.5,0 L17.5,0 C7.83501688,0 0,7.83289181 0,17.5 L0,29.0031815 C0,32.3151329 2.6882755,35 5.99681848,35 L17.5,35 C27.1649831,35 35,27.1671082 35,17.5 L35,17.5 L35,17.5 ") + context.fillPath() + } case .bottom: let _ = try? drawSvgPath(context, path: "M6,17.5 L6,5.99681848 C6,2.6882755 8.68486709,0 11.9968185,0 L23.5,0 C33.1671082,0 41,7.83501688 41,17.5 C41,27.1671082 33.1649831,35 23.5,35 C19.2941198,35 15.4354328,33.5169337 12.4179496,31.0453367 C9.05531719,34.9894816 -2.41103066e-08,35 0,35 C5.972003,31.5499861 6,26.8616169 6,26.8616169 L6,17.5 L6,17.5 ") context.strokePath() diff --git a/TelegramUI/ChatMessageBubbleItemNode.swift b/TelegramUI/ChatMessageBubbleItemNode.swift index 298776a67a..7726128825 100644 --- a/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/TelegramUI/ChatMessageBubbleItemNode.swift @@ -472,7 +472,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { var backgroundHiding: ChatMessageBubbleContentBackgroundHiding? var hasSolidWallpaper = false - if case .color = item.presentationData.wallpaper { + if case .color = item.presentationData.theme.wallpaper { hasSolidWallpaper = true } var alignment: ChatMessageBubbleContentAlignment = .none @@ -530,7 +530,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { prepareContentPosition = .linear(top: topPosition, bottom: refinedBottomPosition) } - let contentItem = ChatMessageBubbleContentItem(account: item.account, controllerInteraction: item.controllerInteraction, message: message, read: read, presentationData: item.presentationData) + let contentItem = ChatMessageBubbleContentItem(account: item.account, controllerInteraction: item.controllerInteraction, message: message, read: read, presentationData: item.presentationData, associatedData: item.associatedData) var itemSelection: Bool? if case .mosaic = prepareContentPosition { @@ -599,7 +599,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { } if let rawAuthorNameColor = authorNameColor { var dimColors = false - switch item.presentationData.theme.name { + switch item.presentationData.theme.theme.name { case .builtin(.nightAccent), .builtin(.nightGrayscale): dimColors = true default: @@ -710,12 +710,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { headerSize.height += 5.0 } - let inlineBotNameColor = incoming ? item.presentationData.theme.chat.bubble.incomingAccentTextColor : item.presentationData.theme.chat.bubble.outgoingAccentTextColor + let inlineBotNameColor = incoming ? item.presentationData.theme.theme.chat.bubble.incomingAccentTextColor : item.presentationData.theme.theme.chat.bubble.outgoingAccentTextColor let attributedString: NSAttributedString var adminBadgeString: NSAttributedString? if authorIsAdmin { - adminBadgeString = NSAttributedString(string: " \(item.presentationData.strings.Conversation_Admin)", font: inlineBotPrefixFont, textColor: incoming ? item.presentationData.theme.chat.bubble.incomingSecondaryTextColor : item.presentationData.theme.chat.bubble.outgoingSecondaryTextColor) + adminBadgeString = NSAttributedString(string: " \(item.presentationData.strings.Conversation_Admin)", font: inlineBotPrefixFont, textColor: incoming ? item.presentationData.theme.theme.chat.bubble.incomingSecondaryTextColor : item.presentationData.theme.theme.chat.bubble.outgoingSecondaryTextColor) } if let authorNameString = authorNameString, let authorNameColor = authorNameColor, let inlineBotNameString = inlineBotNameString { @@ -770,7 +770,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { forwardSource = forwardInfo.author forwardAuthorSignature = nil } - let sizeAndApply = forwardInfoLayout(item.presentationData.theme, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, forwardAuthorSignature, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude)) + let sizeAndApply = forwardInfoLayout(item.presentationData.theme.theme, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, forwardAuthorSignature, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude)) forwardInfoSizeApply = (sizeAndApply.0, { sizeAndApply.1() }) forwardInfoOriginY = headerSize.height @@ -784,7 +784,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { } else { headerSize.height += 2.0 } - let sizeAndApply = replyInfoLayout(item.presentationData.theme, item.presentationData.strings, item.account, .bubble(incoming: incoming), replyMessage, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude)) + let sizeAndApply = replyInfoLayout(item.presentationData.theme.theme, item.presentationData.strings, item.account, .bubble(incoming: incoming), replyMessage, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude)) replyInfoSizeApply = (sizeAndApply.0, { sizeAndApply.1() }) replyInfoOriginY = headerSize.height @@ -833,7 +833,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { var actionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animated: Bool) -> ChatMessageActionButtonsNode))? if let replyMarkup = replyMarkup { - let (minWidth, buttonsLayout) = actionButtonsLayout(item.account, item.presentationData.theme, item.presentationData.strings, replyMarkup, item.message, maximumNodeWidth) + let (minWidth, buttonsLayout) = actionButtonsLayout(item.account, item.presentationData.theme.theme, item.presentationData.strings, replyMarkup, item.message, maximumNodeWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } @@ -1060,18 +1060,18 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { updatedShareButtonNode = currentShareButtonNode if item.presentationData.theme !== currentItem?.presentationData.theme { if item.message.id.peerId == item.account.peerId { - updatedShareButtonBackground = PresentationResourcesChat.chatBubbleNavigateButtonImage(item.presentationData.theme) + updatedShareButtonBackground = PresentationResourcesChat.chatBubbleNavigateButtonImage(item.presentationData.theme.theme) } else { - updatedShareButtonBackground = PresentationResourcesChat.chatBubbleShareButtonImage(item.presentationData.theme) + updatedShareButtonBackground = PresentationResourcesChat.chatBubbleShareButtonImage(item.presentationData.theme.theme) } } } else { let buttonNode = HighlightableButtonNode() let buttonIcon: UIImage? if item.message.id.peerId == item.account.peerId { - buttonIcon = PresentationResourcesChat.chatBubbleNavigateButtonImage(item.presentationData.theme) + buttonIcon = PresentationResourcesChat.chatBubbleNavigateButtonImage(item.presentationData.theme.theme) } else { - buttonIcon = PresentationResourcesChat.chatBubbleShareButtonImage(item.presentationData.theme) + buttonIcon = PresentationResourcesChat.chatBubbleShareButtonImage(item.presentationData.theme.theme) } buttonNode.setBackgroundImage(buttonIcon, for: [.normal]) updatedShareButtonNode = buttonNode @@ -1080,7 +1080,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { let layout = ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets) - let graphics = PresentationResourcesChat.principalGraphics(item.presentationData.theme) + let graphics = PresentationResourcesChat.principalGraphics(item.presentationData.theme.theme, wallpaper: !item.presentationData.theme.wallpaper.isEmpty) var updatedMergedTop = mergedBottom var updatedMergedBottom = mergedTop @@ -1091,6 +1091,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { if headerSize.height.isZero && contentNodePropertiesAndFinalize.first?.0.forceFullCorners ?? false { updatedMergedBottom = .none } + if actionButtonsSizeAndApply != nil { + updatedMergedTop = .fullyMerged + } } return (layout, { [weak self] animation in @@ -1513,9 +1516,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { switch tapAction { case .none, .ignore: break - case let .url(url): + case let .url(url, concealed): foundTapAction = true - self.item?.controllerInteraction.openUrl(url) + self.item?.controllerInteraction.openUrl(url, concealed) break loop case let .peerMention(peerId, _): foundTapAction = true @@ -1569,7 +1572,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { switch tapAction { case .none, .ignore: break - case let .url(url): + case let .url(url, _): foundTapAction = true item.controllerInteraction.longTap(.url(url)) break loop @@ -1771,7 +1774,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { selectionNode.frame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentBounds.size.width, height: self.contentBounds.size.height)) self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0); } else { - let selectionNode = ChatMessageSelectionNode(theme: item.presentationData.theme, toggle: { [weak self] value in + let selectionNode = ChatMessageSelectionNode(theme: item.presentationData.theme.theme, toggle: { [weak self] value in if let strongSelf = self, let item = strongSelf.item { switch item.content { case let .message(message, _, _, _): @@ -1844,7 +1847,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { if self.highlightedState != highlighted { self.highlightedState = highlighted if let backgroundType = self.backgroundType { - let graphics = PresentationResourcesChat.principalGraphics(item.presentationData.theme) + let graphics = PresentationResourcesChat.principalGraphics(item.presentationData.theme.theme, wallpaper: !item.presentationData.theme.wallpaper.isEmpty) if highlighted { self.backgroundNode.setType(type: backgroundType, highlighted: true, graphics: graphics, transition: .immediate) @@ -1869,7 +1872,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { case .text: item.controllerInteraction.sendMessage(button.title) case let .url(url): - item.controllerInteraction.openUrl(url) + item.controllerInteraction.openUrl(url, true) case .requestMap: item.controllerInteraction.shareCurrentLocation() case .requestPhone: @@ -1938,7 +1941,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { if translation.x < -45.0, self.swipeToReplyNode == nil, let item = self.item { self.swipeToReplyFeedback?.impact() - let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: item.presentationData.theme.chat.bubble.shareButtonFillColor, strokeColor: item.presentationData.theme.chat.bubble.shareButtonStrokeColor, foregroundColor: item.presentationData.theme.chat.bubble.shareButtonForegroundColor) + let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: item.presentationData.theme.theme.chat.bubble.shareButtonFillColor, strokeColor: item.presentationData.theme.theme.chat.bubble.shareButtonStrokeColor, foregroundColor: item.presentationData.theme.theme.chat.bubble.shareButtonForegroundColor) self.swipeToReplyNode = swipeToReplyNode self.addSubnode(swipeToReplyNode) animateReplyNodeIn = true diff --git a/TelegramUI/ChatMessageCallBubbleContentNode.swift b/TelegramUI/ChatMessageCallBubbleContentNode.swift index 40b54ad9ef..95e31d6166 100644 --- a/TelegramUI/ChatMessageCallBubbleContentNode.swift +++ b/TelegramUI/ChatMessageCallBubbleContentNode.swift @@ -68,7 +68,7 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode { let horizontalInset = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right let textConstrainedSize = CGSize(width: constrainedSize.width - horizontalInset, height: constrainedSize.height) - let bubbleTheme = item.presentationData.theme.chat.bubble + let bubbleTheme = item.presentationData.theme.theme.chat.bubble var titleString: String? var callDuration: Int32? @@ -127,9 +127,9 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode { var buttonImage: UIImage? if incoming { - buttonImage = PresentationResourcesChat.chatBubbleIncomingCallButtonImage(item.presentationData.theme) + buttonImage = PresentationResourcesChat.chatBubbleIncomingCallButtonImage(item.presentationData.theme.theme) } else { - buttonImage = PresentationResourcesChat.chatBubbleOutgoingCallButtonImage(item.presentationData.theme) + buttonImage = PresentationResourcesChat.chatBubbleOutgoingCallButtonImage(item.presentationData.theme.theme) } let dateText = stringForMessageTimestampStatus(message: item.message, timeFormat: item.presentationData.timeFormat, strings: item.presentationData.strings) diff --git a/TelegramUI/ChatMessageContactBubbleContentNode.swift b/TelegramUI/ChatMessageContactBubbleContentNode.swift index f785ecfb4e..a20d5c8223 100644 --- a/TelegramUI/ChatMessageContactBubbleContentNode.swift +++ b/TelegramUI/ChatMessageContactBubbleContentNode.swift @@ -79,16 +79,16 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { } else { displayName = selectedContact.lastName } - titleString = NSAttributedString(string: displayName, font: titleFont, textColor: item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.chat.bubble.incomingAccentTextColor : item.presentationData.theme.chat.bubble.outgoingAccentTextColor) + titleString = NSAttributedString(string: displayName, font: titleFont, textColor: item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.theme.chat.bubble.incomingAccentTextColor : item.presentationData.theme.theme.chat.bubble.outgoingAccentTextColor) let phone: String - if let previousContact = previousContact, previousContact.isEqual(selectedContact), let contactPhone = previousContactPhone { + if let previousContact = previousContact, previousContact.isEqual(to: selectedContact), let contactPhone = previousContactPhone { phone = contactPhone } else { phone = formatPhoneNumber(selectedContact.phoneNumber) } updatedPhone = phone - textString = NSAttributedString(string: phone, font: textFont, textColor: item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.chat.bubble.incomingPrimaryTextColor : item.presentationData.theme.chat.bubble.outgoingPrimaryTextColor) + textString = NSAttributedString(string: phone, font: textFont, textColor: item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.theme.chat.bubble.incomingPrimaryTextColor : item.presentationData.theme.theme.chat.bubble.outgoingPrimaryTextColor) } else { updatedPhone = nil } @@ -152,15 +152,19 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { let titleColor: UIColor let titleHighlightedColor: UIColor if item.message.effectivelyIncoming(item.account.peerId) { - buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonIncoming(item.presentationData.theme)! - buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIncoming(item.presentationData.theme)! - titleColor = item.presentationData.theme.chat.bubble.incomingAccentTextColor - titleHighlightedColor = item.presentationData.theme.chat.bubble.incomingFillColor + buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonIncoming(item.presentationData.theme.theme)! + buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIncoming(item.presentationData.theme.theme)! + titleColor = item.presentationData.theme.theme.chat.bubble.incomingAccentTextColor + + let bubbleColors = bubbleColorComponents(theme: item.presentationData.theme.theme, incoming: true, wallpaper: !item.presentationData.theme.wallpaper.isEmpty) + titleHighlightedColor = bubbleColors.fill } else { - buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonOutgoing(item.presentationData.theme)! - buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonOutgoing(item.presentationData.theme)! - titleColor = item.presentationData.theme.chat.bubble.outgoingAccentTextColor - titleHighlightedColor = item.presentationData.theme.chat.bubble.outgoingFillColor + buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonOutgoing(item.presentationData.theme.theme)! + buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonOutgoing(item.presentationData.theme.theme)! + titleColor = item.presentationData.theme.theme.chat.bubble.outgoingAccentTextColor + + let bubbleColors = bubbleColorComponents(theme: item.presentationData.theme.theme, incoming: false, wallpaper: !item.presentationData.theme.wallpaper.isEmpty) + titleHighlightedColor = bubbleColors.fill } let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, buttonImage, buttonHighlightedImage, nil, nil, item.presentationData.strings.Conversation_ViewContactDetails, titleColor, titleHighlightedColor) diff --git a/TelegramUI/ChatMessageDateAndStatusNode.swift b/TelegramUI/ChatMessageDateAndStatusNode.swift index 91ce59909e..36d6524145 100644 --- a/TelegramUI/ChatMessageDateAndStatusNode.swift +++ b/TelegramUI/ChatMessageDateAndStatusNode.swift @@ -111,7 +111,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { private var impressionIcon: ASImageNode? private var type: ChatMessageDateAndStatusType? - private var theme: PresentationTheme? + private var theme: ChatPresentationThemeData? override init() { self.dateNode = TextNode() @@ -125,7 +125,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { self.addSubnode(self.dateNode) } - func asyncLayout() -> (_ theme: PresentationTheme, _ strings: PresentationStrings, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize) -> (CGSize, (Bool) -> Void) { + func asyncLayout() -> (_ theme: ChatPresentationThemeData, _ strings: PresentationStrings, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize) -> (CGSize, (Bool) -> Void) { let dateLayout = TextNode.asyncLayout(self.dateNode) var checkReadNode = self.checkReadNode @@ -151,13 +151,13 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { let clockMinImage: UIImage? var impressionImage: UIImage? - let themeUpdated = theme !== currentTheme || type != currentType + let themeUpdated = theme != currentTheme || type != currentType - let graphics = PresentationResourcesChat.principalGraphics(theme) + let graphics = PresentationResourcesChat.principalGraphics(theme.theme, wallpaper: !theme.wallpaper.isEmpty) switch type { case .BubbleIncoming: - dateColor = theme.chat.bubble.incomingSecondaryTextColor + dateColor = theme.theme.chat.bubble.incomingSecondaryTextColor leftInset = 10.0 loadedCheckFullImage = graphics.checkBubbleFullImage loadedCheckPartialImage = graphics.checkBubblePartialImage @@ -167,7 +167,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { impressionImage = graphics.incomingDateAndStatusImpressionIcon } case let .BubbleOutgoing(status): - dateColor = theme.chat.bubble.outgoingSecondaryTextColor + dateColor = theme.theme.chat.bubble.outgoingSecondaryTextColor outgoingStatus = status leftInset = 10.0 loadedCheckFullImage = graphics.checkBubbleFullImage @@ -178,7 +178,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { impressionImage = graphics.outgoingDateAndStatusImpressionIcon } case .ImageIncoming: - dateColor = theme.chat.bubble.mediaDateAndStatusTextColor + dateColor = theme.theme.chat.bubble.mediaDateAndStatusTextColor backgroundImage = graphics.dateAndStatusMediaBackground leftInset = 0.0 loadedCheckFullImage = graphics.checkMediaFullImage @@ -189,7 +189,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { impressionImage = graphics.mediaImpressionIcon } case let .ImageOutgoing(status): - dateColor = theme.chat.bubble.mediaDateAndStatusTextColor + dateColor = theme.theme.chat.bubble.mediaDateAndStatusTextColor outgoingStatus = status backgroundImage = graphics.dateAndStatusMediaBackground leftInset = 0.0 @@ -201,7 +201,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { impressionImage = graphics.mediaImpressionIcon } case .FreeIncoming: - dateColor = theme.chat.serviceMessage.serviceMessagePrimaryTextColor + dateColor = theme.theme.chat.serviceMessage.serviceMessagePrimaryTextColor backgroundImage = graphics.dateAndStatusFreeBackground leftInset = 0.0 loadedCheckFullImage = graphics.checkMediaFullImage @@ -212,7 +212,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { impressionImage = graphics.mediaImpressionIcon } case let .FreeOutgoing(status): - dateColor = theme.chat.serviceMessage.serviceMessagePrimaryTextColor + dateColor = theme.theme.chat.serviceMessage.serviceMessagePrimaryTextColor outgoingStatus = status backgroundImage = graphics.dateAndStatusFreeBackground leftInset = 0.0 @@ -501,7 +501,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { } } - static func asyncLayout(_ node: ChatMessageDateAndStatusNode?) -> (_ theme: PresentationTheme, _ strings: PresentationStrings, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode) { + static func asyncLayout(_ node: ChatMessageDateAndStatusNode?) -> (_ theme: ChatPresentationThemeData, _ strings: PresentationStrings, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode) { let currentLayout = node?.asyncLayout() return { theme, strings, edited, impressionCount, dateText, type, constrainedSize in let resultNode: ChatMessageDateAndStatusNode diff --git a/TelegramUI/ChatMessageDateHeader.swift b/TelegramUI/ChatMessageDateHeader.swift index 9c07d8e3d4..e00e26f352 100644 --- a/TelegramUI/ChatMessageDateHeader.swift +++ b/TelegramUI/ChatMessageDateHeader.swift @@ -17,10 +17,10 @@ final class ChatMessageDateHeader: ListViewItemHeader { private let roundedTimestamp: Int32 let id: Int64 - let theme: PresentationTheme + let theme: ChatPresentationThemeData let strings: PresentationStrings - init(timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings) { + init(timestamp: Int32, theme: ChatPresentationThemeData, strings: PresentationStrings) { self.timestamp = timestamp self.theme = theme self.strings = strings @@ -81,13 +81,13 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { let stickBackgroundNode: ASImageNode private let localTimestamp: Int32 - private var theme: PresentationTheme + private var theme: ChatPresentationThemeData private var strings: PresentationStrings private var flashingOnScrolling = false private var stickDistanceFactor: CGFloat = 0.0 - init(localTimestamp: Int32, theme: PresentationTheme, strings: PresentationStrings) { + init(localTimestamp: Int32, theme: ChatPresentationThemeData, strings: PresentationStrings) { self.localTimestamp = localTimestamp self.theme = theme self.strings = strings @@ -113,7 +113,7 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) - let graphics = PresentationResourcesChat.principalGraphics(theme) + let graphics = PresentationResourcesChat.principalGraphics(theme.theme, wallpaper: !theme.wallpaper.isEmpty) self.backgroundNode.image = graphics.dateStaticBackground self.stickBackgroundNode.image = graphics.dateFloatingBackground @@ -145,7 +145,7 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { text = strings.Date_ChatDateHeaderYear(monthAtIndex(Int(timeinfo.tm_mon), strings: strings), "\(timeinfo.tm_mday)", "\(1900 + timeinfo.tm_year)").0 } - let attributedString = NSAttributedString(string: text, font: titleFont, textColor: theme.chat.serviceMessage.dateTextColor) + let attributedString = NSAttributedString(string: text, font: titleFont, textColor: theme.theme.chat.serviceMessage.dateTextColor) let labelLayout = TextNode.asyncLayout(self.labelNode) let (size, apply) = labelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 320.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -166,11 +166,11 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) } - func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + func updateThemeAndStrings(theme: ChatPresentationThemeData, strings: PresentationStrings) { self.theme = theme self.strings = strings - let graphics = PresentationResourcesChat.principalGraphics(theme) + let graphics = PresentationResourcesChat.principalGraphics(theme.theme, wallpaper: !theme.wallpaper.isEmpty) self.backgroundNode.image = graphics.dateStaticBackground self.stickBackgroundNode.image = graphics.dateFloatingBackground diff --git a/TelegramUI/ChatMessageEventLogPreviousDescriptionContentNode.swift b/TelegramUI/ChatMessageEventLogPreviousDescriptionContentNode.swift index 8462ed8c02..941f0e2ce0 100644 --- a/TelegramUI/ChatMessageEventLogPreviousDescriptionContentNode.swift +++ b/TelegramUI/ChatMessageEventLogPreviousDescriptionContentNode.swift @@ -43,7 +43,7 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble } let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.account, item.controllerInteraction, item.message, true, title, subtitle, text, messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, constrainedSize) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.account, item.controllerInteraction, item.message, true, title, subtitle, text, messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, constrainedSize) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) diff --git a/TelegramUI/ChatMessageEventLogPreviousLinkContentNode.swift b/TelegramUI/ChatMessageEventLogPreviousLinkContentNode.swift index 8f992e7c9d..665bf53f56 100644 --- a/TelegramUI/ChatMessageEventLogPreviousLinkContentNode.swift +++ b/TelegramUI/ChatMessageEventLogPreviousLinkContentNode.swift @@ -38,7 +38,7 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent let text: String = item.message.text let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.account, item.controllerInteraction, item.message, true, title, subtitle, text, messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, constrainedSize) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.account, item.controllerInteraction, item.message, true, title, subtitle, text, messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, constrainedSize) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) diff --git a/TelegramUI/ChatMessageEventLogPreviousMessageContentNode.swift b/TelegramUI/ChatMessageEventLogPreviousMessageContentNode.swift index 0125c37c40..6f1fe46dc9 100644 --- a/TelegramUI/ChatMessageEventLogPreviousMessageContentNode.swift +++ b/TelegramUI/ChatMessageEventLogPreviousMessageContentNode.swift @@ -43,7 +43,7 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont } let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.account, item.controllerInteraction, item.message, true, title, subtitle, text, messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, constrainedSize) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.account, item.controllerInteraction, item.message, true, title, subtitle, text, messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, constrainedSize) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) diff --git a/TelegramUI/ChatMessageFileBubbleContentNode.swift b/TelegramUI/ChatMessageFileBubbleContentNode.swift index a74abed3c8..7a96904e50 100644 --- a/TelegramUI/ChatMessageFileBubbleContentNode.swift +++ b/TelegramUI/ChatMessageFileBubbleContentNode.swift @@ -59,9 +59,9 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { } var automaticDownload = false - automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peer: item.message.peers[item.message.id.peerId], media: selectedFile!) + automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, media: selectedFile!) - let (initialWidth, refineLayout) = interactiveFileLayout(item.account, item.presentationData, item.message, selectedFile!, automaticDownload, item.message.effectivelyIncoming(item.account.peerId), statusType, CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height)) + let (initialWidth, refineLayout) = interactiveFileLayout(item.account, item.presentationData, item.message, selectedFile!, automaticDownload, item.message.effectivelyIncoming(item.account.peerId), item.associatedData.isRecentActions, statusType, CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height)) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) diff --git a/TelegramUI/ChatMessageGameBubbleContentNode.swift b/TelegramUI/ChatMessageGameBubbleContentNode.swift index 56bba8c0ab..5b5b6c628a 100644 --- a/TelegramUI/ChatMessageGameBubbleContentNode.swift +++ b/TelegramUI/ChatMessageGameBubbleContentNode.swift @@ -65,7 +65,7 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode { } } - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.account, item.controllerInteraction, item.message, item.read, title, subtitle, item.message.text.isEmpty ? text : item.message.text, item.message.text.isEmpty ? nil : messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, constrainedSize) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.account, item.controllerInteraction, item.message, item.read, title, subtitle, item.message.text.isEmpty ? text : item.message.text, item.message.text.isEmpty ? nil : messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, constrainedSize) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) diff --git a/TelegramUI/ChatMessageInstantVideoItemNode.swift b/TelegramUI/ChatMessageInstantVideoItemNode.swift index 36a2e01d65..c54dc83e0c 100644 --- a/TelegramUI/ChatMessageInstantVideoItemNode.swift +++ b/TelegramUI/ChatMessageInstantVideoItemNode.swift @@ -117,7 +117,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { let displaySize = CGSize(width: 212.0, height: 212.0) - let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(account: item.account, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, presentationData: item.presentationData), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, .free) + let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(account: item.account, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, presentationData: item.presentationData, associatedData: item.associatedData), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, .free) let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left)), y: 0.0), size: videoLayout.contentSize) @@ -127,14 +127,14 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { for attribute in item.message.attributes { if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { let availableWidth = max(60.0, params.width - params.leftInset - params.rightInset - videoLayout.contentSize.width - 20.0 - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left) - replyInfoApply = makeReplyInfoLayout(item.presentationData.theme, item.presentationData.strings, item.account, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + replyInfoApply = makeReplyInfoLayout(item.presentationData.theme.theme, item.presentationData.strings, item.account, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) if let currentReplyBackgroundNode = currentReplyBackgroundNode { updatedReplyBackgroundNode = currentReplyBackgroundNode } else { updatedReplyBackgroundNode = ASImageNode() } - replyBackgroundImage = PresentationResourcesChat.chatServiceBubbleFillImage(item.presentationData.theme) + replyBackgroundImage = PresentationResourcesChat.chatServiceBubbleFillImage(item.presentationData.theme.theme) break } } @@ -162,7 +162,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { forwardAuthorSignature = nil } let availableWidth = max(60.0, availableContentWidth - videoLayout.contentSize.width + 6.0) - forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData.theme, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData.theme.theme, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) if let currentForwardBackgroundNode = currentForwardBackgroundNode { updatedForwardBackgroundNode = currentForwardBackgroundNode @@ -170,7 +170,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { updatedForwardBackgroundNode = ASImageNode() } - forwardBackgroundImage = PresentationResourcesChat.chatServiceBubbleFillImage(item.presentationData.theme) + forwardBackgroundImage = PresentationResourcesChat.chatServiceBubbleFillImage(item.presentationData.theme.theme) } return (ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: videoLayout.contentSize.height), insets: layoutInsets), { [weak self] animation in @@ -317,7 +317,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { if translation.x < -45.0, self.swipeToReplyNode == nil, let item = self.item { self.swipeToReplyFeedback?.impact() - let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: item.presentationData.theme.chat.bubble.shareButtonFillColor, strokeColor: item.presentationData.theme.chat.bubble.shareButtonStrokeColor, foregroundColor: item.presentationData.theme.chat.bubble.shareButtonForegroundColor) + let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: item.presentationData.theme.theme.chat.bubble.shareButtonFillColor, strokeColor: item.presentationData.theme.theme.chat.bubble.shareButtonStrokeColor, foregroundColor: item.presentationData.theme.theme.chat.bubble.shareButtonForegroundColor) self.swipeToReplyNode = swipeToReplyNode self.addSubnode(swipeToReplyNode) animateReplyNodeIn = true @@ -387,7 +387,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { selectionNode.frame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentBounds.size.width, height: self.contentBounds.size.height)) self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0); } else { - let selectionNode = ChatMessageSelectionNode(theme: item.presentationData.theme, toggle: { [weak self] value in + let selectionNode = ChatMessageSelectionNode(theme: item.presentationData.theme.theme, toggle: { [weak self] value in if let strongSelf = self, let item = strongSelf.item { item.controllerInteraction.toggleMessagesSelection([item.message.id], value) } diff --git a/TelegramUI/ChatMessageInteractiveFileNode.swift b/TelegramUI/ChatMessageInteractiveFileNode.swift index 83983f08d8..c411fb8a25 100644 --- a/TelegramUI/ChatMessageInteractiveFileNode.swift +++ b/TelegramUI/ChatMessageInteractiveFileNode.swift @@ -38,7 +38,7 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { private var account: Account? private var message: Message? - private var themeAndStrings: (PresentationTheme, PresentationStrings)? + private var themeAndStrings: (ChatPresentationThemeData, PresentationStrings)? private var file: TelegramMediaFile? init() { @@ -113,7 +113,7 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { } } - func asyncLayout() -> (_ account: Account, _ presentationData: ChatPresentationData, _ message: Message, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, () -> Void))) { + func asyncLayout() -> (_ account: Account, _ presentationData: ChatPresentationData, _ message: Message, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, () -> Void))) { let currentFile = self.file let titleAsyncLayout = TextNode.asyncLayout(self.titleNode) @@ -123,10 +123,10 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { let currentMessage = self.message let currentTheme = self.themeAndStrings?.0 - return { account, presentationData, message, file, automaticDownload, incoming, dateAndStatusType, constrainedSize in - var updatedTheme: PresentationTheme? + return { account, presentationData, message, file, automaticDownload, incoming, isRecentActions, dateAndStatusType, constrainedSize in + var updatedTheme: ChatPresentationThemeData? - if presentationData.theme !== currentTheme { + if presentationData.theme != currentTheme { updatedTheme = presentationData.theme } @@ -157,7 +157,7 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { updatedFetchControls = FetchControls(fetch: { [weak self] in if let strongSelf = self { - strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(account: account, message: message, file: file).start()) + strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(account: account, message: message, file: file, userInitiated: true).start()) } }, cancel: { messageMediaFileCancelInteractiveFetch(account: account, messageId: message.id, file: file) @@ -165,8 +165,8 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { } if statusUpdated { - updatedStatusSignal = messageFileMediaResourceStatus(account: account, file: file, message: message) - updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(account: account, file: file, message: message) + updatedStatusSignal = messageFileMediaResourceStatus(account: account, file: file, message: message, isRecentActions: isRecentActions) + updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(account: account, file: file, message: message, isRecentActions: isRecentActions) } var statusSize: CGSize? @@ -177,9 +177,9 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { if let attribute = attribute as? ConsumableContentMessageAttribute { if !attribute.consumed { if incoming { - consumableContentIcon = PresentationResourcesChat.chatBubbleConsumableContentIncomingIcon(presentationData.theme) + consumableContentIcon = PresentationResourcesChat.chatBubbleConsumableContentIncomingIcon(presentationData.theme.theme) } else { - consumableContentIcon = PresentationResourcesChat.chatBubbleConsumableContentOutgoingIcon(presentationData.theme) + consumableContentIcon = PresentationResourcesChat.chatBubbleConsumableContentOutgoingIcon(presentationData.theme.theme) } } break @@ -218,7 +218,7 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { var isVoice = false var audioDuration: Int32 = 0 - let bubbleTheme = presentationData.theme.chat.bubble + let bubbleTheme = presentationData.theme.theme.chat.bubble for attribute in file.attributes { if case let .Audio(voice, duration, title, performer, waveform) = attribute { @@ -315,7 +315,9 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { if hasThumbnail { fileIconImage = nil } else { - fileIconImage = incoming ? PresentationResourcesChat.chatBubbleRadialIndicatorFileIconIncoming(presentationData.theme) : PresentationResourcesChat.chatBubbleRadialIndicatorFileIconOutgoing(presentationData.theme) + let principalGraphics = PresentationResourcesChat.principalGraphics(presentationData.theme.theme, wallpaper: !presentationData.theme.wallpaper.isEmpty) + + fileIconImage = incoming ? principalGraphics.radialIndicatorFileIconIncoming : principalGraphics.radialIndicatorFileIconOutgoing } return (minLayoutWidth, { boundingWidth in @@ -490,9 +492,9 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { if strongSelf.iconNode != nil { statusForegroundColor = bubbleTheme.mediaOverlayControlForegroundColor } else if incoming { - statusForegroundColor = bubbleTheme.incomingFillColor + statusForegroundColor = presentationData.theme.wallpaper.isEmpty ? bubbleTheme.incoming.withoutWallpaper.fill : bubbleTheme.incoming.withWallpaper.fill } else { - statusForegroundColor = bubbleTheme.outgoingFillColor + statusForegroundColor = presentationData.theme.wallpaper.isEmpty ? bubbleTheme.outgoing.withoutWallpaper.fill : bubbleTheme.outgoing.withWallpaper.fill } switch status { case let .fetchStatus(fetchStatus): @@ -503,7 +505,7 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { if isActive { adjustedProgress = max(adjustedProgress, 0.027) } - state = .progress(color: statusForegroundColor, value: CGFloat(adjustedProgress), cancelEnabled: true) + state = .progress(color: statusForegroundColor, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: true) case .Local: if isAudio { state = .play(statusForegroundColor) @@ -563,12 +565,12 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { } } - static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (_ account: Account, _ presentationData: ChatPresentationData, _ message: Message, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, () -> ChatMessageInteractiveFileNode))) { + static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (_ account: Account, _ presentationData: ChatPresentationData, _ message: Message, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, () -> ChatMessageInteractiveFileNode))) { let currentAsyncLayout = node?.asyncLayout() - return { account, presentationData, message, file, automaticDownload, incoming, dateAndStatusType, constrainedSize in + return { account, presentationData, message, file, automaticDownload, incoming, isRecentActions, dateAndStatusType, constrainedSize in var fileNode: ChatMessageInteractiveFileNode - var fileLayout: (_ account: Account, _ presentationData: ChatPresentationData, _ message: Message, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, () -> Void))) + var fileLayout: (_ account: Account, _ presentationData: ChatPresentationData, _ message: Message, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, () -> Void))) if let node = node, let currentAsyncLayout = currentAsyncLayout { fileNode = node @@ -578,7 +580,7 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { fileLayout = fileNode.asyncLayout() } - let (initialWidth, continueLayout) = fileLayout(account, presentationData, message, file, automaticDownload, incoming, dateAndStatusType, constrainedSize) + let (initialWidth, continueLayout) = fileLayout(account, presentationData, message, file, automaticDownload, incoming, isRecentActions, dateAndStatusType, constrainedSize) return (initialWidth, { constrainedSize in let (finalWidth, finalLayout) = continueLayout(constrainedSize) @@ -596,7 +598,7 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { } func transitionNode(media: Media) -> (ASDisplayNode, () -> UIView?)? { - if let iconNode = self.iconNode, let file = self.file, file.isEqual(media) { + if let iconNode = self.iconNode, let file = self.file, file.isEqual(to: media) { return (iconNode, { [weak iconNode] in return iconNode?.view.snapshotContentTree(unhide: true) }) @@ -609,7 +611,7 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { var isHidden = false if let file = self.file, let media = media { for m in media { - if file.isEqual(m) { + if file.isEqual(to: m) { isHidden = true break } diff --git a/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift b/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift index 425c930cb5..d1114ad363 100644 --- a/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift +++ b/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift @@ -25,6 +25,8 @@ enum ChatMessageInteractiveInstantVideoNodeStatusType { class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { private var videoNode: UniversalVideoNode? + private let secretVideoPlaceholderBackground: ASImageNode + private let secretVideoPlaceholder: TransformImageNode private var statusNode: RadialStatusNode? private var playbackStatusNode: InstantVideoRadialStatusNode? @@ -32,6 +34,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { private var item: ChatMessageBubbleContentItem? var telegramFile: TelegramMediaFile? + private var secretProgressIcon: UIImage? private let fetchDisposable = MetaDisposable() @@ -61,6 +64,12 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } override init() { + self.secretVideoPlaceholderBackground = ASImageNode() + self.secretVideoPlaceholderBackground.isLayerBacked = true + self.secretVideoPlaceholderBackground.displaysAsynchronously = false + self.secretVideoPlaceholderBackground.displayWithoutProcessing = true + self.secretVideoPlaceholder = TransformImageNode() + self.infoBackgroundNode = ASImageNode() self.infoBackgroundNode.isLayerBacked = true self.infoBackgroundNode.displayWithoutProcessing = true @@ -107,26 +116,32 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { let makeDateAndStatusLayout = self.dateAndStatusNode.asyncLayout() return { item, width, displaySize, statusDisplayType in - var updatedTheme: PresentationTheme? + var updatedTheme: ChatPresentationThemeData? + var secretVideoPlaceholderBackgroundImage: UIImage? var updatedInfoBackgroundImage: UIImage? var updatedMuteIconImage: UIImage? - if item.presentationData.theme !== currentItem?.presentationData.theme { + if item.presentationData.theme != currentItem?.presentationData.theme { updatedTheme = item.presentationData.theme - updatedInfoBackgroundImage = PresentationResourcesChat.chatInstantMessageInfoBackgroundImage(item.presentationData.theme) - updatedMuteIconImage = PresentationResourcesChat.chatInstantMessageMuteIconImage(item.presentationData.theme) + updatedInfoBackgroundImage = PresentationResourcesChat.chatInstantMessageInfoBackgroundImage(item.presentationData.theme.theme) + updatedMuteIconImage = PresentationResourcesChat.chatInstantMessageMuteIconImage(item.presentationData.theme.theme) } let instantVideoBackgroundImage: UIImage? switch statusDisplayType { case .free: - instantVideoBackgroundImage = PresentationResourcesChat.chatInstantVideoBackgroundImage(item.presentationData.theme) + instantVideoBackgroundImage = PresentationResourcesChat.chatInstantVideoBackgroundImage(item.presentationData.theme.theme, wallpaper: !item.presentationData.theme.wallpaper.isEmpty) case .bubble: instantVideoBackgroundImage = nil } let theme = item.presentationData.theme let isSecretMedia = item.message.containsSecretMedia + var secretProgressIcon: UIImage? + if isSecretMedia { + secretProgressIcon = PresentationResourcesChat.chatBubbleSecretMediaIcon(theme.theme) + secretVideoPlaceholderBackgroundImage = PresentationResourcesChat.chatInstantVideoBackgroundImage(theme.theme, wallpaper: !theme.wallpaper.isEmpty) + } let imageSize = displaySize @@ -162,7 +177,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { var updatedPlaybackStatus: Signal? if let updatedFile = updatedFile, updatedMedia { - updatedPlaybackStatus = combineLatest(messageFileMediaResourceStatus(account: item.account, file: updatedFile, message: item.message), item.account.pendingMessageManager.pendingMessageStatus(item.message.id)) + updatedPlaybackStatus = combineLatest(messageFileMediaResourceStatus(account: item.account, file: updatedFile, message: item.message, isRecentActions: item.associatedData.isRecentActions), item.account.pendingMessageManager.pendingMessageStatus(item.message.id)) |> map { resourceStatus, pendingStatus -> FileMediaResourceStatus in if let pendingStatus = pendingStatus { var progress = pendingStatus.progress @@ -235,6 +250,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { if let strongSelf = self { strongSelf.item = item strongSelf.videoFrame = videoFrame + strongSelf.secretProgressIcon = secretProgressIcon if let updatedInfoBackgroundImage = updatedInfoBackgroundImage { strongSelf.infoBackgroundNode.image = updatedInfoBackgroundImage @@ -244,6 +260,10 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { strongSelf.muteIconNode.image = updatedMuteIconImage } + if let secretVideoPlaceholderBackgroundImage = secretVideoPlaceholderBackgroundImage { + strongSelf.secretVideoPlaceholderBackground.image = secretVideoPlaceholderBackgroundImage + } + strongSelf.telegramFile = updatedFile if let infoBackgroundImage = strongSelf.infoBackgroundNode.image, let muteImage = strongSelf.muteIconNode.image { @@ -255,127 +275,13 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } if let updatedPlaybackStatus = updatedPlaybackStatus { - strongSelf.playbackStatusDisposable.set((updatedPlaybackStatus |> deliverOnMainQueue).start(next: { status in - if let strongSelf = self, let videoFrame = strongSelf.videoFrame { - strongSelf.status = status - - let displayMute: Bool - switch status { - case let .fetchStatus(fetchStatus): - switch fetchStatus { - case .Local: - displayMute = true - default: - displayMute = false - } - case .playbackStatus: - displayMute = false - } - if displayMute != (!strongSelf.infoBackgroundNode.alpha.isZero) { - if displayMute { - strongSelf.infoBackgroundNode.alpha = 1.0 - strongSelf.infoBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) - strongSelf.infoBackgroundNode.layer.animateScale(from: 0.4, to: 1.0, duration: 0.15) - } else { - strongSelf.infoBackgroundNode.alpha = 0.0 - strongSelf.infoBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15) - strongSelf.infoBackgroundNode.layer.animateScale(from: 1.0, to: 0.4, duration: 0.15) - } - } - - var progressRequired = false - if case let .fetchStatus(fetchStatus) = status { - if case .Local = fetchStatus { - if let file = updatedFile, file.isVideo { - progressRequired = true - } else if isSecretMedia { - progressRequired = true - } - } else { - progressRequired = true - } - } - - if progressRequired { - if strongSelf.statusNode == nil { - let statusNode = RadialStatusNode(backgroundNodeColor: theme.chat.bubble.mediaOverlayControlBackgroundColor) - statusNode.isUserInteractionEnabled = false - statusNode.frame = CGRect(origin: CGPoint(x: videoFrame.origin.x + floor((videoFrame.size.width - 50.0) / 2.0), y: videoFrame.origin.y + floor((videoFrame.size.height - 50.0) / 2.0)), size: CGSize(width: 50.0, height: 50.0)) - strongSelf.statusNode = statusNode - strongSelf.addSubnode(statusNode) - } else if let _ = updatedTheme { - - //strongSelf.progressNode?.updateTheme(RadialProgressTheme(backgroundColor: theme.chat.bubble.mediaOverlayControlBackgroundColor, foregroundColor: theme.chat.bubble.mediaOverlayControlForegroundColor, icon: nil)) - } - } else { - if let statusNode = strongSelf.statusNode { - statusNode.transitionToState(.none, completion: { [weak statusNode] in - statusNode?.removeFromSupernode() - }) - strongSelf.statusNode = nil - } - } - - var state: RadialStatusNodeState - let bubbleTheme = theme.chat.bubble - switch status { - case let .fetchStatus(fetchStatus): - switch fetchStatus { - case let .Fetching(isActive, progress): - var adjustedProgress = progress - if isActive { - adjustedProgress = max(adjustedProgress, 0.027) - } - state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, value: CGFloat(adjustedProgress), cancelEnabled: true) - case .Local: - state = .none - /*if isSecretMedia && secretProgressIcon != nil { - state = .customIcon(secretProgressIcon!) - } else */ - case .Remote: - state = .download(bubbleTheme.mediaOverlayControlForegroundColor) - } - default: - state = .none - break - } - if let statusNode = strongSelf.statusNode { - if state == .none { - strongSelf.statusNode = nil - } - statusNode.transitionToState(state, completion: { [weak statusNode] in - if state == .none { - statusNode?.removeFromSupernode() - } - }) - } - - if case .playbackStatus = status { - let playbackStatusNode: InstantVideoRadialStatusNode - if let current = strongSelf.playbackStatusNode { - playbackStatusNode = current - } else { - playbackStatusNode = InstantVideoRadialStatusNode(color: UIColor(white: 1.0, alpha: 0.8)) - strongSelf.addSubnode(playbackStatusNode) - strongSelf.playbackStatusNode = playbackStatusNode - } - playbackStatusNode.frame = videoFrame.insetBy(dx: 1.5, dy: 1.5) - if let updatedFile = updatedFile { - let status = messageFileMediaPlaybackStatus(account: item.account, file: updatedFile, message: item.message) - playbackStatusNode.status = status - strongSelf.durationNode?.status = status |> map(Optional.init) - } - } else { - if let playbackStatusNode = strongSelf.playbackStatusNode { - strongSelf.playbackStatusNode = nil - playbackStatusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak playbackStatusNode] _ in - playbackStatusNode?.removeFromSupernode() - }) - } - - strongSelf.durationNode?.status = .single(nil) - } + strongSelf.playbackStatusDisposable.set((updatedPlaybackStatus + |> deliverOnMainQueue).start(next: { status in + guard let strongSelf = self else { + return } + strongSelf.status = status + strongSelf.updateStatus() })) } @@ -392,14 +298,14 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { let durationFillColor: UIColor switch statusDisplayType { case .free: - durationTextColor = theme.chat.serviceMessage.serviceMessagePrimaryTextColor - durationFillColor = theme.chat.serviceMessage.serviceMessageFillColor + durationTextColor = theme.theme.chat.serviceMessage.serviceMessagePrimaryTextColor + durationFillColor = theme.theme.chat.serviceMessage.serviceMessageFillColor case .bubble: durationFillColor = .clear if item.message.effectivelyIncoming(item.account.peerId) { - durationTextColor = theme.chat.bubble.incomingSecondaryTextColor + durationTextColor = theme.theme.chat.bubble.incomingSecondaryTextColor } else { - durationTextColor = theme.chat.bubble.outgoingSecondaryTextColor + durationTextColor = theme.theme.chat.bubble.outgoingSecondaryTextColor } } let durationNode: ChatInstantVideoMessageDurationNode @@ -434,6 +340,17 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { strongSelf.videoNode = videoNode strongSelf.insertSubnode(videoNode, belowSubnode: previousVideoNode ?? strongSelf.dateAndStatusNode) videoNode.canAttachContent = strongSelf.shouldAcquireVideoContext + + if isSecretMedia { + let updatedSecretPlaceholderSignal = chatSecretMessageVideo(account: item.account, videoReference: .message(message: MessageReference(item.message), media: telegramFile)) + strongSelf.secretVideoPlaceholder.setSignal(updatedSecretPlaceholderSignal) + if strongSelf.secretVideoPlaceholder.supernode == nil { + strongSelf.insertSubnode(strongSelf.secretVideoPlaceholderBackground, belowSubnode: videoNode) + strongSelf.insertSubnode(strongSelf.secretVideoPlaceholder, belowSubnode: videoNode) + } + } else { + strongSelf.secretVideoPlaceholder.removeFromSupernode() + } } if let durationNode = strongSelf.durationNode { @@ -445,11 +362,178 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { videoNode.frame = videoFrame videoNode.updateLayout(size: arguments.boundingSize, transition: .immediate) } + strongSelf.secretVideoPlaceholderBackground.frame = videoFrame + + let placeholderFrame = videoFrame.insetBy(dx: 2.0, dy: 2.0) + strongSelf.secretVideoPlaceholder.frame = placeholderFrame + let makeSecretPlaceholderLayout = strongSelf.secretVideoPlaceholder.asyncLayout() + let arguments = TransformImageArguments(corners: ImageCorners(radius: placeholderFrame.size.width / 2.0), imageSize: placeholderFrame.size, boundingSize: placeholderFrame.size, intrinsicInsets: UIEdgeInsets()) + let applySecretPlaceholder = makeSecretPlaceholderLayout(arguments) + applySecretPlaceholder() + + strongSelf.updateStatus() } }) } } + private func updateStatus() { + guard let item = self.item, let status = self.status, let videoFrame = self.videoFrame else { + return + } + let bubbleTheme = item.presentationData.theme.theme.chat.bubble + + let isSecretMedia = item.message.containsSecretMedia + var secretBeginTimeAndTimeout: (Double, Double)? + if isSecretMedia { + for attribute in item.message.attributes { + if let attribute = attribute as? AutoremoveTimeoutMessageAttribute { + if let countdownBeginTime = attribute.countdownBeginTime { + secretBeginTimeAndTimeout = (Double(countdownBeginTime), Double(attribute.timeout)) + } + break + } + } + } + + var selectedMedia: TelegramMediaFile? + for media in item.message.media { + if let file = media as? TelegramMediaFile { + selectedMedia = file + } else if let webPage = media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content, let file = content.file { + selectedMedia = file + } + } + + guard let file = selectedMedia else { + return + } + + let displayMute: Bool + switch status { + case let .fetchStatus(fetchStatus): + switch fetchStatus { + case .Local: + displayMute = true + default: + displayMute = false + } + case .playbackStatus: + displayMute = false + } + if displayMute != (!self.infoBackgroundNode.alpha.isZero) { + if displayMute { + self.infoBackgroundNode.alpha = 1.0 + self.infoBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + self.infoBackgroundNode.layer.animateScale(from: 0.4, to: 1.0, duration: 0.15) + } else { + self.infoBackgroundNode.alpha = 0.0 + self.infoBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15) + self.infoBackgroundNode.layer.animateScale(from: 1.0, to: 0.4, duration: 0.15) + } + } + + var progressRequired = false + if case let .fetchStatus(fetchStatus) = status { + if case .Local = fetchStatus { + if file.isVideo { + progressRequired = true + } else if isSecretMedia { + progressRequired = true + } + } else { + progressRequired = true + } + } + + if progressRequired { + if self.statusNode == nil { + let statusNode = RadialStatusNode(backgroundNodeColor: item.presentationData.theme.theme.chat.bubble.mediaOverlayControlBackgroundColor) + self.isUserInteractionEnabled = false + statusNode.frame = CGRect(origin: CGPoint(x: videoFrame.origin.x + floor((videoFrame.size.width - 50.0) / 2.0), y: videoFrame.origin.y + floor((videoFrame.size.height - 50.0) / 2.0)), size: CGSize(width: 50.0, height: 50.0)) + self.statusNode = statusNode + self.addSubnode(statusNode) + } + } else { + if let statusNode = self.statusNode { + statusNode.transitionToState(.none, completion: { [weak statusNode] in + statusNode?.removeFromSupernode() + }) + self.statusNode = nil + } + } + + var state: RadialStatusNodeState + switch status { + case let .fetchStatus(fetchStatus): + switch fetchStatus { + case let .Fetching(isActive, progress): + var adjustedProgress = progress + if isActive { + adjustedProgress = max(adjustedProgress, 0.027) + } + state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: true) + case .Local: + if isSecretMedia && self.secretProgressIcon != nil { + if let (beginTime, timeout) = secretBeginTimeAndTimeout { + state = .secretTimeout(color: bubbleTheme.mediaOverlayControlForegroundColor, icon: secretProgressIcon, beginTime: beginTime, timeout: timeout) + } else { + state = .customIcon(secretProgressIcon!) + } + } else { + state = .none + } + case .Remote: + state = .download(bubbleTheme.mediaOverlayControlForegroundColor) + } + default: + state = .none + } + if let statusNode = self.statusNode { + if state == .none { + self.statusNode = nil + } + statusNode.transitionToState(state, completion: { [weak statusNode] in + if state == .none { + statusNode?.removeFromSupernode() + } + }) + } + + if case .playbackStatus = status { + let playbackStatusNode: InstantVideoRadialStatusNode + if let current = self.playbackStatusNode { + playbackStatusNode = current + } else { + playbackStatusNode = InstantVideoRadialStatusNode(color: UIColor(white: 1.0, alpha: 0.8)) + self.addSubnode(playbackStatusNode) + self.playbackStatusNode = playbackStatusNode + } + playbackStatusNode.frame = videoFrame.insetBy(dx: 1.5, dy: 1.5) + + let status = messageFileMediaPlaybackStatus(account: item.account, file: file, message: item.message, isRecentActions: item.associatedData.isRecentActions) + playbackStatusNode.status = status + self.durationNode?.status = status + |> map(Optional.init) + + self.videoNode?.isHidden = false + self.secretVideoPlaceholderBackground.isHidden = true + self.secretVideoPlaceholder.isHidden = true + } else { + if let playbackStatusNode = self.playbackStatusNode { + self.playbackStatusNode = nil + playbackStatusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak playbackStatusNode] _ in + playbackStatusNode?.removeFromSupernode() + }) + } + + self.durationNode?.status = .single(nil) + self.videoNode?.isHidden = isSecretMedia + self.secretVideoPlaceholderBackground.isHidden = !isSecretMedia + self.secretVideoPlaceholder.isHidden = !isSecretMedia + } + } + @objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { switch recognizer.state { case .ended: @@ -462,11 +546,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } if let item = self.item, let videoNode = self.videoNode, videoNode.frame.contains(location) { - if self.infoBackgroundNode.alpha.isZero { - item.account.telegramApplicationContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: .voice) - } else { - let _ = item.controllerInteraction.openMessage(item.message) - } + self.activateVideoPlayback() return } @@ -484,6 +564,17 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } + private func activateVideoPlayback() { + guard let item = self.item else { + return + } + if self.infoBackgroundNode.alpha.isZero { + item.account.telegramApplicationContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: .voice) + } else { + let _ = item.controllerInteraction.openMessage(item.message) + } + } + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if !self.bounds.contains(point) { return nil @@ -503,24 +594,24 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } if let status = self.status { switch status { - case let .fetchStatus(fetchStatus): - switch fetchStatus { - case .Fetching: - if item.message.flags.isSending { - let messageId = item.message.id - let _ = item.account.postbox.transaction({ transaction -> Void in - deleteMessages(transaction: transaction, mediaBox: item.account.postbox.mediaBox, ids: [messageId]) - }).start() - } else { - self.videoNode?.fetchControl(.cancel) - } - case .Remote: - self.videoNode?.fetchControl(.fetch) - case .Local: - break - } - default: - break + case let .fetchStatus(fetchStatus): + switch fetchStatus { + case .Fetching: + if item.message.flags.isSending { + let messageId = item.message.id + let _ = item.account.postbox.transaction({ transaction -> Void in + deleteMessages(transaction: transaction, mediaBox: item.account.postbox.mediaBox, ids: [messageId]) + }).start() + } else { + self.videoNode?.fetchControl(.cancel) + } + case .Remote: + self.videoNode?.fetchControl(.fetch) + case .Local: + self.activateVideoPlayback() + } + default: + break } } } diff --git a/TelegramUI/ChatMessageInteractiveMediaNode.swift b/TelegramUI/ChatMessageInteractiveMediaNode.swift index 87753f2a17..4526c4b590 100644 --- a/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -28,6 +28,7 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { private var media: Media? private var themeAndStrings: (PresentationTheme, PresentationStrings)? private var sizeCalculation: InteractiveMediaNodeSizeCalculation? + private var automaticDownload: Bool? private var automaticPlayback: Bool? private let statusDisposable = MetaDisposable() @@ -35,6 +36,8 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { private var fetchStatus: MediaResourceStatus? private let fetchDisposable = MetaDisposable() + private var secretTimer: SwiftSignalKit.Timer? + var visibility: ListViewItemNodeVisibility = .none { didSet { if let videoNode = self.videoNode { @@ -66,6 +69,7 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { deinit { self.statusDisposable.dispose() self.fetchDisposable.dispose() + self.secretTimer?.invalidate() } override func didLoad() { @@ -122,6 +126,7 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { let currentVideoNode = self.videoNode let hasCurrentVideoNode = currentVideoNode != nil + let previousAutomaticDownload = self.automaticDownload return { [weak self] account, theme, strings, message, media, automaticDownload, automaticPlayback, sizeCalculation, layoutConstants in var nativeSize: CGSize @@ -219,7 +224,7 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { var mediaUpdated = false if let currentMedia = currentMedia { - mediaUpdated = !media.isEqual(currentMedia) + mediaUpdated = !media.isEqual(to: currentMedia) } else { mediaUpdated = true } @@ -293,12 +298,12 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { } } - updatedFetchControls = FetchControls(fetch: { _ in + updatedFetchControls = FetchControls(fetch: { manual in if let strongSelf = self { if file.isAnimated { strongSelf.fetchDisposable.set(fetchedMediaResource(postbox: account.postbox, reference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference(file.resource), statsCategory: statsCategoryForFileWithAttributes(file.attributes)).start()) } else { - strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(account: account, message: message, file: file).start()) + strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(account: account, message: message, file: file, userInitiated: manual).start()) } } }, cancel: { @@ -359,6 +364,7 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { strongSelf.themeAndStrings = (theme, strings) strongSelf.sizeCalculation = sizeCalculation strongSelf.automaticPlayback = automaticPlayback + strongSelf.automaticDownload = automaticDownload transition.updateFrame(node: strongSelf.imageNode, frame: imageFrame) strongSelf.statusNode?.position = CGPoint(x: imageFrame.midX, y: imageFrame.midY) @@ -420,14 +426,16 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { if let updatedFetchControls = updatedFetchControls { let _ = strongSelf.fetchControls.swap(updatedFetchControls) if automaticDownload { - if let image = media as? TelegramMediaImage { + if let _ = media as? TelegramMediaImage { updatedFetchControls.fetch(false) } else if let image = media as? TelegramMediaWebFile { strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: account, image: image).start()) } else if let file = media as? TelegramMediaFile { - strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(account: account, message: message, file: file).start()) + strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(account: account, message: message, file: file, userInitiated: false).start()) } } + } else if previousAutomaticDownload != automaticDownload, automaticDownload { + strongSelf.fetchControls.with({ $0 })?.fetch(false) } imageApply() @@ -445,13 +453,15 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { return } - var secretBeginTimeAndTimeout: (Double, Double)? + var secretBeginTimeAndTimeout: (Double?, Double)? let isSecretMedia = message.containsSecretMedia if isSecretMedia { for attribute in message.attributes { if let attribute = attribute as? AutoremoveTimeoutMessageAttribute { if let countdownBeginTime = attribute.countdownBeginTime { secretBeginTimeAndTimeout = (Double(countdownBeginTime), Double(attribute.timeout)) + } else { + secretBeginTimeAndTimeout = (nil, Double(attribute.timeout)) } break } @@ -469,7 +479,7 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { } var progressRequired = false - if let _ = secretBeginTimeAndTimeout { + if secretBeginTimeAndTimeout?.0 != nil { progressRequired = true } else if let fetchStatus = self.fetchStatus { if case .Local = fetchStatus { @@ -529,10 +539,14 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { if isActive { adjustedProgress = max(adjustedProgress, 0.027) } - if message.flags.isSending && adjustedProgress.isEqual(to: 1.0), case .unconstrained = sizeCalculation { + var wasCheck = false + if let statusNode = self.statusNode, case .check = statusNode.state { + wasCheck = true + } + if adjustedProgress.isEqual(to: 1.0), case .unconstrained = sizeCalculation, (message.flags.contains(.Unsent) || wasCheck) { state = .check(bubbleTheme.mediaOverlayControlForegroundColor) } else { - state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, value: CGFloat(adjustedProgress), cancelEnabled: true) + state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: true) } if case .constrained = sizeCalculation { if let file = media as? TelegramMediaFile, (!file.isAnimated || message.flags.contains(.Unsent)) { @@ -546,7 +560,7 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { case .Local: state = .none let secretProgressIcon = PresentationResourcesChat.chatBubbleSecretMediaIcon(theme) - if isSecretMedia, let (beginTime, timeout) = secretBeginTimeAndTimeout { + if isSecretMedia, let (maybeBeginTime, timeout) = secretBeginTimeAndTimeout, let beginTime = maybeBeginTime { state = .secretTimeout(color: bubbleTheme.mediaOverlayControlForegroundColor, icon: secretProgressIcon, beginTime: beginTime, timeout: timeout) } else if isSecretMedia, let secretProgressIcon = secretProgressIcon { state = .customIcon(secretProgressIcon) @@ -577,6 +591,18 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { } } } + + if badgeContent == nil, isSecretMedia, let (maybeBeginTime, timeout) = secretBeginTimeAndTimeout { + let remainingTime: Int32 + if let beginTime = maybeBeginTime { + let elapsedTime = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - beginTime + remainingTime = Int32(max(0.0, timeout - elapsedTime)) + } else { + remainingTime = Int32(timeout) + } + badgeContent = .text(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: strings.MessageTimer_ShortSeconds(Int32(remainingTime)))) + } + if let statusNode = self.statusNode { if state == .none { self.statusNode = nil @@ -599,6 +625,20 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { self.badgeNode = nil badgeNode.removeFromSupernode() } + + if isSecretMedia, secretBeginTimeAndTimeout?.0 != nil { + if self.secretTimer == nil { + self.secretTimer = SwiftSignalKit.Timer(timeout: 0.3, repeat: true, completion: { [weak self] in + self?.updateFetchStatus() + }, queue: Queue.mainQueue()) + self.secretTimer?.start() + } + } else { + if let secretTimer = self.secretTimer { + self.secretTimer = nil + secretTimer.invalidate() + } + } } static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ account: Account, _ theme: PresentationTheme, _ strings: PresentationStrings, _ message: Message, _ media: Media, _ automaticDownload: Bool, _ automaticPlayback: Bool, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants) -> (CGSize, CGFloat, (CGSize, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition) -> ChatMessageInteractiveMediaNode))) { diff --git a/TelegramUI/ChatMessageInvoiceBubbleContentNode.swift b/TelegramUI/ChatMessageInvoiceBubbleContentNode.swift index 2ccebb977b..15acc0d8fe 100644 --- a/TelegramUI/ChatMessageInvoiceBubbleContentNode.swift +++ b/TelegramUI/ChatMessageInvoiceBubbleContentNode.swift @@ -54,7 +54,7 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode { } } - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.account, item.controllerInteraction, item.message, item.read, title, subtitle, text, nil, mediaAndFlags, nil, nil, false, layoutConstants, constrainedSize) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.account, item.controllerInteraction, item.message, item.read, title, subtitle, text, nil, mediaAndFlags, nil, nil, false, layoutConstants, constrainedSize) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) diff --git a/TelegramUI/ChatMessageItem.swift b/TelegramUI/ChatMessageItem.swift index 67569dcb15..9dfb97b459 100644 --- a/TelegramUI/ChatMessageItem.swift +++ b/TelegramUI/ChatMessageItem.swift @@ -187,11 +187,28 @@ enum ChatMessageMerge: Int32 { } } -public final class ChatMessageItemAssociatedData { +public final class ChatMessageItemAssociatedData: Equatable { let automaticDownloadPeerType: AutomaticMediaDownloadPeerType + let automaticDownloadNetworkType: AutomaticDownloadNetworkType + let isRecentActions: Bool - init(automaticDownloadPeerType: AutomaticMediaDownloadPeerType) { + init(automaticDownloadPeerType: AutomaticMediaDownloadPeerType, automaticDownloadNetworkType: AutomaticDownloadNetworkType, isRecentActions: Bool) { self.automaticDownloadPeerType = automaticDownloadPeerType + self.automaticDownloadNetworkType = automaticDownloadNetworkType + self.isRecentActions = isRecentActions + } + + 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 + } + return true } } diff --git a/TelegramUI/ChatMessageMapBubbleContentNode.swift b/TelegramUI/ChatMessageMapBubbleContentNode.swift index 619cb0ecef..1e80029749 100644 --- a/TelegramUI/ChatMessageMapBubbleContentNode.swift +++ b/TelegramUI/ChatMessageMapBubbleContentNode.swift @@ -76,7 +76,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { } let bubbleInsets: UIEdgeInsets - if case .color = item.presentationData.wallpaper { + if case .color = item.presentationData.theme.wallpaper { bubbleInsets = UIEdgeInsets() } else { bubbleInsets = layoutConstants.image.bubbleInsets @@ -93,12 +93,12 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { imageSize = CGSize(width: fitWidth, height: floor(fitWidth * 0.5)) if let venue = selectedMedia.venue { - titleString = NSAttributedString(string: venue.title, font: titleFont, textColor: item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.chat.bubble.incomingPrimaryTextColor : item.presentationData.theme.chat.bubble.outgoingPrimaryTextColor) + titleString = NSAttributedString(string: venue.title, font: titleFont, textColor: item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.theme.chat.bubble.incomingPrimaryTextColor : item.presentationData.theme.theme.chat.bubble.outgoingPrimaryTextColor) if let address = venue.address, !address.isEmpty { - textString = NSAttributedString(string: address, font: textFont, textColor: item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.chat.bubble.incomingSecondaryTextColor : item.presentationData.theme.chat.bubble.outgoingSecondaryTextColor) + textString = NSAttributedString(string: address, font: textFont, textColor: item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.theme.chat.bubble.incomingSecondaryTextColor : item.presentationData.theme.theme.chat.bubble.outgoingSecondaryTextColor) } } else { - textString = NSAttributedString(string: " ", font: textFont, textColor: item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.chat.bubble.incomingSecondaryTextColor : item.presentationData.theme.chat.bubble.outgoingSecondaryTextColor) + textString = NSAttributedString(string: " ", font: textFont, textColor: item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.theme.chat.bubble.incomingSecondaryTextColor : item.presentationData.theme.theme.chat.bubble.outgoingSecondaryTextColor) } } else { let fitWidth: CGFloat = min(constrainedSize.width, layoutConstants.image.maxDimensions.width) @@ -107,14 +107,14 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { } if selectedMedia.liveBroadcastingTimeout != nil { - titleString = NSAttributedString(string: item.presentationData.strings.Message_LiveLocation, font: liveTitleFont, textColor: item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.chat.bubble.incomingPrimaryTextColor : item.presentationData.theme.chat.bubble.outgoingPrimaryTextColor) + titleString = NSAttributedString(string: item.presentationData.strings.Message_LiveLocation, font: liveTitleFont, textColor: item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.theme.chat.bubble.incomingPrimaryTextColor : item.presentationData.theme.theme.chat.bubble.outgoingPrimaryTextColor) } } else { imageSize = CGSize(width: 75.0, height: 75.0) } var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - if let selectedMedia = selectedMedia, previousMedia == nil || !previousMedia!.isEqual(selectedMedia) { + if let selectedMedia = selectedMedia, previousMedia == nil || !previousMedia!.isEqual(to: selectedMedia) { var updated = true if let previousMedia = previousMedia { if previousMedia.latitude.isEqual(to: selectedMedia.latitude) && previousMedia.longitude.isEqual(to: selectedMedia.longitude) { @@ -143,7 +143,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { pinLiveLocationActive = activeLiveBroadcastingTimeout != nil } } - let (pinSize, pinApply) = makePinLayout(item.account, item.presentationData.theme, pinPeer, pinLiveLocationActive) + let (pinSize, pinApply) = makePinLayout(item.account, item.presentationData.theme.theme, pinPeer, pinLiveLocationActive) return (contentProperties, nil, maximumWidth, { constrainedSize, position in let imageCorners: ImageCorners @@ -339,8 +339,8 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { let timerSize = CGSize(width: 28.0, height: 28.0) strongSelf.liveTimerNode?.frame = CGRect(origin: CGPoint(x: imageFrame.maxX - 10.0 - timerSize.width, y: imageFrame.maxY + 11.0), size: timerSize) - let timerForegroundColor: UIColor = item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.chat.bubble.incomingAccentControlColor : item.presentationData.theme.chat.bubble.outgoingAccentControlColor - let timerTextColor: UIColor = item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.chat.bubble.incomingSecondaryTextColor : item.presentationData.theme.chat.bubble.outgoingSecondaryTextColor + let timerForegroundColor: UIColor = item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.theme.chat.bubble.incomingAccentControlColor : item.presentationData.theme.theme.chat.bubble.outgoingAccentControlColor + let timerTextColor: UIColor = item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.theme.chat.bubble.incomingSecondaryTextColor : item.presentationData.theme.theme.chat.bubble.outgoingSecondaryTextColor strongSelf.liveTimerNode?.update(backgroundColor: timerForegroundColor.withAlphaComponent(0.4), foregroundColor: timerForegroundColor, textColor: timerTextColor, beginTimestamp: Double(item.message.timestamp), timeout: Double(activeLiveBroadcastingTimeout), strings: item.presentationData.strings) if strongSelf.liveTextNode == nil { @@ -421,7 +421,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { } override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, () -> UIView?)? { - if self.item?.message.id == messageId, let currentMedia = self.media, currentMedia.isEqual(media) { + if self.item?.message.id == messageId, let currentMedia = self.media, currentMedia.isEqual(to: media) { let imageNode = self.imageNode return (self.imageNode, { [weak imageNode] in return imageNode?.view.snapshotContentTree(unhide: true) @@ -434,7 +434,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { var mediaHidden = false if let currentMedia = self.media, let media = media { for item in media { - if item.isEqual(currentMedia) { + if item.isEqual(to: currentMedia) { mediaHidden = true break } diff --git a/TelegramUI/ChatMessageMediaBubbleContentNode.swift b/TelegramUI/ChatMessageMediaBubbleContentNode.swift index fdeb421322..3d2f8ea132 100644 --- a/TelegramUI/ChatMessageMediaBubbleContentNode.swift +++ b/TelegramUI/ChatMessageMediaBubbleContentNode.swift @@ -54,10 +54,10 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { for media in item.message.media { if let telegramImage = media as? TelegramMediaImage { selectedMedia = telegramImage - automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peer: item.message.peers[item.message.id.peerId], media: telegramImage) + automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, media: telegramImage) } else if let telegramFile = media as? TelegramMediaFile { selectedMedia = telegramFile - automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peer: item.message.peers[item.message.id.peerId], media: telegramFile) + automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, media: telegramFile) } } @@ -66,7 +66,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { switch preparePosition { case .linear: - if case .color = item.presentationData.wallpaper { + if case .color = item.presentationData.theme.wallpaper { bubbleInsets = UIEdgeInsets() } else { bubbleInsets = layoutConstants.image.bubbleInsets @@ -78,7 +78,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { sizeCalculation = .unconstrained } - let (unboundSize, initialWidth, refineLayout) = interactiveImageLayout(item.account, item.presentationData.theme, item.presentationData.strings, item.message, selectedMedia!, automaticDownload, item.controllerInteraction.automaticMediaDownloadSettings.autoplayGifs, sizeCalculation, layoutConstants) + let (unboundSize, initialWidth, refineLayout) = interactiveImageLayout(item.account, item.presentationData.theme.theme, item.presentationData.strings, item.message, selectedMedia!, automaticDownload, item.controllerInteraction.automaticMediaDownloadSettings.autoplayGifs, sizeCalculation, layoutConstants) var forceFullCorners = false if let media = selectedMedia as? TelegramMediaFile, media.isAnimated { @@ -197,7 +197,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { selectionNode.frame = imageFrame selectionNode.updateSelected(selection, animated: animation.isAnimated) } else { - let selectionNode = GridMessageSelectionNode(theme: item.presentationData.theme, toggle: { value in + let selectionNode = GridMessageSelectionNode(theme: item.presentationData.theme.theme, toggle: { value in item.controllerInteraction.toggleMessagesSelection([item.message.id], value) }) strongSelf.selectionNode = selectionNode @@ -226,7 +226,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, () -> UIView?)? { - if self.item?.message.id == messageId, let currentMedia = self.media, currentMedia.isEqual(media) { + if self.item?.message.id == messageId, let currentMedia = self.media, currentMedia.isEqual(to: media) { let interactiveImageNode = self.interactiveImageNode return (self.interactiveImageNode, { [weak interactiveImageNode] in return interactiveImageNode?.view.snapshotContentTree(unhide: true) @@ -248,7 +248,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { var mediaHidden = false if let currentMedia = self.media, let media = media { for item in media { - if item.isEqual(currentMedia) { + if item.isSemanticallyEqual(to: currentMedia) { mediaHidden = true break } @@ -289,7 +289,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { self.highlightedState = highlighted if highlighted { - self.interactiveImageNode.setOverlayColor(item.presentationData.theme.chat.bubble.mediaHighlightOverlayColor, animated: false) + self.interactiveImageNode.setOverlayColor(item.presentationData.theme.theme.chat.bubble.mediaHighlightOverlayColor, animated: false) } else { self.interactiveImageNode.setOverlayColor(nil, animated: animated) } diff --git a/TelegramUI/ChatMessageReplyInfoNode.swift b/TelegramUI/ChatMessageReplyInfoNode.swift index d31d398f51..6c994e24c1 100644 --- a/TelegramUI/ChatMessageReplyInfoNode.swift +++ b/TelegramUI/ChatMessageReplyInfoNode.swift @@ -126,7 +126,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { var mediaUpdated = false if let updatedMediaReference = updatedMediaReference, let previousMediaReference = previousMediaReference { - mediaUpdated = !updatedMediaReference.media.isEqual(previousMediaReference.media) + mediaUpdated = !updatedMediaReference.media.isEqual(to: previousMediaReference.media) } else if (updatedMediaReference != nil) != (previousMediaReference != nil) { mediaUpdated = true } diff --git a/TelegramUI/ChatMessageStickerItemNode.swift b/TelegramUI/ChatMessageStickerItemNode.swift index 5c4097cb28..b9f2975f8c 100644 --- a/TelegramUI/ChatMessageStickerItemNode.swift +++ b/TelegramUI/ChatMessageStickerItemNode.swift @@ -222,14 +222,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView { for attribute in item.message.attributes { if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { let availableWidth = max(60.0, params.width - params.leftInset - params.rightInset - imageSize.width - 20.0 - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left) - replyInfoApply = makeReplyInfoLayout(item.presentationData.theme, item.presentationData.strings, item.account, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + replyInfoApply = makeReplyInfoLayout(item.presentationData.theme.theme, item.presentationData.strings, item.account, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) if let currentReplyBackgroundNode = currentReplyBackgroundNode { updatedReplyBackgroundNode = currentReplyBackgroundNode } else { updatedReplyBackgroundNode = ASImageNode() } - replyBackgroundImage = PresentationResourcesChat.chatFreeformContentAdditionalInfoBackgroundImage(item.presentationData.theme) + replyBackgroundImage = PresentationResourcesChat.chatFreeformContentAdditionalInfoBackgroundImage(item.presentationData.theme.theme) break } } @@ -242,18 +242,18 @@ class ChatMessageStickerItemNode: ChatMessageItemView { updatedShareButtonNode = currentShareButtonNode if item.presentationData.theme !== currentItem?.presentationData.theme { if item.message.id.peerId == item.account.peerId { - updatedShareButtonBackground = PresentationResourcesChat.chatBubbleNavigateButtonImage(item.presentationData.theme) + updatedShareButtonBackground = PresentationResourcesChat.chatBubbleNavigateButtonImage(item.presentationData.theme.theme) } else { - updatedShareButtonBackground = PresentationResourcesChat.chatBubbleShareButtonImage(item.presentationData.theme) + updatedShareButtonBackground = PresentationResourcesChat.chatBubbleShareButtonImage(item.presentationData.theme.theme) } } } else { let buttonNode = HighlightableButtonNode() let buttonIcon: UIImage? if item.message.id.peerId == item.account.peerId { - buttonIcon = PresentationResourcesChat.chatBubbleNavigateButtonImage(item.presentationData.theme) + buttonIcon = PresentationResourcesChat.chatBubbleNavigateButtonImage(item.presentationData.theme.theme) } else { - buttonIcon = PresentationResourcesChat.chatBubbleShareButtonImage(item.presentationData.theme) + buttonIcon = PresentationResourcesChat.chatBubbleShareButtonImage(item.presentationData.theme.theme) } buttonNode.setBackgroundImage(buttonIcon, for: [.normal]) updatedShareButtonNode = buttonNode @@ -403,7 +403,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { if translation.x < -45.0, self.swipeToReplyNode == nil, let item = self.item { self.swipeToReplyFeedback?.impact() - let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: item.presentationData.theme.chat.bubble.shareButtonFillColor, strokeColor: item.presentationData.theme.chat.bubble.shareButtonStrokeColor, foregroundColor: item.presentationData.theme.chat.bubble.shareButtonForegroundColor) + let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: item.presentationData.theme.theme.chat.bubble.shareButtonFillColor, strokeColor: item.presentationData.theme.theme.chat.bubble.shareButtonStrokeColor, foregroundColor: item.presentationData.theme.theme.chat.bubble.shareButtonForegroundColor) self.swipeToReplyNode = swipeToReplyNode self.addSubnode(swipeToReplyNode) animateReplyNodeIn = true @@ -474,7 +474,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { selectionNode.frame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentBounds.size.width, height: self.contentBounds.size.height)) self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0); } else { - let selectionNode = ChatMessageSelectionNode(theme: item.presentationData.theme, toggle: { [weak self] value in + let selectionNode = ChatMessageSelectionNode(theme: item.presentationData.theme.theme, toggle: { [weak self] value in if let strongSelf = self, let item = strongSelf.item { item.controllerInteraction.toggleMessagesSelection([item.message.id], value) } @@ -532,7 +532,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { self.highlightedState = highlighted if highlighted { - self.imageNode.setOverlayColor(item.presentationData.theme.chat.bubble.mediaHighlightOverlayColor, animated: false) + self.imageNode.setOverlayColor(item.presentationData.theme.theme.chat.bubble.mediaHighlightOverlayColor, animated: false) } else { self.imageNode.setOverlayColor(nil, animated: animated) } diff --git a/TelegramUI/ChatMessageTextBubbleContentNode.swift b/TelegramUI/ChatMessageTextBubbleContentNode.swift index d7820eec03..df57d97920 100644 --- a/TelegramUI/ChatMessageTextBubbleContentNode.swift +++ b/TelegramUI/ChatMessageTextBubbleContentNode.swift @@ -158,7 +158,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } - let bubbleTheme = item.presentationData.theme.chat.bubble + let bubbleTheme = item.presentationData.theme.theme.chat.bubble if let entities = entities { attributedText = stringWithAppliedEntities(message.text, entities: entities, baseColor: incoming ? bubbleTheme.incomingPrimaryTextColor : bubbleTheme.outgoingPrimaryTextColor, linkColor: incoming ? bubbleTheme.incomingLinkTextColor : bubbleTheme.outgoingLinkTextColor, baseFont: item.presentationData.messageFont, linkFont: item.presentationData.messageFont, boldFont: item.presentationData.messageBoldFont, italicFont: item.presentationData.messageItalicFont, fixedFont: item.presentationData.messageFixedFont) @@ -281,9 +281,13 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction { let textNodeFrame = self.textNode.frame - if let (_, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { - if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] as? String { - return .url(url) + if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { + if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { + var concealed = true + if let attributeText = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) { + concealed = !doesUrlMatchText(url: url, text: attributeText) + } + return .url(url: url, concealed: concealed) } else if let peerMention = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention { return .peerMention(peerMention.peerId, peerMention.mention) } else if let peerName = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { @@ -307,7 +311,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { let textNodeFrame = self.textNode.frame if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { let possibleNames: [String] = [ - TelegramTextAttributes.Url, + TelegramTextAttributes.URL, TelegramTextAttributes.PeerMention, TelegramTextAttributes.PeerTextMention, TelegramTextAttributes.BotCommand, @@ -327,7 +331,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { if let current = self.linkHighlightingNode { linkHighlightingNode = current } else { - linkHighlightingNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.chat.bubble.incomingLinkHighlightColor : item.presentationData.theme.chat.bubble.outgoingLinkHighlightColor) + linkHighlightingNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.theme.chat.bubble.incomingLinkHighlightColor : item.presentationData.theme.theme.chat.bubble.outgoingLinkHighlightColor) self.linkHighlightingNode = linkHighlightingNode self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode) } @@ -346,8 +350,8 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { if let item = self.item { let textNodeFrame = self.textNode.frame if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { - if let value = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] as? String { - if let rects = self.textNode.attributeRects(name: TelegramTextAttributes.Url, at: index), !rects.isEmpty { + if let value = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { + if let rects = self.textNode.attributeRects(name: TelegramTextAttributes.URL, at: index), !rects.isEmpty { var rect = rects[0] for i in 1 ..< rects.count { rect = rect.union(rects[i]) diff --git a/TelegramUI/ChatMessageWebpageBubbleContentNode.swift b/TelegramUI/ChatMessageWebpageBubbleContentNode.swift index 70ff66a864..0996055b5f 100644 --- a/TelegramUI/ChatMessageWebpageBubbleContentNode.swift +++ b/TelegramUI/ChatMessageWebpageBubbleContentNode.swift @@ -102,11 +102,23 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { self.addSubnode(self.contentNode) self.contentNode.openMedia = { [weak self] in if let strongSelf = self, let item = strongSelf.item { - if let webPage = strongSelf.webPage, case let .Loaded(content) = webPage.content, content.instantPage != nil { - item.controllerInteraction.openInstantPage(item.message) - } else { - let _ = item.controllerInteraction.openMessage(item.message) + if let webPage = strongSelf.webPage, case let .Loaded(content) = webPage.content, let image = content.image, let instantPage = content.instantPage { + var isGallery = false + switch websiteType(of: content) { + case .instagram, .twitter: + let count = instantPageGalleryMedia(webpageId: webPage.webpageId, page: instantPage, galleryMedia: image).count + if count > 1 { + isGallery = true + } + default: + break + } + if !isGallery { + item.controllerInteraction.openInstantPage(item.message) + return + } } + let _ = item.controllerInteraction.openMessage(item.message) } } self.contentNode.activateAction = { [weak self] in @@ -121,7 +133,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } } if let webpage = webPageContent { - item.controllerInteraction.openUrl(webpage.url) + item.controllerInteraction.openUrl(webpage.url, false) } } } @@ -231,7 +243,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } } - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.account, item.controllerInteraction, item.message, item.read, title, subtitle, text, entities, mediaAndFlags, actionIcon, actionTitle, true, layoutConstants, constrainedSize) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.account, item.controllerInteraction, item.message, item.read, title, subtitle, text, entities, mediaAndFlags, actionIcon, actionTitle, true, layoutConstants, constrainedSize) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) @@ -287,9 +299,9 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } switch websiteType(of: content) { case .twitter: - return .url("https://twitter.com/\(mention)") + return .url(url: "https://twitter.com/\(mention)", concealed: false) case .instagram: - return .url("https://instagram.com/\(mention)") + return .url(url: "https://instagram.com/\(mention)", concealed: false) default: break } @@ -315,7 +327,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { if let media = media { var updatedMedia = media for item in media { - if let webpage = item as? TelegramMediaWebpage, let current = self.webPage, webpage.isEqual(current) { + if let webpage = item as? TelegramMediaWebpage, let current = self.webPage, webpage.isEqual(to: current) { var mediaList: [Media] = [webpage] if case let .Loaded(content) = webpage.content { if let image = content.image { @@ -342,7 +354,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { if let result = self.contentNode.transitionNode(media: media) { return result } - if let webpage = media as? TelegramMediaWebpage, let current = self.webPage, webpage.isEqual(current) { + if let webpage = media as? TelegramMediaWebpage, let current = self.webPage, webpage.isEqual(to: current) { if case let .Loaded(content) = webpage.content { if let image = content.image, let result = self.contentNode.transitionNode(media: image) { return result diff --git a/TelegramUI/ChatPanelInterfaceInteraction.swift b/TelegramUI/ChatPanelInterfaceInteraction.swift index bf3b26dcac..ca81ae50b9 100644 --- a/TelegramUI/ChatPanelInterfaceInteraction.swift +++ b/TelegramUI/ChatPanelInterfaceInteraction.swift @@ -77,18 +77,20 @@ final class ChatPanelInterfaceInteraction { let pinMessage: (MessageId) -> Void let unpinMessage: () -> Void let reportPeer: () -> Void + let presentPeerContact: () -> Void let dismissReportPeer: () -> Void let deleteChat: () -> Void let beginCall: () -> Void let toggleMessageStickerStarred: (MessageId) -> Void let presentController: (ViewController, Any?) -> Void + let getNavigationController: () -> NavigationController? let presentGlobalOverlayController: (ViewController, Any?) -> Void let navigateFeed: () -> Void let openGrouping: () -> Void let toggleSilentPost: () -> Void let statuses: ChatPanelInterfaceInteractionStatuses? - init(setupReplyMessage: @escaping (MessageId) -> Void, setupEditMessage: @escaping (MessageId?) -> Void, beginMessageSelection: @escaping ([MessageId]) -> Void, deleteSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void, reportMessages: @escaping ([Message]) -> Void, deleteMessages: @escaping ([Message]) -> Void, forwardSelectedMessages: @escaping () -> Void, forwardMessages: @escaping ([Message]) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, editMessage: @escaping () -> Void, beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void, dismissMessageSearch: @escaping () -> Void, updateMessageSearch: @escaping (String) -> Void, navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, navigateToMessage: @escaping (MessageId) -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult) -> Void, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginMediaRecording: @escaping (Bool) -> Void, finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void, deleteRecordedMedia: @escaping () -> Void, sendRecordedMedia: @escaping () -> Void, displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject) -> Void, switchMediaRecordingMode: @escaping () -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, sendSticker: @escaping (FileMediaReference) -> Void, unblockPeer: @escaping () -> Void, pinMessage: @escaping (MessageId) -> Void, unpinMessage: @escaping () -> Void, reportPeer: @escaping () -> Void, dismissReportPeer: @escaping () -> Void, deleteChat: @escaping () -> Void, beginCall: @escaping () -> Void, toggleMessageStickerStarred: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigateFeed: @escaping () -> Void, openGrouping: @escaping () -> Void, toggleSilentPost: @escaping () -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) { + init(setupReplyMessage: @escaping (MessageId) -> Void, setupEditMessage: @escaping (MessageId?) -> Void, beginMessageSelection: @escaping ([MessageId]) -> Void, deleteSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void, reportMessages: @escaping ([Message]) -> Void, deleteMessages: @escaping ([Message]) -> Void, forwardSelectedMessages: @escaping () -> Void, forwardMessages: @escaping ([Message]) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, editMessage: @escaping () -> Void, beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void, dismissMessageSearch: @escaping () -> Void, updateMessageSearch: @escaping (String) -> Void, navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, navigateToMessage: @escaping (MessageId) -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult) -> Void, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginMediaRecording: @escaping (Bool) -> Void, finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void, deleteRecordedMedia: @escaping () -> Void, sendRecordedMedia: @escaping () -> Void, displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject) -> Void, switchMediaRecordingMode: @escaping () -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, sendSticker: @escaping (FileMediaReference) -> Void, unblockPeer: @escaping () -> Void, pinMessage: @escaping (MessageId) -> Void, unpinMessage: @escaping () -> Void, reportPeer: @escaping () -> Void, presentPeerContact: @escaping () -> Void, dismissReportPeer: @escaping () -> Void, deleteChat: @escaping () -> Void, beginCall: @escaping () -> Void, toggleMessageStickerStarred: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigateFeed: @escaping () -> Void, openGrouping: @escaping () -> Void, toggleSilentPost: @escaping () -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) { self.setupReplyMessage = setupReplyMessage self.setupEditMessage = setupEditMessage self.beginMessageSelection = beginMessageSelection @@ -129,11 +131,13 @@ final class ChatPanelInterfaceInteraction { self.pinMessage = pinMessage self.unpinMessage = unpinMessage self.reportPeer = reportPeer + self.presentPeerContact = presentPeerContact self.dismissReportPeer = dismissReportPeer self.deleteChat = deleteChat self.beginCall = beginCall self.toggleMessageStickerStarred = toggleMessageStickerStarred self.presentController = presentController + self.getNavigationController = getNavigationController self.presentGlobalOverlayController = presentGlobalOverlayController self.navigateFeed = navigateFeed self.openGrouping = openGrouping diff --git a/TelegramUI/ChatPinnedMessageTitlePanelNode.swift b/TelegramUI/ChatPinnedMessageTitlePanelNode.swift index 34cc217947..2b0aea6a01 100644 --- a/TelegramUI/ChatPinnedMessageTitlePanelNode.swift +++ b/TelegramUI/ChatPinnedMessageTitlePanelNode.swift @@ -90,8 +90,10 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { let panelHeight: CGFloat = 50.0 + var themeUpdated = false if self.theme !== interfaceState.theme { + themeUpdated = true self.theme = interfaceState.theme self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(interfaceState.theme), for: []) self.lineNode.image = PresentationResourcesChat.chatInputPanelVerticalSeparatorLineImage(interfaceState.theme) @@ -108,7 +110,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { messageUpdated = true } - if messageUpdated { + if messageUpdated || themeUpdated { let previousMessageWasNil = self.currentMessage == nil self.currentMessage = interfaceState.pinnedMessage @@ -188,7 +190,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { var mediaUpdated = false if let updatedMediaReference = updatedMediaReference, let previousMediaReference = previousMediaReference { - mediaUpdated = !updatedMediaReference.media.isEqual(previousMediaReference.media) + mediaUpdated = !updatedMediaReference.media.isEqual(to: previousMediaReference.media) } else if (updatedMediaReference != nil) != (previousMediaReference != nil) { mediaUpdated = true } diff --git a/TelegramUI/ChatPresentationData.swift b/TelegramUI/ChatPresentationData.swift index 2a063136ef..c08c23c517 100644 --- a/TelegramUI/ChatPresentationData.swift +++ b/TelegramUI/ChatPresentationData.swift @@ -6,24 +6,52 @@ extension PresentationFontSize { var baseDisplaySize: CGFloat { switch self { case .extraSmall: - return 13.0 + return 14.0 case .small: return 15.0 + case .medium: + return 16.0 case .regular: return 17.0 case .large: return 19.0 case .extraLarge: - return 21.0 + return 23.0 + case .extraLargeX2: + return 26.0 } } } +extension TelegramWallpaper { + var isEmpty: Bool { + switch self { + case .builtin, .image: + return false + case .color: + return true + } + } +} + +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: PresentationTheme + let theme: ChatPresentationThemeData let fontSize: PresentationFontSize let strings: PresentationStrings - let wallpaper: TelegramWallpaper let timeFormat: PresentationTimeFormat let messageFont: UIFont @@ -31,11 +59,10 @@ public final class ChatPresentationData { let messageItalicFont: UIFont let messageFixedFont: UIFont - init(theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, wallpaper: TelegramWallpaper, timeFormat: PresentationTimeFormat) { + init(theme: ChatPresentationThemeData, fontSize: PresentationFontSize, strings: PresentationStrings, timeFormat: PresentationTimeFormat) { self.theme = theme self.fontSize = fontSize self.strings = strings - self.wallpaper = wallpaper self.timeFormat = timeFormat let baseFontSize = fontSize.baseDisplaySize diff --git a/TelegramUI/ChatPresentationInterfaceState.swift b/TelegramUI/ChatPresentationInterfaceState.swift index 16fc521941..b73327070e 100644 --- a/TelegramUI/ChatPresentationInterfaceState.swift +++ b/TelegramUI/ChatPresentationInterfaceState.swift @@ -359,10 +359,11 @@ final class ChatRecordedMediaPreview: Equatable { } } -struct ChatPresentationInterfaceState: Equatable { +final class ChatPresentationInterfaceState: Equatable { let interfaceState: ChatInterfaceState let chatLocation: ChatLocation let renderedPeer: RenderedPeer? + let isContact: Bool let inputTextPanelState: ChatTextInputPanelState let editMessageState: ChatEditInterfaceMessageState? let recordedMediaPreview: ChatRecordedMediaPreview? @@ -395,6 +396,7 @@ struct ChatPresentationInterfaceState: Equatable { self.recordedMediaPreview = nil self.chatLocation = chatLocation self.renderedPeer = nil + self.isContact = false self.inputQueryResults = [:] self.inputMode = .none self.titlePanelContexts = [] @@ -418,10 +420,11 @@ struct ChatPresentationInterfaceState: Equatable { self.mode = mode } - init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: Message?, peerIsBlocked: Bool, peerIsMuted: Bool, canReportPeer: Bool, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: (String, TelegramMediaWebpage)?, editingUrlPreview: (String, TelegramMediaWebpage)?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, accountPeerId: PeerId, mode: ChatControllerPresentationMode) { + init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isContact: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: Message?, peerIsBlocked: Bool, peerIsMuted: Bool, canReportPeer: Bool, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: (String, TelegramMediaWebpage)?, editingUrlPreview: (String, TelegramMediaWebpage)?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, accountPeerId: PeerId, mode: ChatControllerPresentationMode) { self.interfaceState = interfaceState self.chatLocation = chatLocation self.renderedPeer = renderedPeer + self.isContact = isContact self.inputTextPanelState = inputTextPanelState self.editMessageState = editMessageState self.recordedMediaPreview = recordedMediaPreview @@ -455,6 +458,9 @@ struct ChatPresentationInterfaceState: Equatable { if lhs.renderedPeer != rhs.renderedPeer { return false } + if lhs.isContact != rhs.isContact { + return false + } if lhs.inputTextPanelState != rhs.inputTextPanelState { return false @@ -530,7 +536,7 @@ struct ChatPresentationInterfaceState: Equatable { if lhsUrlPreview.0 != rhsUrlPreview.0 { return false } - if !lhsUrlPreview.1.isEqual(rhsUrlPreview.1) { + if !lhsUrlPreview.1.isEqual(to: rhsUrlPreview.1) { return false } } else if (lhs.urlPreview != nil) != (rhs.urlPreview != nil) { @@ -541,7 +547,7 @@ struct ChatPresentationInterfaceState: Equatable { if lhsEditingUrlPreview.0 != rhsEditingUrlPreview.0 { return false } - if !lhsEditingUrlPreview.1.isEqual(rhsEditingUrlPreview.1) { + if !lhsEditingUrlPreview.1.isEqual(to: rhsEditingUrlPreview.1) { return false } } else if (lhs.editingUrlPreview != nil) != (rhs.editingUrlPreview != nil) { @@ -584,11 +590,15 @@ struct ChatPresentationInterfaceState: Equatable { } func updatedInterfaceState(_ f: (ChatInterfaceState) -> ChatInterfaceState) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedPeer(_ f: (RenderedPeer?) -> RenderedPeer?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: f(self.renderedPeer), inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: f(self.renderedPeer), isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + } + + func updatedIsContact(_ isContact: Bool) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedInputQueryResult(queryKind: ChatPresentationInputQueryKind, _ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { @@ -599,79 +609,91 @@ struct ChatPresentationInterfaceState: Equatable { } else { inputQueryResults.removeValue(forKey: queryKind) } - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedInputTextPanelState(_ f: (ChatTextInputPanelState) -> ChatTextInputPanelState) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: f(self.inputTextPanelState), editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: f(self.inputTextPanelState), editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedEditMessageState(_ editMessageState: ChatEditInterfaceMessageState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedRecordedMediaPreview(_ recordedMediaPreview: ChatRecordedMediaPreview?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedInputMode(_ f: (ChatInputMode) -> ChatInputMode) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: f(self.inputMode), titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: f(self.inputMode), titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedTitlePanelContext(_ f: ([ChatTitlePanelContext]) -> [ChatTitlePanelContext]) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: f(self.titlePanelContexts), keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: f(self.titlePanelContexts), keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedKeyboardButtonsMessage(_ message: Message?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: message, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: message, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedPinnedMessage(_ pinnedMessage: Message?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedPeerIsBlocked(_ peerIsBlocked: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedPeerIsMuted(_ peerIsMuted: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedCanReportPeer(_ canReportPeer: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedBotStartPayload(_ botStartPayload: String?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedChatHistoryState(_ chatHistoryState: ChatHistoryNodeHistoryState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedUrlPreview(_ urlPreview: (String, TelegramMediaWebpage)?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedEditingUrlPreview(_ editingUrlPreview: (String, TelegramMediaWebpage)?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedSearch(_ search: ChatSearchData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedSearchQuerySuggestionResult(_ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: f(self.searchQuerySuggestionResult), chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: f(self.searchQuerySuggestionResult), chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedMode(_ mode: ChatControllerPresentationMode) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: mode) + } + + func updatedTheme(_ theme: PresentationTheme) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + } + + func updatedStrings(_ strings: PresentationStrings) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + } + + func updatedChatWallpaper(_ chatWallpaper: TelegramWallpaper) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } } diff --git a/TelegramUI/ChatRecentActionsController.swift b/TelegramUI/ChatRecentActionsController.swift index 3396b1c338..36359297e8 100644 --- a/TelegramUI/ChatRecentActionsController.swift +++ b/TelegramUI/ChatRecentActionsController.swift @@ -4,7 +4,7 @@ import TelegramCore import Postbox import SwiftSignalKit -final class ChatRecentActionsController: ViewController { +final class ChatRecentActionsController: TelegramController { private var controllerNode: ChatRecentActionsControllerNode { return self.displayNode as! ChatRecentActionsControllerNode } @@ -26,7 +26,7 @@ final class ChatRecentActionsController: ViewController { self.titleView = ChatRecentActionsTitleView(color: self.presentationData.theme.rootController.navigationBar.primaryTextColor) - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(account: account, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), enableMediaAccessoryPanel: true, locationBroadcastPanelSource: .none) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style @@ -76,11 +76,14 @@ final class ChatRecentActionsController: ViewController { }, pinMessage: { _ in }, unpinMessage: { }, reportPeer: { + }, presentPeerContact: { }, dismissReportPeer: { }, deleteChat: { }, beginCall: { }, toggleMessageStickerStarred: { _ in }, presentController: { _, _ in + }, getNavigationController: { + return nil }, presentGlobalOverlayController: { _, _ in }, navigateFeed: { }, openGrouping: { diff --git a/TelegramUI/ChatRecentActionsControllerNode.swift b/TelegramUI/ChatRecentActionsControllerNode.swift index 435a676c28..be5b540722 100644 --- a/TelegramUI/ChatRecentActionsControllerNode.swift +++ b/TelegramUI/ChatRecentActionsControllerNode.swift @@ -52,11 +52,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { private var isLoading: Bool = false { didSet { if self.isLoading != oldValue { - if self.isLoading { - self.listNode.supernode?.insertSubnode(self.loadingNode, aboveSubnode: self.listNode) - } else { - self.loadingNode.removeFromSupernode() - } + self.loadingNode.isHidden = !self.isLoading } } } @@ -97,11 +93,11 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { self.listNode.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0) self.loadingNode = ChatLoadingNode(theme: self.presentationData.theme) self.emptyNode = ChatRecentActionsEmptyNode(theme: self.presentationData.theme) - self.emptyNode.isHidden = true + self.emptyNode.alpha = 0.0 self.state = ChatRecentActionsControllerState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, fontSize: self.presentationData.fontSize) - self.chatPresentationDataPromise = Promise(ChatPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.fontSize, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, timeFormat: self.presentationData.timeFormat)) + self.chatPresentationDataPromise = Promise(ChatPresentationData(theme: ChatPresentationThemeData(theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper), fontSize: self.presentationData.fontSize, strings: self.presentationData.strings, timeFormat: self.presentationData.timeFormat)) self.context = ChannelAdminEventLogContext(postbox: self.account.postbox, network: self.account.network, peerId: self.peer.id) @@ -111,6 +107,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { self.addSubnode(self.backgroundNode) self.addSubnode(self.listNode) + self.addSubnode(self.loadingNode) self.addSubnode(self.emptyNode) self.addSubnode(self.panelBackgroundNode) self.addSubnode(self.panelSeparatorNode) @@ -201,7 +198,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { self?.openPeerMention(name) }, openMessageContextMenu: { [weak self] message, node, frame in self?.openMessageContextMenu(message: message, node: node, frame: frame) - }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url in + }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _ in self?.openUrl(url) }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message in if let strongSelf = self, let navigationController = strongSelf.getNavigationController() { @@ -403,9 +400,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } - return .single(chatRecentActionsHistoryPreparedTransition(from: previous ?? [], to: processedView, type: update.2, canLoadEarlier: update.1, displayingResults: !processedView.isEmpty || update.1, account: account, peer: peer, controllerInteraction: controllerInteraction)) - - //return preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: reverse, account: account, chatLocation: chatLocation, controllerInteraction: controllerInteraction, scrollPosition: updatedScrollPosition, initialData: initialData?.initialData, keyboardButtonsMessage: view.topTaggedMessages.first, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData) |> map({ mappedChatHistoryViewListTransition(account: account, chatLocation: chatLocation, controllerInteraction: controllerInteraction, mode: mode, transition: $0) }) |> runOn(prepareOnMainQueue ? Queue.mainQueue() : messageViewQueue) + return .single(chatRecentActionsHistoryPreparedTransition(from: previous ?? [], to: processedView, type: update.2, canLoadEarlier: update.1, displayingResults: update.3, account: account, peer: peer, controllerInteraction: controllerInteraction)) } let appliedTransition = historyViewTransition |> deliverOnMainQueue |> mapToQueue { [weak self] transition -> Signal in @@ -471,6 +466,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { transition.updatePosition(node: self.listNode, position: CGRect(origin: CGPoint(), size: layout.size).center) transition.updateFrame(node: self.loadingNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + loadingNode.updateLayout(size: layout.size, insets: insets, transition: transition) let emptyFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight - panelHeight)) transition.updateFrame(node: self.emptyNode, frame: emptyFrame) @@ -535,16 +531,18 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } } - let isEmpty = transition.filteredEntries.isEmpty let displayingResults = transition.displayingResults + let isEmpty = transition.isEmpty + let displayEmptyNode = isEmpty && displayingResults self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: ChatRecentActionsListOpaqueState(entries: transition.filteredEntries, canLoadEarlier: transition.canLoadEarlier), completion: { [weak self] _ in if let strongSelf = self { - if displayingResults != !strongSelf.listNode.isHidden { - strongSelf.listNode.isHidden = !displayingResults - strongSelf.backgroundColor = displayingResults ? strongSelf.presentationData.theme.list.plainBackgroundColor : nil + if displayEmptyNode != strongSelf.listNode.isHidden { + strongSelf.listNode.isHidden = displayEmptyNode + strongSelf.backgroundColor = !displayEmptyNode ? strongSelf.presentationData.theme.list.plainBackgroundColor : nil - strongSelf.emptyNode.isHidden = displayingResults - if !displayingResults { + strongSelf.emptyNode.alpha = displayEmptyNode ? 1.0 : 0.0 + strongSelf.emptyNode.layer.animateAlpha(from: displayEmptyNode ? 0.0 : 1.0, to: displayEmptyNode ? 1.0 : 0.0, duration: 0.25) + if displayEmptyNode { var text: String = "" if let query = strongSelf.filter.query { text = strongSelf.presentationData.strings.Channel_AdminLog_EmptyFilterQueryText(query).0 @@ -553,8 +551,12 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } strongSelf.emptyNode.setup(title: strongSelf.presentationData.strings.Channel_AdminLog_EmptyFilterTitle, text: text) } - //strongSelf.isLoading = isEmpty && !displayingResults } + let isLoading = !displayingResults + if !isLoading && strongSelf.isLoading { + strongSelf.listNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + strongSelf.isLoading = isLoading } }) } else { @@ -582,7 +584,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { if let peer = peer { peerSignal = .single(peer) } else { - peerSignal = self.account.postbox.loadedPeerWithId(peerId) |> map { Optional($0) } + peerSignal = self.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 { diff --git a/TelegramUI/ChatRecentActionsHistoryTransition.swift b/TelegramUI/ChatRecentActionsHistoryTransition.swift index 9d1781c780..c346f42bd2 100644 --- a/TelegramUI/ChatRecentActionsHistoryTransition.swift +++ b/TelegramUI/ChatRecentActionsHistoryTransition.swift @@ -111,7 +111,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) case let .changeAbout(prev, new): var peers = SimpleDictionary() var author: Peer? @@ -142,14 +142,14 @@ 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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) 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 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, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false), additionalContent: !prev.isEmpty ? .eventLogPreviousDescription(prevMessage) : nil) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false), additionalContent: !prev.isEmpty ? .eventLogPreviousDescription(prevMessage) : nil) } case let .changeUsername(prev, new): var peers = SimpleDictionary() @@ -180,7 +180,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } 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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) case .content: var previousAttributes: [MessageAttribute] = [] var attributes: [MessageAttribute] = [] @@ -199,7 +199,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) } case let .changePhoto(_, new): var peers = SimpleDictionary() @@ -217,7 +217,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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) case let .toggleInvites(value): var peers = SimpleDictionary() var author: Peer? @@ -244,7 +244,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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) case let .toggleSignatures(value): var peers = SimpleDictionary() var author: Peer? @@ -271,7 +271,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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) case let .updatePinned(message): switch self.id.contentIndex { case .header: @@ -293,7 +293,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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) case .content: if let message = message { var peers = SimpleDictionary() @@ -311,7 +311,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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) } else { var peers = SimpleDictionary() var author: Peer? @@ -333,7 +333,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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) } } case let .editMessage(prev, message): @@ -352,7 +352,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { var mediaUpdated = false if prev.media.count == message.media.count { for i in 0 ..< prev.media.count { - if !prev.media[i].isEqual(message.media[i]) { + if !prev.media[i].isEqual(to: message.media[i]) { mediaUpdated = true break } @@ -378,7 +378,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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) case .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -395,7 +395,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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, isAdmin: false), additionalContent: !prev.text.isEmpty || !message.text.isEmpty ? .eventLogPreviousMessage(filterOriginalMessageFlags(prev)) : nil) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, isAdmin: false), additionalContent: !prev.text.isEmpty || !message.text.isEmpty ? .eventLogPreviousMessage(filterOriginalMessageFlags(prev)) : nil) } case let .deleteMessage(message): switch self.id.contentIndex { @@ -421,7 +421,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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) case .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -445,7 +445,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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) } case .participantJoin, .participantLeave: var peers = SimpleDictionary() @@ -463,7 +463,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { 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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) case let .participantInvite(participant): var peers = SimpleDictionary() var author: Peer? @@ -479,7 +479,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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) case let .participantToggleBan(prev, new): var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -580,7 +580,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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) case let .participantToggleAdmin(prev, new): var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -659,7 +659,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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) case let .changeStickerPack(_, new): var peers = SimpleDictionary() var author: Peer? @@ -688,7 +688,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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) case let .togglePreHistoryHidden(value): var peers = SimpleDictionary() var author: Peer? @@ -718,7 +718,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: []) - return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) + return ChatMessageItem(presentationData: self.presentationData, account: account, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, isAdmin: false)) } } } @@ -744,6 +744,7 @@ struct ChatRecentActionsHistoryTransition { let updates: [ListViewUpdateItem] let canLoadEarlier: Bool let displayingResults: Bool + let isEmpty: Bool } func chatRecentActionsHistoryPreparedTransition(from fromEntries: [ChatRecentActionsEntry], to toEntries: [ChatRecentActionsEntry], type: ChannelAdminEventLogUpdateType, canLoadEarlier: Bool, displayingResults: Bool, account: Account, peer: Peer, controllerInteraction: ChatControllerInteraction) -> ChatRecentActionsHistoryTransition { @@ -753,5 +754,5 @@ func chatRecentActionsHistoryPreparedTransition(from fromEntries: [ChatRecentAct let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, peer: peer, controllerInteraction: controllerInteraction), directionHint: nil) } let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, peer: peer, controllerInteraction: controllerInteraction), directionHint: nil) } - return ChatRecentActionsHistoryTransition(filteredEntries: toEntries, type: type, deletions: deletions, insertions: insertions, updates: updates, canLoadEarlier: canLoadEarlier, displayingResults: displayingResults) + return ChatRecentActionsHistoryTransition(filteredEntries: toEntries, type: type, deletions: deletions, insertions: insertions, updates: updates, canLoadEarlier: canLoadEarlier, displayingResults: displayingResults, isEmpty: toEntries.isEmpty) } diff --git a/TelegramUI/ChatReportPeerTitlePanelNode.swift b/TelegramUI/ChatReportPeerTitlePanelNode.swift index f1cbdef9e8..0ee802dc97 100644 --- a/TelegramUI/ChatReportPeerTitlePanelNode.swift +++ b/TelegramUI/ChatReportPeerTitlePanelNode.swift @@ -6,17 +6,25 @@ import TelegramCore private enum ChatReportPeerTitleButton { case reportSpam + case addContact func title(strings: PresentationStrings) -> String { switch self { case .reportSpam: - return strings.ReportPeer_ReasonSpam + return strings.Conversation_Report + case .addContact: + return strings.Conversation_AddContact } } } private func peerButtons(_ state: ChatPresentationInterfaceState) -> [ChatReportPeerTitleButton] { - return [.reportSpam] + var buttons: [ChatReportPeerTitleButton] = [] + if let user = state.renderedPeer?.peer as? TelegramUser, let phone = user.phone, !phone.isEmpty, !state.isContact { + buttons.append(.addContact) + } + buttons.append(.reportSpam) + return buttons } final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode { @@ -115,6 +123,8 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode { switch button { case .reportSpam: self.interfaceInteraction?.reportPeer() + case .addContact: + self.interfaceInteraction?.presentPeerContact() } break } diff --git a/TelegramUI/ChatTextInputMediaRecordingButton.swift b/TelegramUI/ChatTextInputMediaRecordingButton.swift index 426571f6c4..5cf2979b3e 100644 --- a/TelegramUI/ChatTextInputMediaRecordingButton.swift +++ b/TelegramUI/ChatTextInputMediaRecordingButton.swift @@ -308,6 +308,10 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto self.icon = PresentationResourcesChat.chatInputPanelVideoActiveButtonImage(self.theme) self.innerIconView.image = PresentationResourcesChat.chatInputPanelVideoButtonImage(self.theme) } + + let inputPanelTheme = theme.chat.inputPanel + + self.pallete = TGModernConversationInputMicPallete(dark: theme.overallDarkAppearance, buttonColor: inputPanelTheme.actionControlFillColor, iconColor: inputPanelTheme.actionControlForegroundColor, backgroundColor: inputPanelTheme.panelBackgroundColor, borderColor: inputPanelTheme.panelStrokeColor, lock: inputPanelTheme.panelControlAccentColor, textColor: inputPanelTheme.primaryTextColor, secondaryTextColor: inputPanelTheme.secondaryTextColor, recording: inputPanelTheme.mediaRecordingDotColor) } deinit { diff --git a/TelegramUI/ChatTextInputPanelNode.swift b/TelegramUI/ChatTextInputPanelNode.swift index 9935cd025f..f7bdfc23cd 100644 --- a/TelegramUI/ChatTextInputPanelNode.swift +++ b/TelegramUI/ChatTextInputPanelNode.swift @@ -18,84 +18,96 @@ private let searchLayoutProgressImage = generateImage(CGSize(width: 22.0, height context.clear(CGRect(origin: CGPoint(x: (size.width - cutoutWidth) / 2.0, y: 0.0), size: CGSize(width: cutoutWidth, height: size.height / 2.0))) }) +private let accessoryButtonFont = Font.medium(14.0) + private final class AccessoryItemIconButton: HighlightableButton { private let item: ChatTextInputAccessoryItem + private var width: CGFloat init(item: ChatTextInputAccessoryItem, theme: PresentationTheme, strings: PresentationStrings) { self.item = item + let (image, text, alpha, insets) = AccessoryItemIconButton.imageAndInsets(item: item, theme: theme, strings: strings) + + self.width = AccessoryItemIconButton.calculateWidth(item: item, image: image, text: text, strings: strings) + super.init(frame: CGRect()) - switch item { - case .keyboard: - self.setImage(PresentationResourcesChat.chatInputTextFieldKeyboardImage(theme), for: []) - case let .stickers(enabled): - self.setImage(PresentationResourcesChat.chatInputTextFieldStickersImage(theme), for: []) - self.imageView?.alpha = enabled ? 1.0 : 0.5 - case .inputButtons: - self.setImage(PresentationResourcesChat.chatInputTextFieldInputButtonsImage(theme), for: []) - case .commands: - self.setImage(PresentationResourcesChat.chatInputTextFieldCommandsImage(theme), for: []) - case let .silentPost(value): - if value { - self.setImage(PresentationResourcesChat.chatInputTextFieldSilentPostOnImage(theme), for: []) - } else { - self.setImage(PresentationResourcesChat.chatInputTextFieldSilentPostOffImage(theme), for: []) - } - case let .messageAutoremoveTimeout(timeout): - if let timeout = timeout { - self.setImage(nil, for: []) - self.titleLabel?.font = Font.regular(12.0) - self.setTitleColor(theme.chat.inputPanel.inputControlColor, for: []) - self.setTitle(shortTimeIntervalString(strings: strings, value: timeout), for: []) - } else { - self.setImage(PresentationResourcesChat.chatInputTextFieldTimerImage(theme), for: []) - self.imageEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 1.0, right: 0.0) - } + if let text = text { + self.titleLabel?.font = accessoryButtonFont + self.setTitleColor(theme.chat.inputPanel.inputControlColor, for: []) + self.setTitle(text, for: []) } + + self.setImage(image, for: []) + self.imageEdgeInsets = insets + self.imageView?.alpha = alpha } func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { - switch self.item { - case .keyboard: - self.setImage(PresentationResourcesChat.chatInputTextFieldKeyboardImage(theme), for: []) - case .stickers: - self.setImage(PresentationResourcesChat.chatInputTextFieldStickersImage(theme), for: []) - case .inputButtons: - self.setImage(PresentationResourcesChat.chatInputTextFieldInputButtonsImage(theme), for: []) - case .commands: - self.setImage(PresentationResourcesChat.chatInputTextFieldCommandsImage(theme), for: []) - case let .silentPost(value): - if value { - self.setImage(PresentationResourcesChat.chatInputTextFieldSilentPostOnImage(theme), for: []) - } else { - self.setImage(PresentationResourcesChat.chatInputTextFieldSilentPostOffImage(theme), for: []) - } - case let .messageAutoremoveTimeout(timeout): - if let timeout = timeout { - self.setImage(nil, for: []) - self.titleLabel?.font = Font.regular(12.0) - self.setTitleColor(theme.chat.inputPanel.inputControlColor, for: []) - self.setTitle(shortTimeIntervalString(strings: strings, value: timeout), for: []) - } else { - self.setImage(PresentationResourcesChat.chatInputTextFieldTimerImage(theme), for: []) - self.imageEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 1.0, right: 0.0) - } + let (image, text, alpha, insets) = AccessoryItemIconButton.imageAndInsets(item: item, theme: theme, strings: strings) + + self.width = AccessoryItemIconButton.calculateWidth(item: item, image: image, text: text, strings: strings) + + if let text = text { + self.titleLabel?.font = accessoryButtonFont + self.setTitleColor(theme.chat.inputPanel.inputControlColor, for: []) + self.setTitle(text, for: []) + } else { + self.setTitle("", for: []) } + + self.setImage(image, for: []) + self.imageEdgeInsets = insets + self.imageView?.alpha = alpha } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - var buttonWidth: CGFloat { - switch self.item { - case .keyboard, .stickers, .inputButtons, .silentPost, .commands: - return (self.image(for: [])?.size.width ?? 0.0) + CGFloat(8.0) + static func imageAndInsets(item: ChatTextInputAccessoryItem, theme: PresentationTheme, strings: PresentationStrings) -> (UIImage?, String?, CGFloat, UIEdgeInsets) { + switch item { + case .keyboard: + return (PresentationResourcesChat.chatInputTextFieldKeyboardImage(theme), nil, 1.0, UIEdgeInsets()) + case let .stickers(enabled): + return (PresentationResourcesChat.chatInputTextFieldStickersImage(theme), nil, enabled ? 1.0 : 0.5, UIEdgeInsets()) + case .inputButtons: + return (PresentationResourcesChat.chatInputTextFieldInputButtonsImage(theme), nil, 1.0, UIEdgeInsets()) + case .commands: + return (PresentationResourcesChat.chatInputTextFieldCommandsImage(theme), nil, 1.0, UIEdgeInsets()) + case let .silentPost(value): + if value { + return (PresentationResourcesChat.chatInputTextFieldSilentPostOnImage(theme), nil, 1.0, UIEdgeInsets()) + } else { + return (PresentationResourcesChat.chatInputTextFieldSilentPostOffImage(theme), nil, 1.0, UIEdgeInsets()) + } case let .messageAutoremoveTimeout(timeout): - return 24.0 + if let timeout = timeout { + return (nil, shortTimeIntervalString(strings: strings, value: timeout), 1.0, UIEdgeInsets()) + } else { + return (PresentationResourcesChat.chatInputTextFieldTimerImage(theme), nil, 1.0, UIEdgeInsets(top: 0.0, left: 0.0, bottom: 1.0, right: 0.0)) + } } } + + static func calculateWidth(item: ChatTextInputAccessoryItem, image: UIImage?, text: String?, strings: PresentationStrings) -> CGFloat { + switch item { + case .keyboard, .stickers, .inputButtons, .silentPost, .commands: + return (image?.size.width ?? 0.0) + CGFloat(8.0) + case let .messageAutoremoveTimeout(timeout): + var imageWidth = (image?.size.width ?? 0.0) + CGFloat(8.0) + if let _ = timeout, let text = text { + imageWidth = ceil((text as NSString).size(withAttributes: [.font: accessoryButtonFont]).width) + 10.0 + } + + return max(imageWidth, 24.0) + } + } + + var buttonWidth: CGFloat { + return self.width + } } private func calclulateTextFieldMinHeight(_ presentationInterfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat { @@ -270,8 +282,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { super.init() - self.view.disablesInteractiveTransitionGestureRecognizer = true - self.attachmentButton.addTarget(self, action: #selector(self.attachmentButtonPressed), for: .touchUpInside) self.view.addSubview(self.attachmentButton) @@ -393,6 +403,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { textInputNode.tintColor = tintColor textInputNode.textView.scrollIndicatorInsets = UIEdgeInsets(top: 9.0, left: 0.0, bottom: 9.0, right: -13.0) self.textInputContainer.addSubnode(textInputNode) + textInputNode.view.disablesInteractiveTransitionGestureRecognizer = true self.textInputNode = textInputNode if let presentationInterfaceState = self.presentationInterfaceState { @@ -527,6 +538,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { let previousState = self.presentationInterfaceState self.presentationInterfaceState = interfaceState + let themeUpdated = previousState?.theme !== interfaceState.theme + var updateSendButtonIcon = false if (previousState?.interfaceState.editMessage != nil) != (interfaceState.interfaceState.editMessage != nil) { updateSendButtonIcon = true @@ -555,6 +568,10 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { case .dark: keyboardAppearance = .dark } + + if let textInputNode = self.textInputNode, textInputNode.keyboardAppearance != keyboardAppearance, textInputNode.isFirstResponder() { + textInputNode.resignFirstResponder() + } self.textInputNode?.keyboardAppearance = keyboardAppearance self.textInputContainer.backgroundColor = interfaceState.theme.chat.inputPanel.inputBackgroundColor @@ -604,7 +621,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } } - if let peer = interfaceState.renderedPeer?.peer, previousState?.renderedPeer?.peer == nil || !peer.isEqual(previousState!.renderedPeer!.peer!) || previousState?.interfaceState.silentPosting != interfaceState.interfaceState.silentPosting { + if let peer = interfaceState.renderedPeer?.peer, previousState?.renderedPeer?.peer == nil || !peer.isEqual(previousState!.renderedPeer!.peer!) || previousState?.interfaceState.silentPosting != interfaceState.interfaceState.silentPosting || themeUpdated { let placeholder: String if let channel = peer as? TelegramChannel, case .broadcast = channel.info { if interfaceState.interfaceState.silentPosting { @@ -615,7 +632,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } else { placeholder = interfaceState.strings.Conversation_InputTextPlaceholder } - if self.currentPlaceholder != placeholder { + if self.currentPlaceholder != placeholder || themeUpdated { self.currentPlaceholder = placeholder let placeholderLayout = TextNode.asyncLayout(self.textPlaceholderNode) let baseFontSize = max(17.0, interfaceState.fontSize.baseDisplaySize) @@ -1421,7 +1438,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { func frameForInputActionButton() -> CGRect? { if !self.actionButtons.micButton.alpha.isZero { - return self.actionButtons.frame.insetBy(dx: 0.0, dy: 6.0) + return self.actionButtons.frame.insetBy(dx: 0.0, dy: 6.0).offsetBy(dx: 2.0, dy: 0.0) } return nil } diff --git a/TelegramUI/ChatTitleView.swift b/TelegramUI/ChatTitleView.swift index f2c23985f9..7fbc6e8d1d 100644 --- a/TelegramUI/ChatTitleView.swift +++ b/TelegramUI/ChatTitleView.swift @@ -46,6 +46,13 @@ private final class ChatTitleNetworkStatusNode: ASDisplayNode { self.addSubnode(self.activityIndicator) } + func updateTheme(theme: PresentationTheme) { + self.theme = theme + + self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor) + self.activityIndicator.type = .custom(self.theme.rootController.navigationBar.primaryTextColor, 22.0, 1.5) + } + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { let indicatorSize = self.activityIndicator.bounds.size let indicatorPadding = indicatorSize.width + 6.0 @@ -469,6 +476,20 @@ final class ChatTitleView: UIView, NavigationBarTitleView { } } + func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + self.theme = theme + self.strings = strings + + self.networkStatusNode?.updateTheme(theme: theme) + let titleContent = self.titleContent + self.titleContent = titleContent + self.updateStatus() + + if let (size, clearBounds) = self.validLayout { + self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) + } + } + func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) { self.validLayout = (size, clearBounds) diff --git a/TelegramUI/ChatUnreadItem.swift b/TelegramUI/ChatUnreadItem.swift index 5c8c035476..a16dbed3ad 100644 --- a/TelegramUI/ChatUnreadItem.swift +++ b/TelegramUI/ChatUnreadItem.swift @@ -9,11 +9,11 @@ private let titleFont = UIFont.systemFont(ofSize: 13.0) class ChatUnreadItem: ListViewItem { let index: MessageIndex - let theme: PresentationTheme + let theme: ChatPresentationThemeData let strings: PresentationStrings let header: ChatMessageDateHeader - init(index: MessageIndex, theme: PresentationTheme, strings: PresentationStrings) { + init(index: MessageIndex, theme: ChatPresentationThemeData, strings: PresentationStrings) { self.index = index self.theme = theme self.strings = strings @@ -57,7 +57,7 @@ class ChatUnreadItemNode: ListViewItemNode { let backgroundNode: ASImageNode let labelNode: TextNode - private var theme: PresentationTheme? + private var theme: ChatPresentationThemeData? private let layoutConstants = ChatMessageItemLayoutConstants() @@ -108,11 +108,11 @@ class ChatUnreadItemNode: ListViewItemNode { return { item, params, dateAtBottom in var updatedBackgroundImage: UIImage? - if currentTheme !== item.theme { - updatedBackgroundImage = PresentationResourcesChat.chatUnreadBarBackgroundImage(item.theme) + if currentTheme != item.theme { + updatedBackgroundImage = PresentationResourcesChat.chatUnreadBarBackgroundImage(item.theme.theme) } - let (size, apply) = labelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Conversation_UnreadMessages, font: titleFont, textColor: item.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 (size, apply) = labelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Conversation_UnreadMessages, font: titleFont, textColor: item.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) diff --git a/TelegramUI/ComposeController.swift b/TelegramUI/ComposeController.swift index 13e0b5e897..9e00ec3230 100644 --- a/TelegramUI/ComposeController.swift +++ b/TelegramUI/ComposeController.swift @@ -148,7 +148,7 @@ public class ComposeController: ViewController { self.contactsNode.openCreateNewChannel = { [weak self] in if let strongSelf = self { - (strongSelf.navigationController as? NavigationController)?.pushViewController(createChannelController(account: strongSelf.account)) + (strongSelf.navigationController as? NavigationController)?.pushViewController(legacyChannelIntroController(account: strongSelf.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings)) } } diff --git a/TelegramUI/ContactListNameIndexHeader.swift b/TelegramUI/ContactListNameIndexHeader.swift index 13f20c0fd7..c382dc6417 100644 --- a/TelegramUI/ContactListNameIndexHeader.swift +++ b/TelegramUI/ContactListNameIndexHeader.swift @@ -44,6 +44,11 @@ final class ContactListNameIndexHeaderNode: ListViewItemHeaderNode { self.addSubnode(self.sectionHeaderNode) } + func updateTheme(theme: PresentationTheme) { + self.theme = theme + self.sectionHeaderNode.updateTheme(theme: theme) + } + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { self.sectionHeaderNode.frame = CGRect(origin: CGPoint(), size: size) self.sectionHeaderNode.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset) diff --git a/TelegramUI/ContactListNode.swift b/TelegramUI/ContactListNode.swift index 373e58a9f6..2589d88138 100644 --- a/TelegramUI/ContactListNode.swift +++ b/TelegramUI/ContactListNode.swift @@ -118,7 +118,7 @@ enum ContactListPeer: Equatable { private enum ContactListNodeEntry: Comparable, Identifiable { case search(PresentationTheme, PresentationStrings) case option(Int, ContactListAdditionalOption, PresentationTheme, PresentationStrings) - case peer(Int, ContactListPeer, PeerPresence?, ListViewItemHeader?, ContactsPeerItemSelection, PresentationTheme, PresentationStrings) + case peer(Int, ContactListPeer, PeerPresence?, ListViewItemHeader?, ContactsPeerItemSelection, PresentationTheme, PresentationStrings, PresentationTimeFormat) var stableId: ContactListNodeEntryId { switch self { @@ -126,7 +126,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { return .search case let .option(index, _, _, _): return .option(index: index) - case let .peer(_, peer, _, _, _, _, _): + case let .peer(_, peer, _, _, _, _, _, _): switch peer { case let .peer(peer, _): return .peerId(peer.id.toInt64()) @@ -144,7 +144,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { }) case let .option(_, option, theme, _): return ContactListActionItem(theme: theme, title: option.title, icon: option.icon, action: option.action) - case let .peer(_, peer, presence, header, selection, theme, strings): + case let .peer(_, peer, presence, header, selection, theme, strings, timeFormat): let status: ContactsPeerItemStatus let itemPeer: ContactsPeerItemPeer switch peer { @@ -152,7 +152,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { if isGlobal, let _ = peer.addressName { status = .addressName("") } else if let presence = presence { - status = .presence(presence) + status = .presence(presence, timeFormat) } else { status = .none } @@ -181,9 +181,9 @@ private enum ContactListNodeEntry: Comparable, Identifiable { } else { return false } - case let .peer(lhsIndex, lhsPeer, lhsPresence, lhsHeader, lhsSelection, lhsTheme, lhsStrings): + case let .peer(lhsIndex, lhsPeer, lhsPresence, lhsHeader, lhsSelection, lhsTheme, lhsStrings, lhsTimeFormat): switch rhs { - case let .peer(rhsIndex, rhsPeer, rhsPresence, rhsHeader, rhsSelection, rhsTheme, rhsStrings): + case let .peer(rhsIndex, rhsPeer, rhsPresence, rhsHeader, rhsSelection, rhsTheme, rhsStrings, rhsTimeFormat): if lhsIndex != rhsIndex { return false } @@ -209,6 +209,9 @@ private enum ContactListNodeEntry: Comparable, Identifiable { if lhsStrings !== rhsStrings { return false } + if lhsTimeFormat != rhsTimeFormat { + return false + } return true default: return false @@ -229,11 +232,11 @@ private enum ContactListNodeEntry: Comparable, Identifiable { case .peer: return true } - case let .peer(lhsIndex, _, _, _, _, _, _): + case let .peer(lhsIndex, _, _, _, _, _, _, _): switch rhs { case .search, .option: return false - case let .peer(rhsIndex, _, _, _, _, _, _): + case let .peer(rhsIndex, _, _, _, _, _, _, _): return lhsIndex < rhsIndex } } @@ -276,7 +279,7 @@ private extension PeerIndexNameRepresentation { } } -private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer], presences: [PeerId: PeerPresence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings) -> [ContactListNodeEntry] { +private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer], presences: [PeerId: PeerPresence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat) -> [ContactListNodeEntry] { var entries: [ContactListNodeEntry] = [] var orderedPeers: [ContactListPeer] @@ -406,7 +409,7 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer] if case let .peer(peer, _) = orderedPeers[i] { presence = presences[peer.id] } - entries.append(.peer(i, orderedPeers[i], presence, header, selection, theme, strings)) + entries.append(.peer(i, orderedPeers[i], presence, header, selection, theme, strings, timeFormat)) } return entries } @@ -532,7 +535,7 @@ final class ContactListNode: ASDisplayNode { private var presentationData: PresentationData private var presentationDataDisposable: Disposable? - private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings)> + private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationTimeFormat)> init(account: Account, presentation: ContactListPresentation, filter: ContactListFilter = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil) { self.account = account @@ -543,7 +546,7 @@ final class ContactListNode: ASDisplayNode { self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings)) + self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings, self.presentationData.timeFormat)) super.init() @@ -636,7 +639,7 @@ final class ContactListNode: ASDisplayNode { peers.append(.deviceContact(stableId, contact)) } - let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: [:], presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1) + let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: [:], presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, timeFormat: themeAndStrings.2) let previous = previousEntries.swap(entries) return .single(preparedContactListNodeTransition(account: account, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, animated: false)) } @@ -652,7 +655,7 @@ final class ContactListNode: ASDisplayNode { transition = (combineLatest(self.contactPeersViewPromise.get(), selectionStateSignal, themeAndStringsPromise.get()) |> mapToQueue { view, selectionState, themeAndStrings -> Signal in let signal = deferred { () -> Signal in - let entries = contactListNodeEntries(accountPeer: view.accountPeer, peers: view.peers.map({ ContactListPeer.peer(peer: $0, isGlobal: false) }), presences: view.peerPresences, presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1) + let entries = contactListNodeEntries(accountPeer: view.accountPeer, peers: view.peers.map({ ContactListPeer.peer(peer: $0, isGlobal: false) }), presences: view.peerPresences, presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, timeFormat: themeAndStrings.2) let previous = previousEntries.swap(entries) let animated: Bool if let previous = previous { @@ -676,19 +679,33 @@ final class ContactListNode: ASDisplayNode { })) self.presentationDataDisposable = (account.telegramApplicationContext.presentationData - |> deliverOnMainQueue).start(next: { [weak self] presentationData in - if let strongSelf = self { - let previousTheme = strongSelf.presentationData.theme - let previousStrings = strongSelf.presentationData.strings + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + let previousTheme = strongSelf.presentationData.theme + let previousStrings = strongSelf.presentationData.strings + + strongSelf.presentationData = presentationData + + if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { + strongSelf.backgroundColor = presentationData.theme.chatList.backgroundColor + strongSelf.themeAndStringsPromise.set(.single((presentationData.theme, presentationData.strings, presentationData.timeFormat))) - strongSelf.presentationData = presentationData + strongSelf.listNode.forEachAccessoryItemNode({ accessoryItemNode in + if let accessoryItemNode = accessoryItemNode as? ContactsSectionHeaderAccessoryItemNode { + accessoryItemNode.updateTheme(theme: presentationData.theme) + } + }) - if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { - strongSelf.backgroundColor = presentationData.theme.chatList.backgroundColor - strongSelf.themeAndStringsPromise.set(.single((presentationData.theme, presentationData.strings))) - } + strongSelf.listNode.forEachItemHeaderNode({ itemHeaderNode in + if let itemHeaderNode = itemHeaderNode as? ContactListNameIndexHeaderNode { + itemHeaderNode.updateTheme(theme: presentationData.theme) + } else if let itemHeaderNode = itemHeaderNode as? ChatListSearchItemHeaderNode { + itemHeaderNode.updateTheme(theme: presentationData.theme) + } + }) } - }) + } + }) self.listNode.didEndScrolling = { [weak self] in guard let strongSelf = self else { diff --git a/TelegramUI/ContactMultiselectionController.swift b/TelegramUI/ContactMultiselectionController.swift index 0f54844652..ac4dffce6d 100644 --- a/TelegramUI/ContactMultiselectionController.swift +++ b/TelegramUI/ContactMultiselectionController.swift @@ -8,6 +8,7 @@ import TelegramCore enum ContactMultiselectionControllerMode { case groupCreation case peerSelection + case channelCreation } class ContactMultiselectionController: ViewController { @@ -36,6 +37,19 @@ class ContactMultiselectionController: ViewController { private var rightNavigationButton: UIBarButtonItem? + var displayProgress: Bool = false { + didSet { + if self.displayProgress != oldValue { + if self.displayProgress { + let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.presentationData.theme.rootController.navigationBar.accentTextColor)) + self.navigationItem.rightBarButtonItem = item + } else { + self.navigationItem.rightBarButtonItem = self.rightNavigationButton + } + } + } + } + private var didPlayPresentationAnimation = false private var presentationData: PresentationData @@ -117,6 +131,12 @@ class ContactMultiselectionController: ViewController { self.rightNavigationButton = rightNavigationButton self.navigationItem.rightBarButtonItem = self.rightNavigationButton rightNavigationButton.isEnabled = false + case .channelCreation: + self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.GroupInfo_AddParticipantTitle, counter: "") + let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed)) + self.rightNavigationButton = rightNavigationButton + self.navigationItem.rightBarButtonItem = self.rightNavigationButton + rightNavigationButton.isEnabled = true case .peerSelection: self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.PrivacyLastSeenSettings_EmpryUsersPlaceholder, counter: "") let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed)) @@ -172,12 +192,18 @@ class ContactMultiselectionController: ViewController { } if let updatedCount = updatedCount { - strongSelf.rightNavigationButton?.isEnabled = updatedCount != 0 + switch strongSelf.mode { + case .groupCreation, .peerSelection: + strongSelf.rightNavigationButton?.isEnabled = updatedCount != 0 + case .channelCreation: + break + } + switch strongSelf.mode { case .groupCreation: let maxCount: Int32 = strongSelf.limitsConfiguration?.maxSupergroupMemberCount ?? 5000 strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.Compose_NewGroup, counter: "\(updatedCount)/\(maxCount)") - case .peerSelection: + case .peerSelection, .channelCreation: break } } @@ -225,12 +251,17 @@ class ContactMultiselectionController: ViewController { } if let updatedCount = updatedCount { - strongSelf.rightNavigationButton?.isEnabled = updatedCount != 0 + switch strongSelf.mode { + case .groupCreation, .peerSelection: + strongSelf.rightNavigationButton?.isEnabled = updatedCount != 0 + case .channelCreation: + break + } switch strongSelf.mode { case .groupCreation: let maxCount: Int32 = strongSelf.limitsConfiguration?.maxSupergroupMemberCount ?? 5000 strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.Compose_NewGroup, counter: "\(updatedCount)/\(maxCount)") - case .peerSelection: + case .peerSelection, .channelCreation: break } } diff --git a/TelegramUI/ContactSelectionController.swift b/TelegramUI/ContactSelectionController.swift index 14f43a1ae5..7f942a3b17 100644 --- a/TelegramUI/ContactSelectionController.swift +++ b/TelegramUI/ContactSelectionController.swift @@ -7,11 +7,22 @@ import TelegramCore class ContactSelectionController: ViewController { private let account: Account + private let autoDismiss: Bool private var contactsNode: ContactSelectionControllerNode { return self.displayNode as! ContactSelectionControllerNode } + var displayProgress: Bool = false { + didSet { + if self.displayProgress != oldValue { + if self.isNodeLoaded { + self.contactsNode.displayProgress = self.displayProgress + } + } + } + } + private let index: PeerNameIndex = .lastNameFirst private let titleProducer: (PresentationStrings) -> String private let options: [ContactListAdditionalOption] @@ -28,6 +39,7 @@ class ContactSelectionController: ViewController { } private let confirmation: (ContactListPeer) -> Signal + var dismissed: (() -> Void)? private let createActionDisposable = MetaDisposable() private let confirmationDisposable = MetaDisposable() @@ -47,8 +59,9 @@ class ContactSelectionController: ViewController { } } - init(account: Account, title: @escaping (PresentationStrings) -> String, options: [ContactListAdditionalOption] = [], displayDeviceContacts: Bool = false, confirmation: @escaping (ContactListPeer) -> Signal = { _ in .single(true) }) { + init(account: Account, autoDismiss: Bool = true, title: @escaping (PresentationStrings) -> String, options: [ContactListAdditionalOption] = [], displayDeviceContacts: Bool = false, confirmation: @escaping (ContactListPeer) -> Signal = { _ in .single(true) }) { self.account = account + self.autoDismiss = autoDismiss self.titleProducer = title self.options = options self.displayDeviceContacts = displayDeviceContacts @@ -105,14 +118,7 @@ class ContactSelectionController: ViewController { @objc func cancelPressed() { self._result.set(.single(nil)) - if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments { - switch presentationArguments.presentationAnimation { - case .modalSheet: - self.contactsNode.animateOut() - case .none: - break - } - } + self.dismiss() } override func loadDisplayNode() { @@ -196,6 +202,7 @@ class ContactSelectionController: ViewController { private func deactivateSearch() { if !self.displayNavigationBar { + self.contactsNode.prepareDeactivateSearch() self.setDisplayNavigationBar(true, transition: .animated(duration: 0.5, curve: .spring)) self.contactsNode.deactivateSearch() } @@ -207,7 +214,9 @@ class ContactSelectionController: ViewController { if let strongSelf = self { if value { strongSelf._result.set(.single(peer)) - strongSelf.dismiss() + if strongSelf.autoDismiss { + strongSelf.dismiss() + } } } })) @@ -217,8 +226,10 @@ class ContactSelectionController: ViewController { if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments { switch presentationArguments.presentationAnimation { case .modalSheet: + self.dismissed?() self.contactsNode.animateOut(completion: completion) case .none: + self.dismissed?() completion?() } } diff --git a/TelegramUI/ContactSelectionControllerNode.swift b/TelegramUI/ContactSelectionControllerNode.swift index d1944c264b..84256d665d 100644 --- a/TelegramUI/ContactSelectionControllerNode.swift +++ b/TelegramUI/ContactSelectionControllerNode.swift @@ -6,9 +6,19 @@ import TelegramCore import SwiftSignalKit final class ContactSelectionControllerNode: ASDisplayNode { + var displayProgress: Bool = false { + didSet { + if self.displayProgress != oldValue { + self.dimNode.alpha = self.displayProgress ? 1.0 : 0.0 + self.dimNode.isUserInteractionEnabled = self.displayProgress + } + } + } + let displayDeviceContacts: Bool let contactListNode: ContactListNode + private let dimNode: ASDisplayNode private let account: Account private var searchDisplayController: SearchDisplayController? @@ -30,6 +40,8 @@ final class ContactSelectionControllerNode: ASDisplayNode { self.contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: true, options: options)) + self.dimNode = ASDisplayNode() + self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } super.init() @@ -43,15 +55,20 @@ final class ContactSelectionControllerNode: ASDisplayNode { self.addSubnode(self.contactListNode) self.presentationDataDisposable = (account.telegramApplicationContext.presentationData - |> deliverOnMainQueue).start(next: { [weak self] presentationData in - if let strongSelf = self { - let previousTheme = strongSelf.presentationData.theme - strongSelf.presentationData = presentationData - if previousTheme !== presentationData.theme { - strongSelf.updateTheme() - } + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + let previousTheme = strongSelf.presentationData.theme + strongSelf.presentationData = presentationData + if previousTheme !== presentationData.theme { + strongSelf.updateTheme() } - }) + } + }) + + self.dimNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor.withAlphaComponent(0.5) + self.dimNode.alpha = 0.0 + self.dimNode.isUserInteractionEnabled = false + self.addSubnode(self.dimNode) } deinit { @@ -61,11 +78,14 @@ final class ContactSelectionControllerNode: ASDisplayNode { private func updateTheme() { self.backgroundColor = self.presentationData.theme.chatList.backgroundColor self.searchDisplayController?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) + self.dimNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor.withAlphaComponent(0.5) } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { self.containerLayout = (layout, navigationBarHeight) + transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + var insets = layout.insets(options: [.input]) insets.top += navigationBarHeight @@ -118,11 +138,15 @@ final class ContactSelectionControllerNode: ASDisplayNode { self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { subnode in - self.insertSubnode(subnode, belowSubnode: navigationBar) + self.insertSubnode(subnode, belowSubnode: self.dimNode) }, placeholder: placeholderNode) } } + func prepareDeactivateSearch() { + self.searchDisplayController?.isDeactivating = true + } + func deactivateSearch() { if let searchDisplayController = self.searchDisplayController { var maybePlaceholderNode: SearchBarPlaceholderNode? diff --git a/TelegramUI/ContactsController.swift b/TelegramUI/ContactsController.swift index 072d4896f0..a0f0c490f4 100644 --- a/TelegramUI/ContactsController.swift +++ b/TelegramUI/ContactsController.swift @@ -181,7 +181,19 @@ public class ContactsController: ViewController { } if let value = value, value { - (strongSelf.navigationController as? NavigationController)?.pushViewController(createContactController(account: strongSelf.account)) + let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: "")]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: []) + strongSelf.present(deviceContactInfoController(account: strongSelf.account, subject: .create(peer: nil, contactData: contactData, completion: { peer, stableId, contactData in + guard let strongSelf = self else { + return + } + if let peer = peer { + if let infoController = peerInfoController(account: strongSelf.account, peer: peer) { + (strongSelf.navigationController as? NavigationController)?.pushViewController(infoController) + } + } else { + (strongSelf.navigationController as? NavigationController)?.pushViewController(deviceContactInfoController(account: strongSelf.account, subject: .vcard(nil, stableId, contactData))) + } + })), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } else { let presentationData = strongSelf.presentationData strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { diff --git a/TelegramUI/ContactsPeerItem.swift b/TelegramUI/ContactsPeerItem.swift index 3ef574e218..3f60ed8ad1 100644 --- a/TelegramUI/ContactsPeerItem.swift +++ b/TelegramUI/ContactsPeerItem.swift @@ -16,7 +16,7 @@ private let selectableImage = UIImage(bundleImageName: "Contact List/SelectionUn enum ContactsPeerItemStatus { case none - case presence(PeerPresence) + case presence(PeerPresence, PresentationTimeFormat) case addressName(String) case custom(String) } @@ -463,11 +463,11 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode { switch item.status { case .none: break - case let .presence(presence): + case let .presence(presence, timeFormat): if let presence = presence as? TelegramUserPresence { userPresence = presence let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - let (string, activity) = stringAndActivityForUserPresence(strings: item.strings, timeFormat: .regular, presence: presence, relativeTo: Int32(timestamp)) + let (string, activity) = stringAndActivityForUserPresence(strings: item.strings, timeFormat: timeFormat, presence: presence, relativeTo: Int32(timestamp)) statusAttributedString = NSAttributedString(string: string, font: statusFont, textColor: activity ? item.theme.list.itemAccentColor : item.theme.list.itemSecondaryTextColor) } case let .addressName(suffix): @@ -791,7 +791,7 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode { } } - override func revealOptionSelected(_ option: ItemListRevealOption) { + override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { if let item = self.item { switch item.peer { case let .peer(peer, _): diff --git a/TelegramUI/ContactsSearchContainerNode.swift b/TelegramUI/ContactsSearchContainerNode.swift index 2f4dee82f1..35343d9012 100644 --- a/TelegramUI/ContactsSearchContainerNode.swift +++ b/TelegramUI/ContactsSearchContainerNode.swift @@ -43,13 +43,21 @@ private struct ContactListSearchEntry: Identifiable, Comparable { func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, openPeer: @escaping (ContactListPeer) -> Void) -> ListViewItem { let header: ListViewItemHeader + let status: ContactsPeerItemStatus switch self.group { case .contacts: header = ChatListSearchItemHeader(type: .contacts, theme: theme, strings: strings, actionTitle: nil, action: nil) + status = .none case .global: header = ChatListSearchItemHeader(type: .globalPeers, theme: theme, strings: strings, actionTitle: nil, action: nil) + if case let .peer(peer, _) = self.peer, let addressName = peer.addressName { + status = .addressName("") + } else { + status = .none + } case .deviceContacts: header = ChatListSearchItemHeader(type: .deviceContacts, theme: theme, strings: strings, actionTitle: nil, action: nil) + status = .none } let peer = self.peer let peerItem: ContactsPeerItemPeer @@ -59,7 +67,7 @@ private struct ContactListSearchEntry: Identifiable, Comparable { case let .deviceContact(stableId, contact): peerItem = .deviceContact(stableId: stableId, contact: contact) } - return ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .peer, peer: peerItem, status: .none, enabled: self.enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in + return ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .peer, peer: peerItem, status: status, enabled: self.enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in openPeer(peer) }) } @@ -185,19 +193,26 @@ final class ContactsSearchContainerNode: SearchDisplayControllerContentNode { } index += 1 } - /*for peer in remotePeers.1 { - if !existingPeerIds.contains(peer.peer.id) { - existingPeerIds.insert(peer.peer.id) - var enabled = true - if onlyWriteable { - enabled = canSendMessagesToPeer(peer.peer) - } - - entries.append(ContactListSearchEntry(index: index, peer: peer.peer, enabled: enabled)) - index += 1 - } - }*/ if let remotePeers = remotePeers { + for peer in remotePeers.0 { + if !(peer.peer is TelegramUser) { + continue + } + if !existingPeerIds.contains(peer.peer.id) { + existingPeerIds.insert(peer.peer.id) + + var enabled = true + if onlyWriteable { + enabled = canSendMessagesToPeer(peer.peer) + } + + entries.append(ContactListSearchEntry(index: index, peer: .peer(peer: peer.peer, isGlobal: true), group: .global, enabled: enabled)) + if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { + existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) + } + index += 1 + } + } for peer in remotePeers.1 { if !existingPeerIds.contains(peer.peer.id) { existingPeerIds.insert(peer.peer.id) diff --git a/TelegramUI/ContactsSectionHeaderAccessoryItem.swift b/TelegramUI/ContactsSectionHeaderAccessoryItem.swift index b58111bee2..4dd3a00f98 100644 --- a/TelegramUI/ContactsSectionHeaderAccessoryItem.swift +++ b/TelegramUI/ContactsSectionHeaderAccessoryItem.swift @@ -46,7 +46,7 @@ final class ContactsSectionHeaderAccessoryItem: ListViewAccessoryItem { } } -private final class ContactsSectionHeaderAccessoryItemNode: ListViewAccessoryItemNode { +final class ContactsSectionHeaderAccessoryItemNode: ListViewAccessoryItemNode { private let sectionHeader: ContactsSectionHeader private let sectionHeaderNode: ListSectionHeaderNode private var theme: PresentationTheme @@ -68,6 +68,11 @@ private final class ContactsSectionHeaderAccessoryItemNode: ListViewAccessoryIte self.addSubnode(self.sectionHeaderNode) } + func updateTheme(theme: PresentationTheme) { + self.theme = theme + self.sectionHeaderNode.updateTheme(theme: theme) + } + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { self.sectionHeaderNode.frame = CGRect(origin: CGPoint(), size: size) self.sectionHeaderNode.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset) diff --git a/TelegramUI/CreateChannelController.swift b/TelegramUI/CreateChannelController.swift index 3ef37bef77..35f75e6e21 100644 --- a/TelegramUI/CreateChannelController.swift +++ b/TelegramUI/CreateChannelController.swift @@ -239,7 +239,7 @@ public func createChannelController(account: Account) -> ViewController { return $0.avatar } if let _ = updatingAvatar { - let _ = updatePeerPhoto(account: account, peerId: peerId, photo: uploadedAvatar.get()).start() + let _ = updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedAvatar.get()).start() } let controller = channelVisibilityController(account: account, peerId: peerId, mode: .initialSetup) @@ -272,7 +272,7 @@ public func createChannelController(account: Account) -> ViewController { let resource = LocalFileMediaResource(fileId: arc4random64()) account.postbox.mediaBox.storeResourceData(resource.id, data: data) let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource) - uploadedAvatar.set(uploadedPeerPhoto(account: account, resource: resource)) + uploadedAvatar.set(uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) updateState { current in var current = current current.avatar = .image(representation) diff --git a/TelegramUI/CreateContactController.swift b/TelegramUI/CreateContactController.swift deleted file mode 100644 index 2dd50de38e..0000000000 --- a/TelegramUI/CreateContactController.swift +++ /dev/null @@ -1,370 +0,0 @@ -import Foundation -import Display -import SwiftSignalKit -import Postbox -import TelegramCore - -private final class CreateContactControllerArguments { - let account: Account - let updateEditingName: (ItemListAvatarAndNameInfoItemName) -> Void - let updatePhone: (Int64, String) -> Void - let openLabelSelection: (Int64, String) -> Void - let addPhone: () -> Void - let deletePhone: (Int64) -> Void - - init(account: Account, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updatePhone: @escaping (Int64, String) -> Void, openLabelSelection: @escaping (Int64, String) -> Void, addPhone: @escaping () -> Void, deletePhone: @escaping (Int64) -> Void) { - self.account = account - self.updateEditingName = updateEditingName - self.updatePhone = updatePhone - self.openLabelSelection = openLabelSelection - self.addPhone = addPhone - self.deletePhone = deletePhone - } -} - -private enum CreateContactSection: ItemListSectionId { - case info - case phones -} - -private enum CreateContactEntryId: Hashable { - case index(Int) - case phone(Int64) - - static func ==(lhs: CreateContactEntryId, rhs: CreateContactEntryId) -> Bool { - switch lhs { - case let .index(value): - if case .index(value) = rhs { - return true - } else { - return false - } - case let .phone(value): - if case .phone(value) = rhs { - return true - } else { - return false - } - } - } - - var hashValue: Int { - switch self { - case let .index(value): - return value.hashValue - case let .phone(value): - return value.hashValue - } - } -} - -private enum CreateContactEntry: ItemListNodeEntry { - case info(PresentationTheme, PresentationStrings, state: ItemListAvatarAndNameInfoItemState) - case phoneNumber(PresentationTheme, PresentationStrings, Int64, Int, String, String, Bool) - case addPhone(PresentationTheme, String) - - var section: ItemListSectionId { - switch self { - case .info: - return CreateContactSection.info.rawValue - case .phoneNumber, .addPhone: - return CreateContactSection.phones.rawValue - } - } - - var stableId: CreateContactEntryId { - switch self { - case .info: - return .index(0) - case let .phoneNumber(_, _, id, _, _, _, _): - return .phone(id) - case .addPhone: - return .index(1000) - } - } - - static func ==(lhs: CreateContactEntry, rhs: CreateContactEntry) -> Bool { - switch lhs { - case let .info(lhsTheme, lhsStrings, lhsState): - if case let .info(rhsTheme, rhsStrings, rhsState) = rhs { - if lhsTheme !== rhsTheme { - return false - } - if lhsStrings !== rhsStrings { - return false - } - if lhsState != rhsState { - return false - } - return true - } else { - return false - } - case let .phoneNumber(lhsTheme, lhsStrings, lhsId, lhsIndex, lhsLabel, lhsValue, lhsHasActiveRevealControls): - if case let .phoneNumber(rhsTheme, rhsStrings, rhsId, rhsIndex, rhsLabel, rhsValue, rhsHasActiveRevealControls) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsId == rhsId, lhsIndex == rhsIndex, lhsLabel == rhsLabel, lhsValue == rhsValue, lhsHasActiveRevealControls == rhsHasActiveRevealControls { - return true - } else { - return false - } - case let .addPhone(lhsTheme, lhsTitle): - if case let .addPhone(rhsTheme, rhsTitle) = rhs { - if lhsTheme !== rhsTheme { - return false - } - if lhsTitle != rhsTitle { - return false - } - return true - } else { - return false - } - } - } - - private var sortIndex: Int { - switch self { - case .info: - return 0 - case let .phoneNumber(_, _, _, index, _, _, _): - return 2 + index - case .addPhone: - return 1000 - } - } - - static func <(lhs: CreateContactEntry, rhs: CreateContactEntry) -> Bool { - return lhs.sortIndex < rhs.sortIndex - } - - func item(_ arguments: CreateContactControllerArguments) -> ListViewItem { - switch self { - case let .info(theme, strings, state): - var firstName = "" - var lastName = "" - if let editingName = state.editingName { - switch editingName { - case let .personName(first, last): - firstName = first - lastName = last - default: - break - } - } - return ItemListAvatarAndNameInfoItem(account: arguments.account, theme: theme, strings: strings, mode: .generic, peer: TelegramUser(id: PeerId(namespace: -1, id: 0), accessHash: nil, firstName: firstName, lastName: lastName, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []), presence: nil, cachedData: nil, state: state, sectionId: self.section, style: .plain, editingNameUpdated: { editingName in - arguments.updateEditingName(editingName) - }, avatarTapped: { - }, context: nil, call: nil) - case let .phoneNumber(theme, strings, id, _, label, value, hasActiveRevealControls): - return UserInfoEditingPhoneItem(theme: theme, strings: strings, id: id, label: label, value: value, editing: UserInfoEditingPhoneItemEditing(editable: true, hasActiveRevealControls: hasActiveRevealControls), sectionId: self.section, setPhoneIdWithRevealedOptions: { _, _ in - }, updated: { value in - arguments.updatePhone(id, value) - }, selectLabel: { - arguments.openLabelSelection(id, label) - }, delete: { - arguments.deletePhone(id) - }, tag: nil) - case let .addPhone(theme, title): - return UserInfoEditingPhoneActionItem(theme: theme, title: title, sectionId: self.section, action: { - arguments.addPhone() - }) - } - } -} - -private struct CreateContactPhoneNumber: Equatable { - let id: Int64 - let label: String - let value: String - - static func ==(lhs: CreateContactPhoneNumber, rhs: CreateContactPhoneNumber) -> Bool { - if lhs.id != rhs.id { - return false - } - if lhs.label != rhs.label { - return false - } - if lhs.value != rhs.value { - return false - } - return true - } - - func withUpdatedLabel(_ label: String) -> CreateContactPhoneNumber { - return CreateContactPhoneNumber(id: self.id, label: label, value: self.value) - } - - func withUpdatedValue(_ value: String) -> CreateContactPhoneNumber { - return CreateContactPhoneNumber(id: self.id, label: self.label, value: value) - } -} - -private struct CreateContactState: Equatable { - var editingName: ItemListAvatarAndNameInfoItemName - var phoneNumbers: [CreateContactPhoneNumber] - var revealedPhoneId: Int64? - - init(editingName: ItemListAvatarAndNameInfoItemName = .personName(firstName: "", lastName: ""), phoneNumbers: [CreateContactPhoneNumber] = [], revealedPhoneId: Int64? = nil) { - self.editingName = editingName - self.phoneNumbers = phoneNumbers - self.revealedPhoneId = revealedPhoneId - } - - static func ==(lhs: CreateContactState, rhs: CreateContactState) -> Bool { - if lhs.editingName != rhs.editingName { - return false - } - if lhs.phoneNumbers != rhs.phoneNumbers { - return false - } - return true - } -} - -private func createContactEntries(account: Account, presentationData: PresentationData, state: CreateContactState) -> [CreateContactEntry] { - var entries: [CreateContactEntry] = [] - - entries.append(.info(presentationData.theme, presentationData.strings, state: ItemListAvatarAndNameInfoItemState(editingName: state.editingName, updatingName: nil))) - - var index = 0 - for phoneNumber in state.phoneNumbers { - entries.append(.phoneNumber(presentationData.theme, presentationData.strings, phoneNumber.id, index, phoneNumber.label, phoneNumber.value, state.revealedPhoneId == phoneNumber.id)) - index += 1 - } - - entries.append(.addPhone(presentationData.theme, presentationData.strings.UserInfo_AddPhone)) - - return entries -} - -public func createContactController(account: Account) -> ViewController { - var initialState = CreateContactState() - initialState.phoneNumbers.append(CreateContactPhoneNumber(id: arc4random64(), label: "mobile", value: "")) - let statePromise = ValuePromise(initialState, ignoreRepeated: true) - let stateValue = Atomic(value: initialState) - let updateState: ((CreateContactState) -> CreateContactState) -> Void = { f in - statePromise.set(stateValue.modify { f($0) }) - } - - var dismissImpl: (() -> Void)? - var presentControllerImpl: ((ViewController, Any?) -> Void)? - - let actionsDisposable = DisposableSet() - - let arguments = CreateContactControllerArguments(account: account, - updateEditingName: { editingName in - updateState { state in - var state = state - state.editingName = editingName - return state - } - }, updatePhone: { id, value in - updateState { state in - var state = state - for i in 0 ..< state.phoneNumbers.count { - if state.phoneNumbers[i].id == id { - state.phoneNumbers[i] = state.phoneNumbers[i].withUpdatedValue(value) - break - } - } - return state - } - }, openLabelSelection: { id, label in - - }, addPhone: { - updateState { state in - var state = state - state.phoneNumbers.append(CreateContactPhoneNumber(id: arc4random64(), label: "mobile", value: "")) - return state - } - }, deletePhone: { id in - updateState { state in - var state = state - for i in 0 ..< state.phoneNumbers.count { - if state.phoneNumbers[i].id == id { - state.phoneNumbers.remove(at: i) - break - } - } - return state - } - }) - - let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get()) - |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, CreateContactEntry.ItemGenerationArguments)) in - - var canSave = true - switch state.editingName { - case let .personName(first, last): - if first.isEmpty && last.isEmpty { - canSave = false - } - default: - canSave = false - } - - var hasPhoneNumbers = false - for phoneNumber in state.phoneNumbers { - if !phoneNumber.value.isEmpty { - hasPhoneNumbers = true - } - } - - if !hasPhoneNumbers { - canSave = false - } - - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { - dismissImpl?() - }) - let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: canSave, action: { - var state: CreateContactState? - updateState { - state = $0 - return $0 - } - if let state = state { - var firstName = "" - var lastName = "" - switch state.editingName { - case let .personName(first, last): - firstName = first - lastName = last - default: - break - } - var phoneNumbers: [DeviceContactPhoneNumber] = [] - for number in state.phoneNumbers { - if !number.value.isEmpty { - phoneNumbers.append(DeviceContactPhoneNumber(label: number.label, number: DeviceContactPhoneNumberValue(plain: number.value, normalized: DeviceContactNormalizedPhoneNumber(rawValue: number.value)))) - } - } - let _ = (account.telegramApplicationContext.contactsManager.add(firstName: firstName, lastName: lastName, phoneNumbers: phoneNumbers) - |> deliverOnMainQueue).start(next: { _ in - dismissImpl?() - }) - } - }) - - let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.NewContact_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: nil) - let listState = ItemListNodeState(entries: createContactEntries(account: account, presentationData: presentationData, state: state), style: .plain) - - return (controllerState, (listState, arguments)) - } |> afterDisposed { - actionsDisposable.dispose() - } - - let controller = ItemListController(account: account, state: signal) - dismissImpl = { [weak controller] in - if let navigationController = controller?.navigationController as? NavigationController { - let _ = navigationController.popViewController(animated: true) - } else { - controller?.dismiss() - } - } - presentControllerImpl = { [weak controller] value, presentationArguments in - controller?.present(value, in: .window(.root), with: presentationArguments) - } - - return controller -} - diff --git a/TelegramUI/CreateGroupController.swift b/TelegramUI/CreateGroupController.swift index ff585546eb..8199ec0230 100644 --- a/TelegramUI/CreateGroupController.swift +++ b/TelegramUI/CreateGroupController.swift @@ -260,7 +260,7 @@ public func createGroupController(account: Account, peerIds: [PeerId]) -> ViewCo return $0.avatar } if let _ = updatingAvatar { - let _ = updatePeerPhoto(account: account, peerId: peerId, photo: uploadedAvatar.get()).start() + let _ = updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedAvatar.get()).start() } let controller = ChatController(account: account, chatLocation: .peer(peerId)) replaceControllerImpl?(controller) @@ -290,7 +290,7 @@ public func createGroupController(account: Account, peerIds: [PeerId]) -> ViewCo let resource = LocalFileMediaResource(fileId: arc4random64()) account.postbox.mediaBox.storeResourceData(resource.id, data: data) let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource) - uploadedAvatar.set(uploadedPeerPhoto(account: account, resource: resource)) + uploadedAvatar.set(uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) updateState { current in var current = current current.avatar = .image(representation) diff --git a/TelegramUI/DataAndStorageSettingsController.swift b/TelegramUI/DataAndStorageSettingsController.swift index 761db78a78..55c8289d8d 100644 --- a/TelegramUI/DataAndStorageSettingsController.swift +++ b/TelegramUI/DataAndStorageSettingsController.swift @@ -270,7 +270,9 @@ private enum DataAndStorageEntry: ItemListNodeEntry { }) case let .automaticDownloadReset(theme, text, enabled): return ItemListActionItem(theme: theme, title: text, kind: enabled ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: { + if enabled { arguments.resetAutomaticDownload() + } }) case let .voiceCallsHeader(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) @@ -382,6 +384,7 @@ func dataAndStorageController(account: Account) -> ViewController { let statePromise = ValuePromise(initialState, ignoreRepeated: true) var pushControllerImpl: ((ViewController) -> Void)? + var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? let actionsDisposable = DisposableSet() @@ -433,13 +436,27 @@ func dataAndStorageController(account: Account) -> ViewController { }, openAutomaticDownloadCategory: { category in pushControllerImpl?(autodownloadMediaCategoryController(account: account, category: category)) }, resetAutomaticDownload: { - let _ = updateMediaDownloadSettingsInteractively(postbox: account.postbox, { settings in - var settings = settings - let defaultSettings = AutomaticMediaDownloadSettings.defaultSettings - settings.masterEnabled = defaultSettings.masterEnabled - settings.peers = defaultSettings.peers - return settings - }).start() + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + let actionSheet = ActionSheetController(presentationTheme: presentationData.theme) + actionSheet.setItemGroups([ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: presentationData.strings.AutoDownloadSettings_ResetHelp), + ActionSheetButtonItem(title: presentationData.strings.AutoDownloadSettings_Reset, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + + let _ = updateMediaDownloadSettingsInteractively(postbox: account.postbox, { settings in + var settings = settings + let defaultSettings = AutomaticMediaDownloadSettings.defaultSettings + settings.masterEnabled = defaultSettings.masterEnabled + settings.peers = defaultSettings.peers + return settings + }).start() + }) + ]), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + presentControllerImpl?(actionSheet, nil) }, openVoiceUseLessData: { pushControllerImpl?(voiceCallDataSavingController(account: account)) }, toggleSaveIncomingPhotos: { value in @@ -478,6 +495,9 @@ func dataAndStorageController(account: Account) -> ViewController { (controller.navigationController as? NavigationController)?.pushViewController(c) } } + presentControllerImpl = { [weak controller] c, a in + controller?.present(c, in: .window(.root), with: a) + } return controller } diff --git a/TelegramUI/DataPrivacySettingsController.swift b/TelegramUI/DataPrivacySettingsController.swift index 5438e4dd4f..962205523a 100644 --- a/TelegramUI/DataPrivacySettingsController.swift +++ b/TelegramUI/DataPrivacySettingsController.swift @@ -254,13 +254,13 @@ private struct DataPrivacyControllerState: Equatable { private func dataPrivacyControllerEntries(presentationData: PresentationData, state: DataPrivacyControllerState, secretChatLinkPreviews: Bool?, synchronizeDeviceContacts: Bool, frequentContacts: Bool) -> [PrivacyAndSecurityEntry] { var entries: [PrivacyAndSecurityEntry] = [] - entries.append(.contactsHeader(presentationData.theme, presentationData.strings.PrivacySettings_Contacts)) - entries.append(.deleteContacts(presentationData.theme, presentationData.strings.PrivacySettings_DeleteContacts, !state.deletingContacts)) - entries.append(.syncContacts(presentationData.theme, presentationData.strings.PrivacySettings_SyncContacts, synchronizeDeviceContacts)) - entries.append(.syncContactsInfo(presentationData.theme, presentationData.strings.PrivacySettings_SyncContactsInfo)) + entries.append(.contactsHeader(presentationData.theme, presentationData.strings.Privacy_ContactsTitle)) + entries.append(.deleteContacts(presentationData.theme, presentationData.strings.Privacy_ContactsReset, !state.deletingContacts)) + entries.append(.syncContacts(presentationData.theme, presentationData.strings.Privacy_ContactsSync, synchronizeDeviceContacts)) + entries.append(.syncContactsInfo(presentationData.theme, presentationData.strings.Privacy_ContactsSyncHelp)) - entries.append(.frequentContacts(presentationData.theme, presentationData.strings.PrivacySettings_SuggestFrequentContacts, frequentContacts)) - entries.append(.frequentContactsInfo(presentationData.theme, presentationData.strings.PrivacySettings_SuggestFrequentContactsInfo)) + entries.append(.frequentContacts(presentationData.theme, presentationData.strings.Privacy_TopPeers, frequentContacts)) + entries.append(.frequentContactsInfo(presentationData.theme, presentationData.strings.Privacy_TopPeersHelp)) entries.append(.chatsHeader(presentationData.theme, presentationData.strings.Privacy_ChatsTitle)) entries.append(.deleteCloudDrafts(presentationData.theme, presentationData.strings.Privacy_DeleteDrafts, !state.deletingCloudDrafts)) @@ -268,9 +268,9 @@ private func dataPrivacyControllerEntries(presentationData: PresentationData, st entries.append(.clearPaymentInfo(presentationData.theme, presentationData.strings.Privacy_PaymentsClearInfo, !state.clearingPaymentInfo)) entries.append(.paymentInfo(presentationData.theme, presentationData.strings.Privacy_PaymentsClearInfoHelp)) - entries.append(.secretChatLinkPreviewsHeader(presentationData.theme, presentationData.strings.PrivacySettings_SecretChats)) - entries.append(.secretChatLinkPreviews(presentationData.theme, presentationData.strings.PrivacySettings_LinkPreviews, secretChatLinkPreviews ?? true)) - entries.append(.secretChatLinkPreviewsInfo(presentationData.theme, presentationData.strings.PrivacySettings_LinkPreviewsInfo)) + entries.append(.secretChatLinkPreviewsHeader(presentationData.theme, presentationData.strings.Privacy_SecretChatsTitle)) + entries.append(.secretChatLinkPreviews(presentationData.theme, presentationData.strings.Privacy_SecretChatsLinkPreviews, secretChatLinkPreviews ?? true)) + entries.append(.secretChatLinkPreviewsInfo(presentationData.theme, presentationData.strings.Privacy_SecretChatsLinkPreviewsHelp)) return entries } @@ -282,8 +282,6 @@ public func dataPrivacyController(account: Account) -> ViewController { statePromise.set(stateValue.modify { f($0) }) } - var pushControllerImpl: ((ViewController) -> Void)? - var pushControllerInstantImpl: ((ViewController) -> Void)? var presentControllerImpl: ((ViewController) -> Void)? let actionsDisposable = DisposableSet() @@ -341,7 +339,7 @@ public func dataPrivacyController(account: Account) -> ViewController { } if canBegin { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: "This will remove your contacts from the Telegram servers.", actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: { + presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Privacy_ContactsResetConfirmation, actions: [TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: { var begin = false updateState { state in var state = state @@ -370,7 +368,7 @@ public func dataPrivacyController(account: Account) -> ViewController { return state } let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.PrivacySettings_DeleteContactsSuccess, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])) + presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .success)) })) }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {})])) } @@ -391,7 +389,7 @@ public func dataPrivacyController(account: Account) -> ViewController { } if !value { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.PrivacySettings_SuggestFrequentContactsDisableNotice, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { + presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Privacy_TopPeersWarning, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { apply() })])) } else { @@ -473,12 +471,6 @@ public func dataPrivacyController(account: Account) -> ViewController { } let controller = ItemListController(account: account, state: signal) - pushControllerImpl = { [weak controller] c in - (controller?.navigationController as? NavigationController)?.pushViewController(c) - } - pushControllerInstantImpl = { [weak controller] c in - (controller?.navigationController as? NavigationController)?.pushViewController(c, animated: false) - } presentControllerImpl = { [weak controller] c in controller?.present(c, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } diff --git a/TelegramUI/DateFormat.swift b/TelegramUI/DateFormat.swift index 542bf3bea8..de88317bc9 100644 --- a/TelegramUI/DateFormat.swift +++ b/TelegramUI/DateFormat.swift @@ -28,10 +28,14 @@ func stringForShortTimestamp(hours: Int32, minutes: Int32, timeFormat: Presentat } } -func stringForMessageTimestamp(timestamp: Int32, timeFormat: PresentationTimeFormat) -> String { +func stringForMessageTimestamp(timestamp: Int32, timeFormat: PresentationTimeFormat, local: Bool = true) -> String { var t = Int(timestamp) var timeinfo = tm() - localtime_r(&t, &timeinfo) + if local { + localtime_r(&t, &timeinfo) + } else { + gmtime_r(&t, &timeinfo) + } return stringForShortTimestamp(hours: timeinfo.tm_hour, minutes: timeinfo.tm_min, timeFormat: timeFormat) } diff --git a/TelegramUI/DebugController.swift b/TelegramUI/DebugController.swift index ef921dfb05..292a6a2a2c 100644 --- a/TelegramUI/DebugController.swift +++ b/TelegramUI/DebugController.swift @@ -28,6 +28,7 @@ private enum DebugControllerSection: Int32 { private enum DebugControllerEntry: ItemListNodeEntry { case sendLogs(PresentationTheme) + case sendOneLog(PresentationTheme) case accounts(PresentationTheme) case clearPaymentData(PresentationTheme) case logToFile(PresentationTheme, Bool) @@ -35,11 +36,12 @@ private enum DebugControllerEntry: ItemListNodeEntry { case redactSensitiveData(PresentationTheme, Bool) case enableRaiseToSpeak(PresentationTheme, Bool) case keepChatNavigationStack(PresentationTheme, Bool) - case testHashing(PresentationTheme) + case clearTips(PresentationTheme) + case reimport(PresentationTheme) var section: ItemListSectionId { switch self { - case .sendLogs: + case .sendLogs, .sendOneLog: return DebugControllerSection.logs.rawValue case .accounts: return DebugControllerSection.logs.rawValue @@ -49,7 +51,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.logging.rawValue case .enableRaiseToSpeak, .keepChatNavigationStack: return DebugControllerSection.experiments.rawValue - case .testHashing: + case .clearTips, .reimport: return DebugControllerSection.experiments.rawValue } } @@ -58,22 +60,26 @@ private enum DebugControllerEntry: ItemListNodeEntry { switch self { case .sendLogs: return 0 - case .accounts: + case .sendOneLog: return 1 - case .clearPaymentData: + case .accounts: return 2 - case .logToFile: + case .clearPaymentData: return 3 - case .logToConsole: + case .logToFile: return 4 - case .redactSensitiveData: + case .logToConsole: return 5 - case .enableRaiseToSpeak: + case .redactSensitiveData: return 6 - case .keepChatNavigationStack: + case .enableRaiseToSpeak: return 7 - case .testHashing: + case .keepChatNavigationStack: return 8 + case .clearTips: + return 9 + case .reimport: + return 10 } } @@ -85,6 +91,12 @@ private enum DebugControllerEntry: ItemListNodeEntry { } else { return false } + case let .sendOneLog(lhsTheme): + if case let .sendOneLog(rhsTheme) = rhs, lhsTheme === rhsTheme { + return true + } else { + return false + } case let .accounts(lhsTheme): if case let .accounts(rhsTheme) = rhs, lhsTheme === rhsTheme { return true @@ -127,8 +139,14 @@ private enum DebugControllerEntry: ItemListNodeEntry { } else { return false } - case let .testHashing(lhsTheme): - if case let .testHashing(rhsTheme) = rhs, lhsTheme === rhsTheme { + case let .clearTips(lhsTheme): + if case let .clearTips(rhsTheme) = rhs, lhsTheme === rhsTheme { + return true + } else { + return false + } + case let .reimport(lhsTheme): + if case let .reimport(rhsTheme) = rhs, lhsTheme === rhsTheme { return true } else { return false @@ -162,6 +180,28 @@ private enum DebugControllerEntry: ItemListNodeEntry { arguments.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet)) }) }) + case let .sendOneLog(theme): + return ItemListDisclosureItem(theme: theme, title: "Send Latest Log", label: "", sectionId: self.section, style: .blocks, action: { + let _ = (Logger.shared.collectLogs() + |> deliverOnMainQueue).start(next: { logs in + let controller = PeerSelectionController(account: arguments.account) + controller.peerSelected = { [weak controller] peerId in + if let strongController = controller { + strongController.dismiss() + + let updatedLogs = logs.last.flatMap({ [$0] }) ?? [] + + let messages = updatedLogs.map { (name, path) -> EnqueueMessage in + let id = arc4random64() + let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], mimeType: "application/text", size: nil, attributes: [.FileName(fileName: name)]) + return .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil) + } + let _ = enqueueMessages(account: arguments.account, peerId: peerId, messages: messages).start() + } + } + arguments.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet)) + }) + }) case let .accounts(theme): return ItemListDisclosureItem(theme: theme, title: "Accounts", label: "", sectionId: self.section, style: .blocks, action: { arguments.pushController(debugAccountsController(account: arguments.account, accountManager: arguments.accountManager)) @@ -202,32 +242,36 @@ private enum DebugControllerEntry: ItemListNodeEntry { return settings }).start() }) - case let .testHashing(theme): - return ItemListActionItem(theme: theme, title: "Test Hashing", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { - let passwordData = "case let .testHashing(theme):".data(using: .utf8)! - let salt = Data(count: 16) + case let .clearTips(theme): + return ItemListActionItem(theme: theme, title: "Clear Tips", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + let _ = (arguments.account.postbox.transaction { transaction -> Void in + transaction.clearNoticeEntries() + }).start() + }) + case let .reimport(theme): + return ItemListActionItem(theme: theme, title: "Reimport Application Data", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + let appGroupName = "group.\(Bundle.main.bundleIdentifier!)" + let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName) - var startTime = CFAbsoluteTimeGetCurrent() - let result1 = MTPBKDF2(passwordData, salt, 100000) - let duration1 = CFAbsoluteTimeGetCurrent() - startTime + guard let appGroupUrl = maybeAppGroupUrl else { + return + } - startTime = CFAbsoluteTimeGetCurrent() - let result2 = MTArgon2(passwordData, salt, 5) - let duration2 = CFAbsoluteTimeGetCurrent() - startTime - if result1 == nil || result2 == nil { - arguments.presentController(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: theme), title: nil, text: "Error", actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})]), nil) - } else { - arguments.presentController(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: theme), title: nil, text: "PBKDF2 \(duration1 * 1000.0) ms\nArgon2 \(duration2 * 1000.0) ms", actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})]), nil) + let statusPath = appGroupUrl.path + "/Documents/importcompleted" + if FileManager.default.fileExists(atPath: statusPath) { + let _ = try? FileManager.default.removeItem(at: URL(fileURLWithPath: statusPath)) + exit(0) } }) } } } -private func debugControllerEntries(presentationData: PresentationData, loggingSettings: LoggingSettings, mediaInputSettings: MediaInputSettings, experimentalSettings: ExperimentalUISettings) -> [DebugControllerEntry] { +private func debugControllerEntries(presentationData: PresentationData, loggingSettings: LoggingSettings, mediaInputSettings: MediaInputSettings, experimentalSettings: ExperimentalUISettings, hasLegacyAppData: Bool) -> [DebugControllerEntry] { var entries: [DebugControllerEntry] = [] entries.append(.sendLogs(presentationData.theme)) + entries.append(.sendOneLog(presentationData.theme)) entries.append(.accounts(presentationData.theme)) entries.append(.clearPaymentData(presentationData.theme)) @@ -237,7 +281,10 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS entries.append(.enableRaiseToSpeak(presentationData.theme, mediaInputSettings.enableRaiseToSpeak)) entries.append(.keepChatNavigationStack(presentationData.theme, experimentalSettings.keepChatNavigationStack)) - entries.append(.testHashing(presentationData.theme)) + entries.append(.clearTips(presentationData.theme)) + if hasLegacyAppData { + entries.append(.reimport(presentationData.theme)) + } return entries } @@ -252,6 +299,15 @@ public func debugController(account: Account, accountManager: AccountManager) -> pushControllerImpl?(controller) }) + let appGroupName = "group.\(Bundle.main.bundleIdentifier!)" + let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName) + + var hasLegacyAppData = false + if let appGroupUrl = maybeAppGroupUrl { + let statusPath = appGroupUrl.path + "/Documents/importcompleted" + hasLegacyAppData = FileManager.default.fileExists(atPath: statusPath) + } + let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, account.postbox.preferencesView(keys: [PreferencesKeys.loggingSettings, ApplicationSpecificPreferencesKeys.mediaInputSettings, ApplicationSpecificPreferencesKeys.experimentalUISettings])) |> map { presentationData, preferencesView -> (ItemListControllerState, (ItemListNodeState, DebugControllerEntry.ItemGenerationArguments)) in let loggingSettings: LoggingSettings @@ -271,7 +327,7 @@ public func debugController(account: Account, accountManager: AccountManager) -> let experimentalSettings: ExperimentalUISettings = (preferencesView.values[ApplicationSpecificPreferencesKeys.experimentalUISettings] as? ExperimentalUISettings) ?? ExperimentalUISettings.defaultSettings let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text("Debug"), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) - let listState = ItemListNodeState(entries: debugControllerEntries(presentationData: presentationData, loggingSettings: loggingSettings, mediaInputSettings: mediaInputSettings, experimentalSettings: experimentalSettings), style: .blocks) + let listState = ItemListNodeState(entries: debugControllerEntries(presentationData: presentationData, loggingSettings: loggingSettings, mediaInputSettings: mediaInputSettings, experimentalSettings: experimentalSettings, hasLegacyAppData: hasLegacyAppData), style: .blocks) return (controllerState, (listState, arguments)) } diff --git a/TelegramUI/DeclareEncodables.swift b/TelegramUI/DeclareEncodables.swift index 0a949749de..256be99d4a 100644 --- a/TelegramUI/DeclareEncodables.swift +++ b/TelegramUI/DeclareEncodables.swift @@ -13,6 +13,7 @@ private var telegramUIDeclaredEncodables: Void = { declareEncodable(PresentationThemeSettings.self, f: { PresentationThemeSettings(decoder: $0) }) declareEncodable(ApplicationSpecificBoolNotice.self, f: { ApplicationSpecificBoolNotice(decoder: $0) }) declareEncodable(ApplicationSpecificVariantNotice.self, f: { ApplicationSpecificVariantNotice(decoder: $0) }) + declareEncodable(ApplicationSpecificCounterNotice.self, f: { ApplicationSpecificCounterNotice(decoder: $0) }) declareEncodable(CallListSettings.self, f: { CallListSettings(decoder: $0) }) declareEncodable(ExperimentalSettings.self, f: { ExperimentalSettings(decoder: $0) }) declareEncodable(ExperimentalUISettings.self, f: { ExperimentalUISettings(decoder: $0) }) @@ -21,6 +22,7 @@ private var telegramUIDeclaredEncodables: Void = { declareEncodable(MediaInputSettings.self, f: { MediaInputSettings(decoder: $0) }) declareEncodable(ContactSynchronizationSettings.self, f: { ContactSynchronizationSettings(decoder: $0) }) declareEncodable(CachedChannelAdminIds.self, f: { CachedChannelAdminIds(decoder: $0) }) + declareEncodable(StickerSettings.self, f: { StickerSettings(decoder: $0) }) return }() diff --git a/TelegramUI/DefaultDarkAccentPresentationTheme.swift b/TelegramUI/DefaultDarkAccentPresentationTheme.swift index 4439d57ff6..d3b1831b43 100644 --- a/TelegramUI/DefaultDarkAccentPresentationTheme.swift +++ b/TelegramUI/DefaultDarkAccentPresentationTheme.swift @@ -26,7 +26,7 @@ private let rootNavigationBar = PresentationThemeRootNavigationBar( buttonColor: accentColor, primaryTextColor: UIColor(rgb: 0xffffff), secondaryTextColor: UIColor(rgb: 0x8B9197), - controlColor: accentColor, + controlColor: UIColor(rgb: 0x8B9197), accentTextColor: accentColor, backgroundColor: UIColor(rgb: 0x213040), separatorColor: UIColor(rgb: 0x131A23), @@ -131,17 +131,9 @@ private let chatList = PresentationThemeChatList( ) private let bubble = PresentationThemeChatBubble( - incomingFillColor: UIColor(rgb: 0x213040), - incomingFillHighlightedColor: UIColor(rgb: 0x2D3A49), - incomingStrokeColor: UIColor(rgb: 0x213040), - outgoingFillColor: UIColor(rgb: 0x3D6A97), - outgoingFillHighlightedColor: UIColor(rgb: 0x5079A1), - outgoingStrokeColor: UIColor(rgb: 0x3D6A97), - freeformFillColor: UIColor(rgb: 0x213040), - freeformFillHighlightedColor: UIColor(rgb: 0x2A2A2A), //!!! - freeformStrokeColor: UIColor(rgb: 0x213040), - infoFillColor: UIColor(rgb: 0x213040), - infoStrokeColor: UIColor(rgb: 0x213040), + incoming: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x213040), highlightedFill: UIColor(rgb: 0x2D3A49), stroke: UIColor(rgb: 0x213040)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x213040), highlightedFill: UIColor(rgb: 0x2D3A49), stroke: UIColor(rgb: 0x213040))), + outgoing: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x3D6A97), highlightedFill: UIColor(rgb: 0x5079A1), stroke: UIColor(rgb: 0x3D6A97)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x3D6A97), highlightedFill: UIColor(rgb: 0x5079A1), stroke: UIColor(rgb: 0x3D6A97))), + freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x213040), highlightedFill: UIColor(rgb: 0x2D3A49), stroke: UIColor(rgb: 0x213040)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x213040), highlightedFill: UIColor(rgb: 0x2D3A49), stroke: UIColor(rgb: 0x213040))), incomingPrimaryTextColor: UIColor(rgb: 0xffffff), incomingSecondaryTextColor: UIColor(rgb: 0xDBF5FF, alpha: 0.5), incomingLinkTextColor: accentColor, diff --git a/TelegramUI/DefaultDarkPresentationTheme.swift b/TelegramUI/DefaultDarkPresentationTheme.swift index d8bba6f88f..561c12e819 100644 --- a/TelegramUI/DefaultDarkPresentationTheme.swift +++ b/TelegramUI/DefaultDarkPresentationTheme.swift @@ -131,17 +131,9 @@ private let chatList = PresentationThemeChatList( ) private let bubble = PresentationThemeChatBubble( - incomingFillColor: UIColor(rgb: 0x1f1f1f), - incomingFillHighlightedColor: UIColor(rgb: 0x2A2A2A), - incomingStrokeColor: UIColor(rgb: 0x1f1f1f), - outgoingFillColor: UIColor(rgb: 0x313131), - outgoingFillHighlightedColor: UIColor(rgb: 0x464646), - outgoingStrokeColor: UIColor(rgb: 0x313131), - freeformFillColor: UIColor(rgb: 0x1f1f1f), - freeformFillHighlightedColor: UIColor(rgb: 0x2A2A2A), - freeformStrokeColor: UIColor(rgb: 0x1f1f1f), - infoFillColor: UIColor(rgb: 0x1f1f1f), - infoStrokeColor: UIColor(rgb: 0x000000), + incoming: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1f1f1f), highlightedFill: UIColor(rgb: 0x2A2A2A), stroke: UIColor(rgb: 0x1f1f1f)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1f1f1f), highlightedFill: UIColor(rgb: 0x2A2A2A), stroke: UIColor(rgb: 0x1f1f1f))), + outgoing: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x313131), highlightedFill: UIColor(rgb: 0x464646), stroke: UIColor(rgb: 0x313131)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x313131), highlightedFill: UIColor(rgb: 0x464646), stroke: UIColor(rgb: 0x313131))), + freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1f1f1f), highlightedFill: UIColor(rgb: 0x2A2A2A), stroke: UIColor(rgb: 0x1f1f1f)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0x1f1f1f), highlightedFill: UIColor(rgb: 0x2A2A2A), stroke: UIColor(rgb: 0x1f1f1f))), incomingPrimaryTextColor: UIColor(rgb: 0xffffff), incomingSecondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.5), incomingLinkTextColor: accentColor, diff --git a/TelegramUI/DefaultPresentationTheme.swift b/TelegramUI/DefaultPresentationTheme.swift index 32994e1254..b38c1d8c3a 100644 --- a/TelegramUI/DefaultPresentationTheme.swift +++ b/TelegramUI/DefaultPresentationTheme.swift @@ -1,441 +1,428 @@ import Foundation import UIKit -private let accentColor: UIColor = UIColor(rgb: 0x007ee5) -private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30) -private let constructiveColor: UIColor = UIColor(rgb: 0x4cd964) -private let secretColor: UIColor = UIColor(rgb: 0x00B12C) - -private let rootStatusBar = PresentationThemeRootNavigationStatusBar( - style: .black -) - -private let rootTabBar = PresentationThemeRootTabBar( - backgroundColor: UIColor(rgb: 0xf7f7f7), - separatorColor: UIColor(rgb: 0xa3a3a3), - iconColor: UIColor(rgb: 0xA1A1A1), - selectedIconColor: accentColor, - textColor: UIColor(rgb: 0xA1A1A1), - selectedTextColor: accentColor, - badgeBackgroundColor: UIColor(rgb: 0xff3b30), - badgeStrokeColor: UIColor(rgb: 0xff3b30), - badgeTextColor: .white -) - -private let rootNavigationBar = PresentationThemeRootNavigationBar( - buttonColor: accentColor, - primaryTextColor: .black, - secondaryTextColor: UIColor(rgb: 0x787878), - controlColor: UIColor(rgb: 0x7e8791), - accentTextColor: accentColor, - backgroundColor: UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0), - separatorColor: UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0), - badgeBackgroundColor: UIColor(rgb: 0xff3b30), - badgeStrokeColor: UIColor(rgb: 0xff3b30), - badgeTextColor: .white -) - -private let activeNavigationSearchBar = PresentationThemeActiveNavigationSearchBar( - backgroundColor: .white, - accentColor: accentColor, - inputFillColor: UIColor(rgb: 0xe9e9e9), - inputTextColor: .black, - inputPlaceholderTextColor: UIColor(rgb: 0x8e8e93), - inputIconColor: UIColor(rgb: 0x8e8e93), - inputClearButtonColor: UIColor(rgb: 0x7b7b81), - separatorColor: UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0) -) - -private let rootController = PresentationThemeRootController( - statusBar: rootStatusBar, - tabBar: rootTabBar, - navigationBar: rootNavigationBar, - activeNavigationSearchBar: activeNavigationSearchBar -) - -private let switchColors = PresentationThemeSwitch( - frameColor: UIColor(rgb: 0xe0e0e0), - handleColor: UIColor(rgb: 0xffffff), - contentColor: UIColor(rgb: 0x42d451) -) - -private let list = PresentationThemeList( - blocksBackgroundColor: UIColor(rgb: 0xefeff4), - plainBackgroundColor: .white, - itemPrimaryTextColor: .black, - itemSecondaryTextColor: UIColor(rgb: 0x8e8e93), - itemDisabledTextColor: UIColor(rgb: 0x8e8e93), - itemAccentColor: accentColor, - itemDestructiveColor: destructiveColor, - itemPlaceholderTextColor: UIColor(rgb: 0xc8c8ce), - itemBlocksBackgroundColor: .white, - itemHighlightedBackgroundColor: UIColor(rgb: 0xd9d9d9), - itemBlocksSeparatorColor: UIColor(rgb: 0xc8c7cc), - itemPlainSeparatorColor: UIColor(rgb: 0xc8c7cc), - disclosureArrowColor: UIColor(rgb: 0xbab9be), - sectionHeaderTextColor: UIColor(rgb: 0x6d6d72), - freeTextColor: UIColor(rgb: 0x6d6d72), - freeTextErrorColor: UIColor(rgb: 0xcf3030), - freeTextSuccessColor: UIColor(rgb: 0x26972c), - freeMonoIcon: UIColor(rgb: 0x7e7e87), - itemSwitchColors: switchColors, - itemDisclosureActions: PresentationThemeItemDisclosureActions( - neutral1: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xbcbcc3), foregroundColor: .white), - neutral2: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xaaaab3), foregroundColor: .white), - destructive: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xff3824), foregroundColor: .white), - constructive: PresentationThemeItemDisclosureAction(fillColor: constructiveColor, foregroundColor: .white), - accent: PresentationThemeItemDisclosureAction(fillColor: accentColor, foregroundColor: .white), - warning: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xff9500), foregroundColor: .white) - ), - itemCheckColors: PresentationThemeCheck( - strokeColor: UIColor(rgb: 0xC7C7CC), - fillColor: accentColor, - foregroundColor: .white - ), - controlSecondaryColor: UIColor(rgb: 0xdedede), - freeInputField: PresentationInputFieldTheme( - backgroundColor: UIColor(rgb: 0xd6d6dc), - placeholderColor: UIColor(rgb: 0x96979d), - primaryColor: .black +private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> PresentationTheme { + let destructiveColor: UIColor = UIColor(rgb: 0xff3b30) + let constructiveColor: UIColor = UIColor(rgb: 0x4cd964) + let secretColor: UIColor = UIColor(rgb: 0x00B12C) + + let rootStatusBar = PresentationThemeRootNavigationStatusBar( + style: .black ) -) - -private let chatList = PresentationThemeChatList( - backgroundColor: .white, - itemSeparatorColor: UIColor(rgb: 0xc8c7cc), - itemBackgroundColor: .white, - pinnedItemBackgroundColor: UIColor(rgb: 0xf7f7f7), - itemHighlightedBackgroundColor: UIColor(rgb: 0xd9d9d9), - titleColor: .black, - secretTitleColor: secretColor, - dateTextColor: UIColor(rgb: 0x8e8e93), - authorNameColor: .black, - messageTextColor: UIColor(rgb: 0x8e8e93), - messageDraftTextColor: UIColor(rgb: 0xdd4b39), - checkmarkColor: UIColor(rgb: 0x21c004), - pendingIndicatorColor: UIColor(rgb: 0x8e8e93), - muteIconColor: UIColor(rgb: 0xa7a7ad), - unreadBadgeActiveBackgroundColor: accentColor, - unreadBadgeActiveTextColor: .white, - unreadBadgeInactiveBackgroundColor: UIColor(rgb: 0xb6b6bb), - unreadBadgeInactiveTextColor: .white, - pinnedBadgeColor: UIColor(rgb: 0x939399), - pinnedSearchBarColor: UIColor(rgb: 0xe5e5e5), - regularSearchBarColor: UIColor(rgb: 0xe9e9e9), - sectionHeaderFillColor: UIColor(rgb: 0xf7f7f7), - sectionHeaderTextColor: UIColor(rgb: 0x8e8e93), - searchBarKeyboardColor: .light, - verifiedIconFillColor: accentColor, - verifiedIconForegroundColor: .white, - secretIconColor: secretColor -) - -private let chatListDay = PresentationThemeChatList( - backgroundColor: .white, - itemSeparatorColor: UIColor(rgb: 0xc8c7cc), - itemBackgroundColor: .white, - pinnedItemBackgroundColor: UIColor(rgb: 0xf7f7f7), - itemHighlightedBackgroundColor: UIColor(rgb: 0xd9d9d9), - titleColor: .black, - secretTitleColor: secretColor, - dateTextColor: UIColor(rgb: 0x8e8e93), - authorNameColor: .black, - messageTextColor: UIColor(rgb: 0x8e8e93), - messageDraftTextColor: UIColor(rgb: 0xdd4b39), - checkmarkColor: accentColor, - pendingIndicatorColor: UIColor(rgb: 0x8e8e93), - muteIconColor: UIColor(rgb: 0xa7a7ad), - unreadBadgeActiveBackgroundColor: accentColor, - unreadBadgeActiveTextColor: .white, - unreadBadgeInactiveBackgroundColor: UIColor(rgb: 0xb6b6bb), - unreadBadgeInactiveTextColor: .white, - pinnedBadgeColor: UIColor(rgb: 0x939399), - pinnedSearchBarColor: UIColor(rgb: 0xe5e5e5), - regularSearchBarColor: UIColor(rgb: 0xe9e9e9), - sectionHeaderFillColor: UIColor(rgb: 0xf7f7f7), - sectionHeaderTextColor: UIColor(rgb: 0x8e8e93), - searchBarKeyboardColor: .light, - verifiedIconFillColor: accentColor, - verifiedIconForegroundColor: .white, - secretIconColor: secretColor -) - -private let bubble = PresentationThemeChatBubble( - incomingFillColor: UIColor(rgb: 0xffffff), - incomingFillHighlightedColor: UIColor(rgb: 0xd9f4ff), - incomingStrokeColor: UIColor(rgb: 0x86A9C9, alpha: 0.5), - outgoingFillColor: UIColor(rgb: 0xE1FFC7), - outgoingFillHighlightedColor: UIColor(rgb: 0xc8ffa6), - outgoingStrokeColor: UIColor(rgb: 0x86A9C9, alpha: 0.5), - freeformFillColor: UIColor(rgb: 0xffffff), - freeformFillHighlightedColor: UIColor(rgb: 0xd9f4ff), - freeformStrokeColor: UIColor(rgb: 0x86A9C9, alpha: 0.5), - infoFillColor: UIColor(rgb: 0xffffff), - infoStrokeColor: UIColor(rgb: 0x86A9C9, alpha: 0.5), - incomingPrimaryTextColor: UIColor(rgb: 0x000000), - incomingSecondaryTextColor: UIColor(rgb: 0x525252, alpha: 0.6), - incomingLinkTextColor: UIColor(rgb: 0x004bad), - incomingLinkHighlightColor: accentColor.withAlphaComponent(0.3), - outgoingPrimaryTextColor: UIColor(rgb: 0x000000), - outgoingSecondaryTextColor: UIColor(rgb: 0x008c09, alpha: 0.8), - outgoingLinkTextColor: UIColor(rgb: 0x004bad), - outgoingLinkHighlightColor: accentColor.withAlphaComponent(0.3), - infoPrimaryTextColor: UIColor(rgb: 0x000000), - infoLinkTextColor: UIColor(rgb: 0x004bad), - incomingAccentTextColor: UIColor(rgb: 0x3ca7fe), - outgoingAccentTextColor: UIColor(rgb: 0x00a700), - incomingAccentControlColor: UIColor(rgb: 0x3ca7fe), - outgoingAccentControlColor: UIColor(rgb: 0x3FC33B), - incomingMediaActiveControlColor: UIColor(rgb: 0x3ca7fe), - outgoingMediaActiveControlColor: UIColor(rgb: 0x3FC33B), - incomingMediaInactiveControlColor: UIColor(rgb: 0xcacaca), - outgoingMediaInactiveControlColor: UIColor(rgb: 0x93D987), - outgoingCheckColor: UIColor(rgb: 0x19C700), - incomingPendingActivityColor: UIColor(rgb: 0x525252, alpha: 0.6), - outgoingPendingActivityColor: UIColor(rgb: 0x42b649), - mediaDateAndStatusFillColor: UIColor(white: 0.0, alpha: 0.5), - mediaDateAndStatusTextColor: .white, - incomingFileTitleColor: UIColor(rgb: 0x0b8bed), - outgoingFileTitleColor: UIColor(rgb: 0x3faa3c), - incomingFileDescriptionColor: UIColor(rgb: 0x999999), - outgoingFileDescriptionColor: UIColor(rgb: 0x6fb26a), - incomingFileDurationColor: UIColor(rgb: 0x525252, alpha: 0.6), - outgoingFileDurationColor: UIColor(rgb: 0x008c09, alpha: 0.8), - shareButtonFillColor: UIColor(rgb: 0x748391, alpha: 0.45), - shareButtonStrokeColor: .clear, - shareButtonForegroundColor: .white, - mediaOverlayControlBackgroundColor: UIColor(white: 0.0, alpha: 0.6), - mediaOverlayControlForegroundColor: UIColor(white: 1.0, alpha: 1.0), - actionButtonsIncomingFillColor: UIColor(rgb: 0x596E89, alpha: 0.35), - actionButtonsIncomingStrokeColor: .clear, - actionButtonsIncomingTextColor: .white, - actionButtonsOutgoingFillColor: UIColor(rgb: 0x596E89, alpha: 0.35), - actionButtonsOutgoingStrokeColor: .clear, - actionButtonsOutgoingTextColor: .white, - selectionControlBorderColor: UIColor(rgb: 0xC7C7CC), - selectionControlFillColor: accentColor, - selectionControlForegroundColor: .white, - mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.2) -) - -private let bubbleDay = PresentationThemeChatBubble( - incomingFillColor: UIColor(rgb: 0xF1F1F4), - incomingFillHighlightedColor: UIColor(rgb: 0xDADADE), - incomingStrokeColor: UIColor(rgb: 0xF1F1F4), - outgoingFillColor: UIColor(rgb: 0x3996ee), - outgoingFillHighlightedColor: UIColor(rgb: 0x3387D6), - outgoingStrokeColor: UIColor(rgb: 0x3996ee, alpha: 1.0), - freeformFillColor: UIColor(rgb: 0xE5E5EA), - freeformFillHighlightedColor: UIColor(rgb: 0xDADADE), - freeformStrokeColor: UIColor(rgb: 0xE5E5EA), - infoFillColor: UIColor(rgb: 0xE5E5EA), - infoStrokeColor: UIColor(rgb: 0xE5E5EA, alpha: 1.0), - incomingPrimaryTextColor: UIColor(rgb: 0x000000), - incomingSecondaryTextColor: UIColor(rgb: 0x525252, alpha: 0.6), - incomingLinkTextColor: UIColor(rgb: 0x004bad), - incomingLinkHighlightColor: accentColor.withAlphaComponent(0.3), - outgoingPrimaryTextColor: UIColor(rgb: 0xffffff), - outgoingSecondaryTextColor: UIColor(rgb: 0xAFD5F8, alpha: 1.0), - outgoingLinkTextColor: UIColor(rgb: 0xffffff), - outgoingLinkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.3), - infoPrimaryTextColor: UIColor(rgb: 0x000000), - infoLinkTextColor: UIColor(rgb: 0x004bad), - incomingAccentTextColor: accentColor, - outgoingAccentTextColor: UIColor(white: 1.0, alpha: 0.7), - incomingAccentControlColor: accentColor, - outgoingAccentControlColor: UIColor(white: 1.0, alpha: 0.7), - incomingMediaActiveControlColor: accentColor, - outgoingMediaActiveControlColor: UIColor(white: 1.0, alpha: 1.0), - incomingMediaInactiveControlColor: UIColor(rgb: 0xcacaca), - outgoingMediaInactiveControlColor: UIColor(white: 1.0, alpha: 0.6), - outgoingCheckColor: UIColor(rgb: 0xAFD5F8), - incomingPendingActivityColor: UIColor(rgb: 0x525252, alpha: 0.6), - outgoingPendingActivityColor: UIColor(white: 1.0, alpha: 0.7), - mediaDateAndStatusFillColor: UIColor(white: 0.0, alpha: 0.5), - mediaDateAndStatusTextColor: .white, - incomingFileTitleColor: UIColor(rgb: 0x0b8bed), - outgoingFileTitleColor: UIColor(rgb: 0xffffff), - incomingFileDescriptionColor: UIColor(rgb: 0x999999), - outgoingFileDescriptionColor: UIColor(white: 1.0, alpha: 0.7), - incomingFileDurationColor: UIColor(rgb: 0x525252, alpha: 0.6), - outgoingFileDurationColor: UIColor(white: 1.0, alpha: 0.7), - shareButtonFillColor: UIColor(rgb: 0xffffff, alpha: 0.5), - shareButtonStrokeColor: UIColor(rgb: 0xE5E5EA), - shareButtonForegroundColor: accentColor, - mediaOverlayControlBackgroundColor: UIColor(white: 0.0, alpha: 0.6), - mediaOverlayControlForegroundColor: UIColor(white: 1.0, alpha: 1.0), - actionButtonsIncomingFillColor: UIColor(rgb: 0xffffff, alpha: 0.5), - actionButtonsIncomingStrokeColor: UIColor(rgb: 0x3996ee), - actionButtonsIncomingTextColor: UIColor(rgb: 0x3996ee), - actionButtonsOutgoingFillColor: UIColor(rgb: 0xffffff, alpha: 0.5), - actionButtonsOutgoingStrokeColor: UIColor(rgb: 0x3996ee), - actionButtonsOutgoingTextColor: UIColor(rgb: 0x3996ee), - selectionControlBorderColor: UIColor(rgb: 0xC7C7CC), - selectionControlFillColor: accentColor, - selectionControlForegroundColor: .white, - mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.2) -) - -private let serviceMessage = PresentationThemeServiceMessage( - serviceMessageFillColor: UIColor(rgb: 0x748391, alpha: 0.45), - serviceMessagePrimaryTextColor: .white, - serviceMessageLinkHighlightColor: UIColor(rgb: 0x748391, alpha: 0.25), - unreadBarFillColor: UIColor(white: 1.0, alpha: 0.9), - unreadBarStrokeColor: UIColor(white: 0.0, alpha: 0.2), - unreadBarTextColor: UIColor(rgb: 0x86868d), - dateFillStaticColor: UIColor(rgb: 0x748391, alpha: 0.45), - dateFillFloatingColor: UIColor(rgb: 0x939fab, alpha: 0.5), - dateTextColor: .white -) - -private let serviceMessageDay = PresentationThemeServiceMessage( - serviceMessageFillColor: UIColor(rgb: 0xffffff, alpha: 0.45), - serviceMessagePrimaryTextColor: UIColor(rgb: 0x8D8E93), - serviceMessageLinkHighlightColor: UIColor(rgb: 0x748391, alpha: 0.25), - unreadBarFillColor: UIColor(white: 1.0, alpha: 1.0), - unreadBarStrokeColor: UIColor(white: 1.0, alpha: 1.0), - unreadBarTextColor: UIColor(rgb: 0x8D8E93), - dateFillStaticColor: UIColor(rgb: 0xffffff, alpha: 0.45), - dateFillFloatingColor: UIColor(rgb: 0xffffff, alpha: 0.8), - dateTextColor: UIColor(rgb: 0x8D8E93) -) - -private let inputPanelMediaRecordingControl = PresentationThemeChatInputPanelMediaRecordingControl( - buttonColor: accentColor, - micLevelColor: accentColor.withAlphaComponent(0.2), - activeIconColor: .white, - panelControlFillColor: UIColor(rgb: 0xf7f7f7), - panelControlStrokeColor: UIColor(rgb: 0xb2b2b2), - panelControlContentPrimaryColor: UIColor(rgb: 0x9597a0), - panelControlContentAccentColor: accentColor -) - -private let inputPanel = PresentationThemeChatInputPanel( - panelBackgroundColor: UIColor(rgb: 0xf7f7f7), - panelStrokeColor: UIColor(rgb: 0xb2b2b2), - panelControlAccentColor: accentColor, - panelControlColor: UIColor(rgb: 0x858e99), - panelControlDisabledColor: UIColor(rgb: 0x727b87, alpha: 0.5), - panelControlDestructiveColor: UIColor(rgb: 0xff3b30), - inputBackgroundColor: UIColor(rgb: 0xffffff), - inputStrokeColor: UIColor(rgb: 0xd9dcdf), - inputPlaceholderColor: UIColor(rgb: 0xbebec0), - inputTextColor: .black, - inputControlColor: UIColor(rgb: 0xa0a7b0), - actionControlFillColor: accentColor, - actionControlForegroundColor: .white, - primaryTextColor: .black, - secondaryTextColor: UIColor(rgb: 0x5e5e5e), - mediaRecordingDotColor: UIColor(rgb: 0xed2521), - keyboardColor: .light, - mediaRecordingControl: inputPanelMediaRecordingControl -) - -private let inputMediaPanel = PresentationThemeInputMediaPanel( - panelSerapatorColor: UIColor(rgb: 0xBEC2C6), - panelIconColor: UIColor(rgb: 0x858e99), - panelHighlightedIconBackgroundColor: UIColor(rgb: 0x858e99, alpha: 0.2), - stickersBackgroundColor: UIColor(rgb: 0xe8ebf0), - stickersSectionTextColor: UIColor(rgb: 0x9099A2), - stickersSearchBackgroundColor: UIColor(rgb: 0xd9dbe1), - stickersSearchPlaceholderColor: UIColor(rgb: 0x8e8e93), - stickersSearchPrimaryColor: .black, - stickersSearchControlColor: UIColor(rgb: 0x8e8e93), - gifsBackgroundColor: .white -) - -private let inputButtonPanel = PresentationThemeInputButtonPanel( - panelSerapatorColor: UIColor(rgb: 0xBEC2C6), - panelBackgroundColor: UIColor(rgb: 0xdee2e6), - buttonFillColor: .white, - buttonStrokeColor: UIColor(rgb: 0xc3c7c9), - buttonHighlightedFillColor: UIColor(rgb: 0xa8b3c0), - buttonHighlightedStrokeColor: UIColor(rgb: 0xc3c7c9), - buttonTextColor: .black -) - -private let historyNavigation = PresentationThemeChatHistoryNavigation( - fillColor: .white, - strokeColor: UIColor(rgb: 0x000000, alpha: 0.15), - foregroundColor: UIColor(rgb: 0x88888D), - badgeBackgroundColor: accentColor, - badgeStrokeColor: accentColor, - badgeTextColor: .white -) - -private let chat = PresentationThemeChat( - bubble: bubble, - serviceMessage: serviceMessage, - inputPanel: inputPanel, - inputMediaPanel: inputMediaPanel, - inputButtonPanel: inputButtonPanel, - historyNavigation: historyNavigation -) - -private let chatDay = PresentationThemeChat( - bubble: bubbleDay, - serviceMessage: serviceMessageDay, - inputPanel: inputPanel, - inputMediaPanel: inputMediaPanel, - inputButtonPanel: inputButtonPanel, - historyNavigation: historyNavigation -) - -private let actionSheet = PresentationThemeActionSheet( - dimColor: UIColor(white: 0.0, alpha: 0.4), - backgroundType: .light, - opaqueItemBackgroundColor: .white, - itemBackgroundColor: UIColor(white: 1.0, alpha: 0.8), - opaqueItemHighlightedBackgroundColor: UIColor(white: 0.9, alpha: 1.0), - itemHighlightedBackgroundColor: UIColor(white: 0.9, alpha: 0.7), - standardActionTextColor: accentColor, - opaqueItemSeparatorColor: UIColor(white: 0.9, alpha: 1.0), - destructiveActionTextColor: destructiveColor, - disabledActionTextColor: UIColor(rgb: 0x4d4d4d), - primaryTextColor: .black, - secondaryTextColor: UIColor(rgb: 0x5e5e5e), - controlAccentColor: accentColor, - inputBackgroundColor: UIColor(rgb: 0xe9e9e9), - inputPlaceholderColor: UIColor(rgb: 0x818086), - inputTextColor: .black, - inputClearButtonColor: UIColor(rgb: 0x7b7b81), - checkContentColor: .white -) - -private let inAppNotification = PresentationThemeInAppNotification( - fillColor: .white, - primaryTextColor: .black, - expandedNotification: PresentationThemeExpandedNotification( - backgroundType: .light, - navigationBar: PresentationThemeExpandedNotificationNavigationBar( - backgroundColor: .white, - primaryTextColor: .black, - controlColor: UIColor(rgb: 0x7e8791), - separatorColor: UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0) + + let rootTabBar = PresentationThemeRootTabBar( + backgroundColor: UIColor(rgb: 0xf7f7f7), + separatorColor: UIColor(rgb: 0xa3a3a3), + iconColor: UIColor(rgb: 0xA1A1A1), + selectedIconColor: accentColor, + textColor: UIColor(rgb: 0xA1A1A1), + selectedTextColor: accentColor, + badgeBackgroundColor: UIColor(rgb: 0xff3b30), + badgeStrokeColor: UIColor(rgb: 0xff3b30), + badgeTextColor: .white + ) + + let rootNavigationBar = PresentationThemeRootNavigationBar( + buttonColor: accentColor, + primaryTextColor: .black, + secondaryTextColor: UIColor(rgb: 0x787878), + controlColor: UIColor(rgb: 0x7e8791), + accentTextColor: accentColor, + backgroundColor: UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0), + separatorColor: UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0), + badgeBackgroundColor: UIColor(rgb: 0xff3b30), + badgeStrokeColor: UIColor(rgb: 0xff3b30), + badgeTextColor: .white + ) + + let activeNavigationSearchBar = PresentationThemeActiveNavigationSearchBar( + backgroundColor: .white, + accentColor: accentColor, + inputFillColor: UIColor(rgb: 0xe9e9e9), + inputTextColor: .black, + inputPlaceholderTextColor: UIColor(rgb: 0x8e8e93), + inputIconColor: UIColor(rgb: 0x8e8e93), + inputClearButtonColor: UIColor(rgb: 0x7b7b81), + separatorColor: UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0) + ) + + let rootController = PresentationThemeRootController( + statusBar: rootStatusBar, + tabBar: rootTabBar, + navigationBar: rootNavigationBar, + activeNavigationSearchBar: activeNavigationSearchBar + ) + + let switchColors = PresentationThemeSwitch( + frameColor: UIColor(rgb: 0xe0e0e0), + handleColor: UIColor(rgb: 0xffffff), + contentColor: UIColor(rgb: 0x42d451) + ) + + let list = PresentationThemeList( + blocksBackgroundColor: UIColor(rgb: 0xefeff4), + plainBackgroundColor: .white, + itemPrimaryTextColor: .black, + itemSecondaryTextColor: UIColor(rgb: 0x8e8e93), + itemDisabledTextColor: UIColor(rgb: 0x8e8e93), + itemAccentColor: accentColor, + itemDestructiveColor: destructiveColor, + itemPlaceholderTextColor: UIColor(rgb: 0xc8c8ce), + itemBlocksBackgroundColor: .white, + itemHighlightedBackgroundColor: UIColor(rgb: 0xd9d9d9), + itemBlocksSeparatorColor: UIColor(rgb: 0xc8c7cc), + itemPlainSeparatorColor: UIColor(rgb: 0xc8c7cc), + disclosureArrowColor: UIColor(rgb: 0xbab9be), + sectionHeaderTextColor: UIColor(rgb: 0x6d6d72), + freeTextColor: UIColor(rgb: 0x6d6d72), + freeTextErrorColor: UIColor(rgb: 0xcf3030), + freeTextSuccessColor: UIColor(rgb: 0x26972c), + freeMonoIcon: UIColor(rgb: 0x7e7e87), + itemSwitchColors: switchColors, + itemDisclosureActions: PresentationThemeItemDisclosureActions( + neutral1: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xbcbcc3), foregroundColor: .white), + neutral2: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xaaaab3), foregroundColor: .white), + destructive: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xff3824), foregroundColor: .white), + constructive: PresentationThemeItemDisclosureAction(fillColor: constructiveColor, foregroundColor: .white), + accent: PresentationThemeItemDisclosureAction(fillColor: accentColor, foregroundColor: .white), + warning: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xff9500), foregroundColor: .white) + ), + itemCheckColors: PresentationThemeCheck( + strokeColor: UIColor(rgb: 0xC7C7CC), + fillColor: accentColor, + foregroundColor: .white + ), + controlSecondaryColor: UIColor(rgb: 0xdedede), + freeInputField: PresentationInputFieldTheme( + backgroundColor: UIColor(rgb: 0xd6d6dc), + placeholderColor: UIColor(rgb: 0x96979d), + primaryColor: .black ) ) -) + + let chatList = PresentationThemeChatList( + backgroundColor: .white, + itemSeparatorColor: UIColor(rgb: 0xc8c7cc), + itemBackgroundColor: .white, + pinnedItemBackgroundColor: UIColor(rgb: 0xf7f7f7), + itemHighlightedBackgroundColor: UIColor(rgb: 0xd9d9d9), + titleColor: .black, + secretTitleColor: secretColor, + dateTextColor: UIColor(rgb: 0x8e8e93), + authorNameColor: .black, + messageTextColor: UIColor(rgb: 0x8e8e93), + messageDraftTextColor: UIColor(rgb: 0xdd4b39), + checkmarkColor: UIColor(rgb: 0x21c004), + pendingIndicatorColor: UIColor(rgb: 0x8e8e93), + muteIconColor: UIColor(rgb: 0xa7a7ad), + unreadBadgeActiveBackgroundColor: accentColor, + unreadBadgeActiveTextColor: .white, + unreadBadgeInactiveBackgroundColor: UIColor(rgb: 0xb6b6bb), + unreadBadgeInactiveTextColor: .white, + pinnedBadgeColor: UIColor(rgb: 0x939399), + pinnedSearchBarColor: UIColor(rgb: 0xe5e5e5), + regularSearchBarColor: UIColor(rgb: 0xe9e9e9), + sectionHeaderFillColor: UIColor(rgb: 0xf7f7f7), + sectionHeaderTextColor: UIColor(rgb: 0x8e8e93), + searchBarKeyboardColor: .light, + verifiedIconFillColor: accentColor, + verifiedIconForegroundColor: .white, + secretIconColor: secretColor + ) + + let chatListDay = PresentationThemeChatList( + backgroundColor: .white, + itemSeparatorColor: UIColor(rgb: 0xc8c7cc), + itemBackgroundColor: .white, + pinnedItemBackgroundColor: UIColor(rgb: 0xf7f7f7), + itemHighlightedBackgroundColor: UIColor(rgb: 0xd9d9d9), + titleColor: .black, + secretTitleColor: secretColor, + dateTextColor: UIColor(rgb: 0x8e8e93), + authorNameColor: .black, + messageTextColor: UIColor(rgb: 0x8e8e93), + messageDraftTextColor: UIColor(rgb: 0xdd4b39), + checkmarkColor: accentColor, + pendingIndicatorColor: UIColor(rgb: 0x8e8e93), + muteIconColor: UIColor(rgb: 0xa7a7ad), + unreadBadgeActiveBackgroundColor: accentColor, + unreadBadgeActiveTextColor: .white, + unreadBadgeInactiveBackgroundColor: UIColor(rgb: 0xb6b6bb), + unreadBadgeInactiveTextColor: .white, + pinnedBadgeColor: UIColor(rgb: 0x939399), + pinnedSearchBarColor: UIColor(rgb: 0xe5e5e5), + regularSearchBarColor: UIColor(rgb: 0xe9e9e9), + sectionHeaderFillColor: UIColor(rgb: 0xf7f7f7), + sectionHeaderTextColor: UIColor(rgb: 0x8e8e93), + searchBarKeyboardColor: .light, + verifiedIconFillColor: accentColor, + verifiedIconForegroundColor: .white, + secretIconColor: secretColor + ) + + let bubble = PresentationThemeChatBubble( + incoming: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: UIColor(rgb: 0x86A9C9, alpha: 0.5)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: UIColor(rgb: 0x86A9C9, alpha: 0.5))), + outgoing: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xE1FFC7), highlightedFill: UIColor(rgb: 0xc8ffa6), stroke: UIColor(rgb: 0x86A9C9, alpha: 0.5)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xE1FFC7), highlightedFill: UIColor(rgb: 0xc8ffa6), stroke: UIColor(rgb: 0x86A9C9, alpha: 0.5))), + freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: UIColor(rgb: 0x86A9C9, alpha: 0.5)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xd9f4ff), stroke: UIColor(rgb: 0x86A9C9, alpha: 0.5))), + incomingPrimaryTextColor: UIColor(rgb: 0x000000), + incomingSecondaryTextColor: UIColor(rgb: 0x525252, alpha: 0.6), + incomingLinkTextColor: UIColor(rgb: 0x004bad), + incomingLinkHighlightColor: accentColor.withAlphaComponent(0.3), + outgoingPrimaryTextColor: UIColor(rgb: 0x000000), + outgoingSecondaryTextColor: UIColor(rgb: 0x008c09, alpha: 0.8), + outgoingLinkTextColor: UIColor(rgb: 0x004bad), + outgoingLinkHighlightColor: accentColor.withAlphaComponent(0.3), + infoPrimaryTextColor: UIColor(rgb: 0x000000), + infoLinkTextColor: UIColor(rgb: 0x004bad), + incomingAccentTextColor: UIColor(rgb: 0x3ca7fe), + outgoingAccentTextColor: UIColor(rgb: 0x00a700), + incomingAccentControlColor: UIColor(rgb: 0x3ca7fe), + outgoingAccentControlColor: UIColor(rgb: 0x3FC33B), + incomingMediaActiveControlColor: UIColor(rgb: 0x3ca7fe), + outgoingMediaActiveControlColor: UIColor(rgb: 0x3FC33B), + incomingMediaInactiveControlColor: UIColor(rgb: 0xcacaca), + outgoingMediaInactiveControlColor: UIColor(rgb: 0x93D987), + outgoingCheckColor: UIColor(rgb: 0x19C700), + incomingPendingActivityColor: UIColor(rgb: 0x525252, alpha: 0.6), + outgoingPendingActivityColor: UIColor(rgb: 0x42b649), + mediaDateAndStatusFillColor: UIColor(white: 0.0, alpha: 0.5), + mediaDateAndStatusTextColor: .white, + incomingFileTitleColor: UIColor(rgb: 0x0b8bed), + outgoingFileTitleColor: UIColor(rgb: 0x3faa3c), + incomingFileDescriptionColor: UIColor(rgb: 0x999999), + outgoingFileDescriptionColor: UIColor(rgb: 0x6fb26a), + incomingFileDurationColor: UIColor(rgb: 0x525252, alpha: 0.6), + outgoingFileDurationColor: UIColor(rgb: 0x008c09, alpha: 0.8), + shareButtonFillColor: UIColor(rgb: 0x748391, alpha: 0.45), + shareButtonStrokeColor: .clear, + shareButtonForegroundColor: .white, + mediaOverlayControlBackgroundColor: UIColor(white: 0.0, alpha: 0.6), + mediaOverlayControlForegroundColor: UIColor(white: 1.0, alpha: 1.0), + actionButtonsIncomingFillColor: UIColor(rgb: 0x596E89, alpha: 0.35), + actionButtonsIncomingStrokeColor: .clear, + actionButtonsIncomingTextColor: .white, + actionButtonsOutgoingFillColor: UIColor(rgb: 0x596E89, alpha: 0.35), + actionButtonsOutgoingStrokeColor: .clear, + actionButtonsOutgoingTextColor: .white, + selectionControlBorderColor: UIColor(rgb: 0xC7C7CC), + selectionControlFillColor: accentColor, + selectionControlForegroundColor: .white, + mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.2) + ) + + let bubbleDay = PresentationThemeChatBubble( + incoming: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xffffff), highlightedFill: UIColor(rgb: 0xDADADE), stroke: UIColor(rgb: 0xffffff)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xF1F1F4), highlightedFill: UIColor(rgb: 0xDADADE), stroke: UIColor(rgb: 0xF1F1F4))), + outgoing: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: accentColor, highlightedFill: accentColor.withMultipliedBrightnessBy(0.7), stroke: accentColor), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: accentColor, highlightedFill: accentColor.withMultipliedBrightnessBy(0.7), stroke: accentColor)), + freeform: PresentationThemeBubbleColor(withWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xE5E5EA), highlightedFill: UIColor(rgb: 0xDADADE), stroke: UIColor(rgb: 0xE5E5EA)), withoutWallpaper: PresentationThemeBubbleColorComponents(fill: UIColor(rgb: 0xE5E5EA), highlightedFill: UIColor(rgb: 0xDADADE), stroke: UIColor(rgb: 0xE5E5EA))), + incomingPrimaryTextColor: UIColor(rgb: 0x000000), + incomingSecondaryTextColor: UIColor(rgb: 0x525252, alpha: 0.6), + incomingLinkTextColor: UIColor(rgb: 0x004bad), + incomingLinkHighlightColor: accentColor.withAlphaComponent(0.3), + outgoingPrimaryTextColor: UIColor(rgb: 0xffffff), + outgoingSecondaryTextColor: UIColor(rgb: 0xffffff, alpha: 0.6), + outgoingLinkTextColor: UIColor(rgb: 0xffffff), + outgoingLinkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.3), + infoPrimaryTextColor: UIColor(rgb: 0x000000), + infoLinkTextColor: UIColor(rgb: 0x004bad), + incomingAccentTextColor: accentColor, + outgoingAccentTextColor: UIColor(white: 1.0, alpha: 0.7), + incomingAccentControlColor: accentColor, + outgoingAccentControlColor: UIColor(white: 1.0, alpha: 0.7), + incomingMediaActiveControlColor: accentColor, + outgoingMediaActiveControlColor: UIColor(white: 1.0, alpha: 1.0), + incomingMediaInactiveControlColor: UIColor(rgb: 0xcacaca), + outgoingMediaInactiveControlColor: UIColor(white: 1.0, alpha: 0.6), + outgoingCheckColor: UIColor(rgb: 0xffffff, alpha: 0.6), + incomingPendingActivityColor: UIColor(rgb: 0x525252, alpha: 0.6), + outgoingPendingActivityColor: UIColor(white: 1.0, alpha: 0.7), + mediaDateAndStatusFillColor: UIColor(white: 0.0, alpha: 0.5), + mediaDateAndStatusTextColor: .white, + incomingFileTitleColor: UIColor(rgb: 0x0b8bed), + outgoingFileTitleColor: UIColor(rgb: 0xffffff), + incomingFileDescriptionColor: UIColor(rgb: 0x999999), + outgoingFileDescriptionColor: UIColor(white: 1.0, alpha: 0.7), + incomingFileDurationColor: UIColor(rgb: 0x525252, alpha: 0.6), + outgoingFileDurationColor: UIColor(white: 1.0, alpha: 0.7), + shareButtonFillColor: UIColor(rgb: 0xffffff, alpha: 0.5), + shareButtonStrokeColor: UIColor(rgb: 0xE5E5EA), + shareButtonForegroundColor: accentColor, + mediaOverlayControlBackgroundColor: UIColor(white: 0.0, alpha: 0.6), + mediaOverlayControlForegroundColor: UIColor(white: 1.0, alpha: 1.0), + actionButtonsIncomingFillColor: UIColor(rgb: 0xffffff, alpha: 0.5), + actionButtonsIncomingStrokeColor: UIColor(rgb: 0x3996ee), + actionButtonsIncomingTextColor: UIColor(rgb: 0x3996ee), + actionButtonsOutgoingFillColor: UIColor(rgb: 0xffffff, alpha: 0.5), + actionButtonsOutgoingStrokeColor: UIColor(rgb: 0x3996ee), + actionButtonsOutgoingTextColor: UIColor(rgb: 0x3996ee), + selectionControlBorderColor: UIColor(rgb: 0xC7C7CC), + selectionControlFillColor: accentColor, + selectionControlForegroundColor: .white, + mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.2) + ) + + let serviceMessage = PresentationThemeServiceMessage( + serviceMessageFillColor: UIColor(rgb: 0x748391, alpha: 0.45), + serviceMessagePrimaryTextColor: .white, + serviceMessageLinkHighlightColor: UIColor(rgb: 0x748391, alpha: 0.25), + unreadBarFillColor: UIColor(white: 1.0, alpha: 0.9), + unreadBarStrokeColor: UIColor(white: 0.0, alpha: 0.2), + unreadBarTextColor: UIColor(rgb: 0x86868d), + dateFillStaticColor: UIColor(rgb: 0x748391, alpha: 0.45), + dateFillFloatingColor: UIColor(rgb: 0x939fab, alpha: 0.5), + dateTextColor: .white + ) + + let serviceMessageDay = PresentationThemeServiceMessage( + serviceMessageFillColor: UIColor(rgb: 0xffffff, alpha: 0.45), + serviceMessagePrimaryTextColor: UIColor(rgb: 0x8D8E93), + serviceMessageLinkHighlightColor: UIColor(rgb: 0x748391, alpha: 0.25), + unreadBarFillColor: UIColor(white: 1.0, alpha: 1.0), + unreadBarStrokeColor: UIColor(white: 1.0, alpha: 1.0), + unreadBarTextColor: UIColor(rgb: 0x8D8E93), + dateFillStaticColor: UIColor(rgb: 0xffffff, alpha: 0.45), + dateFillFloatingColor: UIColor(rgb: 0xffffff, alpha: 0.8), + dateTextColor: UIColor(rgb: 0x8D8E93) + ) + + let inputPanelMediaRecordingControl = PresentationThemeChatInputPanelMediaRecordingControl( + buttonColor: accentColor, + micLevelColor: accentColor.withAlphaComponent(0.2), + activeIconColor: .white, + panelControlFillColor: UIColor(rgb: 0xf7f7f7), + panelControlStrokeColor: UIColor(rgb: 0xb2b2b2), + panelControlContentPrimaryColor: UIColor(rgb: 0x9597a0), + panelControlContentAccentColor: accentColor + ) + + let inputPanel = PresentationThemeChatInputPanel( + panelBackgroundColor: UIColor(rgb: 0xf7f7f7), + panelStrokeColor: UIColor(rgb: 0xb2b2b2), + panelControlAccentColor: accentColor, + panelControlColor: UIColor(rgb: 0x858e99), + panelControlDisabledColor: UIColor(rgb: 0x727b87, alpha: 0.5), + panelControlDestructiveColor: UIColor(rgb: 0xff3b30), + inputBackgroundColor: UIColor(rgb: 0xffffff), + inputStrokeColor: UIColor(rgb: 0xd9dcdf), + inputPlaceholderColor: UIColor(rgb: 0xbebec0), + inputTextColor: .black, + inputControlColor: UIColor(rgb: 0xa0a7b0), + actionControlFillColor: accentColor, + actionControlForegroundColor: .white, + primaryTextColor: .black, + secondaryTextColor: UIColor(rgb: 0x5e5e5e), + mediaRecordingDotColor: UIColor(rgb: 0xed2521), + keyboardColor: .light, + mediaRecordingControl: inputPanelMediaRecordingControl + ) + + let inputMediaPanel = PresentationThemeInputMediaPanel( + panelSerapatorColor: UIColor(rgb: 0xBEC2C6), + panelIconColor: UIColor(rgb: 0x858e99), + panelHighlightedIconBackgroundColor: UIColor(rgb: 0x858e99, alpha: 0.2), + stickersBackgroundColor: UIColor(rgb: 0xe8ebf0), + stickersSectionTextColor: UIColor(rgb: 0x9099A2), + stickersSearchBackgroundColor: UIColor(rgb: 0xd9dbe1), + stickersSearchPlaceholderColor: UIColor(rgb: 0x8e8e93), + stickersSearchPrimaryColor: .black, + stickersSearchControlColor: UIColor(rgb: 0x8e8e93), + gifsBackgroundColor: .white + ) + + let inputButtonPanel = PresentationThemeInputButtonPanel( + panelSerapatorColor: UIColor(rgb: 0xBEC2C6), + panelBackgroundColor: UIColor(rgb: 0xdee2e6), + buttonFillColor: .white, + buttonStrokeColor: UIColor(rgb: 0xc3c7c9), + buttonHighlightedFillColor: UIColor(rgb: 0xa8b3c0), + buttonHighlightedStrokeColor: UIColor(rgb: 0xc3c7c9), + buttonTextColor: .black + ) + + let historyNavigation = PresentationThemeChatHistoryNavigation( + fillColor: .white, + strokeColor: UIColor(rgb: 0x000000, alpha: 0.15), + foregroundColor: UIColor(rgb: 0x88888D), + badgeBackgroundColor: accentColor, + badgeStrokeColor: accentColor, + badgeTextColor: .white + ) + + let chat = PresentationThemeChat( + bubble: bubble, + serviceMessage: serviceMessage, + inputPanel: inputPanel, + inputMediaPanel: inputMediaPanel, + inputButtonPanel: inputButtonPanel, + historyNavigation: historyNavigation + ) + + let chatDay = PresentationThemeChat( + bubble: bubbleDay, + serviceMessage: serviceMessageDay, + inputPanel: inputPanel, + inputMediaPanel: inputMediaPanel, + inputButtonPanel: inputButtonPanel, + historyNavigation: historyNavigation + ) + + let actionSheet = PresentationThemeActionSheet( + dimColor: UIColor(white: 0.0, alpha: 0.4), + backgroundType: .light, + opaqueItemBackgroundColor: .white, + itemBackgroundColor: UIColor(white: 1.0, alpha: 0.8), + opaqueItemHighlightedBackgroundColor: UIColor(white: 0.9, alpha: 1.0), + itemHighlightedBackgroundColor: UIColor(white: 0.9, alpha: 0.7), + standardActionTextColor: accentColor, + opaqueItemSeparatorColor: UIColor(white: 0.9, alpha: 1.0), + destructiveActionTextColor: destructiveColor, + disabledActionTextColor: UIColor(rgb: 0x4d4d4d), + primaryTextColor: .black, + secondaryTextColor: UIColor(rgb: 0x5e5e5e), + controlAccentColor: accentColor, + inputBackgroundColor: UIColor(rgb: 0xe9e9e9), + inputPlaceholderColor: UIColor(rgb: 0x818086), + inputTextColor: .black, + inputClearButtonColor: UIColor(rgb: 0x7b7b81), + checkContentColor: .white + ) + + let inAppNotification = PresentationThemeInAppNotification( + fillColor: .white, + primaryTextColor: .black, + expandedNotification: PresentationThemeExpandedNotification( + backgroundType: .light, + navigationBar: PresentationThemeExpandedNotificationNavigationBar( + backgroundColor: .white, + primaryTextColor: .black, + controlColor: UIColor(rgb: 0x7e8791), + separatorColor: UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0) + ) + ) + ) + + return PresentationTheme( + name: .builtin(day ? .day : .dayClassic), + overallDarkAppearance: false, + allowsCustomWallpapers: true, + rootController: rootController, + list: list, + chatList: day ? chatListDay : chatList, + chat: day ? chatDay : chat, + actionSheet: actionSheet, + inAppNotification: inAppNotification + ) +} -public let defaultPresentationTheme = PresentationTheme( - name: .builtin(.dayClassic), - overallDarkAppearance: false, - allowsCustomWallpapers: true, - rootController: rootController, - list: list, - chatList: chatList, - chat: chat, - actionSheet: actionSheet, - inAppNotification: inAppNotification -) +public let defaultPresentationTheme = makeDefaultPresentationTheme(accentColor: UIColor(rgb: 0x007ee5), day: false) -let defaultDayPresentationTheme = PresentationTheme( - name: .builtin(.day), - overallDarkAppearance: false, - allowsCustomWallpapers: false, - rootController: rootController, - list: list, - chatList: chatListDay, - chat: chatDay, - actionSheet: actionSheet, - inAppNotification: inAppNotification -) +let defaultDayAccentColor: Int32 = 0x007ee5 + +func makeDefaultDayPresentationTheme(accentColor: Int32?) -> PresentationTheme { + let color: UIColor + if let accentColor = accentColor { + color = UIColor(rgb: UInt32(bitPattern: accentColor)) + } else { + color = UIColor(rgb: UInt32(bitPattern: defaultDayAccentColor)) + } + return makeDefaultPresentationTheme(accentColor: color, day: true) +} diff --git a/TelegramUI/DeviceContactData.swift b/TelegramUI/DeviceContactData.swift index 5a430868f5..71a1fc13a9 100644 --- a/TelegramUI/DeviceContactData.swift +++ b/TelegramUI/DeviceContactData.swift @@ -1,5 +1,7 @@ - import Foundation +import Foundation import Contacts +import Postbox +import TelegramCore public final class DeviceContactPhoneNumberData: Equatable { public let label: String @@ -383,3 +385,12 @@ extension DeviceContactExtendedData { self.init(basicData: basicData, middleName: contact.middleName, prefix: contact.namePrefix, suffix: contact.nameSuffix, organization: contact.organizationName, jobTitle: contact.jobTitle, department: contact.departmentName, emailAddresses: emailAddresses, urls: urls, addresses: addresses, birthdayDate: birthdayDate, socialProfiles: socialProfiles, instantMessagingProfiles: instantMessagingProfiles) } } + +extension DeviceContactExtendedData { + convenience init?(peer: Peer) { + guard let user = peer as? TelegramUser, let phone = user.phone, !phone.isEmpty else { + return nil + } + self.init(basicData: DeviceContactBasicData(firstName: user.firstName ?? "", lastName: user.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phone)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: []) + } +} diff --git a/TelegramUI/DeviceContactInfoController.swift b/TelegramUI/DeviceContactInfoController.swift index 35191188e4..4c1e1ddfcb 100644 --- a/TelegramUI/DeviceContactInfoController.swift +++ b/TelegramUI/DeviceContactInfoController.swift @@ -495,7 +495,17 @@ private func deviceContactInfoEntries(account: Account, presentationData: Presen jobSummary = contactData.jobTitle } - entries.append(.info(entries.count, presentationData.theme, presentationData.strings, peer: peer ?? TelegramUser(id: PeerId(namespace: -1, id: 0), accessHash: nil, firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []), state: ItemListAvatarAndNameInfoItemState(editingName: editingName, updatingName: nil), job: jobSummary)) + var personName: (String, String) = (contactData.basicData.firstName, contactData.basicData.lastName) + if let editingName = editingName { + switch editingName { + case let .personName(firstName, lastName): + personName = (firstName, lastName) + default: + break + } + } + + entries.append(.info(entries.count, presentationData.theme, presentationData.strings, peer: peer ?? TelegramUser(id: PeerId(namespace: -1, id: 0), accessHash: nil, firstName: personName.0, lastName: personName.1, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []), state: ItemListAvatarAndNameInfoItemState(editingName: editingName, updatingName: nil), job: jobSummary)) if !selecting { if let _ = peer { @@ -589,7 +599,7 @@ private func deviceContactInfoEntries(account: Account, presentationData: Presen enum DeviceContactInfoSubject { case vcard(Peer?, DeviceContactStableId?, DeviceContactExtendedData) case filter(peer: Peer?, contactId: DeviceContactStableId?, contactData: DeviceContactExtendedData, completion: (Peer?, DeviceContactExtendedData) -> Void) - case create(peer: Peer?, contactData: DeviceContactExtendedData, completion: (Peer?, DeviceContactExtendedData) -> Void) + case create(peer: Peer?, contactData: DeviceContactExtendedData, completion: (Peer?, DeviceContactStableId, DeviceContactExtendedData) -> Void) var peer: Peer? { switch self { @@ -793,7 +803,7 @@ func deviceContactInfoController(account: Account, subject: DeviceContactInfoSub presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } case .createContact: - presentControllerImpl?(deviceContactInfoController(account: account, subject: .create(peer: subject.peer, contactData: subject.contactData, completion: { peer, contactData in + presentControllerImpl?(deviceContactInfoController(account: account, subject: .create(peer: subject.peer, contactData: subject.contactData, completion: { peer, stableId, contactData in dismissImpl?(false) if let peer = peer { @@ -949,6 +959,7 @@ func deviceContactInfoController(account: Account, subject: DeviceContactInfoSub if let navigationController = controller?.navigationController as? NavigationController { let _ = navigationController.popViewController(animated: animated) } else { + controller?.view.endEditing(true) controller?.dismiss() } } @@ -1037,7 +1048,7 @@ private func addContactToExisting(account: Account, parentController: ViewContro let _ = (dataSignal |> deliverOnMainQueue).start(next: { peer, stableId in guard let stableId = stableId else { - parentController.present(deviceContactInfoController(account: account, subject: .create(peer: peer, contactData: contactData, completion: { peer, contactData in + parentController.present(deviceContactInfoController(account: account, subject: .create(peer: peer, contactData: contactData, completion: { peer, stableId, contactData in })), in: .window(.root)) return @@ -1070,3 +1081,38 @@ private func addContactToExisting(account: Account, parentController: ViewContro } }) } + +func addContactOptionsController(account: Account, peer: Peer?, contactData: DeviceContactExtendedData) -> ActionSheetController { + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + let controller = ActionSheetController(presentationTheme: presentationData.theme) + let dismissAction: () -> Void = { [weak controller] in + controller?.dismissAnimated() + } + + controller.setItemGroups([ + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Profile_CreateNewContact, action: { [weak controller] in + controller?.present(deviceContactInfoController(account: account, subject: .create(peer: peer, contactData: contactData, completion: { peer, stableId, contactData in + + if let peer = peer { + + } else { + + } + })), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + dismissAction() + }), + ActionSheetButtonItem(title: presentationData.strings.Profile_AddToExisting, action: { [weak controller] in + guard let controller = controller else { + return + } + addContactToExisting(account: account, parentController: controller, contactData: contactData, completion: { peer, contactId, contactData in + + }) + dismissAction() + }) + ]), + ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) + ]) + return controller +} diff --git a/TelegramUI/EDSunriseSet.h b/TelegramUI/EDSunriseSet.h new file mode 100755 index 0000000000..73f2714c08 --- /dev/null +++ b/TelegramUI/EDSunriseSet.h @@ -0,0 +1,51 @@ +// +// EDSunriseSet.h +// +// Created by Ernesto García on 20/08/11. +// Copyright 2011 Ernesto García. All rights reserved. +// + +// C/C++ sun calculations created by Paul Schlyter +// sunriset.c +// http://stjarnhimlen.se/english.html +// SUNRISET.C - computes Sun rise/set times, start/end of twilight, and +// the length of the day at any date and latitude +// Written as DAYLEN.C, 1989-08-16 +// Modified to SUNRISET.C, 1992-12-01 +// (c) Paul Schlyter, 1989, 1992 +// Released to the public domain by Paul Schlyter, December 1992 +// + +#import + +#if ! __has_feature(objc_arc) +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag in this file. +#endif + +@interface EDSunriseSet : NSObject + +@property (readonly, strong) NSDate *date; +@property (readonly, strong) NSDate *sunset; +@property (readonly, strong) NSDate *sunrise; +@property (readonly, strong) NSDate *civilTwilightStart; +@property (readonly, strong) NSDate *civilTwilightEnd; +@property (readonly, strong) NSDate *nauticalTwilightStart; +@property (readonly, strong) NSDate *nauticalTwilightEnd; +@property (readonly, strong) NSDate *astronomicalTwilightStart; +@property (readonly, strong) NSDate *astronomicalTwilightEnd; + +@property (readonly, strong) NSDateComponents* localSunrise; +@property (readonly, strong) NSDateComponents* localSunset; +@property (readonly, strong) NSDateComponents* localCivilTwilightStart; +@property (readonly, strong) NSDateComponents* localCivilTwilightEnd; +@property (readonly, strong) NSDateComponents* localNauticalTwilightStart; +@property (readonly, strong) NSDateComponents* localNauticalTwilightEnd; +@property (readonly, strong) NSDateComponents* localAstronomicalTwilightStart; +@property (readonly, strong) NSDateComponents* localAstronomicalTwilightEnd; + + +-(instancetype)initWithDate:(NSDate*)date timezone:(NSTimeZone*)timezone latitude:(double)latitude longitude:(double)longitude NS_DESIGNATED_INITIALIZER; ++(instancetype)sunrisesetWithDate:(NSDate*)date timezone:(NSTimeZone*)timezone latitude:(double)latitude longitude:(double)longitude; +-(instancetype) init __attribute__((unavailable("init not available. Use initWithDate:timeZone:latitude:longitude: instead"))); + +@end diff --git a/TelegramUI/EDSunriseSet.m b/TelegramUI/EDSunriseSet.m new file mode 100755 index 0000000000..27b4632e0e --- /dev/null +++ b/TelegramUI/EDSunriseSet.m @@ -0,0 +1,447 @@ +// +// EDSunriseSet.m +// +// Created by Ernesto García on 20/08/11. +// Copyright 2011 Ernesto García. All rights reserved. +// + +// C/C++ sun calculations created by Paul Schlyter +// sunriset.c +// http://stjarnhimlen.se/english.html +// SUNRISET.C - computes Sun rise/set times, start/end of twilight, and +// the length of the day at any date and latitude +// Written as DAYLEN.C, 1989-08-16 +// Modified to SUNRISET.C, 1992-12-01 +// (c) Paul Schlyter, 1989, 1992 +// Released to the public domain by Paul Schlyter, December 1992 +// + +#import "EDSunriseSet.h" + +// +// Defines from sunriset.c +// +#define INV360 ( 1.0 / 360.0 ) + +#define RADEG ( 180.0 / M_PI ) +#define DEGRAD ( M_PI / 180.0 ) + +/* The trigonometric functions in degrees */ + +#define sind(x) sin((x)*DEGRAD) +#define cosd(x) cos((x)*DEGRAD) +#define tand(x) tan((x)*DEGRAD) + +#define atand(x) (RADEG*atan(x)) +#define asind(x) (RADEG*asin(x)) +#define acosd(x) (RADEG*acos(x)) +#define atan2d(y,x) (RADEG*atan2(y,x)) + +/* A macro to compute the number of days elapsed since 2000 Jan 0.0 */ +/* (which is equal to 1999 Dec 31, 0h UT) */ +#define days_since_2000_Jan_0(y,m,d) \ +(367L*(y)-((7*((y)+(((m)+9)/12)))/4)+((275*(m))/9)+(d)-730530L) + + +#if defined(__IPHONE_8_0) || defined (__MAC_10_10) +#define EDGregorianCalendar NSCalendarIdentifierGregorian +#else +#define EDGregorianCalendar NSGregorianCalendar +#endif + + +#pragma mark - Readwrite accessors only private +@interface EDSunriseSet() + +@property (nonatomic) double latitude; +@property (nonatomic) double longitude; +@property (nonatomic, strong) NSTimeZone *timezone; +@property (nonatomic, strong) NSCalendar *calendar; +@property (nonatomic, strong) NSTimeZone *utcTimeZone; + +@property (readwrite, strong) NSDate *date; +@property (readwrite, strong) NSDate *sunset; +@property (readwrite, strong) NSDate *sunrise; +@property (readwrite, strong) NSDate *civilTwilightStart; +@property (readwrite, strong) NSDate *civilTwilightEnd; +@property (readwrite, strong) NSDate *nauticalTwilightStart; +@property (readwrite, strong) NSDate *nauticalTwilightEnd; +@property (readwrite, strong) NSDate *astronomicalTwilightStart; +@property (readwrite, strong) NSDate *astronomicalTwilightEnd; + +@property (readwrite, strong) NSDateComponents* localSunrise; +@property (readwrite, strong) NSDateComponents* localSunset; +@property (readwrite, strong) NSDateComponents* localCivilTwilightStart; +@property (readwrite, strong) NSDateComponents* localCivilTwilightEnd; +@property (readwrite, strong) NSDateComponents* localNauticalTwilightStart; +@property (readwrite, strong) NSDateComponents* localNauticalTwilightEnd; +@property (readwrite, strong) NSDateComponents* localAstronomicalTwilightStart; +@property (readwrite, strong) NSDateComponents* localAstronomicalTwilightEnd; + +@end + +#pragma mark - Calculations from sunriset.c +@implementation EDSunriseSet(Calculations) + +/*****************************************/ +/* Reduce angle to within 0..360 degrees */ +/*****************************************/ +-(double) revolution:(double) x +{ + return( x - 360.0 * floor( x * INV360 ) ); +} + +/*********************************************/ +/* Reduce angle to within -180..+180 degrees */ +/*********************************************/ +-(double) rev180:(double) x +{ + return( x - 360.0 * floor( x * INV360 + 0.5 ) ); +} + +-(double) GMST0:(double) d +{ + double sidtim0; + /* Sidtime at 0h UT = L (Sun's mean longitude) + 180.0 degr */ + /* L = M + w, as defined in sunpos(). Since I'm too lazy to */ + /* add these numbers, I'll let the C compiler do it for me. */ + /* Any decent C compiler will add the constants at compile */ + /* time, imposing no runtime or code overhead. */ + sidtim0 = [self revolution: ( 180.0 + 356.0470 + 282.9404 ) + + ( 0.9856002585 + 4.70935E-5 ) * d]; + return sidtim0; +} + +/******************************************************/ +/* Computes the Sun's ecliptic longitude and distance */ +/* at an instant given in d, number of days since */ +/* 2000 Jan 0.0. The Sun's ecliptic latitude is not */ +/* computed, since it's always very near 0. */ +/******************************************************/ +-(void) sunposAtDay:(double)d longitude:(double*)lon r:(double *)r +{ + double M, /* Mean anomaly of the Sun */ + w, /* Mean longitude of perihelion */ + /* Note: Sun's mean longitude = M + w */ + e, /* Eccentricity of Earth's orbit */ + E, /* Eccentric anomaly */ + x, y, /* x, y coordinates in orbit */ + v; /* True anomaly */ + + /* Compute mean elements */ + M = [self revolution:( 356.0470 + 0.9856002585 * d )]; + w = 282.9404 + 4.70935E-5 * d; + e = 0.016709 - 1.151E-9 * d; + + /* Compute true longitude and radius vector */ + E = M + e * RADEG * sind(M) * ( 1.0 + e * cosd(M) ); + x = cosd(E) - e; + y = sqrt( 1.0 - e*e ) * sind(E); + *r = sqrt( x*x + y*y ); /* Solar distance */ + v = atan2d( y, x ); /* True anomaly */ + *lon = v + w; /* True solar longitude */ + if ( *lon >= 360.0 ) + *lon -= 360.0; /* Make it 0..360 degrees */ +} + +-(void) sun_RA_decAtDay:(double)d RA:(double*)RA decl:(double *)dec r:(double *)r +{ + double lon, obl_ecl; + double xs, ys, zs; + double xe, ye, ze; + + /* Compute Sun's ecliptical coordinates */ + //sunpos( d, &lon, r ); + [self sunposAtDay:d longitude:&lon r:r]; + + /* Compute ecliptic rectangular coordinates */ + xs = *r * cosd(lon); + ys = *r * sind(lon); + zs = 0; /* because the Sun is always in the ecliptic plane! */ + + /* Compute obliquity of ecliptic (inclination of Earth's axis) */ + obl_ecl = 23.4393 - 3.563E-7 * d; + + /* Convert to equatorial rectangular coordinates - x is unchanged */ + xe = xs; + ye = ys * cosd(obl_ecl); + ze = ys * sind(obl_ecl); + + /* Convert to spherical coordinates */ + *RA = atan2d( ye, xe ); + *dec = atan2d( ze, sqrt(xe*xe + ye*ye) ); + +} /* sun_RA_dec */ + +#define sun_rise_set(year,month,day,lon,lat,rise,set) \ +__sunriset__( year, month, day, lon, lat, -35.0/60.0, 1, rise, set ) + +-(int)sunRiseSetForYear:(int)year month:(int)month day:(int)day longitude:(double)lon latitude:(double)lat + trise:(double *)trise tset:(double *)tset +{ + + return [self sunRiseSetHelperForYear:year month:month day:day longitude:lon latitude:lat altitude:(-35.0/60.0) + upper_limb:1 trise:trise tset:tset]; + +} +/* + #define civil_twilight(year,month,day,lon,lat,start,end) \ + __sunriset__( year, month, day, lon, lat, -6.0, 0, start, end ) + */ +-(int) civilTwilightForYear:(int)year month:(int)month day:(int)day longitude:(double)lon latitude:(double)lat + trise:(double *)trise tset:(double *)tset +{ + return [self sunRiseSetHelperForYear:year month:month day:day longitude:lon latitude:lat altitude:-6.0 + upper_limb:0 trise:trise tset:tset]; +} +/* + #define nautical_twilight(year,month,day,lon,lat,start,end) \ + __sunriset__( year, month, day, lon, lat, -12.0, 0, start, end ) + */ +-(int) nauticalTwilightForYear:(int)year month:(int)month day:(int)day longitude:(double)lon latitude:(double)lat + trise:(double *)trise tset:(double *)tset +{ + return [self sunRiseSetHelperForYear:year month:month day:day longitude:lon latitude:lat altitude:-12.0 + upper_limb:0 trise:trise tset:tset]; +} +/* + #define astronomical_twilight(year,month,day,lon,lat,start,end) \ + __sunriset__( year, month, day, lon, lat, -18.0, 0, start, end ) + */ +-(int) astronomicalTwilightForYear:(int)year month:(int)month day:(int)day longitude:(double)lon latitude:(double)lat + trise:(double *)trise tset:(double *)tset +{ + return [self sunRiseSetHelperForYear:year month:month day:day longitude:lon latitude:lat altitude:-18.0 + upper_limb:0 trise:trise tset:tset]; +} + +/***************************************************************************/ +/* Note: year,month,date = calendar date, 1801-2099 only. */ +/* Eastern longitude positive, Western longitude negative */ +/* Northern latitude positive, Southern latitude negative */ +/* The longitude value IS critical in this function! */ +/* altit = the altitude which the Sun should cross */ +/* Set to -35/60 degrees for rise/set, -6 degrees */ +/* for civil, -12 degrees for nautical and -18 */ +/* degrees for astronomical twilight. */ +/* upper_limb: non-zero -> upper limb, zero -> center */ +/* Set to non-zero (e.g. 1) when computing rise/set */ +/* times, and to zero when computing start/end of */ +/* twilight. */ +/* *rise = where to store the rise time */ +/* *set = where to store the set time */ +/* Both times are relative to the specified altitude, */ +/* and thus this function can be used to comupte */ +/* various twilight times, as well as rise/set times */ +/* Return value: 0 = sun rises/sets this day, times stored at */ +/* *trise and *tset. */ +/* +1 = sun above the specified "horizon" 24 hours. */ +/* *trise set to time when the sun is at south, */ +/* minus 12 hours while *tset is set to the south */ +/* time plus 12 hours. "Day" length = 24 hours */ +/* -1 = sun is below the specified "horizon" 24 hours */ +/* "Day" length = 0 hours, *trise and *tset are */ +/* both set to the time when the sun is at south. */ +/* */ +/**********************************************************************/ +-(int)sunRiseSetHelperForYear:(int)year month:(int)month day:(int)day longitude:(double)lon latitude:(double)lat + altitude:(double)altit upper_limb:(int)upper_limb trise:(double *)trise tset:(double *)tset +{ + double d, /* Days since 2000 Jan 0.0 (negative before) */ + sr, /* Solar distance, astronomical units */ + sRA, /* Sun's Right Ascension */ + sdec, /* Sun's declination */ + sradius, /* Sun's apparent radius */ + t, /* Diurnal arc */ + tsouth, /* Time when Sun is at south */ + sidtime; /* Local sidereal time */ + + int rc = 0; /* Return cde from function - usually 0 */ + + /* Compute d of 12h local mean solar time */ + d = days_since_2000_Jan_0(year,month,day) + 0.5 - lon/360.0; + + + /* Compute local sideral time of this moment */ + //sidtime = revolution( GMST0(d) + 180.0 + lon ); + sidtime = [self revolution:[self GMST0:d] + 180.0 + lon]; + /* Compute Sun's RA + Decl at this moment */ + //sun_RA_dec( d, &sRA, &sdec, &sr ); + [self sun_RA_decAtDay:d RA: &sRA decl:&sdec r:&sr]; + + /* Compute time when Sun is at south - in hours UT */ + //tsouth = 12.0 - rev180(sidtime - sRA)/15.0; + tsouth = 12.0 - [self rev180:sidtime - sRA] / 15.0; + + /* Compute the Sun's apparent radius, degrees */ + sradius = 0.2666 / sr; + + /* Do correction to upper limb, if necessary */ + if ( upper_limb ) + altit -= sradius; + + /* Compute the diurnal arc that the Sun traverses to reach */ + /* the specified altitide altit: */ + { + double cost; + cost = ( sind(altit) - sind(lat) * sind(sdec) ) / + ( cosd(lat) * cosd(sdec) ); + if ( cost >= 1.0 ) + rc = -1, t = 0.0; /* Sun always below altit */ + else if ( cost <= -1.0 ) + rc = +1, t = 12.0; /* Sun always above altit */ + else + t = acosd(cost)/15.0; /* The diurnal arc, hours */ + } + + /* Store rise and set times - in hours UT */ + *trise = tsouth - t; + *tset = tsouth + t; + + return rc; +} /* __sunriset__ */ + + +@end + + +#pragma mark - Private Implementation + +@implementation EDSunriseSet(Private) + +static const int kSecondsInHour= 60.0*60.0; + + +-(NSDate*)utcTime:(NSDateComponents*)dateComponents withOffset:(NSTimeInterval)interval +{ + [self.calendar setTimeZone:self.utcTimeZone]; + return [[self.calendar dateFromComponents:dateComponents] dateByAddingTimeInterval:(NSTimeInterval)(interval)]; +} + +-(NSDateComponents*)localTime:(NSDate*)refDate +{ + [self.calendar setTimeZone:self.timezone]; + // Return only hour, minute, seconds + NSDateComponents *dc = [self.calendar components:( NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond) fromDate:refDate] ; + + return dc; +} + +- (instancetype) init { + [super doesNotRecognizeSelector:_cmd]; + return nil; +} + +-(NSString *)description +{ + return [NSString stringWithFormat: + @"Date: %@\nTimeZone: %@\n" + @"Local Sunrise: %@\n" + @"Local Sunset: %@\n" + @"Local Civil Twilight Start: %@\n" + @"Local Civil Twilight End: %@\n" + @"Local Nautical Twilight Start: %@\n" + @"Local Nautical Twilight End: %@\n" + @"Local Astronomical Twilight Start: %@\n" + @"Local Astronomical Twilight End: %@\n", + self.date.description, self.timezone.name, + self.localSunrise.description, self.localSunset.description, + self.localCivilTwilightStart, self.localCivilTwilightEnd, + self.localNauticalTwilightStart, self.localNauticalTwilightEnd, + self.localAstronomicalTwilightStart, self.localAstronomicalTwilightEnd + ]; +} + +#pragma mark - Calculation methods + +-(void)calculateSunriseSunset +{ + // Get date components + [self.calendar setTimeZone:self.timezone]; + NSDateComponents *dateComponents = [self.calendar components:( NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay ) fromDate:self.date]; + + // Calculate sunrise and sunset + double rise=0.0, set=0.0; + [self sunRiseSetForYear:(int)[dateComponents year] month:(int)[dateComponents month] day:(int)[dateComponents day] longitude:self.longitude latitude:self.latitude + trise:&rise tset:&set ]; + NSTimeInterval secondsRise = rise*kSecondsInHour; + NSTimeInterval secondsSet = set*kSecondsInHour; + + self.sunrise = [self utcTime:dateComponents withOffset:(NSTimeInterval)secondsRise]; + self.sunset = [self utcTime:dateComponents withOffset:(NSTimeInterval)secondsSet]; + self.localSunrise = [self localTime:self.sunrise]; + self.localSunset = [self localTime:self.sunset]; +} + +-(void)calculateTwilight +{ + // Get date components + [self.calendar setTimeZone:self.timezone]; + NSDateComponents *dateComponents = [self.calendar components:( NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay ) fromDate:self.date]; + double start=0.0, end=0.0; + + // Civil twilight + [self civilTwilightForYear:(int)[dateComponents year] month:(int)[dateComponents month] day:(int)[dateComponents day] longitude:self.longitude latitude:self.latitude + trise:&start tset:&end ]; + self.civilTwilightStart = [self utcTime:dateComponents withOffset:(NSTimeInterval)(start*kSecondsInHour)]; + self.civilTwilightEnd = [self utcTime:dateComponents withOffset:(NSTimeInterval)(end*kSecondsInHour)]; + self.localCivilTwilightStart = [self localTime:self.civilTwilightStart]; + self.localCivilTwilightEnd = [self localTime:self.civilTwilightEnd]; + + // Nautical twilight + [self nauticalTwilightForYear:(int)[dateComponents year] month:(int)[dateComponents month] day:(int)[dateComponents day] longitude:self.longitude latitude:self.latitude + trise:&start tset:&end ]; + self.nauticalTwilightStart = [self utcTime:dateComponents withOffset:(NSTimeInterval)(start*kSecondsInHour)]; + self.nauticalTwilightEnd = [self utcTime:dateComponents withOffset:(NSTimeInterval)(end*kSecondsInHour)]; + self.localNauticalTwilightStart = [self localTime:self.nauticalTwilightStart]; + self.localNauticalTwilightEnd = [self localTime:self.nauticalTwilightEnd]; + // Astronomical twilight + [self astronomicalTwilightForYear:(int)[dateComponents year] month:(int)[dateComponents month] day:(int)[dateComponents day] longitude:self.longitude latitude:self.latitude + trise:&start tset:&end ]; + self.astronomicalTwilightStart = [self utcTime:dateComponents withOffset:(NSTimeInterval)(start*kSecondsInHour)]; + self.astronomicalTwilightEnd = [self utcTime:dateComponents withOffset:(NSTimeInterval)(end*kSecondsInHour)]; + self.localAstronomicalTwilightStart = [self localTime:self.astronomicalTwilightStart]; + self.localAstronomicalTwilightEnd = [self localTime:self.astronomicalTwilightEnd]; +} + +-(void)calculate +{ + [self calculateSunriseSunset]; + [self calculateTwilight]; +} + +@end + + +#pragma mark - Public Implementation + +@implementation EDSunriseSet + +#pragma mark - Initialization + +-(EDSunriseSet*)initWithDate:(NSDate*)date timezone:(NSTimeZone*)tz latitude:(double)latitude longitude:(double)longitude { + self = [super init]; + if( self ) + { + self.latitude = latitude; + self.longitude = longitude; + self.timezone = tz; + self.date = date; + + self.calendar = [[NSCalendar alloc] initWithCalendarIdentifier:EDGregorianCalendar]; + self.utcTimeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; + + [self calculate]; + + } + return self; +} + ++(EDSunriseSet*)sunrisesetWithDate:(NSDate*)date timezone:(NSTimeZone*)tz latitude:(double)latitude longitude:(double)longitude { + return [[EDSunriseSet alloc] initWithDate:date timezone:tz latitude:latitude longitude:longitude]; +} + +@end + + + diff --git a/TelegramUI/EditAccessoryPanelNode.swift b/TelegramUI/EditAccessoryPanelNode.swift index 326c38299a..d8049af9e0 100644 --- a/TelegramUI/EditAccessoryPanelNode.swift +++ b/TelegramUI/EditAccessoryPanelNode.swift @@ -36,7 +36,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode { strongSelf.statusNode.transitionToState(.none, completion: {}) } else { strongSelf.activityIndicator.isHidden = true - strongSelf.statusNode.transitionToState(.progress(color: strongSelf.theme.chat.inputPanel.panelControlAccentColor, value: CGFloat(value), cancelEnabled: false), completion: {}) + strongSelf.statusNode.transitionToState(.progress(color: strongSelf.theme.chat.inputPanel.panelControlAccentColor, lineWidth: nil, value: CGFloat(value), cancelEnabled: false), completion: {}) } } else { strongSelf.activityIndicator.isHidden = true @@ -169,7 +169,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode { var mediaUpdated = false if let updatedMediaReference = updatedMediaReference, let previousMediaReference = self.previousMediaReference { - mediaUpdated = !updatedMediaReference.media.isEqual(previousMediaReference.media) + mediaUpdated = !updatedMediaReference.media.isEqual(to: previousMediaReference.media) } else if (updatedMediaReference != nil) != (self.previousMediaReference != nil) { mediaUpdated = true } @@ -268,7 +268,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode { let editMediaReference = interfaceState.editMessageState?.mediaReference var updatedEditMedia = false if let currentEditMediaReference = self.currentEditMediaReference, let editMediaReference = editMediaReference { - if !currentEditMediaReference.media.isEqual(editMediaReference.media) { + if !currentEditMediaReference.media.isEqual(to: editMediaReference.media) { updatedEditMedia = true } } else if (editMediaReference != nil) != (self.currentEditMediaReference != nil) { diff --git a/TelegramUI/ExternalMusicAlbumArtResources.swift b/TelegramUI/ExternalMusicAlbumArtResources.swift index af02de54a1..52287c1f11 100644 --- a/TelegramUI/ExternalMusicAlbumArtResources.swift +++ b/TelegramUI/ExternalMusicAlbumArtResources.swift @@ -13,7 +13,7 @@ private func urlEncodedStringFromString(_ string: String) -> String { return result as String } -func fetchExternalMusicAlbumArtResource(account: Account, resource: ExternalMusicAlbumArtResource) -> Signal { +func fetchExternalMusicAlbumArtResource(account: Account, resource: ExternalMusicAlbumArtResource) -> Signal { return Signal { subscriber in subscriber.putNext(.reset) diff --git a/TelegramUI/FetchCachedRepresentations.swift b/TelegramUI/FetchCachedRepresentations.swift index dc06bdb324..dcbc9a8132 100644 --- a/TelegramUI/FetchCachedRepresentations.swift +++ b/TelegramUI/FetchCachedRepresentations.swift @@ -138,6 +138,25 @@ private func fetchCachedScaledImageRepresentation(account: Account, resource: Me }) |> runOn(account.graphicsThreadPool) } +func generateVideoFirstFrame(_ path: String, maxDimensions: CGSize) -> UIImage? { + let tempFilePath = NSTemporaryDirectory() + "\(arc4random()).mov" + + do { + let _ = try? FileManager.default.removeItem(atPath: tempFilePath) + try FileManager.default.linkItem(atPath: path, toPath: tempFilePath) + + let asset = AVAsset(url: URL(fileURLWithPath: tempFilePath)) + let imageGenerator = AVAssetImageGenerator(asset: asset) + imageGenerator.maximumSize = maxDimensions + imageGenerator.appliesPreferredTrackTransform = true + let fullSizeImage = try imageGenerator.copyCGImage(at: CMTime(seconds: 0.0, preferredTimescale: asset.duration.timescale), actualTime: nil) + let _ = try? FileManager.default.removeItem(atPath: tempFilePath) + return UIImage(cgImage: fullSizeImage) + } catch { + return nil + } +} + private func fetchCachedVideoFirstFrameRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedVideoFirstFrameRepresentation) -> Signal { return Signal { subscriber in if resourceData.complete { diff --git a/TelegramUI/FetchManagerLocation.swift b/TelegramUI/FetchManagerLocation.swift index 6b7d01623f..6aa3af3a11 100644 --- a/TelegramUI/FetchManagerLocation.swift +++ b/TelegramUI/FetchManagerLocation.swift @@ -4,6 +4,8 @@ import Postbox enum FetchManagerCategory: Int32 { case image case file + case voice + case animation } enum FetchManagerLocationKey: Comparable, Hashable { diff --git a/TelegramUI/FetchMediaUtils.swift b/TelegramUI/FetchMediaUtils.swift index 2834f365c5..38a48be2b4 100644 --- a/TelegramUI/FetchMediaUtils.swift +++ b/TelegramUI/FetchMediaUtils.swift @@ -11,12 +11,22 @@ func cancelFreeMediaFileInteractiveFetch(account: Account, file: TelegramMediaFi account.postbox.mediaBox.cancelInteractiveResourceFetch(file.resource) } -func messageMediaFileInteractiveFetched(account: Account, message: Message, file: TelegramMediaFile) -> Signal { - return account.telegramApplicationContext.fetchManager.interactivelyFetched(category: .file, location: .chat(message.id.peerId), locationKey: .messageId(message.id), resourceReference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference(file.resource), statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: true) +private func fetchCategoryForFile(_ file: TelegramMediaFile) -> FetchManagerCategory { + if file.isVoice || file.isInstantVideo { + return .voice + } else if file.isAnimated { + return .animation + } else { + return .file + } +} + +func messageMediaFileInteractiveFetched(account: Account, message: Message, file: TelegramMediaFile, userInitiated: Bool) -> Signal { + return account.telegramApplicationContext.fetchManager.interactivelyFetched(category: fetchCategoryForFile(file), location: .chat(message.id.peerId), locationKey: .messageId(message.id), resourceReference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference(file.resource), statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: userInitiated) } func messageMediaFileCancelInteractiveFetch(account: Account, messageId: MessageId, file: TelegramMediaFile) { - account.telegramApplicationContext.fetchManager.cancelInteractiveFetches(category: .file, location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: file.resource) + account.telegramApplicationContext.fetchManager.cancelInteractiveFetches(category: fetchCategoryForFile(file), location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: file.resource) } func messageMediaImageInteractiveFetched(account: Account, message: Message, image: TelegramMediaImage, resource: MediaResource) -> Signal { @@ -28,5 +38,5 @@ func messageMediaImageCancelInteractiveFetch(account: Account, messageId: Messag } func messageMediaFileStatus(account: Account, messageId: MessageId, file: TelegramMediaFile) -> Signal { - return account.telegramApplicationContext.fetchManager.fetchStatus(category: .file, location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: file.resource) + return account.telegramApplicationContext.fetchManager.fetchStatus(category: fetchCategoryForFile(file), location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: file.resource) } diff --git a/TelegramUI/FetchPhotoLibraryImageResource.swift b/TelegramUI/FetchPhotoLibraryImageResource.swift index e675cc1586..de7c5cf26f 100644 --- a/TelegramUI/FetchPhotoLibraryImageResource.swift +++ b/TelegramUI/FetchPhotoLibraryImageResource.swift @@ -8,7 +8,7 @@ private final class RequestId { var invalidated: Bool = false } -func fetchPhotoLibraryResource(localIdentifier: String) -> Signal { +func fetchPhotoLibraryResource(localIdentifier: String) -> Signal { return Signal { subscriber in let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil) let requestId = Atomic(value: RequestId()) diff --git a/TelegramUI/FetchResource.swift b/TelegramUI/FetchResource.swift index a7c405649b..a390ee2a60 100644 --- a/TelegramUI/FetchResource.swift +++ b/TelegramUI/FetchResource.swift @@ -3,7 +3,7 @@ import Postbox import TelegramCore import SwiftSignalKit -func fetchResource(account: Account, resource: MediaResource, ranges: Signal) -> Signal? { +func fetchResource(account: Account, resource: MediaResource, ranges: Signal) -> Signal? { return nil } diff --git a/TelegramUI/FetchVideoMediaResource.swift b/TelegramUI/FetchVideoMediaResource.swift index 692a3480f4..23530a8d01 100644 --- a/TelegramUI/FetchVideoMediaResource.swift +++ b/TelegramUI/FetchVideoMediaResource.swift @@ -48,7 +48,7 @@ class VideoConversionWatcher: TGMediaVideoFileWatcher { } } -public func fetchVideoLibraryMediaResource(resource: VideoLibraryMediaResource) -> Signal { +public func fetchVideoLibraryMediaResource(resource: VideoLibraryMediaResource) -> Signal { return Signal { subscriber in subscriber.putNext(.reset) @@ -142,7 +142,7 @@ public func fetchVideoLibraryMediaResource(resource: VideoLibraryMediaResource) } } -func fetchLocalFileVideoMediaResource(resource: LocalFileVideoMediaResource) -> Signal { +func fetchLocalFileVideoMediaResource(resource: LocalFileVideoMediaResource) -> Signal { return Signal { subscriber in subscriber.putNext(.reset) diff --git a/TelegramUI/FileMediaResourceStatus.swift b/TelegramUI/FileMediaResourceStatus.swift index 6a33272785..23a4fa6955 100644 --- a/TelegramUI/FileMediaResourceStatus.swift +++ b/TelegramUI/FileMediaResourceStatus.swift @@ -13,8 +13,12 @@ enum FileMediaResourceStatus { case playbackStatus(FileMediaResourcePlaybackStatus) } -private func internalMessageFileMediaPlaybackStatus(account: Account, file: TelegramMediaFile, message: Message) -> Signal { - if let playerType = peerMessageMediaPlayerType(message), let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message) { +private func internalMessageFileMediaPlaybackStatus(account: Account, file: TelegramMediaFile, message: Message, isRecentActions: Bool) -> Signal { + guard let playerType = peerMessageMediaPlayerType(message) else { + return .single(nil) + } + + if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions) { return account.telegramApplicationContext.mediaManager.filteredPlaylistState(playlistId: playlistId, itemId: itemId, type: playerType) |> mapToSignal { state -> Signal in return .single(state?.status) @@ -24,19 +28,19 @@ private func internalMessageFileMediaPlaybackStatus(account: Account, file: Tele } } -func messageFileMediaPlaybackStatus(account: Account, file: TelegramMediaFile, message: Message) -> Signal { +func messageFileMediaPlaybackStatus(account: Account, 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, seekId: 0, status: .paused) - return internalMessageFileMediaPlaybackStatus(account: account, file: file, message: message) |> map { status in + let defaultStatus = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused) + return internalMessageFileMediaPlaybackStatus(account: account, file: file, message: message, isRecentActions: isRecentActions) |> map { status in return status ?? defaultStatus } } -func messageFileMediaResourceStatus(account: Account, file: TelegramMediaFile, message: Message) -> Signal { - let playbackStatus = internalMessageFileMediaPlaybackStatus(account: account, file: file, message: message) |> map { status -> MediaPlayerPlaybackStatus? in +func messageFileMediaResourceStatus(account: Account, file: TelegramMediaFile, message: Message, isRecentActions: Bool) -> Signal { + let playbackStatus = internalMessageFileMediaPlaybackStatus(account: account, file: file, message: message, isRecentActions: isRecentActions) |> map { status -> MediaPlayerPlaybackStatus? in return status?.status } diff --git a/TelegramUI/GalleryController.swift b/TelegramUI/GalleryController.swift index 46dbef6b5e..60c1b2088f 100644 --- a/TelegramUI/GalleryController.swift +++ b/TelegramUI/GalleryController.swift @@ -128,7 +128,13 @@ func galleryItemForEntry(account: Account, theme: PresentationTheme, strings: Pr return ChatImageGalleryItem(account: account, theme: theme, strings: strings, message: message, location: location) } else if let file = media as? TelegramMediaFile { if file.isVideo || file.mimeType.hasPrefix("video/") { - return UniversalVideoGalleryItem(account: account, theme: theme, strings: strings, content: NativeVideoContent(id: .message(message.id, message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), streamVideo: streamVideos, loopVideo: loopVideos), originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: message.text, hideControls: hideControls, playbackCompleted: playbackCompleted) + let content: UniversalVideoContent + if file.isAnimated { + content = NativeVideoContent(id: .message(message.id, message.stableId + 1, file.fileId), fileReference: .message(message: MessageReference(message), media: file), streamVideo: streamVideos, loopVideo: true) + } else { + content = NativeVideoContent(id: .message(message.id, message.stableId, file.fileId), fileReference: .message(message: MessageReference(message), media: file), streamVideo: streamVideos, loopVideo: loopVideos) + } + return UniversalVideoGalleryItem(account: account, theme: theme, strings: strings, content: content, originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: message.text, hideControls: hideControls, playbackCompleted: playbackCompleted) } else { if file.mimeType.hasPrefix("image/") && file.mimeType != "image/gif" { if file.size == nil || file.size! < 5 * 1024 * 1024 { @@ -267,26 +273,26 @@ class GalleryController: ViewController { } let messageView = message - |> filter({ $0 != nil }) - |> mapToSignal { message -> Signal in - switch source { - case .peerMessagesAtId: - if !streamSingleVideo, let tags = tagsForMessage(message!) { - let view = account.postbox.aroundMessageHistoryViewForLocation(.peer(message!.id.peerId), index: .message(MessageIndex(message!)), anchorIndex: .message(MessageIndex(message!)), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, orderStatistics: [.combinedLocation]) - - return view - |> mapToSignal { (view, _, _) -> Signal in - let mapped = GalleryMessageHistoryView.view(view) - return .single(mapped) - } - } else { - return .single(GalleryMessageHistoryView.single(MessageHistoryEntry.MessageEntry(message!, false, nil, nil))) + |> filter({ $0 != nil }) + |> mapToSignal { message -> Signal in + switch source { + case .peerMessagesAtId: + if !streamSingleVideo, let tags = tagsForMessage(message!) { + let view = account.postbox.aroundMessageHistoryViewForLocation(.peer(message!.id.peerId), index: .message(MessageIndex(message!)), anchorIndex: .message(MessageIndex(message!)), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, orderStatistics: [.combinedLocation]) + + return view + |> mapToSignal { (view, _, _) -> Signal in + let mapped = GalleryMessageHistoryView.view(view) + return .single(mapped) } - case .standaloneMessage: + } else { return .single(GalleryMessageHistoryView.single(MessageHistoryEntry.MessageEntry(message!, false, nil, nil))) - } + } + case .standaloneMessage: + return .single(GalleryMessageHistoryView.single(MessageHistoryEntry.MessageEntry(message!, false, nil, nil))) } - |> take(1) + } + |> take(1) let semaphore: DispatchSemaphore? if synchronousLoad { diff --git a/TelegramUI/GalleryHiddenMediaManager.swift b/TelegramUI/GalleryHiddenMediaManager.swift index d2e3a8c646..f7a9df8e46 100644 --- a/TelegramUI/GalleryHiddenMediaManager.swift +++ b/TelegramUI/GalleryHiddenMediaManager.swift @@ -8,7 +8,7 @@ enum GalleryHiddenMediaId: Hashable { static func ==(lhs: GalleryHiddenMediaId, rhs: GalleryHiddenMediaId) -> Bool { switch lhs { case let .chat(lhsMessageId, lhsMedia): - if case let .chat(rhsMessageId, rhsMedia) = rhs, lhsMessageId == rhsMessageId, lhsMedia.isEqual(rhsMedia) { + if case let .chat(rhsMessageId, rhsMedia) = rhs, lhsMessageId == rhsMessageId, lhsMedia.isEqual(to: rhsMedia) { return true } else { return false diff --git a/TelegramUI/GridMessageItem.swift b/TelegramUI/GridMessageItem.swift index f4d1b11940..c1728ea84e 100644 --- a/TelegramUI/GridMessageItem.swift +++ b/TelegramUI/GridMessageItem.swift @@ -182,7 +182,7 @@ final class GridMessageItemNode: GridItemNode { } func setup(account: Account, item: GridMessageItem, media: Media, messageId: MessageId, controllerInteraction: ChatControllerInteraction) { - if self.currentState == nil || self.currentState!.0 !== account || !self.currentState!.1.isEqual(media) { + if self.currentState == nil || self.currentState!.0 !== account || !self.currentState!.1.isEqual(to: media) { var mediaDimensions: CGSize? if let image = media as? TelegramMediaImage, let largestSize = largestImageRepresentation(image.representations)?.dimensions { mediaDimensions = largestSize @@ -209,7 +209,7 @@ final class GridMessageItemNode: GridItemNode { if isActive { adjustedProgress = max(adjustedProgress, 0.027) } - statusState = .progress(color: .white, value: CGFloat(adjustedProgress), cancelEnabled: true) + statusState = .progress(color: .white, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: true) case .Local: statusState = .play(.white) case .Remote: @@ -345,7 +345,7 @@ final class GridMessageItemNode: GridItemNode { case .Local: let _ = controllerInteraction.openMessage(message) case .Remote: - self.fetchDisposable.set(messageMediaFileInteractiveFetched(account: account, message: message, file: file).start()) + self.fetchDisposable.set(messageMediaFileInteractiveFetched(account: account, message: message, file: file, userInitiated: true).start()) } } } else { diff --git a/TelegramUI/GroupAdminsController.swift b/TelegramUI/GroupAdminsController.swift index 04c3256307..897939a72d 100644 --- a/TelegramUI/GroupAdminsController.swift +++ b/TelegramUI/GroupAdminsController.swift @@ -301,7 +301,8 @@ public func groupAdminsController(account: Account, peerId: PeerId) -> ViewContr updateState { state in return state.withUpdatedUpdatingAllAdminsValue(value) } - toggleAllAdminsDisposable.set((updateGroupManagementType(account: account, peerId: peerId, type: value ? .unrestricted : .restrictedToAdmins) |> deliverOnMainQueue).start(error: { + toggleAllAdminsDisposable.set((updateGroupManagementType(account: account, peerId: peerId, type: value ? .unrestricted : .restrictedToAdmins) + |> deliverOnMainQueue).start(error: { _ in updateState { state in return state.withUpdatedUpdatingAllAdminsValue(nil) } diff --git a/TelegramUI/GroupInfoController.swift b/TelegramUI/GroupInfoController.swift index 525ec7aac0..b64d0f5204 100644 --- a/TelegramUI/GroupInfoController.swift +++ b/TelegramUI/GroupInfoController.swift @@ -735,7 +735,7 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa let notificationsText: String switch peerNotificationSettings.muteState { case .default: - notificationsText = "Default" + notificationsText = presentationData.strings.UserInfo_NotificationsDefault case .muted: notificationsText = presentationData.strings.UserInfo_NotificationsDisabled case .unmuted: @@ -1087,6 +1087,8 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl let addMemberDisposable = MetaDisposable() actionsDisposable.add(addMemberDisposable) + let selectAddMemberDisposable = MetaDisposable() + actionsDisposable.add(selectAddMemberDisposable) let removeMemberDisposable = MetaDisposable() actionsDisposable.add(removeMemberDisposable) @@ -1162,7 +1164,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl updateState { $0.withUpdatedUpdatingAvatar(.image(representation)) } - updateAvatarDisposable.set((updatePeerPhoto(account: account, peerId: peerId, photo: uploadedPeerPhoto(account: account, resource: resource)) |> deliverOnMainQueue).start(next: { result in + updateAvatarDisposable.set((updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) |> deliverOnMainQueue).start(next: { result in switch result { case .complete: updateState { @@ -1184,7 +1186,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl return $0.withUpdatedUpdatingAvatar(.none) } } - updateAvatarDisposable.set((updatePeerPhoto(account: account, peerId: peerId, photo: nil) |> deliverOnMainQueue).start(next: { result in + updateAvatarDisposable.set((updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: nil) |> deliverOnMainQueue).start(next: { result in switch result { case .complete: updateState { @@ -1234,7 +1236,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl } controller.setItemGroups([ ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: "Default", action: { + ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsDefault, action: { dismissAction() notificationAction(nil) }), @@ -1258,7 +1260,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl dismissAction() notificationAction(Int32.max) }) - ]), + ]), ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ]) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) @@ -1306,115 +1308,129 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl } }, addMember: { let _ = (account.postbox.loadedPeerWithId(peerId) - |> deliverOnMainQueue).start(next: { groupPeer in - var confirmationImpl: ((PeerId) -> Signal)? - var options: [ContactListAdditionalOption] = [] - let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - var inviteByLinkImpl: (() -> Void)? - options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/LinkActionIcon"), color: presentationData.theme.list.itemAccentColor), action: { - inviteByLinkImpl?() - })) - - let contactsController = ContactSelectionController(account: account, title: { $0.GroupInfo_AddParticipantTitle }, options: options, confirmation: { peer in - if let confirmationImpl = confirmationImpl, case let .peer(peer, _) = peer { - return confirmationImpl(peer.id) - } else { - return .single(false) - } - }) - confirmationImpl = { [weak contactsController] peerId in - return account.postbox.loadedPeerWithId(peerId) - |> deliverOnMainQueue - |> mapToSignal { peer in - let result = ValuePromise() - let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - if let contactsController = contactsController { - let alertController = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.GroupInfo_AddParticipantConfirmation(peer.displayTitle).0, actions: [ - TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: { - result.set(false) - }), - TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: { - result.set(true) - }) - ]) - contactsController.present(alertController, in: .window(.root)) - } - - return result.get() - } + |> deliverOnMainQueue).start(next: { groupPeer in + var confirmationImpl: ((PeerId) -> Signal)? + var options: [ContactListAdditionalOption] = [] + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + var inviteByLinkImpl: (() -> Void)? + options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/LinkActionIcon"), color: presentationData.theme.list.itemAccentColor), action: { + inviteByLinkImpl?() + })) + + let contactsController = ContactSelectionController(account: account, autoDismiss: false, title: { $0.GroupInfo_AddParticipantTitle }, options: options, confirmation: { peer in + if let confirmationImpl = confirmationImpl, case let .peer(peer, _) = peer { + return confirmationImpl(peer.id) + } else { + return .single(false) } - - let addMember = contactsController.result + }) + confirmationImpl = { [weak contactsController] peerId in + return account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue - |> mapToSignal { memberPeer -> Signal in - if let memberPeer = memberPeer, case let .peer(selectedPeer, _) = memberPeer { - let memberId = selectedPeer.id - if peerId.namespace == Namespaces.Peer.CloudChannel { - return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.addMember(account: account, peerId: peerId, memberId: memberId) - } - - return account.postbox.peerView(id: memberId) - |> take(1) - |> deliverOnMainQueue - |> mapToSignal { view -> Signal in - if let peer = view.peers[memberId] { - updateState { state in - var found = false - for participant in state.temporaryParticipants { - if participant.peer.id == memberId { - found = true - break - } - } - if !found { - let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - var temporaryParticipants = state.temporaryParticipants - temporaryParticipants.append(TemporaryParticipant(peer: peer, presence: view.peerPresences[memberId], timestamp: timestamp)) - return state.withUpdatedTemporaryParticipants(temporaryParticipants) - } else { - return state + |> mapToSignal { peer in + let result = ValuePromise() + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + if let contactsController = contactsController { + let alertController = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.GroupInfo_AddParticipantConfirmation(peer.displayTitle).0, actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: { + result.set(false) + }), + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: { + result.set(true) + }) + ]) + contactsController.present(alertController, in: .window(.root)) + } + + return result.get() + } + } + + let addMember: (ContactListPeer) -> Signal = { memberPeer -> Signal in + if case let .peer(selectedPeer, _) = memberPeer { + let memberId = selectedPeer.id + if peerId.namespace == Namespaces.Peer.CloudChannel { + return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.addMember(account: account, peerId: peerId, memberId: memberId) + } + + return account.postbox.peerView(id: memberId) + |> take(1) + |> deliverOnMainQueue + |> mapToSignal { view -> Signal in + if let peer = view.peers[memberId] { + updateState { state in + var found = false + for participant in state.temporaryParticipants { + if participant.peer.id == memberId { + found = true + break } } + if !found { + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + var temporaryParticipants = state.temporaryParticipants + temporaryParticipants.append(TemporaryParticipant(peer: peer, presence: view.peerPresences[memberId], timestamp: timestamp)) + return state.withUpdatedTemporaryParticipants(temporaryParticipants) + } else { + return state + } + } + } + + return addPeerMember(account: account, peerId: peerId, memberId: memberId) + |> deliverOnMainQueue + |> afterCompleted { + updateState { state in + var successfullyAddedParticipantIds = state.successfullyAddedParticipantIds + successfullyAddedParticipantIds.insert(memberId) + + return state.withUpdatedSuccessfullyAddedParticipantIds(successfullyAddedParticipantIds) + } + } + |> `catch` { _ -> Signal in + updateState { state in + var temporaryParticipants = state.temporaryParticipants + for i in 0 ..< temporaryParticipants.count { + if temporaryParticipants[i].peer.id == memberId { + temporaryParticipants.remove(at: i) + break + } + } + var successfullyAddedParticipantIds = state.successfullyAddedParticipantIds + successfullyAddedParticipantIds.remove(memberId) + + return state.withUpdatedTemporaryParticipants(temporaryParticipants).withUpdatedSuccessfullyAddedParticipantIds(successfullyAddedParticipantIds) } - return addPeerMember(account: account, peerId: peerId, memberId: memberId) - |> deliverOnMainQueue - |> afterCompleted { - updateState { state in - var successfullyAddedParticipantIds = state.successfullyAddedParticipantIds - successfullyAddedParticipantIds.insert(memberId) - - return state.withUpdatedSuccessfullyAddedParticipantIds(successfullyAddedParticipantIds) - } - } |> `catch` { _ -> Signal in - updateState { state in - var temporaryParticipants = state.temporaryParticipants - for i in 0 ..< temporaryParticipants.count { - if temporaryParticipants[i].peer.id == memberId { - temporaryParticipants.remove(at: i) - break - } - } - var successfullyAddedParticipantIds = state.successfullyAddedParticipantIds - successfullyAddedParticipantIds.remove(memberId) - - return state.withUpdatedTemporaryParticipants(temporaryParticipants).withUpdatedSuccessfullyAddedParticipantIds(successfullyAddedParticipantIds) - } - - return .complete() - } - } - } else { return .complete() } } - inviteByLinkImpl = { [weak contactsController] in - contactsController?.dismiss() - - presentControllerImpl?(channelVisibilityController(account: account, peerId: peerId, mode: .privateLink), ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet)) + } else { + return .complete() } - presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - addMemberDisposable.set(addMember.start()) + } + inviteByLinkImpl = { [weak contactsController] in + contactsController?.dismiss() + + presentControllerImpl?(channelVisibilityController(account: account, peerId: peerId, mode: .privateLink), ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet)) + } + selectAddMemberDisposable.set((contactsController.result + |> deliverOnMainQueue).start(next: { [weak contactsController] memberPeer in + guard let memberPeer = memberPeer else { + return + } + + contactsController?.displayProgress = true + addMemberDisposable.set((addMember(memberPeer) + |> deliverOnMainQueue).start(completed: { + contactsController?.dismiss() + })) + })) + presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + contactsController.dismissed = { + selectAddMemberDisposable.set(nil) + addMemberDisposable.set(nil) + } }) }, promotePeer: { participant in presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in @@ -1727,7 +1743,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl func handlePeerInfoAboutTextAction(account: Account, peerId: PeerId, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) { let openPeerImpl: (PeerId) -> Void = { [weak controller] peerId in let peerSignal: Signal - peerSignal = account.postbox.loadedPeerWithId(peerId) |> map { Optional($0) } + peerSignal = account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) navigateDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { peer in if let controller = controller, let peer = peer { if let infoController = peerInfoController(account: account, peer: peer) { diff --git a/TelegramUI/GroupPreHistorySetupController.swift b/TelegramUI/GroupPreHistorySetupController.swift index 2d777e7b6e..1b2cb3cbdd 100644 --- a/TelegramUI/GroupPreHistorySetupController.swift +++ b/TelegramUI/GroupPreHistorySetupController.swift @@ -148,7 +148,7 @@ public func groupPreHistorySetupController(account: Account, peerId: PeerId) -> return state } if let value = value, value != defaultValue { - applyDisposable.set((updateChannelHistoryAvailabilitySettingsInteractively(postbox: account.postbox, network: account.network, peerId: peerId, historyAvailableForNewMembers: value) + applyDisposable.set((updateChannelHistoryAvailabilitySettingsInteractively(postbox: account.postbox, network: account.network, accountStateManager: account.stateManager, peerId: peerId, historyAvailableForNewMembers: value) |> deliverOnMainQueue).start(completed: { dismissImpl?() })) diff --git a/TelegramUI/GroupStickerPackCurrentItem.swift b/TelegramUI/GroupStickerPackCurrentItem.swift index be03f08292..582ad99df8 100644 --- a/TelegramUI/GroupStickerPackCurrentItem.swift +++ b/TelegramUI/GroupStickerPackCurrentItem.swift @@ -193,7 +193,7 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { var fileUpdated = false if let file = file, let previousFile = previousFile { - fileUpdated = !file.isEqual(previousFile) + fileUpdated = !file.isEqual(to: previousFile) } else if (file != nil) != (previousFile != nil) { fileUpdated = true } diff --git a/TelegramUI/GroupStickerPackSetupController.swift b/TelegramUI/GroupStickerPackSetupController.swift index 2220f25caa..2f3b14c4bd 100644 --- a/TelegramUI/GroupStickerPackSetupController.swift +++ b/TelegramUI/GroupStickerPackSetupController.swift @@ -430,7 +430,7 @@ public func groupStickerPackSetupController(account: Account, peerId: PeerId, cu return state } saveDisposable.set((updateGroupSpecificStickerset(postbox: account.postbox, network: account.network, peerId: peerId, info: info) - |> deliverOnMainQueue).start(error: { + |> deliverOnMainQueue).start(error: { _ in updateState { state in var state = state state.isSaving = false diff --git a/TelegramUI/GroupsInCommonController.swift b/TelegramUI/GroupsInCommonController.swift index a84bef3400..070414a45d 100644 --- a/TelegramUI/GroupsInCommonController.swift +++ b/TelegramUI/GroupsInCommonController.swift @@ -151,7 +151,7 @@ public func groupsInCommonController(account: Account, peerId: PeerId) -> ViewCo return result } } - |> map { Optional($0) }) + |> map(Optional.init)) peersPromise.set(peersSignal) diff --git a/TelegramUI/HashtagSearchController.swift b/TelegramUI/HashtagSearchController.swift index 6ba9d2e61f..9769687c1e 100644 --- a/TelegramUI/HashtagSearchController.swift +++ b/TelegramUI/HashtagSearchController.swift @@ -59,7 +59,7 @@ final class HashtagSearchController: TelegramController { }, setPeerMuted: { _, _ in }, deletePeer: { _ in }, updatePeerGrouping: { _, _ in - }, togglePeerMarkedUnread: { _ in + }, togglePeerMarkedUnread: { _, _ in }) let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil) diff --git a/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift b/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift index 89cbd709f2..a5a80e235a 100644 --- a/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift +++ b/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift @@ -135,17 +135,36 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont strongSelf.listView.forEachItemNode { itemNode in if itemNode.frame.contains(convertedPoint), let itemNode = itemNode as? HorizontalListContextResultsChatInputPanelItemNode, let item = itemNode.item { if case let .internalReference(internalReference) = item.result, let file = internalReference.file, file.isSticker { - selectedItemNodeAndContent = (itemNode, StickerPreviewPeekContent(account: item.account, item: .found(FoundStickerItem(file: file, stringRepresentations: [])), menu: [ - PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, action: { - item.resultSelected(item.result) - }) - ])) + var menuItems: [PeekControllerMenuItem] = [] + for case let .Sticker(_, packReference, _) in file.attributes { + guard let packReference = packReference else { + continue + } + menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: { + if let strongSelf = self { + let controller = StickerPackPreviewController(account: strongSelf.account, stickerPack: packReference, parentNavigationController: strongSelf.interfaceInteraction?.getNavigationController()) + controller.sendSticker = { file in + if let strongSelf = self { + strongSelf.interfaceInteraction?.sendSticker(file) + } + } + + strongSelf.interfaceInteraction?.getNavigationController()?.view.window?.endEditing(true) + strongSelf.interfaceInteraction?.presentController(controller, nil) + } + })) + } + menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, action: { + item.resultSelected(item.result) + })) + + selectedItemNodeAndContent = (itemNode, StickerPreviewPeekContent(account: item.account, item: .found(FoundStickerItem(file: file, stringRepresentations: [])), menu: menuItems)) } else { - selectedItemNodeAndContent = (itemNode, ChatContextResultPeekContent(account: item.account, contextResult: item.result, menu: [ - PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, action: { - item.resultSelected(item.result) - }) - ])) + var menuItems: [PeekControllerMenuItem] = [] + menuItems.append(PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, action: { + item.resultSelected(item.result) + })) + selectedItemNodeAndContent = (itemNode, ChatContextResultPeekContent(account: item.account, contextResult: item.result, menu: menuItems)) } } } diff --git a/TelegramUI/HorizontalListContextResultsChatInputPanelItem.swift b/TelegramUI/HorizontalListContextResultsChatInputPanelItem.swift index a0c168b384..bd06acd81e 100644 --- a/TelegramUI/HorizontalListContextResultsChatInputPanelItem.swift +++ b/TelegramUI/HorizontalListContextResultsChatInputPanelItem.swift @@ -261,7 +261,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode var updatedVideoFile = false if let currentVideoFile = currentVideoFile, let videoFile = videoFile { - if !currentVideoFile.isEqual(videoFile) { + if !currentVideoFile.isEqual(to: videoFile) { updatedVideoFile = true } } else if (currentVideoFile != nil) != (videoFile != nil) { @@ -271,7 +271,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode if updatedImageResource { if let imageResource = imageResource { if let stickerFile = stickerFile { - updateImageSignal = chatMessageSticker(account: item.account, file: stickerFile, small: false) + updateImageSignal = chatMessageSticker(account: item.account, file: stickerFile, small: false, fetched: true) } else { let tmpRepresentation = TelegramMediaImageRepresentation(dimensions: CGSize(width: fittedImageDimensions.width * 2.0, height: fittedImageDimensions.height * 2.0), resource: imageResource) let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [tmpRepresentation], reference: nil, partialReference: nil) diff --git a/TelegramUI/ICloudResources.swift b/TelegramUI/ICloudResources.swift index 8f3cb8d001..13e1e6247c 100644 --- a/TelegramUI/ICloudResources.swift +++ b/TelegramUI/ICloudResources.swift @@ -164,7 +164,7 @@ private final class ICloudFileResourceCopyItem: MediaResourceDataFetchCopyLocalI } } -func fetchICloudFileResource(resource: ICloudFileResource) -> Signal { +func fetchICloudFileResource(resource: ICloudFileResource) -> Signal { return Signal { subscriber in subscriber.putNext(.reset) diff --git a/TelegramUI/InAppNotificationSettings.swift b/TelegramUI/InAppNotificationSettings.swift index c930612c4d..08aaeb7fc2 100644 --- a/TelegramUI/InAppNotificationSettings.swift +++ b/TelegramUI/InAppNotificationSettings.swift @@ -8,10 +8,10 @@ public enum TotalUnreadCountDisplayStyle: Int32 { } public struct InAppNotificationSettings: PreferencesEntry, Equatable { - public let playSounds: Bool - public let vibrate: Bool - public let displayPreviews: Bool - public let totalUnreadCountDisplayStyle: TotalUnreadCountDisplayStyle + public var playSounds: Bool + public var vibrate: Bool + public var displayPreviews: Bool + public var totalUnreadCountDisplayStyle: TotalUnreadCountDisplayStyle public static var defaultSettings: InAppNotificationSettings { return InAppNotificationSettings(playSounds: true, vibrate: false, displayPreviews: true, totalUnreadCountDisplayStyle: .filtered) diff --git a/TelegramUI/InstalledStickerPacksController.swift b/TelegramUI/InstalledStickerPacksController.swift index d9c3328f24..533121fd1a 100644 --- a/TelegramUI/InstalledStickerPacksController.swift +++ b/TelegramUI/InstalledStickerPacksController.swift @@ -14,8 +14,9 @@ private final class InstalledStickerPacksControllerArguments { let openMasks: () -> Void let openFeatured: () -> Void let openArchived: () -> Void + let openSuggestOptions: () -> Void - init(account: Account, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, removePack: @escaping (ItemCollectionId) -> Void, openStickersBot: @escaping () -> Void, openMasks: @escaping () -> Void, openFeatured: @escaping () -> Void, openArchived: @escaping () -> Void) { + init(account: Account, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, removePack: @escaping (ItemCollectionId) -> Void, openStickersBot: @escaping () -> Void, openMasks: @escaping () -> Void, openFeatured: @escaping () -> Void, openArchived: @escaping () -> Void, openSuggestOptions: @escaping () -> Void) { self.account = account self.openStickerPack = openStickerPack self.setPackIdWithRevealedOptions = setPackIdWithRevealedOptions @@ -24,6 +25,7 @@ private final class InstalledStickerPacksControllerArguments { self.openMasks = openMasks self.openFeatured = openFeatured self.openArchived = openArchived + self.openSuggestOptions = openSuggestOptions } } @@ -64,6 +66,7 @@ private enum InstalledStickerPacksEntryId: Hashable { } private enum InstalledStickerPacksEntry: ItemListNodeEntry { + case suggestOptions(PresentationTheme, String, String) case trending(PresentationTheme, String, Int32) case archived(PresentationTheme, String) case masks(PresentationTheme, String) @@ -73,7 +76,7 @@ private enum InstalledStickerPacksEntry: ItemListNodeEntry { var section: ItemListSectionId { switch self { - case .trending, .masks, .archived: + case .suggestOptions, .trending, .masks, .archived: return InstalledStickerPacksSection.service.rawValue case .packsTitle, .pack, .packsInfo: return InstalledStickerPacksSection.stickers.rawValue @@ -82,23 +85,31 @@ private enum InstalledStickerPacksEntry: ItemListNodeEntry { var stableId: InstalledStickerPacksEntryId { switch self { - case .trending: + case .suggestOptions: return .index(0) - case .archived: + case .trending: return .index(1) - case .masks: + case .archived: return .index(2) - case .packsTitle: + case .masks: return .index(3) + case .packsTitle: + return .index(4) case let .pack(_, _, _, info, _, _, _, _): return .pack(info.id) case .packsInfo: - return .index(4) + return .index(5) } } static func ==(lhs: InstalledStickerPacksEntry, rhs: InstalledStickerPacksEntry) -> Bool { switch lhs { + case let .suggestOptions(lhsTheme, lhsText, lhsValue): + if case let .suggestOptions(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + return true + } else { + return false + } case let .trending(lhsTheme, lhsText, lhsCount): if case let .trending(rhsTheme, rhsText, rhsCount) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsCount == rhsCount { return true @@ -164,30 +175,37 @@ private enum InstalledStickerPacksEntry: ItemListNodeEntry { static func <(lhs: InstalledStickerPacksEntry, rhs: InstalledStickerPacksEntry) -> Bool { switch lhs { + case .suggestOptions: + switch rhs { + case .suggestOptions: + return false + default: + return true + } case .trending: switch rhs { - case .trending: + case .suggestOptions, .trending: return false default: return true } case .archived: switch rhs { - case .trending, .archived: + case .suggestOptions, .trending, .archived: return false default: return true } case .masks: switch rhs { - case .trending, .archived, .masks: + case .suggestOptions, .trending, .archived, .masks: return false default: return true } case .packsTitle: switch rhs { - case .trending, .masks, .archived, .packsTitle: + case .suggestOptions, .trending, .masks, .archived, .packsTitle: return false default: return true @@ -213,6 +231,10 @@ private enum InstalledStickerPacksEntry: ItemListNodeEntry { func item(_ arguments: InstalledStickerPacksControllerArguments) -> ListViewItem { switch self { + case let .suggestOptions(theme, text, value): + return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, action: { + arguments.openSuggestOptions() + }) case let .trending(theme, text, count): return ItemListDisclosureItem(theme: theme, title: text, label: count == 0 ? "" : "\(count)", sectionId: self.section, style: .blocks, action: { arguments.openFeatured() @@ -287,11 +309,22 @@ private func namespaceForMode(_ mode: InstalledStickerPacksControllerMode) -> It } } -private func installedStickerPacksControllerEntries(presentationData: PresentationData, state: InstalledStickerPacksControllerState, mode: InstalledStickerPacksControllerMode, view: CombinedView, featured: [FeaturedStickerPackItem]) -> [InstalledStickerPacksEntry] { +private func installedStickerPacksControllerEntries(presentationData: PresentationData, state: InstalledStickerPacksControllerState, mode: InstalledStickerPacksControllerMode, view: CombinedView, featured: [FeaturedStickerPackItem], stickerSettings: StickerSettings) -> [InstalledStickerPacksEntry] { var entries: [InstalledStickerPacksEntry] = [] switch mode { case .general, .modal: + let suggestString: String + switch stickerSettings.emojiStickerSuggestionMode { + case .none: + suggestString = presentationData.strings.Stickers_SuggestNone + case .all: + suggestString = presentationData.strings.Stickers_SuggestAll + case .installed: + suggestString = presentationData.strings.Stickers_SuggestAdded + } + entries.append(.suggestOptions(presentationData.theme, presentationData.strings.Stickers_SuggestStickers, suggestString)) + if featured.count != 0 { var unreadCount: Int32 = 0 for item in featured { @@ -406,6 +439,44 @@ public func installedStickerPacksController(account: Account, mode: InstalledSti pushControllerImpl?(featuredStickerPacksController(account: account)) }, openArchived: { pushControllerImpl?(archivedStickerPacksController(account: account)) + }, openSuggestOptions: { + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + let controller = ActionSheetController(presentationTheme: presentationData.theme) + let dismissAction: () -> Void = { [weak controller] in + controller?.dismissAnimated() + } + let options: [(EmojiStickerSuggestionMode, String)] = [ + (.all, presentationData.strings.Stickers_SuggestAll), + (.installed, presentationData.strings.Stickers_SuggestAdded), + (.none, presentationData.strings.Stickers_SuggestNone) + ] + var items: [ActionSheetItem] = [] + items.append(ActionSheetTextItem(title: presentationData.strings.Stickers_SuggestStickers)) + for (option, title) in options { + items.append(ActionSheetButtonItem(title: title, color: .accent, action: { + dismissAction() + let _ = updateStickerSettingsInteractively(postbox: account.postbox, { current in + return current.withUpdatedEmojiStickerSuggestionMode(option) + }).start() + })) + } + controller.setItemGroups([ + ActionSheetItemGroup(items: items), + ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) + ]) + presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + /* + let suggestString: String + switch stickerSettings.emojiStickerSuggestionMode { + case .none: + suggestString = presentationData.strings.Stickers_SuggestNone + case .all: + + case .installed: + suggestString = presentationData.strings.Stickers_SuggestAdded + } + */ + }) let stickerPacks = Promise() stickerPacks.set(account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [namespaceForMode(mode)])])) @@ -420,9 +491,20 @@ public func installedStickerPacksController(account: Account, mode: InstalledSti var previousPackCount: Int? - let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, featured.get() |> deliverOnMainQueue) + let stickerSettingsKey = ApplicationSpecificPreferencesKeys.stickerSettings + let preferencesKey: PostboxViewKey = .preferences(keys: Set([stickerSettingsKey])) + let preferencesView = account.postbox.combinedView(keys: [preferencesKey]) + + let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, featured.get() |> deliverOnMainQueue, preferencesView |> deliverOnMainQueue) |> deliverOnMainQueue - |> map { presentationData, state, view, featured -> (ItemListControllerState, (ItemListNodeState, InstalledStickerPacksEntry.ItemGenerationArguments)) in + |> map { presentationData, state, view, featured, preferencesView -> (ItemListControllerState, (ItemListNodeState, InstalledStickerPacksEntry.ItemGenerationArguments)) in + var stickerSettings = StickerSettings.defaultSettings + if let view = preferencesView.views[preferencesKey] as? PreferencesView { + if let value = view.values[stickerSettingsKey] as? StickerSettings { + stickerSettings = value + } + } + var packCount: Int? = nil if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [namespaceForMode(mode)])] as? ItemCollectionInfosView, let entries = stickerPacksView.entriesByNamespace[namespaceForMode(mode)] { packCount = entries.count @@ -472,7 +554,7 @@ public func installedStickerPacksController(account: Account, mode: InstalledSti let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(entries: installedStickerPacksControllerEntries(presentationData: presentationData, state: state, mode: mode, view: view, featured: featured), style: .blocks, animateChanges: previous != nil && packCount != nil && (previous! != 0 && previous! >= packCount! - 10)) + let listState = ItemListNodeState(entries: installedStickerPacksControllerEntries(presentationData: presentationData, state: state, mode: mode, view: view, featured: featured, stickerSettings: stickerSettings), style: .blocks, animateChanges: previous != nil && packCount != nil && (previous! != 0 && previous! >= packCount! - 10)) return (controllerState, (listState, arguments)) } |> afterDisposed { actionsDisposable.dispose() diff --git a/TelegramUI/InstantImageGalleryItem.swift b/TelegramUI/InstantImageGalleryItem.swift index bb58304593..24b1470dc0 100644 --- a/TelegramUI/InstantImageGalleryItem.swift +++ b/TelegramUI/InstantImageGalleryItem.swift @@ -115,7 +115,7 @@ final class InstantImageGalleryItemNode: ZoomableContentGalleryItemNode { } fileprivate func setImage(imageReference: ImageMediaReference) { - if self.accountAndMedia == nil || !self.accountAndMedia!.1.media.isEqual(imageReference.media) { + if self.accountAndMedia == nil || !self.accountAndMedia!.1.media.isEqual(to: imageReference.media) { if let largestSize = largestRepresentationForPhoto(imageReference.media) { let displaySize = largestSize.dimensions.fitted(CGSize(width: 1280.0, height: 1280.0)).dividedByScreenScale().integralFloor self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))() @@ -131,7 +131,7 @@ final class InstantImageGalleryItemNode: ZoomableContentGalleryItemNode { } func setFile(account: Account, fileReference: FileMediaReference) { - if self.accountAndMedia == nil || !self.accountAndMedia!.1.media.isEqual(fileReference.media) { + if self.accountAndMedia == nil || !self.accountAndMedia!.1.media.isEqual(to: fileReference.media) { if let largestSize = fileReference.media.dimensions { let displaySize = largestSize.dividedByScreenScale() self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))() diff --git a/TelegramUI/InstantPageAudioNode.swift b/TelegramUI/InstantPageAudioNode.swift index 7fc1ff5dbb..6fcb53466a 100644 --- a/TelegramUI/InstantPageAudioNode.swift +++ b/TelegramUI/InstantPageAudioNode.swift @@ -176,7 +176,7 @@ final class InstantPageAudioNode: ASDisplayNode, InstantPageNode { self.scrubbingNode.status = account.telegramApplicationContext.mediaManager.filteredPlaylistState(playlistId: InstantPageMediaPlaylistId(webpageId: webPage.webpageId), itemId: InstantPageMediaPlaylistItemId(index: self.media.index), type: self.playlistType) |> map { playbackState -> MediaPlayerStatus in - return playbackState?.status ?? MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, seekId: 0, status: .paused) + return playbackState?.status ?? MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused) } self.playerStatusDisposable = (account.telegramApplicationContext.mediaManager.filteredPlaylistState(playlistId: InstantPageMediaPlaylistId(webpageId: webPage.webpageId), itemId: InstantPageMediaPlaylistItemId(index: self.media.index), type: playlistType) diff --git a/TelegramUI/InstantPageMedia.swift b/TelegramUI/InstantPageMedia.swift index 60698f60de..de8e356870 100644 --- a/TelegramUI/InstantPageMedia.swift +++ b/TelegramUI/InstantPageMedia.swift @@ -8,6 +8,6 @@ struct InstantPageMedia: Equatable { let caption: String? static func ==(lhs: InstantPageMedia, rhs: InstantPageMedia) -> Bool { - return lhs.index == rhs.index && lhs.media.isEqual(rhs.media) && lhs.caption == rhs.caption + return lhs.index == rhs.index && lhs.media.isEqual(to: rhs.media) && lhs.caption == rhs.caption } } diff --git a/TelegramUI/InstantPageTextItem.swift b/TelegramUI/InstantPageTextItem.swift index 693549709d..85ecdf2b81 100644 --- a/TelegramUI/InstantPageTextItem.swift +++ b/TelegramUI/InstantPageTextItem.swift @@ -211,8 +211,8 @@ final class InstantPageTextItem: InstantPageItem { func linkSelectionRects(at point: CGPoint) -> [CGRect] { if let (index, dict) = self.attributesAtPoint(point) { - if let _ = dict[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] { - if let rects = self.attributeRects(name: NSAttributedStringKey(rawValue: TelegramTextAttributes.Url), at: index) { + if let _ = dict[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] { + if let rects = self.attributeRects(name: NSAttributedStringKey(rawValue: TelegramTextAttributes.URL), at: index) { return rects } } @@ -223,7 +223,7 @@ final class InstantPageTextItem: InstantPageItem { func urlAttribute(at point: CGPoint) -> InstantPageUrlItem? { if let (_, dict) = self.attributesAtPoint(point) { - if let url = dict[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] as? InstantPageUrlItem { + if let url = dict[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? InstantPageUrlItem { return url } } @@ -285,7 +285,7 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt case let .plain(string): var attributes = styleStack.textAttributes() if let url = url { - attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] = url + attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] = url } return NSAttributedString(string: string, attributes: attributes) case let .bold(text): diff --git a/TelegramUI/InstantVideoRadialStatusNode.swift b/TelegramUI/InstantVideoRadialStatusNode.swift index de0db2307c..09af713305 100644 --- a/TelegramUI/InstantVideoRadialStatusNode.swift +++ b/TelegramUI/InstantVideoRadialStatusNode.swift @@ -102,14 +102,14 @@ final class InstantVideoRadialStatusNode: ASDisplayNode { } private func updateProgress() { - let timestampAndDuration: (timestamp: Double, duration: Double)? + let timestampAndDuration: (timestamp: Double, duration: Double, baseRate: Double)? if let statusValue = self.statusValue, Double(0.0).isLess(than: statusValue.duration) { - timestampAndDuration = (statusValue.timestamp, statusValue.duration) + timestampAndDuration = (statusValue.timestamp, statusValue.duration, statusValue.baseRate) } else { timestampAndDuration = nil } - if let (timestamp, duration) = timestampAndDuration, let statusValue = self.statusValue { + if let (timestamp, duration, baseRate) = timestampAndDuration, let statusValue = self.statusValue { let progress = CGFloat(timestamp / duration) if progress.isNaN || !progress.isFinite || statusValue.generationTimestamp.isZero { @@ -134,20 +134,12 @@ final class InstantVideoRadialStatusNode: ASDisplayNode { animation.fromValue = progress as NSNumber animation.toValue = 1.0 as NSNumber animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) - animation.duration = max(0.0, duration - timestamp) + animation.duration = max(0.0, duration - timestamp) / baseRate animation.completionBlock = { [weak self] _, _ in } animation.beginTime = statusValue.generationTimestamp - //animation.offset = timestamp self.pop_add(animation, forKey: "progress") - - /*let fromBounds = CGRect(origin: CGPoint(), size: fromRect.size) - let toBounds = CGRect(origin: CGPoint(), size: toRect.size) - - foregroundNode.frame = toRect - foregroundNode.layer.add(self.preparedAnimation(keyPath: "bounds", from: NSValue(cgRect: fromBounds), to: NSValue(cgRect: toBounds), duration: duration, beginTime: statusValue.generationTimestamp, offset: timestamp, speed: statusValue.status == .playing ? 1.0 : 0.0), forKey: "playback-bounds") - foregroundNode.layer.add(self.preparedAnimation(keyPath: "position", from: NSValue(cgPoint: CGPoint(x: fromRect.midX, y: fromRect.midY)), to: NSValue(cgPoint: CGPoint(x: toRect.midX, y: toRect.midY)), duration: duration, beginTime: statusValue.generationTimestamp, offset: timestamp, speed: statusValue.status == .playing ? 1.0 : 0.0), forKey: "playback-position")*/ } } else { self.pop_removeAnimation(forKey: "progress") diff --git a/TelegramUI/ItemListAvatarAndNameItem.swift b/TelegramUI/ItemListAvatarAndNameItem.swift index 35aa977d83..f191f3abc9 100644 --- a/TelegramUI/ItemListAvatarAndNameItem.swift +++ b/TelegramUI/ItemListAvatarAndNameItem.swift @@ -271,6 +271,14 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite return self.item?.tag } + var callButtonFrame: CGRect? { + if !self.callButton.alpha.isZero && self.callButton.supernode != nil { + return self.callButton.frame + } else { + return nil + } + } + init() { self.backgroundNode = ASDisplayNode() self.backgroundNode.isLayerBacked = true diff --git a/TelegramUI/ItemListCheckboxItem.swift b/TelegramUI/ItemListCheckboxItem.swift index 979878db47..19e686c6ee 100644 --- a/TelegramUI/ItemListCheckboxItem.swift +++ b/TelegramUI/ItemListCheckboxItem.swift @@ -8,19 +8,26 @@ enum ItemListCheckboxItemStyle { case right } +enum ItemListCheckboxItemColor { + case accent + case secondary +} + class ItemListCheckboxItem: ListViewItem, ItemListItem { let theme: PresentationTheme let title: String let style: ItemListCheckboxItemStyle + let color: ItemListCheckboxItemColor let checked: Bool let zeroSeparatorInsets: Bool let sectionId: ItemListSectionId let action: () -> Void - init(theme: PresentationTheme, title: String, style: ItemListCheckboxItemStyle, checked: Bool, zeroSeparatorInsets: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void) { + init(theme: PresentationTheme, title: String, style: ItemListCheckboxItemStyle, color: ItemListCheckboxItemColor = .accent, checked: Bool, zeroSeparatorInsets: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void) { self.theme = theme self.title = title self.style = style + self.color = color self.checked = checked self.zeroSeparatorInsets = zeroSeparatorInsets self.sectionId = sectionId @@ -137,9 +144,17 @@ class ItemListCheckboxItemNode: ListViewItemNode { if currentItem?.theme !== item.theme { updatedTheme = item.theme - updateCheckImage = PresentationResourcesItemList.checkIconImage(item.theme) } + if currentItem?.theme !== item.theme || currentItem?.color != item.color { + switch item.color { + case .accent: + updateCheckImage = PresentationResourcesItemList.checkIconImage(item.theme) + case .secondary: + updateCheckImage = PresentationResourcesItemList.secondaryCheckIconImage(item.theme) + } + } + return (layout, { [weak self] in if let strongSelf = self { strongSelf.item = item diff --git a/TelegramUI/ItemListController.swift b/TelegramUI/ItemListController.swift index 19d7cade16..a1ff39c726 100644 --- a/TelegramUI/ItemListController.swift +++ b/TelegramUI/ItemListController.swift @@ -140,6 +140,8 @@ class ItemListController: ViewController { private var strings: PresentationStrings private var didPlayPresentationAnimation = false + private(set) var didAppearOnce = false + var didAppear: (() -> Void)? var titleControlValueChanged: ((Int) -> Void)? @@ -412,6 +414,11 @@ class ItemListController: ViewController { (self.displayNode as! ItemListControllerNode).animateIn() } } + + if !self.didAppearOnce { + self.didAppearOnce = true + self.didAppear?() + } } override func viewWillDisappear(_ animated: Bool) { diff --git a/TelegramUI/ItemListDisclosureItem.swift b/TelegramUI/ItemListDisclosureItem.swift index e3d2e176dd..dd510b4a13 100644 --- a/TelegramUI/ItemListDisclosureItem.swift +++ b/TelegramUI/ItemListDisclosureItem.swift @@ -3,6 +3,11 @@ import Display import AsyncDisplayKit import SwiftSignalKit +enum ItemListDisclosureItemTitleColor { + case primary + case accent +} + enum ItemListDisclosureStyle { case arrow case none @@ -11,12 +16,14 @@ enum ItemListDisclosureStyle { enum ItemListDisclosureLabelStyle { case text case badge + case color(UIColor) } class ItemListDisclosureItem: ListViewItem, ItemListItem { let theme: PresentationTheme let icon: UIImage? let title: String + let titleColor: ItemListDisclosureItemTitleColor let label: String let labelStyle: ItemListDisclosureLabelStyle let sectionId: ItemListSectionId @@ -24,10 +31,11 @@ class ItemListDisclosureItem: ListViewItem, ItemListItem { let disclosureStyle: ItemListDisclosureStyle let action: (() -> Void)? - init(theme: PresentationTheme, icon: UIImage? = nil, title: String, label: String, labelStyle: ItemListDisclosureLabelStyle = .text, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, action: (() -> Void)?) { + init(theme: PresentationTheme, icon: UIImage? = nil, title: String, titleColor: ItemListDisclosureItemTitleColor = .primary, label: String, labelStyle: ItemListDisclosureLabelStyle = .text, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, action: (() -> Void)?) { self.theme = theme self.icon = icon self.title = title + self.titleColor = titleColor self.labelStyle = labelStyle self.label = label self.sectionId = sectionId @@ -88,6 +96,7 @@ class ItemListDisclosureItemNode: ListViewItemNode { let labelNode: TextNode let arrowNode: ASImageNode let labelBadgeNode: ASImageNode + let labelImageNode: ASImageNode private var item: ItemListDisclosureItem? @@ -126,6 +135,7 @@ class ItemListDisclosureItemNode: ListViewItemNode { self.arrowNode.isLayerBacked = true self.labelBadgeNode = ASImageNode() + self.labelImageNode = ASImageNode() self.labelBadgeNode.displayWithoutProcessing = true self.labelBadgeNode.displaysAsynchronously = false self.labelBadgeNode.isLayerBacked = true @@ -161,11 +171,21 @@ class ItemListDisclosureItemNode: ListViewItemNode { var updatedTheme: PresentationTheme? var updatedLabelBadgeImage: UIImage? + var updatedLabelImage: UIImage? var hasBadge = false if case .badge = item.labelStyle { hasBadge = true } + if case let .color(color) = item.labelStyle { + var updatedColor = true + if let currentItem = currentItem, case let .color(previousColor) = currentItem.labelStyle, color.isEqual(previousColor) { + updatedColor = false + } + if updatedColor { + updatedLabelImage = generateFilledCircleImage(diameter: 17.0, color: color) + } + } if currentItem?.theme !== item.theme { updatedTheme = item.theme @@ -209,11 +229,10 @@ class ItemListDisclosureItemNode: ListViewItemNode { leftInset += 43.0 } - let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.titleColor == .accent ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: titleFont, textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset - 40.0 - titleLayout.size.width - 10.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) - let layoutSize = layout.size return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in if let strongSelf = self { @@ -291,6 +310,21 @@ class ItemListDisclosureItemNode: ListViewItemNode { strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size) strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: params.width - rightInset - labelLayout.size.width, y: 11.0), size: labelLayout.size) + if case .color = item.labelStyle { + if let updatedLabelImage = updatedLabelImage { + strongSelf.labelImageNode.image = updatedLabelImage + } + if strongSelf.labelImageNode.supernode == nil { + strongSelf.addSubnode(strongSelf.labelImageNode) + } + if let image = strongSelf.labelImageNode.image { + strongSelf.labelImageNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 50.0, y: floor((layout.contentSize.height - image.size.height) / 2.0)), size: image.size) + } + } else if strongSelf.labelImageNode.supernode != nil { + strongSelf.labelImageNode.removeFromSupernode() + strongSelf.labelImageNode.image = nil + } + if let arrowImage = strongSelf.arrowNode.image { strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 15.0 - arrowImage.size.width, y: 15.0), size: arrowImage.size) } diff --git a/TelegramUI/ItemListEditableItem.swift b/TelegramUI/ItemListEditableItem.swift index 68f8c2135e..481ec36d3c 100644 --- a/TelegramUI/ItemListEditableItem.swift +++ b/TelegramUI/ItemListEditableItem.swift @@ -64,6 +64,7 @@ class ItemListRevealOptionsItemNode: ListViewItemNode { private(set) var revealOffset: CGFloat = 0.0 private var recognizer: ItemListRevealOptionsGestureRecognizer? + private var hapticFeedback: HapticFeedback? private var allowAnyDirection = false @@ -204,9 +205,21 @@ class ItemListRevealOptionsItemNode: ListViewItemNode { reveal = false } } - self.updateRevealOffsetInternal(offset: reveal ?revealSize.width : 0.0, transition: .animated(duration: 0.3, curve: .spring)) - if !reveal { - self.revealOptionsInteractivelyClosed() + + var selectedOption: ItemListRevealOption? + if reveal && leftRevealNode.isDisplayingExtendedAction() { + reveal = false + selectedOption = self.revealOptions.left.first + } else { + self.updateRevealOffsetInternal(offset: reveal ?revealSize.width : 0.0, transition: .animated(duration: 0.3, curve: .spring)) + } + + if let selectedOption = selectedOption { + self.revealOptionSelected(selectedOption, animated: true) + } else { + if !reveal { + self.revealOptionsInteractivelyClosed() + } } } else if let rightRevealNode = self.rightRevealNode { let velocity = recognizer.velocity(in: self.view) @@ -240,7 +253,9 @@ class ItemListRevealOptionsItemNode: ListViewItemNode { private func setupAndAddLeftRevealNode() { if !self.revealOptions.left.isEmpty { let revealNode = ItemListRevealOptionsNode(optionSelected: { [weak self] option in - self?.revealOptionSelected(option) + self?.revealOptionSelected(option, animated: false) + }, tapticAction: { [weak self] in + self?.hapticTap() }) revealNode.setOptions(self.revealOptions.left) self.leftRevealNode = revealNode @@ -259,7 +274,9 @@ class ItemListRevealOptionsItemNode: ListViewItemNode { private func setupAndAddRightRevealNode() { if !self.revealOptions.right.isEmpty { let revealNode = ItemListRevealOptionsNode(optionSelected: { [weak self] option in - self?.revealOptionSelected(option) + self?.revealOptionSelected(option, animated: false) + }, tapticAction: { [weak self] in + self?.hapticTap() }) revealNode.setOptions(self.revealOptions.right) self.rightRevealNode = revealNode @@ -299,7 +316,8 @@ class ItemListRevealOptionsItemNode: ListViewItemNode { let revealSize = leftRevealNode.calculatedSize let revealFrame = CGRect(origin: CGPoint(x: min(self.revealOffset - revealSize.width, 0.0), y: 0.0), size: revealSize) - let revealNodeOffset = max(-self.revealOffset, revealSize.width) + //let revealNodeOffset = max(-self.revealOffset, revealSize.width) + let revealNodeOffset = -self.revealOffset leftRevealNode.updateRevealOffset(offset: revealNodeOffset, rightInset: rightInset, transition: transition) if CGFloat(offset).isLessThanOrEqualTo(0.0) { @@ -375,7 +393,7 @@ class ItemListRevealOptionsItemNode: ListViewItemNode { } } - func revealOptionSelected(_ option: ItemListRevealOption) { + func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { } override var preventsTouchesToOtherItems: Bool { @@ -387,4 +405,11 @@ class ItemListRevealOptionsItemNode: ListViewItemNode { self.setRevealOptionsOpened(false, animated: true) } } + + private func hapticTap() { + if self.hapticFeedback == nil { + self.hapticFeedback = HapticFeedback() + } + self.hapticFeedback?.tap() + } } diff --git a/TelegramUI/ItemListMultilineTextItem.swift b/TelegramUI/ItemListMultilineTextItem.swift index 4421b23b96..dd818cf4c4 100644 --- a/TelegramUI/ItemListMultilineTextItem.swift +++ b/TelegramUI/ItemListMultilineTextItem.swift @@ -327,7 +327,7 @@ class ItemListMultilineTextItemNode: ListViewItemNode { private func linkItemAtPoint(_ point: CGPoint) -> TextLinkItem? { let textNodeFrame = self.textNode.frame if let (_, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { - if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] as? String { + if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { return .url(url) } else if let peerName = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { return .mention(peerName) @@ -351,7 +351,7 @@ class ItemListMultilineTextItemNode: ListViewItemNode { let textNodeFrame = self.textNode.frame if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { let possibleNames: [String] = [ - TelegramTextAttributes.Url, + TelegramTextAttributes.URL, TelegramTextAttributes.PeerMention, TelegramTextAttributes.PeerTextMention, TelegramTextAttributes.BotCommand, diff --git a/TelegramUI/ItemListPeerItem.swift b/TelegramUI/ItemListPeerItem.swift index 27cf9ff87e..c50e000619 100644 --- a/TelegramUI/ItemListPeerItem.swift +++ b/TelegramUI/ItemListPeerItem.swift @@ -686,7 +686,7 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode { } } - override func revealOptionSelected(_ option: ItemListRevealOption) { + override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { self.setRevealOptionsOpened(false, animated: true) self.revealOptionsInteractivelyClosed() diff --git a/TelegramUI/ItemListRecentSessionItem.swift b/TelegramUI/ItemListRecentSessionItem.swift index e45e91136c..1342804195 100644 --- a/TelegramUI/ItemListRecentSessionItem.swift +++ b/TelegramUI/ItemListRecentSessionItem.swift @@ -409,7 +409,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode { } } - override func revealOptionSelected(_ option: ItemListRevealOption) { + override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { self.setRevealOptionsOpened(false, animated: true) self.revealOptionsInteractivelyClosed() diff --git a/TelegramUI/ItemListRevealOptionsNode.swift b/TelegramUI/ItemListRevealOptionsNode.swift index 4a554ad520..ba85e9b404 100644 --- a/TelegramUI/ItemListRevealOptionsNode.swift +++ b/TelegramUI/ItemListRevealOptionsNode.swift @@ -33,9 +33,15 @@ struct ItemListRevealOption: Equatable { private let titleFontWithIcon = Font.regular(13.0) private let titleFontWithoutIcon = Font.regular(17.0) -final class ItemListRevealOptionNode: ASDisplayNode { +private enum ItemListRevealOptionAlignment { + case left + case right +} + +private final class ItemListRevealOptionNode: ASDisplayNode { private let titleNode: ASTextNode private let iconNode: ASImageNode? + var alignment: ItemListRevealOptionAlignment? //private var animView: LOTView? @@ -77,6 +83,37 @@ final class ItemListRevealOptionNode: ASDisplayNode { }*/ } + func updateLayout(baseSize: CGSize, alignment: ItemListRevealOptionAlignment, extendedWidth: CGFloat, transition: ContainedViewLayoutTransition) { + var animateAdditive = false + if transition.isAnimated, self.alignment != alignment { + animateAdditive = true + } + self.alignment = alignment + let titleSize = self.titleNode.calculatedSize + var contentRect = CGRect(origin: CGPoint(), size: baseSize) + switch alignment { + case .left: + contentRect.origin.x = 0.0 + case .right: + contentRect.origin.x = extendedWidth - contentRect.width + } + if let iconNode = self.iconNode, let image = iconNode.image { + let titleIconSpacing: CGFloat = 3.0 + let iconFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - image.size.width) / 2.0), y: contentRect.minY + floor((baseSize.height - image.size.height - titleIconSpacing - titleSize.height) / 2.0)), size: image.size) + if animateAdditive { + transition.animatePositionAdditive(node: iconNode, offset: CGPoint(x: iconNode.frame.minX - iconFrame.minX, y: 0.0)) + } + iconNode.frame = iconFrame + let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width) / 2.0), y: contentRect.minY + floor((baseSize.height - image.size.height - titleIconSpacing - titleSize.height) / 2.0) + image.size.height + titleIconSpacing), size: titleSize) + if animateAdditive { + transition.animatePositionAdditive(node: self.titleNode, offset: CGPoint(x: self.titleNode.frame.minX - titleFrame.minX, y: 0.0)) + } + self.titleNode.frame = titleFrame + } else { + self.titleNode.frame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width) / 2.0), y: contentRect.minY + floor((baseSize.height - titleSize.height) / 2.0)), size: titleSize) + } + } + override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { let titleSize = self.titleNode.measure(constrainedSize) var maxWidth = titleSize.width @@ -85,24 +122,11 @@ final class ItemListRevealOptionNode: ASDisplayNode { } return CGSize(width: max(74.0, maxWidth + 20.0), height: constrainedSize.height) } - - override func layout() { - super.layout() - - let size = self.bounds.size - let titleSize = self.titleNode.calculatedSize - if let iconNode = self.iconNode, let image = iconNode.image { - let titleIconSpacing: CGFloat = 3.0 - iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height - titleIconSpacing - titleSize.height) / 2.0)), size: image.size) - self.titleNode.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - image.size.height - titleIconSpacing - titleSize.height) / 2.0) + image.size.height + titleIconSpacing), size: titleSize) - } else { - self.titleNode.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) - } - } } final class ItemListRevealOptionsNode: ASDisplayNode { private let optionSelected: (ItemListRevealOption) -> Void + private let tapticAction: () -> Void private var options: [ItemListRevealOption] = [] @@ -110,8 +134,9 @@ final class ItemListRevealOptionsNode: ASDisplayNode { private var revealOffset: CGFloat = 0.0 private var rightInset: CGFloat = 0.0 - init(optionSelected: @escaping (ItemListRevealOption) -> Void) { + init(optionSelected: @escaping (ItemListRevealOption) -> Void, tapticAction: @escaping () -> Void) { self.optionSelected = optionSelected + self.tapticAction = tapticAction super.init() } @@ -165,7 +190,23 @@ final class ItemListRevealOptionsNode: ASDisplayNode { for i in 0 ..< self.optionNodes.count { let node = self.optionNodes[i] let nodeWidth = i == (self.optionNodes.count - 1) ? lastNodeWidth : basicNodeWidth - transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: floorToScreenPixels(leftOffset * revealFactor), y: 0.0), size: CGSize(width: nodeWidth, height: size.height))) + var extendedWidth = nodeWidth + var alignment: ItemListRevealOptionAlignment = .left + var nodeTransition = transition + if self.optionNodes.count == 1 { + extendedWidth = nodeWidth * max(1.0, abs(revealFactor)) + if abs(revealFactor) > 1.7 { + alignment = .right + } + } + if let nodeAlignment = node.alignment, alignment != nodeAlignment { + nodeTransition = .animated(duration: 0.2, curve: .spring) + if alignment == .right || !transition.isAnimated { + self.tapticAction() + } + } + transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: floorToScreenPixels(leftOffset * revealFactor), y: 0.0), size: CGSize(width: extendedWidth, height: size.height))) + node.updateLayout(baseSize: CGSize(width: nodeWidth, height: size.height), alignment: alignment, extendedWidth: extendedWidth, transition: nodeTransition) leftOffset += nodeWidth } } @@ -181,4 +222,16 @@ final class ItemListRevealOptionsNode: ASDisplayNode { } } } + + func isDisplayingExtendedAction() -> Bool { + if self.optionNodes.count != 1 { + return false + } + for node in self.optionNodes { + if let alignment = node.alignment, case .right = alignment { + return true + } + } + return false + } } diff --git a/TelegramUI/ItemListStickerPackItem.swift b/TelegramUI/ItemListStickerPackItem.swift index 6ba476522e..f4c68dd973 100644 --- a/TelegramUI/ItemListStickerPackItem.swift +++ b/TelegramUI/ItemListStickerPackItem.swift @@ -202,6 +202,17 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { self.addSubnode(self.selectionIconNode) self.installationActionNode.addTarget(self, action: #selector(self.installationActionPressed), forControlEvents: .touchUpInside) + self.installationActionNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.installationActionImageNode.layer.removeAnimation(forKey: "opacity") + strongSelf.installationActionImageNode.alpha = 0.4 + } else { + strongSelf.installationActionImageNode.alpha = 1.0 + strongSelf.installationActionImageNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } } deinit { @@ -306,7 +317,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { let file = item.topItem?.file var fileUpdated = false if let file = file, let previousFile = previousFile { - fileUpdated = !file.isEqual(previousFile) + fileUpdated = !file.isEqual(to: previousFile) } else if (file != nil) != (previousFile != nil) { fileUpdated = true } @@ -593,7 +604,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { } } - override func revealOptionSelected(_ option: ItemListRevealOption) { + override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { self.setRevealOptionsOpened(false, animated: true) self.revealOptionsInteractivelyClosed() diff --git a/TelegramUI/ItemListTextItem.swift b/TelegramUI/ItemListTextItem.swift index c54e9365d0..e3213b8b71 100644 --- a/TelegramUI/ItemListTextItem.swift +++ b/TelegramUI/ItemListTextItem.swift @@ -104,7 +104,7 @@ class ItemListTextItemNode: ListViewItemNode { attributedText = NSAttributedString(string: text, font: titleFont, textColor: item.theme.list.freeTextColor) case let .markdown(text): attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.theme.list.freeTextColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: item.theme.list.freeTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.theme.list.itemAccentColor), linkAttribute: { contents in - return (TelegramTextAttributes.Url, contents) + return (TelegramTextAttributes.URL, contents) })) } let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -145,7 +145,7 @@ class ItemListTextItemNode: ListViewItemNode { let titleFrame = self.titleNode.frame if let item = self.item, titleFrame.contains(location) { if let (_, attributes) = self.titleNode.attributesAtPoint(CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)) { - if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] as? String { + if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { item.linkAction?(.tap(url)) } } diff --git a/TelegramUI/ItemListTextWithLabelItem.swift b/TelegramUI/ItemListTextWithLabelItem.swift index 0b9cabda11..88fb7bdca7 100644 --- a/TelegramUI/ItemListTextWithLabelItem.swift +++ b/TelegramUI/ItemListTextWithLabelItem.swift @@ -12,6 +12,7 @@ final class ItemListTextWithLabelItem: ListViewItem, ItemListItem { let theme: PresentationTheme let label: String let text: String + let labelColor: ItemListTextWithLabelItemTextColor let textColor: ItemListTextWithLabelItemTextColor let enabledEntitiyTypes: EnabledEntityTypes let multiline: Bool @@ -23,10 +24,11 @@ final class ItemListTextWithLabelItem: ListViewItem, ItemListItem { let tag: Any? - init(theme: PresentationTheme, label: String, text: String, textColor: ItemListTextWithLabelItemTextColor = .primary, enabledEntitiyTypes: EnabledEntityTypes, multiline: Bool, selected: Bool? = nil, sectionId: ItemListSectionId, action: (() -> Void)?, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) { + init(theme: PresentationTheme, label: String, text: String, labelColor: ItemListTextWithLabelItemTextColor = .primary, textColor: ItemListTextWithLabelItemTextColor = .primary, enabledEntitiyTypes: EnabledEntityTypes, multiline: Bool, selected: Bool? = nil, sectionId: ItemListSectionId, action: (() -> Void)?, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) { self.theme = theme self.label = label self.text = text + self.labelColor = labelColor self.textColor = textColor self.enabledEntitiyTypes = enabledEntitiyTypes self.multiline = multiline @@ -176,7 +178,14 @@ class ItemListTextWithLabelItemNode: ListViewItemNode { leftOffset += selectionWidth - 24.0 } - let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftOffset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let labelColor: UIColor + switch item.labelColor { + case .primary: + labelColor = item.theme.list.itemPrimaryTextColor + case .accent: + labelColor = item.theme.list.itemAccentColor + } + let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor: labelColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftOffset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let entities = generateTextEntities(item.text, enabledTypes: item.enabledEntitiyTypes) let baseColor: UIColor @@ -350,7 +359,7 @@ class ItemListTextWithLabelItemNode: ListViewItemNode { private func linkItemAtPoint(_ point: CGPoint) -> TextLinkItem? { let textNodeFrame = self.textNode.frame if let (_, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { - if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] as? String { + if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { return .url(url) } else if let peerName = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { return .mention(peerName) @@ -382,7 +391,7 @@ class ItemListTextWithLabelItemNode: ListViewItemNode { let textNodeFrame = self.textNode.frame if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { let possibleNames: [String] = [ - TelegramTextAttributes.Url, + TelegramTextAttributes.URL, TelegramTextAttributes.PeerMention, TelegramTextAttributes.PeerTextMention, TelegramTextAttributes.BotCommand, diff --git a/TelegramUI/LegacyCamera.swift b/TelegramUI/LegacyCamera.swift index d0c71ad4b3..f5894d5f2b 100644 --- a/TelegramUI/LegacyCamera.swift +++ b/TelegramUI/LegacyCamera.swift @@ -9,7 +9,7 @@ import SwiftSignalKit func presentedLegacyCamera(account: Account, peer: Peer, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, editingMedia: Bool, saveCapturedPhotos: Bool, sendMessagesWithSignals: @escaping ([Any]?) -> Void) { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) - legacyController.supportedOrientations = .portrait + legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) legacyController.statusBar.statusBarStyle = .Hide legacyController.deferScreenEdgeGestures = [.top] @@ -30,7 +30,7 @@ func presentedLegacyCamera(account: Account, peer: Peer, cameraView: TGAttachmen controller.inhibitDocumentCaptions = false controller.suggestionContext = legacySuggestionContext(account: account, peerId: peer.id) controller.recipientName = peer.displayTitle - if (peer is TelegramUser || peer is TelegramSecretChat) && peer.id != account.peerId { + if (peer is TelegramUser) && peer.id != account.peerId { controller.hasTimer = true } @@ -121,7 +121,7 @@ func presentedLegacyCamera(account: Account, peer: Peer, cameraView: TGAttachmen func presentedLegacyShortcutCamera(account: Account, saveCapturedMedia: Bool, saveEditedPhotos: Bool, parentController: ViewController) { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) - legacyController.supportedOrientations = .portrait + legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) legacyController.statusBar.statusBarStyle = .Hide legacyController.deferScreenEdgeGestures = [.top] @@ -156,6 +156,9 @@ func presentedLegacyShortcutCamera(account: Account, saveCapturedMedia: Bool, sa if let parentController = parentController { parentController.present(ShareController(account: account, subject: .fromExternal({ peerIds, text in return legacyAssetPickerEnqueueMessages(account: account, signals: signals!) + |> `catch` { _ -> Signal<[EnqueueMessage], NoError> in + return .single([]) + } |> mapToSignal { messages -> Signal in let resultSignals = peerIds.map({ peerId in return enqueueMessages(account: account, peerId: peerId, messages: messages) diff --git a/TelegramUI/LegacyChannelIntroController.swift b/TelegramUI/LegacyChannelIntroController.swift new file mode 100644 index 0000000000..8e3e47ca3d --- /dev/null +++ b/TelegramUI/LegacyChannelIntroController.swift @@ -0,0 +1,17 @@ +import Foundation +import TelegramCore +import Display + +import TelegramUIPrivateModule + +func legacyChannelIntroController(account: Account, theme: PresentationTheme, strings: PresentationStrings) -> ViewController { + let controller = LegacyController(presentation: .custom, theme: theme) + controller.bind(controller: TGChannelIntroController(context: controller.context, getLocalizedString: { string in + return strings.dict[string!] ?? string + }, theme: TGChannelIntroControllerTheme(backgroundColor: theme.list.plainBackgroundColor, primaryColor: theme.list.itemPrimaryTextColor, secondaryColor: theme.list.itemSecondaryTextColor, accentColor: theme.list.itemAccentColor, backArrowImage: NavigationBarTheme.generateBackArrowImage(color: theme.list.itemAccentColor), introImage: UIImage(bundleImageName: "Chat/Intro/ChannelIntro")), completion: { [weak controller] in + if let navigationController = controller?.navigationController as? NavigationController { + navigationController.replaceTopController(createChannelController(account: account), animated: true) + } + })!) + return controller +} diff --git a/TelegramUI/LegacyController.swift b/TelegramUI/LegacyController.swift index f3edfaf9b9..07a7669ba5 100644 --- a/TelegramUI/LegacyController.swift +++ b/TelegramUI/LegacyController.swift @@ -79,7 +79,7 @@ private final class LegacyComponentsOverlayWindowManagerImpl: NSObject, LegacyCo } final class LegacyControllerContext: NSObject, LegacyComponentsContext { - private weak var controller: ViewController? + private(set) weak var controller: ViewController? private let theme: PresentationTheme? init(controller: ViewController?, theme: PresentationTheme?) { diff --git a/TelegramUI/LegacyEmptyController.swift b/TelegramUI/LegacyEmptyController.swift index 068b738847..6dac6436ca 100644 --- a/TelegramUI/LegacyEmptyController.swift +++ b/TelegramUI/LegacyEmptyController.swift @@ -15,4 +15,35 @@ final class LegacyEmptyController: TGViewController { self.view.backgroundColor = nil self.view.isOpaque = false } + + override func present(context generator: ((LegacyComponentsContext?) -> UIViewController?)!) { + if let context = self.context as? LegacyControllerContext, let controller = context.controller { + let account = legacyAccountGet() + let presentationData = account?.telegramApplicationContext.currentPresentationData.with { $0 } + + let legacyController = LegacyController(presentation: .modal(animateIn: true), theme: presentationData?.theme, initialLayout: controller.currentlyAppliedLayout) + guard let presentedController = generator(legacyController.context) else { + return + } + if let presentationData = presentationData { + legacyController.statusBar.statusBarStyle = presentationData.theme.rootController.statusBar.style.style + } + presentedController.navigation_setDismiss({ [weak legacyController] in + legacyController?.dismiss() + }, rootController: nil) + if let presentedController = presentedController as? TGViewController { + presentedController.customDismissSelf = { [weak legacyController] in + legacyController?.dismiss() + } + } else if let presentedController = presentedController as? TGNavigationController { + presentedController.customDismissSelf = { [weak legacyController] in + legacyController?.dismiss() + } + } + legacyController.bind(controller: presentedController) + controller.present(legacyController, in: .window(.root)) + } else { + super.present(context: generator) + } + } } diff --git a/TelegramUI/LegacyMediaPickers.swift b/TelegramUI/LegacyMediaPickers.swift index d5da3e5625..76e592d110 100644 --- a/TelegramUI/LegacyMediaPickers.swift +++ b/TelegramUI/LegacyMediaPickers.swift @@ -28,20 +28,20 @@ func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, account: controller.shouldShowFileTipIfNeeded = showFileTooltip } -func legacyAssetPicker(applicationContext: TelegramApplicationContext, presentationData: PresentationData, editingMedia: Bool, fileMode: Bool, peer: Peer?, saveEditedPhotos: Bool, allowGrouping: Bool) -> Signal<(LegacyComponentsContext) -> TGMediaAssetsController, NoError> { +func legacyAssetPicker(applicationContext: TelegramApplicationContext, presentationData: PresentationData, editingMedia: Bool, fileMode: Bool, peer: Peer?, saveEditedPhotos: Bool, allowGrouping: Bool) -> Signal<(LegacyComponentsContext) -> TGMediaAssetsController, Void> { return Signal { subscriber in let intent = fileMode ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent DeviceAccess.authorizeAccess(to: .mediaLibrary(.send), presentationData: presentationData, present: applicationContext.presentGlobalController, openSettings: applicationContext.applicationBindings.openSettings, { value in if !value { - subscriber.putError(NoError()) + subscriber.putError(Void()) return } if TGMediaAssetsLibrary.authorizationStatus() == TGMediaLibraryAuthorizationStatusNotDetermined { TGMediaAssetsLibrary.requestAuthorization(for: TGMediaAssetAnyType, completion: { (status, group) in if !LegacyComponentsGlobals.provider().accessChecker().checkPhotoAuthorizationStatus(for: TGPhotoAccessIntentRead, alertDismissCompletion: nil) { - subscriber.putError(NoError()) + subscriber.putError(Void()) } else { Queue.mainQueue().async { subscriber.putNext({ context in @@ -190,7 +190,7 @@ func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String?) -> [A } } -func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signal<[EnqueueMessage], NoError> { +func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signal<[EnqueueMessage], Void> { return Signal { subscriber in let disposable = SSignal.combineSignals(signals).start(next: { anyValues in var messages: [EnqueueMessage] = [] @@ -357,7 +357,7 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa subscriber.putNext(messages) subscriber.putCompletion() }, error: { _ in - subscriber.putError(NoError()) + subscriber.putError(Void()) }, completed: nil) return ActionDisposable { @@ -366,7 +366,7 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa } } -func legacyAssetPickerDataSignals(account: Account, signals: [Any]) -> Signal<[TelegramMediaResource], NoError> { +func legacyAssetPickerDataSignals(account: Account, signals: [Any]) -> Signal<[TelegramMediaResource], Void> { return Signal { subscriber in let disposable = SSignal.combineSignals(signals).start(next: { anyValues in var datas: [TelegramMediaResource] = [] @@ -424,7 +424,7 @@ func legacyAssetPickerDataSignals(account: Account, signals: [Any]) -> Signal<[T subscriber.putNext(datas) subscriber.putCompletion() }, error: { _ in - subscriber.putError(NoError()) + subscriber.putError(Void()) }, completed: nil) return ActionDisposable { diff --git a/TelegramUI/LegacySecureIdAttachmentMenu.swift b/TelegramUI/LegacySecureIdAttachmentMenu.swift index ff80dd3a58..7c6af1f920 100644 --- a/TelegramUI/LegacySecureIdAttachmentMenu.swift +++ b/TelegramUI/LegacySecureIdAttachmentMenu.swift @@ -65,7 +65,6 @@ func presentLegacySecureIdAttachmentMenu(account: Account, present: @escaping (V guard let attachMenu = TGPassportAttachMenu.present(with: legacyController.context, parentController: emptyController, menuController: nil, title: "", intent: mappedIntent, uploadAction: { signal, completed in if let signal = signal { - completed?() let _ = (processedLegacySecureIdAttachmentItems(postbox: account.postbox, signal: signal) |> mapToSignal { resources -> Signal<([TelegramMediaResource], SecureIdRecognizedDocumentData?), NoError> in if case .generic = type { @@ -79,7 +78,10 @@ func presentLegacySecureIdAttachmentMenu(account: Account, present: @escaping (V } |> deliverOnMainQueue).start(next: { resourcesAndData in completion(resourcesAndData.0, resourcesAndData.1) + completed?() }) + } else { + completed?() } }, sourceView: nil, sourceRect: nil, barButtonItem: nil) else { legacyController.dismiss() diff --git a/TelegramUI/ListMessageFileItemNode.swift b/TelegramUI/ListMessageFileItemNode.swift index 7a1d9de5c3..c1cec79dbc 100644 --- a/TelegramUI/ListMessageFileItemNode.swift +++ b/TelegramUI/ListMessageFileItemNode.swift @@ -366,7 +366,7 @@ final class ListMessageFileItemNode: ListMessageNode { var mediaUpdated = false if let currentMedia = currentMedia { if let selectedMedia = selectedMedia { - mediaUpdated = !selectedMedia.isEqual(currentMedia) + mediaUpdated = !selectedMedia.isEqual(to: currentMedia) } else { mediaUpdated = true } @@ -384,7 +384,7 @@ final class ListMessageFileItemNode: ListMessageNode { let account = item.account updatedFetchControls = FetchControls(fetch: { [weak self] in if let strongSelf = self { - strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(account: account, message: message, file: selectedMedia).start()) + strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(account: account, message: message, file: selectedMedia, userInitiated: true).start()) } }, cancel: { messageMediaFileCancelInteractiveFetch(account: account, messageId: message.id, file: selectedMedia) @@ -392,7 +392,7 @@ final class ListMessageFileItemNode: ListMessageNode { } if statusUpdated { - updatedStatusSignal = messageFileMediaResourceStatus(account: item.account, file: selectedMedia, message: message) + updatedStatusSignal = messageFileMediaResourceStatus(account: item.account, file: selectedMedia, message: message, isRecentActions: false) if isAudio { if let currentUpdatedStatusSignal = updatedStatusSignal { diff --git a/TelegramUI/ListMessageSnippetItemNode.swift b/TelegramUI/ListMessageSnippetItemNode.swift index 5ee8b23c43..ae40731e9d 100644 --- a/TelegramUI/ListMessageSnippetItemNode.swift +++ b/TelegramUI/ListMessageSnippetItemNode.swift @@ -207,7 +207,7 @@ final class ListMessageSnippetItemNode: ListMessageNode { let plainUrlString = NSAttributedString(string: content.displayUrl, font: descriptionFont, textColor: item.theme.list.itemAccentColor) let urlString = NSMutableAttributedString() urlString.append(plainUrlString) - urlString.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.Url), value: content.displayUrl, range: NSMakeRange(0, urlString.length)) + urlString.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.URL), value: content.displayUrl, range: NSMakeRange(0, urlString.length)) linkText = urlString descriptionText = mutableDescriptionText @@ -268,7 +268,7 @@ final class ListMessageSnippetItemNode: ListMessageNode { if item.theme.list.itemAccentColor.isEqual(item.theme.list.itemPrimaryTextColor) { urlAttributedString.addAttribute(NSAttributedStringKey.underlineStyle, value: NSUnderlineStyle.styleSingle.rawValue as NSNumber, range: NSMakeRange(0, urlAttributedString.length)) } - urlAttributedString.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.Url), value: urlString, range: NSMakeRange(0, urlAttributedString.length)) + urlAttributedString.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.URL), value: urlString, range: NSMakeRange(0, urlAttributedString.length)) linkText = urlAttributedString descriptionText = mutableDescriptionText @@ -485,7 +485,7 @@ final class ListMessageSnippetItemNode: ListMessageNode { } } else { if !item.controllerInteraction.openMessage(item.message) { - item.controllerInteraction.openUrl(currentPrimaryUrl) + item.controllerInteraction.openUrl(currentPrimaryUrl, false) } } } @@ -511,7 +511,7 @@ final class ListMessageSnippetItemNode: ListMessageNode { let textNodeFrame = self.linkNode.frame if let (_, attributes) = self.linkNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { let possibleNames: [String] = [ - TelegramTextAttributes.Url, + TelegramTextAttributes.URL, ] for name in possibleNames { if let value = attributes[NSAttributedStringKey(rawValue: name)] as? String { @@ -535,10 +535,10 @@ final class ListMessageSnippetItemNode: ListMessageNode { item.controllerInteraction.longTap(ChatControllerInteractionLongTapAction.url(url)) } else if url == self.currentPrimaryUrl { if !item.controllerInteraction.openMessage(item.message) { - item.controllerInteraction.openUrl(url) + item.controllerInteraction.openUrl(url, false) } } else { - item.controllerInteraction.openUrl(url) + item.controllerInteraction.openUrl(url, false) } } case .hold, .doubleTap: @@ -559,7 +559,7 @@ final class ListMessageSnippetItemNode: ListMessageNode { let textNodeFrame = self.linkNode.frame if let (index, attributes) = self.linkNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { let possibleNames: [String] = [ - TelegramTextAttributes.Url + TelegramTextAttributes.URL ] for name in possibleNames { if let _ = attributes[NSAttributedStringKey(rawValue: name)] { diff --git a/TelegramUI/ListSectionHeaderNode.swift b/TelegramUI/ListSectionHeaderNode.swift index 29b15f7d36..1d1cda40fc 100644 --- a/TelegramUI/ListSectionHeaderNode.swift +++ b/TelegramUI/ListSectionHeaderNode.swift @@ -3,14 +3,19 @@ import AsyncDisplayKit import Display final class ListSectionHeaderNode: ASDisplayNode { - private let label: TextNode + private let label: ImmediateTextNode private var actionButton: HighlightableButtonNode? private var theme: PresentationTheme + private var validLayout: (size: CGSize, leftInset: CGFloat, rightInset: CGFloat)? + var title: String? { didSet { - self.calculatedLayoutDidChange() - self.setNeedsLayout() + self.label.attributedText = NSAttributedString(string: self.title ?? "", font: Font.medium(12.0), textColor: self.theme.chatList.sectionHeaderTextColor) + + if let (size, leftInset, rightInset) = self.validLayout { + self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset) + } } } @@ -30,8 +35,10 @@ final class ListSectionHeaderNode: ASDisplayNode { if let action = self.action { self.actionButton?.setAttributedTitle(NSAttributedString(string: action, font: Font.medium(12.0), textColor: self.theme.chatList.sectionHeaderTextColor), for: []) } - self.calculatedLayoutDidChange() - self.setNeedsLayout() + + if let (size, leftInset, rightInset) = self.validLayout { + self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset) + } } } @@ -40,9 +47,8 @@ final class ListSectionHeaderNode: ASDisplayNode { init(theme: PresentationTheme) { self.theme = theme - self.label = TextNode() + self.label = ImmediateTextNode() self.label.isLayerBacked = true - self.label.isOpaque = true super.init() @@ -55,21 +61,23 @@ final class ListSectionHeaderNode: ASDisplayNode { if self.theme !== theme { self.theme = theme + self.label.attributedText = NSAttributedString(string: self.title ?? "", font: Font.medium(12.0), textColor: self.theme.chatList.sectionHeaderTextColor) + self.backgroundColor = theme.chatList.sectionHeaderFillColor if let action = self.action { self.actionButton?.setAttributedTitle(NSAttributedString(string: action, font: Font.medium(12.0), textColor: self.theme.chatList.sectionHeaderTextColor), for: []) } - if !self.bounds.size.width.isZero && !self.bounds.size.height.isZero { - self.layout() + + if let (size, leftInset, rightInset) = self.validLayout { + self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset) } } } func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { - let makeLayout = TextNode.asyncLayout(self.label) - let (labelLayout, labelApply) = makeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: self.title ?? "", font: Font.medium(12.0), textColor: self.theme.chatList.sectionHeaderTextColor), backgroundColor: self.backgroundColor, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, size.width - leftInset - rightInset - 18.0), height: size.height), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let _ = labelApply() - self.label.frame = CGRect(origin: CGPoint(x: leftInset + 9.0, y: 7.0), size: labelLayout.size) + self.validLayout = (size, leftInset, rightInset) + let labelSize = self.label.updateLayout(CGSize(width: max(0.0, size.width - leftInset - rightInset - 18.0), height: size.height)) + self.label.frame = CGRect(origin: CGPoint(x: leftInset + 9.0, y: 7.0), size: labelSize) if let actionButton = self.actionButton { let buttonSize = actionButton.measure(CGSize(width: size.width, height: size.height)) diff --git a/TelegramUI/ManagedAudioRecorder.swift b/TelegramUI/ManagedAudioRecorder.swift index f91e9bdf68..de6ecd12db 100644 --- a/TelegramUI/ManagedAudioRecorder.swift +++ b/TelegramUI/ManagedAudioRecorder.swift @@ -137,6 +137,13 @@ struct RecordedAudioData { let waveform: Data? } +private let beginToneData: TonePlayerData? = { + guard let url = Bundle.main.url(forResource: "begin_record", withExtension: "caf") else { + return nil + } + return loadTonePlayerData(path: url.path) +}() + final class ManagedAudioRecorderContext { private let id: Int32 private let micLevel: ValuePromise @@ -168,8 +175,9 @@ final class ManagedAudioRecorderContext { private var hasAudioSession = false private var audioSessionDisposable: Disposable? - private var toneRenderer: MediaPlayerAudioRenderer? - private var toneRendererAudioSession: MediaPlayerAudioSessionCustomControl? + private var tonePlayer: TonePlayer? + //private var toneRenderer: MediaPlayerAudioRenderer? + //private var toneRendererAudioSession: MediaPlayerAudioSessionCustomControl? private var toneRendererAudioSessionActivated = false private var processSamples = false @@ -192,7 +200,7 @@ final class ManagedAudioRecorderContext { self.dataItem = TGDataItem() self.oggWriter = TGOggOpusWriter() - if let toneData = audioRecordingToneData { + /*if false, let toneData = audioRecordingToneData { self.processSamples = false let toneRenderer = MediaPlayerAudioRenderer(audioSession: .custom({ [weak self] control in queue.async { @@ -206,7 +214,7 @@ final class ManagedAudioRecorderContext { } return ActionDisposable { } - }), playAndRecord: true, forceAudioToSpeaker: false, updatedRate: { + }), playAndRecord: true, forceAudioToSpeaker: false, baseRate: 1.0, updatedRate: { }, audioPaused: {}) self.toneRenderer = toneRenderer @@ -278,6 +286,27 @@ final class ManagedAudioRecorderContext { toneTimer.start() } else { self.processSamples = true + }*/ + + if let beginToneData = beginToneData { + self.tonePlayer = TonePlayer() + self.tonePlayer?.play(data: beginToneData, completed: { [weak self] in + queue.async { + guard let strongSelf = self else { + return + } + let toneTimer = SwiftSignalKit.Timer(timeout: 0.3, repeat: false, completion: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.processSamples = true + }, queue: queue) + strongSelf.toneTimer = toneTimer + toneTimer.start() + } + }) + } else { + self.processSamples = true } addAudioRecorderContext(self.id, self) @@ -302,7 +331,7 @@ final class ManagedAudioRecorderContext { self.audioSessionDisposable?.dispose() - self.toneRenderer?.stop() + //self.toneRenderer?.stop() self.toneTimer?.invalidate() } @@ -365,11 +394,9 @@ final class ManagedAudioRecorderContext { let _ = self.audioUnit.swap(audioUnit) - self.toneRenderer?.setRate(1.0) - if self.audioSessionDisposable == nil { let queue = self.queue - self.audioSessionDisposable = self.mediaManager.audioSession.push(audioSessionType: .playAndRecord, activate: { [weak self] state in + self.audioSessionDisposable = self.mediaManager.audioSession.push(audioSessionType: .record, activate: { [weak self] state in queue.async { if let strongSelf = self, !strongSelf.paused { strongSelf.hasAudioSession = true @@ -393,11 +420,11 @@ final class ManagedAudioRecorderContext { } func audioSessionAcquired(headset: Bool) { - if let toneRendererAudioSession = self.toneRendererAudioSession, headset || self.beginWithTone { + if let tonePlayer = self.tonePlayer, headset || self.beginWithTone { self.beganWithTone(true) if !self.toneRendererAudioSessionActivated { self.toneRendererAudioSessionActivated = true - toneRendererAudioSession.activate() + tonePlayer.start() } } else { self.processSamples = true @@ -438,9 +465,9 @@ final class ManagedAudioRecorderContext { } } - if let toneRendererAudioSession = self.toneRendererAudioSession, self.toneRendererAudioSessionActivated { + if let tonePlayer = self.tonePlayer, self.toneRendererAudioSessionActivated { self.toneRendererAudioSessionActivated = false - toneRendererAudioSession.deactivate() + tonePlayer.stop() } let audioSessionDisposable = self.audioSessionDisposable diff --git a/TelegramUI/ManagedAudioSession.swift b/TelegramUI/ManagedAudioSession.swift index 39c68919b5..c09e550573 100644 --- a/TelegramUI/ManagedAudioSession.swift +++ b/TelegramUI/ManagedAudioSession.swift @@ -1,10 +1,12 @@ import Foundation import SwiftSignalKit import AVFoundation +import UIKit enum ManagedAudioSessionType { case play - case playAndRecord + case playWithPossiblePortOverride + case record case voiceCall } @@ -12,22 +14,35 @@ private func nativeCategoryForType(_ type: ManagedAudioSessionType) -> String { switch type { case .play: return AVAudioSessionCategoryPlayback - case .playAndRecord, .voiceCall: + case .playWithPossiblePortOverride, .record, .voiceCall: return AVAudioSessionCategoryPlayAndRecord } } -private func allowBluetoothForType(_ type: ManagedAudioSessionType) -> Bool { - switch type { - case .play: - return false - case .playAndRecord, .voiceCall: - return true - } +public enum AudioSessionPortType { + case generic + case bluetooth } -public enum AudioSessionOutput { +public struct AudioSessionPort: Equatable { + fileprivate let uid: String + public let name: String + public let type: AudioSessionPortType +} + +public enum AudioSessionOutput: Equatable { + case builtin case speaker + case headphones + case port(AudioSessionPort) +} + +private let bluetoothPortTypes = Set([AVAudioSessionPortBluetoothA2DP, AVAudioSessionPortBluetoothLE, AVAudioSessionPortBluetoothHFP]) + +private extension AudioSessionOutput { + init(description: AVAudioSessionPortDescription) { + self = .port(AudioSessionPort(uid: description.uid, name: description.portName, type: bluetoothPortTypes.contains(description.portType) ? .bluetooth : .generic)) + } } public enum AudioSessionOutputMode: Equatable { @@ -66,18 +81,20 @@ private final class HolderRecord { let activate: (ManagedAudioSessionControl) -> Void let deactivate: () -> Signal let headsetConnectionStatusChanged: (Bool) -> Void + let availableOutputsChanged: ([AudioSessionOutput], AudioSessionOutput?) -> Void let once: Bool var outputMode: AudioSessionOutputMode var active: Bool = false var deactivatingDisposable: Disposable? = nil - init(id: Int32, audioSessionType: ManagedAudioSessionType, control: ManagedAudioSessionControl, activate: @escaping (ManagedAudioSessionControl) -> Void, deactivate: @escaping () -> Signal, headsetConnectionStatusChanged: @escaping (Bool) -> Void, once: Bool, outputMode: AudioSessionOutputMode) { + init(id: Int32, audioSessionType: ManagedAudioSessionType, control: ManagedAudioSessionControl, activate: @escaping (ManagedAudioSessionControl) -> Void, deactivate: @escaping () -> Signal, headsetConnectionStatusChanged: @escaping (Bool) -> Void, availableOutputsChanged: @escaping ([AudioSessionOutput], AudioSessionOutput?) -> Void, once: Bool, outputMode: AudioSessionOutputMode) { self.id = id self.audioSessionType = audioSessionType self.control = control self.activate = activate self.deactivate = deactivate self.headsetConnectionStatusChanged = headsetConnectionStatusChanged + self.availableOutputsChanged = availableOutputsChanged self.once = once self.outputMode = outputMode } @@ -122,39 +139,27 @@ public class ManagedAudioSessionControl { public final class ManagedAudioSession { private var nextId: Int32 = 0 private let queue = Queue() + private let hasLoudspeaker: Bool private var holders: [HolderRecord] = [] private var currentTypeAndOutputMode: (ManagedAudioSessionType, AudioSessionOutputMode)? private var deactivateTimer: SwiftSignalKit.Timer? private var isHeadsetPluggedInValue = false private let outputsToHeadphonesSubscribers = Bag<(Bool) -> Void>() + + private var availableOutputsValue: [AudioSessionOutput] = [] + private var currentOutputValue: AudioSessionOutput? + private let isActiveSubscribers = Bag<(Bool) -> Void>() private let isPlaybackActiveSubscribers = Bag<(Bool) -> Void>() init() { + self.hasLoudspeaker = UIDevice.current.model == "iPhone" + let queue = self.queue NotificationCenter.default.addObserver(forName: .AVAudioSessionRouteChange, object: AVAudioSession.sharedInstance(), queue: nil, using: { [weak self] _ in queue.async { - if let strongSelf = self { - let value = strongSelf.isHeadsetPluggedIn() - if strongSelf.isHeadsetPluggedInValue != value { - strongSelf.isHeadsetPluggedInValue = value - if let (_, outputMode) = strongSelf.currentTypeAndOutputMode { - if case .speakerIfNoHeadphones = outputMode { - strongSelf.updateOutputMode(outputMode) - } - } - for subscriber in strongSelf.outputsToHeadphonesSubscribers.copyItems() { - subscriber(value) - } - for i in 0 ..< strongSelf.holders.count { - if strongSelf.holders[i].active { - strongSelf.holders[i].headsetConnectionStatusChanged(value) - break - } - } - } - } + self?.updateCurrentAudioRouteInfo() } }) @@ -176,6 +181,7 @@ public final class ManagedAudioSession { queue.async { self.isHeadsetPluggedInValue = self.isHeadsetPluggedIn() + self.updateCurrentAudioRouteInfo() } } @@ -183,6 +189,89 @@ public final class ManagedAudioSession { self.deactivateTimer?.invalidate() } + private func updateCurrentAudioRouteInfo() { + let value = self.isHeadsetPluggedIn() + if self.isHeadsetPluggedInValue != value { + self.isHeadsetPluggedInValue = value + if let (_, outputMode) = self.currentTypeAndOutputMode { + if case .speakerIfNoHeadphones = outputMode { + self.updateOutputMode(outputMode) + } + } + for subscriber in self.outputsToHeadphonesSubscribers.copyItems() { + subscriber(value) + } + for i in 0 ..< self.holders.count { + if self.holders[i].active { + self.holders[i].headsetConnectionStatusChanged(value) + break + } + } + } + + let audioSession = AVAudioSession.sharedInstance() + + var availableOutputs: [AudioSessionOutput] = [] + var activeOutput: AudioSessionOutput = .builtin + + if let availableInputs = audioSession.availableInputs { + var hasHeadphones = false + for input in availableInputs { + var isActive = false + for currentInput in audioSession.currentRoute.inputs { + if currentInput.uid == input.uid { + isActive = true + } + } + + if input.portType == AVAudioSessionPortBuiltInMic { + if isActive { + activeOutput = .builtin + inner: for currentOutput in audioSession.currentRoute.outputs { + if currentOutput.portType == AVAudioSessionPortBuiltInSpeaker { + activeOutput = .speaker + break inner + } + } + } + continue + } + if input.portType == AVAudioSessionPortHeadphones { + if isActive { + activeOutput = .headphones + } + hasHeadphones = true + continue + } + let output = AudioSessionOutput(description: input) + availableOutputs.append(output) + if isActive { + activeOutput = output + } + } + + if self.hasLoudspeaker { + availableOutputs.insert(.speaker, at: 0) + } + + if hasHeadphones { + availableOutputs.insert(.headphones, at: 0) + } + availableOutputs.insert(.builtin, at: 0) + } + + if self.availableOutputsValue != availableOutputs || self.currentOutputValue != activeOutput { + self.availableOutputsValue = availableOutputs + self.currentOutputValue = activeOutput + for i in 0 ..< self.holders.count { + if self.holders[i].active { + self.holders[i].availableOutputsChanged(availableOutputs, activeOutput) + break + } + } + } + } + func headsetConnected() -> Signal { let queue = self.queue return Signal { [weak self] subscriber in @@ -261,9 +350,10 @@ public final class ManagedAudioSession { }, deactivate: deactivate) } - func push(audioSessionType: ManagedAudioSessionType, outputMode: AudioSessionOutputMode = .system, once: Bool = false, manualActivate: @escaping (ManagedAudioSessionControl) -> Void, deactivate: @escaping () -> Signal, headsetConnectionStatusChanged: @escaping (Bool) -> Void = { _ in }) -> Disposable { + func push(audioSessionType: ManagedAudioSessionType, outputMode: AudioSessionOutputMode = .system, once: Bool = false, manualActivate: @escaping (ManagedAudioSessionControl) -> Void, deactivate: @escaping () -> Signal, headsetConnectionStatusChanged: @escaping (Bool) -> Void = { _ in }, availableOutputsChanged: @escaping ([AudioSessionOutput], AudioSessionOutput?) -> Void = { _, _ in }) -> Disposable { let id = OSAtomicIncrement32(&self.nextId) - self.queue.async { + let queue = self.queue + queue.async { self.holders.append(HolderRecord(id: id, audioSessionType: audioSessionType, control: ManagedAudioSessionControl(setupImpl: { [weak self] synchronous in if let strongSelf = self { let f: () -> Void = { @@ -309,7 +399,15 @@ public final class ManagedAudioSession { } } } - }), activate: manualActivate, deactivate: deactivate, headsetConnectionStatusChanged: headsetConnectionStatusChanged, once: once, outputMode: outputMode)) + }), activate: { [weak self] state in + manualActivate(state) + queue.async { + if let strongSelf = self { + strongSelf.updateCurrentAudioRouteInfo() + availableOutputsChanged(strongSelf.availableOutputsValue, strongSelf.currentOutputValue) + } + } + }, deactivate: deactivate, headsetConnectionStatusChanged: headsetConnectionStatusChanged, availableOutputsChanged: availableOutputsChanged, once: once, outputMode: outputMode)) self.updateHolders() } return ActionDisposable { [weak self] in @@ -481,7 +579,20 @@ public final class ManagedAudioSession { do { print("ManagedAudioSession setting category for \(type)") - try AVAudioSession.sharedInstance().setCategory(nativeCategoryForType(type), with: AVAudioSessionCategoryOptions(rawValue: allowBluetoothForType(type) ? AVAudioSessionCategoryOptions.allowBluetooth.rawValue : 0)) + var options: AVAudioSessionCategoryOptions = [] + switch type { + case .play: + break + case .playWithPossiblePortOverride: + if #available(iOSApplicationExtension 10.0, *) { + options.insert(.allowBluetoothA2DP) + } else { + options.insert(.allowBluetooth) + } + case .record, .voiceCall: + options.insert(.allowBluetooth) + } + try AVAudioSession.sharedInstance().setCategory(nativeCategoryForType(type), with: options) print("ManagedAudioSession setting active \(type != .none)") try AVAudioSession.sharedInstance().setMode(type == .voiceCall ? AVAudioSessionModeVoiceChat : AVAudioSessionModeDefault) } catch let error { @@ -501,20 +612,53 @@ public final class ManagedAudioSession { } } - private func setupOutputMode(_ outputMode: AudioSessionOutputMode) throws { + private func setupOutputMode(_ outputMode: AudioSessionOutputMode, type: ManagedAudioSessionType) throws { + var resetToBuiltin = false switch outputMode { case .system: - try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none) + resetToBuiltin = true case let .custom(output): switch output { + case .builtin: + resetToBuiltin = true case .speaker: try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker) + case .headphones: + break + case let .port(port): + try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none) + if let routes = AVAudioSession.sharedInstance().availableInputs { + for route in routes { + if route.uid == port.uid { + let _ = try? AVAudioSession.sharedInstance().setPreferredInput(route) + break + } + } + } } case .speakerIfNoHeadphones: if !self.isHeadsetPluggedInValue { try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker) + } else { + try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none) } } + if resetToBuiltin { + try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none) + switch type { + case .voiceCall, .playWithPossiblePortOverride, .record: + if let routes = AVAudioSession.sharedInstance().availableInputs { + for route in routes { + if route.portType == AVAudioSessionPortBuiltInMic { + let _ = try? AVAudioSession.sharedInstance().setPreferredInput(route) + break + } + } + } + default: + break + } + } } private func activate() { @@ -522,7 +666,9 @@ public final class ManagedAudioSession { do { try AVAudioSession.sharedInstance().setActive(true, with: [.notifyOthersOnDeactivation]) - try self.setupOutputMode(outputMode) + self.updateCurrentAudioRouteInfo() + + try self.setupOutputMode(outputMode, type: type) if case .voiceCall = type { try AVAudioSession.sharedInstance().setPreferredIOBufferDuration(0.005) @@ -537,7 +683,7 @@ public final class ManagedAudioSession { if let (type, currentOutputMode) = self.currentTypeAndOutputMode, currentOutputMode != outputMode { self.currentTypeAndOutputMode = (type, outputMode) do { - try self.setupOutputMode(outputMode) + try self.setupOutputMode(outputMode, type: type) } catch let error { print("ManagedAudioSession overrideOutputAudioPort error \(error)") } diff --git a/TelegramUI/MapResources.swift b/TelegramUI/MapResources.swift index c557f5addc..25649c5e2b 100644 --- a/TelegramUI/MapResources.swift +++ b/TelegramUI/MapResources.swift @@ -83,7 +83,7 @@ private func adjustGMapLatitude(_ latitude: Double, offset: Int, zoom: Int) -> D return yToLatitude(latitudeToY(latitude) + t) } -func fetchMapSnapshotResource(resource: MapSnapshotMediaResource) -> Signal { +func fetchMapSnapshotResource(resource: MapSnapshotMediaResource) -> Signal { return Signal { subscriber in let disposable = MetaDisposable() diff --git a/TelegramUI/MediaManager.swift b/TelegramUI/MediaManager.swift index 5a574519c1..0920c7b6cc 100644 --- a/TelegramUI/MediaManager.swift +++ b/TelegramUI/MediaManager.swift @@ -144,12 +144,11 @@ public final class MediaManager: NSObject { return lhs?.0 == rhs?.0 && lhs?.1 == rhs?.1 })) - let commandCenter = MPRemoteCommandCenter.shared() + /*let commandCenter = MPRemoteCommandCenter.shared() - commandCenter.playCommand.isEnabled = false - commandCenter.playCommand.addTarget(self, action: #selector(playCommandEvent(_:))) + commandCenter.playCommand.isEnabled = false*/ - commandCenter.pauseCommand.isEnabled = false + /*commandCenter.pauseCommand.isEnabled = false commandCenter.pauseCommand.addTarget(self, action: #selector(pauseCommandEvent(_:))) commandCenter.previousTrackCommand.isEnabled = false @@ -159,11 +158,11 @@ public final class MediaManager: NSObject { commandCenter.nextTrackCommand.addTarget(self, action: #selector(nextTrackCommandEvent(_:))) commandCenter.togglePlayPauseCommand.isEnabled = false - commandCenter.togglePlayPauseCommand.addTarget(self, action: #selector(togglePlayPauseCommandEvent(_:))) + commandCenter.togglePlayPauseCommand.addTarget(self, action: #selector(togglePlayPauseCommandEvent(_:)))*/ var baseNowPlayingInfo: [String: Any]? - if #available(iOSApplicationExtension 9.1, *) { + /*if #available(iOSApplicationExtension 9.1, *) { commandCenter.changePlaybackPositionCommand.isEnabled = false commandCenter.changePlaybackPositionCommand.addTarget(handler: { [weak self] event in if let strongSelf = self, let event = event as? MPChangePlaybackPositionCommandEvent { @@ -175,7 +174,7 @@ public final class MediaManager: NSObject { return .noActionableNowPlayingItem } }) - } + }*/ var previousState: SharedMediaPlayerItemPlaybackState? var previousDisplayData: SharedMediaPlaybackDisplayData? @@ -201,19 +200,6 @@ public final class MediaManager: NSObject { } } - if currentGlobalControlsOptions != updatedGlobalControlOptions { - currentGlobalControlsOptions = updatedGlobalControlOptions - let commandCenter = MPRemoteCommandCenter.shared() - commandCenter.playCommand.isEnabled = updatedGlobalControlOptions.contains(.play) - commandCenter.pauseCommand.isEnabled = updatedGlobalControlOptions.contains(.pause) - commandCenter.previousTrackCommand.isEnabled = updatedGlobalControlOptions.contains(.previous) - commandCenter.nextTrackCommand.isEnabled = updatedGlobalControlOptions.contains(.next) - commandCenter.togglePlayPauseCommand.isEnabled = !updatedGlobalControlOptions.intersection([.play, .pause]).isEmpty - if #available(iOSApplicationExtension 9.1, *) { - commandCenter.changePlaybackPositionCommand.isEnabled = updatedGlobalControlOptions.contains(.seek) - } - } - if let (state, type) = stateAndType, type == .music, let displayData = state.item.displayData { if previousDisplayData != displayData { previousDisplayData = displayData @@ -258,18 +244,42 @@ public final class MediaManager: NSObject { globalControlsStatus.set(.single(nil)) globalControlsArtwork.set(.single(nil)) - /*let commandCenter = MPRemoteCommandCenter.shared() - commandCenter.playCommand.isEnabled = false - commandCenter.pauseCommand.isEnabled = false - commandCenter.previousTrackCommand.isEnabled = false - commandCenter.nextTrackCommand.isEnabled = false - commandCenter.togglePlayPauseCommand.isEnabled = false*/ - if baseNowPlayingInfo != nil { baseNowPlayingInfo = nil MPNowPlayingInfoCenter.default().nowPlayingInfo = nil } } + + if currentGlobalControlsOptions != updatedGlobalControlOptions { + let commandCenter = MPRemoteCommandCenter.shared() + + var optionsAndCommands: [(GlobalControlOptions, MPRemoteCommand, Selector)] = [ + (.play, commandCenter.playCommand, #selector(self.playCommandEvent(_:))), + (.pause, commandCenter.pauseCommand, #selector(self.pauseCommandEvent(_:))), + (.previous, commandCenter.previousTrackCommand, #selector(self.previousTrackCommandEvent(_:))), + (.next, commandCenter.nextTrackCommand, #selector(self.nextTrackCommandEvent(_:))), + ([.play, .pause], commandCenter.togglePlayPauseCommand, #selector(self.togglePlayPauseCommandEvent(_:))) + ] + if #available(iOSApplicationExtension 9.1, *) { + optionsAndCommands.append((.seek, commandCenter.changePlaybackPositionCommand, #selector(self.changePlaybackPositionCommandEvent(_:)))) + } + + for (option, command, selector) in optionsAndCommands { + let previousValue = !currentGlobalControlsOptions.intersection(option).isEmpty + let updatedValue = !updatedGlobalControlOptions.intersection(option).isEmpty + if previousValue != updatedValue { + if updatedValue { + command.isEnabled = true + command.addTarget(self, action: selector) + } else { + command.isEnabled = false + command.removeTarget(self, action: selector) + } + } + } + + currentGlobalControlsOptions = updatedGlobalControlOptions + } })) self.globalControlsArtworkDisposable.set((self.globalControlsArtwork.get() @@ -405,7 +415,7 @@ public final class MediaManager: NSObject { strongSelf.musicMediaPlayer?.control(.playback(.pause)) strongSelf.voiceMediaPlayer?.stop() if let playlist = playlist { - let voiceMediaPlayer = SharedMediaPlayer(mediaManager: strongSelf, inForeground: strongSelf.inForeground, postbox: strongSelf.postbox, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: .reversed, initialLooping: .none, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: true) + let voiceMediaPlayer = SharedMediaPlayer(mediaManager: strongSelf, inForeground: strongSelf.inForeground, postbox: strongSelf.postbox, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: .reversed, initialLooping: .none, initialPlaybackRate: settings.voicePlaybackRate, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: true) strongSelf.voiceMediaPlayer = voiceMediaPlayer voiceMediaPlayer.playedToEnd = { [weak voiceMediaPlayer] in if let strongSelf = self, let voiceMediaPlayer = voiceMediaPlayer, voiceMediaPlayer === strongSelf.voiceMediaPlayer { @@ -420,7 +430,7 @@ public final class MediaManager: NSObject { strongSelf.musicMediaPlayer?.stop() strongSelf.voiceMediaPlayer?.control(.playback(.pause)) if let playlist = playlist { - strongSelf.musicMediaPlayer = SharedMediaPlayer(mediaManager: strongSelf, inForeground: strongSelf.inForeground, postbox: strongSelf.postbox, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: settings.order, initialLooping: settings.looping, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: false) + strongSelf.musicMediaPlayer = SharedMediaPlayer(mediaManager: strongSelf, inForeground: strongSelf.inForeground, postbox: strongSelf.postbox, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: settings.order, initialLooping: settings.looping, initialPlaybackRate: .x1, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: false) strongSelf.musicMediaPlayer?.control(.playback(.play)) } else { strongSelf.musicMediaPlayer = nil @@ -496,6 +506,10 @@ public final class MediaManager: NSObject { self.playlistControl(.playback(.togglePlayPause)) } + @objc func changePlaybackPositionCommandEvent(_ event: MPChangePlaybackPositionCommandEvent) { + self.playlistControl(.seek(event.positionTime)) + } + func setOverlayVideoNode(_ node: OverlayMediaItemNode?) { if let currentOverlayVideoNode = self.currentOverlayVideoNode { self.overlayMediaManager.controller?.removeNode(currentOverlayVideoNode, customTransition: true) diff --git a/TelegramUI/MediaNavigationAccessoryHeaderNode.swift b/TelegramUI/MediaNavigationAccessoryHeaderNode.swift index 5bb5c6834f..dd6a943035 100644 --- a/TelegramUI/MediaNavigationAccessoryHeaderNode.swift +++ b/TelegramUI/MediaNavigationAccessoryHeaderNode.swift @@ -20,6 +20,7 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode { private let actionButton: HighlightTrackingButtonNode private let actionPauseNode: ASImageNode private let actionPlayNode: ASImageNode + private let rateButton: HighlightableButtonNode private let scrubbingNode: MediaPlayerScrubbingNode @@ -35,8 +36,23 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode { var tapAction: (() -> Void)? var close: (() -> Void)? + var toggleRate: (() -> Void)? var togglePlayPause: (() -> Void)? + var voiceBaseRate: AudioPlaybackRate? = nil { + didSet { + guard self.voiceBaseRate != oldValue, let voiceBaseRate = self.voiceBaseRate else { + return + } + switch voiceBaseRate { + case .x1: + self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: []) + case .x2: + self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: []) + } + } + } + var playbackStatus: Signal? { didSet { self.scrubbingNode.status = self.playbackStatus @@ -65,6 +81,11 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode { self.closeButton.hitTestSlop = UIEdgeInsetsMake(-8.0, -8.0, -8.0, -8.0) self.closeButton.displaysAsynchronously = false + self.rateButton = HighlightableButtonNode() + + self.rateButton.hitTestSlop = UIEdgeInsetsMake(-8.0, -4.0, -8.0, -4.0) + self.rateButton.displaysAsynchronously = false + self.actionButton = HighlightTrackingButtonNode() self.actionButton.hitTestSlop = UIEdgeInsetsMake(-8.0, -8.0, -8.0, -8.0) self.actionButton.displaysAsynchronously = false @@ -98,12 +119,14 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode { self.addSubnode(self.subtitleNode) self.addSubnode(self.closeButton) + self.addSubnode(self.rateButton) self.actionButton.addSubnode(self.actionPauseNode) self.actionButton.addSubnode(self.actionPlayNode) self.addSubnode(self.actionButton) self.closeButton.addTarget(self, action: #selector(self.closeButtonPressed), forControlEvents: .touchUpInside) + self.rateButton.addTarget(self, action: #selector(self.rateButtonPressed), forControlEvents: .touchUpInside) self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside) self.addSubnode(self.scrubbingNode) @@ -122,6 +145,23 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode { } } + self.scrubbingNode.playerStatusUpdated = { [weak self] status in + guard let strongSelf = self else { + return + } + if let status = status { + let baseRate: AudioPlaybackRate + if status.baseRate.isEqual(to: 1.0) { + baseRate = .x1 + } else { + baseRate = .x2 + } + strongSelf.voiceBaseRate = baseRate + } else { + strongSelf.voiceBaseRate = .x1 + } + } + self.scrubbingNode.playbackStatusUpdated = { [weak self] status in if let strongSelf = self { let paused: Bool @@ -170,12 +210,14 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode { if let playbackItem = self.playbackItem, let displayData = playbackItem.displayData { switch displayData { case let .music(title, performer, _): + self.rateButton.isHidden = true let titleText: String = title ?? "Unknown Track" let subtitleText: String = performer ?? "Unknown Artist" titleString = NSAttributedString(string: titleText, font: titleFont, textColor: self.theme.rootController.navigationBar.primaryTextColor) subtitleString = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: self.theme.rootController.navigationBar.secondaryTextColor) case let .voice(author, peer): + self.rateButton.isHidden = false let titleText: String = author?.displayTitle ?? "" let subtitleText: String if let peer = peer { @@ -191,6 +233,7 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode { titleString = NSAttributedString(string: titleText, font: titleFont, textColor: self.theme.rootController.navigationBar.primaryTextColor) subtitleString = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: self.theme.rootController.navigationBar.secondaryTextColor) case let .instantVideo(author, peer, timestamp): + self.rateButton.isHidden = false let titleText: String = author?.displayTitle ?? "" var subtitleText: String @@ -198,10 +241,10 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode { if peer is TelegramGroup || peer is TelegramChannel { subtitleText = peer.displayTitle } else { - subtitleText = self.strings.MusicPlayer_VoiceNote + subtitleText = self.strings.Message_VideoMessage } } else { - subtitleText = self.strings.MusicPlayer_VoiceNote + subtitleText = self.strings.Message_VideoMessage } if titleText == subtitleText { @@ -215,8 +258,13 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode) - let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: size.width - 80.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: size.width - 80.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + var titleSideInset: CGFloat = 80.0 + if !self.rateButton.isHidden { + titleSideInset += 46.0 + } + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: size.width - titleSideInset, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: size.width - titleSideInset, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let _ = titleApply() let _ = subtitleApply() @@ -231,6 +279,8 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode { let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0)) transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 18.0 - closeButtonSize.width, y: minimizedTitleFrame.minY + 8.0), size: closeButtonSize)) + let rateButtonSize = CGSize(width: 24.0, height: minHeight) + transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 18.0 - closeButtonSize.width - 8.0 - rateButtonSize.width, y: 0.0), size: rateButtonSize)) transition.updateFrame(node: self.actionPlayNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 40.0, height: 37.0))) transition.updateFrame(node: self.actionPauseNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 40.0, height: 37.0))) transition.updateFrame(node: self.actionButton, frame: CGRect(origin: CGPoint(x: 0.0, y: minimizedTitleFrame.minY - 4.0), size: CGSize(width: 40.0, height: 37.0))) @@ -243,6 +293,10 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode { self.close?() } + @objc func rateButtonPressed() { + self.toggleRate?() + } + @objc func actionButtonPressed() { self.togglePlayPause?() } diff --git a/TelegramUI/MediaNavigationAccessoryPanel.swift b/TelegramUI/MediaNavigationAccessoryPanel.swift index e98e200f12..888dc93264 100644 --- a/TelegramUI/MediaNavigationAccessoryPanel.swift +++ b/TelegramUI/MediaNavigationAccessoryPanel.swift @@ -7,6 +7,7 @@ final class MediaNavigationAccessoryPanel: ASDisplayNode { let containerNode: MediaNavigationAccessoryContainerNode var close: (() -> Void)? + var toggleRate: (() -> Void)? var togglePlayPause: (() -> Void)? var tapAction: (() -> Void)? @@ -22,6 +23,9 @@ final class MediaNavigationAccessoryPanel: ASDisplayNode { close() } } + containerNode.headerNode.toggleRate = { [weak self] in + self?.toggleRate?() + } containerNode.headerNode.togglePlayPause = { [weak self] in if let strongSelf = self, let togglePlayPause = strongSelf.togglePlayPause { togglePlayPause() diff --git a/TelegramUI/MediaPlayer.swift b/TelegramUI/MediaPlayer.swift index 29034cd13d..af72b9e26d 100644 --- a/TelegramUI/MediaPlayer.swift +++ b/TelegramUI/MediaPlayer.swift @@ -63,6 +63,7 @@ private final class MediaPlayerContext { private let video: Bool private let preferSoftwareDecoding: Bool private var enableSound: Bool + private var baseRate: Double private let fetchAutomatically: Bool private var playAndRecord: Bool private var keepAudioSessionWhilePaused: Bool @@ -83,7 +84,7 @@ private final class MediaPlayerContext { private var stoppedAtEnd = false - init(queue: Queue, audioSessionManager: ManagedAudioSession, playerStatus: ValuePromise, postbox: Postbox, resourceReference: MediaResourceReference, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool, enableSound: Bool, fetchAutomatically: Bool, playAndRecord: Bool, keepAudioSessionWhilePaused: Bool) { + init(queue: Queue, audioSessionManager: ManagedAudioSession, playerStatus: ValuePromise, postbox: Postbox, resourceReference: MediaResourceReference, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, playAndRecord: Bool, keepAudioSessionWhilePaused: Bool) { assert(queue.isCurrent()) self.queue = queue @@ -95,6 +96,7 @@ private final class MediaPlayerContext { self.video = video self.preferSoftwareDecoding = preferSoftwareDecoding self.enableSound = enableSound + self.baseRate = baseRate self.fetchAutomatically = fetchAutomatically self.playAndRecord = playAndRecord self.keepAudioSessionWhilePaused = keepAudioSessionWhilePaused @@ -230,10 +232,10 @@ private final class MediaPlayerContext { audioStatus = audioTrackFrameBuffer.status(at: currentTimestamp) duration = max(duration, CMTimeGetSeconds(audioTrackFrameBuffer.duration)) } - let status = MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: duration, dimensions: CGSize(), timestamp: min(max(timestamp, 0.0), duration), seekId: self.seekId, status: .buffering(initial: false, whilePlaying: action == .play)) + let status = MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: duration, dimensions: CGSize(), timestamp: min(max(timestamp, 0.0), duration), baseRate: self.baseRate, seekId: self.seekId, status: .buffering(initial: false, whilePlaying: action == .play)) self.playerStatus.set(status) } else { - let status = MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: 0.0, dimensions: CGSize(), timestamp: 0.0, seekId: self.seekId, status: .buffering(initial: false, whilePlaying: action == .play)) + let status = MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: self.baseRate, seekId: self.seekId, status: .buffering(initial: false, whilePlaying: action == .play)) self.playerStatus.set(status) } @@ -296,7 +298,7 @@ private final class MediaPlayerContext { self.audioRenderer = nil let queue = self.queue - renderer = MediaPlayerAudioRenderer(audioSession: .manager(self.audioSessionManager), playAndRecord: self.playAndRecord, forceAudioToSpeaker: self.forceAudioToSpeaker, updatedRate: { [weak self] in + renderer = MediaPlayerAudioRenderer(audioSession: .manager(self.audioSessionManager), playAndRecord: self.playAndRecord, forceAudioToSpeaker: self.forceAudioToSpeaker, baseRate: self.baseRate, updatedRate: { [weak self] in queue.async { if let strongSelf = self { strongSelf.tick() @@ -369,7 +371,7 @@ private final class MediaPlayerContext { self.lastStatusUpdateTimestamp = nil if self.enableSound { let queue = self.queue - let renderer = MediaPlayerAudioRenderer(audioSession: .manager(self.audioSessionManager), playAndRecord: self.playAndRecord, forceAudioToSpeaker: self.forceAudioToSpeaker, updatedRate: { [weak self] in + let renderer = MediaPlayerAudioRenderer(audioSession: .manager(self.audioSessionManager), playAndRecord: self.playAndRecord, forceAudioToSpeaker: self.forceAudioToSpeaker, baseRate: self.baseRate, updatedRate: { [weak self] in queue.async { if let strongSelf = self { strongSelf.tick() @@ -452,6 +454,13 @@ private final class MediaPlayerContext { } } + fileprivate func setBaseRate(_ baseRate: Double) { + self.baseRate = baseRate + self.lastStatusUpdateTimestamp = nil + self.tick() + self.audioRenderer?.renderer.setBaseRate(baseRate) + } + fileprivate func setForceAudioToSpeaker(_ value: Bool) { if self.forceAudioToSpeaker != value { self.forceAudioToSpeaker = value @@ -612,9 +621,9 @@ private final class MediaPlayerContext { if let worstStatus = worstStatus, case let .full(fullUntil) = worstStatus, fullUntil.isFinite { if case .playing = self.state { - rate = 1.0 + rate = self.baseRate - let nextTickDelay = max(0.0, fullUntil - timestamp) + let nextTickDelay = max(0.0, fullUntil - timestamp) / self.baseRate let tickTimer = SwiftSignalKit.Timer(timeout: nextTickDelay, repeat: false, completion: { [weak self] in self?.tick() }, queue: self.queue) @@ -624,13 +633,13 @@ private final class MediaPlayerContext { rate = 0.0 } } else if let worstStatus = worstStatus, case let .finished(finishedAt) = worstStatus, finishedAt.isFinite { - let nextTickDelay = max(0.0, finishedAt - timestamp) + let nextTickDelay = max(0.0, finishedAt - timestamp) / self.baseRate if nextTickDelay.isLessThanOrEqualTo(0.0) { rate = 0.0 performActionAtEndNow = true } else { if case .playing = self.state { - rate = 1.0 + rate = self.baseRate let tickTimer = SwiftSignalKit.Timer(timeout: nextTickDelay, repeat: false, completion: { [weak self] in self?.tick() @@ -707,7 +716,7 @@ private final class MediaPlayerContext { if case .seeking(_, timestamp, _, _, _) = self.state { reportTimestamp = timestamp } - let status = MediaPlayerStatus(generationTimestamp: statusTimestamp, duration: duration, dimensions: CGSize(), timestamp: min(max(reportTimestamp, 0.0), duration), seekId: self.seekId, status: playbackStatus) + let status = MediaPlayerStatus(generationTimestamp: statusTimestamp, duration: duration, dimensions: CGSize(), timestamp: min(max(reportTimestamp, 0.0), duration), baseRate: self.baseRate, seekId: self.seekId, status: playbackStatus) self.playerStatus.set(status) } @@ -770,37 +779,16 @@ struct MediaPlayerStatus: Equatable { let duration: Double let dimensions: CGSize let timestamp: Double + let baseRate: Double let seekId: Int let status: MediaPlayerPlaybackStatus - - static func ==(lhs: MediaPlayerStatus, rhs: MediaPlayerStatus) -> Bool { - if !lhs.generationTimestamp.isEqual(to: rhs.generationTimestamp) { - return false - } - if !lhs.duration.isEqual(to: rhs.duration) { - return false - } - if !lhs.dimensions.equalTo(rhs.dimensions) { - return false - } - if !lhs.timestamp.isEqual(to: rhs.timestamp) { - return false - } - if lhs.seekId != rhs.seekId { - return false - } - if lhs.status != rhs.status { - return false - } - return true - } } final class MediaPlayer { private let queue = Queue() private var contextRef: Unmanaged? - private let statusValue = ValuePromise(MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, seekId: 0, status: .paused), ignoreRepeated: true) + private let statusValue = ValuePromise(MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused), ignoreRepeated: true) var status: Signal { return self.statusValue.get() @@ -817,9 +805,9 @@ final class MediaPlayer { } } - init(audioSessionManager: ManagedAudioSession, postbox: Postbox, resourceReference: MediaResourceReference, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool = false, enableSound: Bool, fetchAutomatically: Bool, playAndRecord: Bool = false, keepAudioSessionWhilePaused: Bool = true) { + init(audioSessionManager: ManagedAudioSession, postbox: Postbox, resourceReference: MediaResourceReference, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool = false, enableSound: Bool, baseRate: Double = 1.0, fetchAutomatically: Bool, playAndRecord: Bool = false, keepAudioSessionWhilePaused: Bool = true) { self.queue.async { - let context = MediaPlayerContext(queue: self.queue, audioSessionManager: audioSessionManager, playerStatus: self.statusValue, postbox: postbox, resourceReference: resourceReference, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, playAutomatically: playAutomatically, enableSound: enableSound, fetchAutomatically: fetchAutomatically, playAndRecord: playAndRecord, keepAudioSessionWhilePaused: keepAudioSessionWhilePaused) + let context = MediaPlayerContext(queue: self.queue, audioSessionManager: audioSessionManager, playerStatus: self.statusValue, postbox: postbox, resourceReference: resourceReference, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, playAutomatically: playAutomatically, enableSound: enableSound, baseRate: baseRate, fetchAutomatically: fetchAutomatically, playAndRecord: playAndRecord, keepAudioSessionWhilePaused: keepAudioSessionWhilePaused) self.contextRef = Unmanaged.passRetained(context) } } @@ -895,6 +883,14 @@ final class MediaPlayer { } } + func setBaseRate(_ baseRate: Double) { + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.setBaseRate(baseRate) + } + } + } + func attachPlayerNode(_ node: MediaPlayerNode) { let nodeRef: Unmanaged = Unmanaged.passRetained(node) self.queue.async { diff --git a/TelegramUI/MediaPlayerAudioRenderer.swift b/TelegramUI/MediaPlayerAudioRenderer.swift index 2745ebce35..c751b2892a 100644 --- a/TelegramUI/MediaPlayerAudioRenderer.swift +++ b/TelegramUI/MediaPlayerAudioRenderer.swift @@ -184,7 +184,11 @@ private final class AudioPlayerRendererContext { let audioPaused: () -> Void var paused = true - var audioUnit: AudioComponentInstance? + var baseRate: Double + + var audioGraph: AUGraph? + var timePitchAudioUnit: AudioComponentInstance? + var outputAudioUnit: AudioComponentInstance? var bufferContextId: Int32! let bufferContext: Atomic @@ -204,11 +208,12 @@ private final class AudioPlayerRendererContext { } } - init(controlTimebase: CMTimebase, audioSession: MediaPlayerAudioSessionControl, playAndRecord: Bool, forceAudioToSpeaker: Bool, updatedRate: @escaping () -> Void, audioPaused: @escaping () -> Void) { + init(controlTimebase: CMTimebase, audioSession: MediaPlayerAudioSessionControl, playAndRecord: Bool, forceAudioToSpeaker: Bool, baseRate: Double, updatedRate: @escaping () -> Void, audioPaused: @escaping () -> Void) { assert(audioPlayerRendererQueue.isCurrent()) self.audioSession = audioSession self.forceAudioToSpeaker = forceAudioToSpeaker + self.baseRate = baseRate self.controlTimebase = controlTimebase self.updatedRate = updatedRate @@ -249,6 +254,18 @@ private final class AudioPlayerRendererContext { self.closeAudioUnit() } + fileprivate func setBaseRate(_ baseRate: Double) { + if let timePitchAudioUnit = self.timePitchAudioUnit, !self.baseRate.isEqual(to: baseRate) { + self.baseRate = baseRate + AudioUnitSetParameter(timePitchAudioUnit, kTimePitchParam_Rate, kAudioUnitScope_Global, 0, Float32(baseRate), 0) + self.bufferContext.with { context in + if case .playing = context.state { + context.state = .playing(rate: baseRate, didSetRate: false) + } + } + } + } + fileprivate func setRate(_ rate: Double) { assert(audioPlayerRendererQueue.isCurrent()) @@ -256,11 +273,13 @@ private final class AudioPlayerRendererContext { self.start() } + let baseRate = self.baseRate + self.bufferContext.with { context in if !rate.isZero { if case .playing = context.state { } else { - context.state = .playing(rate: rate, didSetRate: false) + context.state = .playing(rate: baseRate, didSetRate: false) } } else { context.state = .paused @@ -313,58 +332,103 @@ private final class AudioPlayerRendererContext { private func startAudioUnit() { assert(audioPlayerRendererQueue.isCurrent()) - if self.audioUnit == nil { - var desc = AudioComponentDescription() - desc.componentType = kAudioUnitType_Output - desc.componentSubType = kAudioUnitSubType_RemoteIO - desc.componentFlags = 0 - desc.componentFlagsMask = 0 - desc.componentManufacturer = kAudioUnitManufacturer_Apple - guard let inputComponent = AudioComponentFindNext(nil, &desc) else { + if self.audioGraph == nil { + var maybeAudioGraph: AUGraph? + guard NewAUGraph(&maybeAudioGraph) == noErr, let audioGraph = maybeAudioGraph else { return } - var maybeAudioUnit: AudioComponentInstance? - - guard AudioComponentInstanceNew(inputComponent, &maybeAudioUnit) == noErr else { + var converterNode: AUNode = 0 + var converterDescription = AudioComponentDescription() + converterDescription.componentType = kAudioUnitType_FormatConverter + converterDescription.componentSubType = kAudioUnitSubType_AUConverter + converterDescription.componentManufacturer = kAudioUnitManufacturer_Apple + guard AUGraphAddNode(audioGraph, &converterDescription, &converterNode) == noErr else { return } - guard let audioUnit = maybeAudioUnit else { + var timePitchNode: AUNode = 0 + var timePitchDescription = AudioComponentDescription() + timePitchDescription.componentType = kAudioUnitType_FormatConverter + timePitchDescription.componentSubType = kAudioUnitSubType_AUiPodTimeOther + timePitchDescription.componentManufacturer = kAudioUnitManufacturer_Apple + guard AUGraphAddNode(audioGraph, &timePitchDescription, &timePitchNode) == noErr else { return } - var one: UInt32 = 1 - guard AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &one, 4) == noErr else { - AudioComponentInstanceDispose(audioUnit) + var outputNode: AUNode = 0 + var outputDesc = AudioComponentDescription() + outputDesc.componentType = kAudioUnitType_Output + outputDesc.componentSubType = kAudioUnitSubType_RemoteIO + outputDesc.componentFlags = 0 + outputDesc.componentFlagsMask = 0 + outputDesc.componentManufacturer = kAudioUnitManufacturer_Apple + guard AUGraphAddNode(audioGraph, &outputDesc, &outputNode) == noErr else { return } - var audioStreamDescription = self.audioStreamDescription - guard AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &audioStreamDescription, UInt32(MemoryLayout.size)) == noErr else { - AudioComponentInstanceDispose(audioUnit) + guard AUGraphOpen(audioGraph) == noErr else { return } + guard AUGraphConnectNodeInput(audioGraph, converterNode, 0, timePitchNode, 0) == noErr else { + return + } + + guard AUGraphConnectNodeInput(audioGraph, timePitchNode, 0, outputNode, 0) == noErr else { + return + } + + var maybeConverterAudioUnit: AudioComponentInstance? + guard AUGraphNodeInfo(audioGraph, converterNode, &converterDescription, &maybeConverterAudioUnit) == noErr, let converterAudioUnit = maybeConverterAudioUnit else { + return + } + + var maybeTimePitchAudioUnit: AudioComponentInstance? + guard AUGraphNodeInfo(audioGraph, timePitchNode, &timePitchDescription, &maybeTimePitchAudioUnit) == noErr, let timePitchAudioUnit = maybeTimePitchAudioUnit else { + return + } + AudioUnitSetParameter(timePitchAudioUnit, kTimePitchParam_Rate, kAudioUnitScope_Global, 0, Float32(self.baseRate), 0) + + var maybeOutputAudioUnit: AudioComponentInstance? + guard AUGraphNodeInfo(audioGraph, outputNode, &outputDesc, &maybeOutputAudioUnit) == noErr, let outputAudioUnit = maybeOutputAudioUnit else { + return + } + + var outputAudioFormat = audioRendererNativeStreamDescription() + + AudioUnitSetProperty(converterAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &outputAudioFormat, UInt32(MemoryLayout.size)) + + var streamFormat = AudioStreamBasicDescription() + AudioUnitSetProperty(converterAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &streamFormat, UInt32(MemoryLayout.size)) + AudioUnitSetProperty(timePitchAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, UInt32(MemoryLayout.size)) + AudioUnitSetProperty(converterAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &streamFormat, UInt32(MemoryLayout.size)) + var callbackStruct = AURenderCallbackStruct() callbackStruct.inputProc = rendererInputProc callbackStruct.inputProcRefCon = UnsafeMutableRawPointer(bitPattern: intptr_t(self.bufferContextId)) - guard AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct, UInt32(MemoryLayout.size)) == noErr else { - AudioComponentInstanceDispose(audioUnit) + + guard AUGraphSetNodeInputCallback(audioGraph, converterNode, 0, &callbackStruct) == noErr else { return } - guard AudioUnitInitialize(audioUnit) == noErr else { - AudioComponentInstanceDispose(audioUnit) + var one: UInt32 = 1 + guard AudioUnitSetProperty(outputAudioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kOutputBus, &one, 4) == noErr else { return } - self.audioUnit = audioUnit + guard AUGraphInitialize(audioGraph) == noErr else { + return + } + + self.audioGraph = audioGraph + self.timePitchAudioUnit = timePitchAudioUnit + self.outputAudioUnit = outputAudioUnit } switch self.audioSession { case let .manager(manager): - self.audioSessionDisposable.set(manager.push(audioSessionType: self.playAndRecord ? .playAndRecord : .play, outputMode: self.forceAudioToSpeaker ? .speakerIfNoHeadphones : .system, once: true, manualActivate: { [weak self] control in + self.audioSessionDisposable.set(manager.push(audioSessionType: self.playAndRecord ? .playWithPossiblePortOverride : .play, outputMode: self.forceAudioToSpeaker ? .speakerIfNoHeadphones : .system, once: true, manualActivate: { [weak self] control in audioPlayerRendererQueue.async { if let strongSelf = self { strongSelf.audioSessionControl = control @@ -425,8 +489,8 @@ private final class AudioPlayerRendererContext { private func audioSessionAcquired() { assert(audioPlayerRendererQueue.isCurrent()) - if let audioUnit = self.audioUnit { - guard AudioOutputUnitStart(audioUnit) == noErr else { + if let audioGraph = self.audioGraph { + guard AUGraphStart(audioGraph) == noErr else { self.closeAudioUnit() return } @@ -436,23 +500,36 @@ private final class AudioPlayerRendererContext { private func closeAudioUnit() { assert(audioPlayerRendererQueue.isCurrent()) - if let audioUnit = self.audioUnit { + if let audioGraph = self.audioGraph { var status = noErr self.bufferContext.with { context in context.buffer.clear() } - status = AudioOutputUnitStop(audioUnit) + status = AUGraphStop(audioGraph) if status != noErr { - Logger.shared.log("AudioPlayerRenderer", "AudioOutputUnitStop error \(status)") + Logger.shared.log("AudioPlayerRenderer", "AUGraphStop error \(status)") } - status = AudioComponentInstanceDispose(audioUnit); + status = AUGraphUninitialize(audioGraph) if status != noErr { - Logger.shared.log("AudioPlayerRenderer", "AudioComponentInstanceDispose error \(status)") + Logger.shared.log("AudioPlayerRenderer", "AUGraphUninitialize error \(status)") } - self.audioUnit = nil + + status = AUGraphClose(audioGraph) + if status != noErr { + Logger.shared.log("AudioPlayerRenderer", "AUGraphClose error \(status)") + } + + status = DisposeAUGraph(audioGraph) + if status != noErr { + Logger.shared.log("AudioPlayerRenderer", "DisposeAUGraph error \(status)") + } + + self.audioGraph = nil + self.outputAudioUnit = nil + self.timePitchAudioUnit = nil } } @@ -610,7 +687,7 @@ final class MediaPlayerAudioRenderer { private let audioClock: CMClock let audioTimebase: CMTimebase - init(audioSession: MediaPlayerAudioSessionControl, playAndRecord: Bool, forceAudioToSpeaker: Bool, updatedRate: @escaping () -> Void, audioPaused: @escaping () -> Void) { + init(audioSession: MediaPlayerAudioSessionControl, playAndRecord: Bool, forceAudioToSpeaker: Bool, baseRate: Double, updatedRate: @escaping () -> Void, audioPaused: @escaping () -> Void) { var audioClock: CMClock? CMAudioClockCreate(nil, &audioClock) self.audioClock = audioClock! @@ -620,7 +697,7 @@ final class MediaPlayerAudioRenderer { self.audioTimebase = audioTimebase! audioPlayerRendererQueue.async { - let context = AudioPlayerRendererContext(controlTimebase: audioTimebase!, audioSession: audioSession, playAndRecord: playAndRecord, forceAudioToSpeaker: forceAudioToSpeaker, updatedRate: updatedRate, audioPaused: audioPaused) + let context = AudioPlayerRendererContext(controlTimebase: audioTimebase!, audioSession: audioSession, playAndRecord: playAndRecord, forceAudioToSpeaker: forceAudioToSpeaker, baseRate: baseRate, updatedRate: updatedRate, audioPaused: audioPaused) self.contextRef = Unmanaged.passRetained(context) } } @@ -659,6 +736,15 @@ final class MediaPlayerAudioRenderer { } } + func setBaseRate(_ baseRate: Double) { + audioPlayerRendererQueue.async { + if let contextRef = self.contextRef { + let context = contextRef.takeUnretainedValue() + context.setBaseRate(baseRate) + } + } + } + func beginRequestingFrames(queue: DispatchQueue, takeFrame: @escaping () -> MediaTrackFrameResult) { audioPlayerRendererQueue.async { if let contextRef = self.contextRef { diff --git a/TelegramUI/MediaPlayerScrubbingNode.swift b/TelegramUI/MediaPlayerScrubbingNode.swift index 9af34f66c8..df134fb774 100644 --- a/TelegramUI/MediaPlayerScrubbingNode.swift +++ b/TelegramUI/MediaPlayerScrubbingNode.swift @@ -594,21 +594,21 @@ final class MediaPlayerScrubbingNode: ASDisplayNode { let toBounds = CGRect(origin: CGPoint(), size: toRect.size) node.foregroundNode.frame = toRect - node.foregroundNode.layer.add(self.preparedAnimation(keyPath: "bounds", from: NSValue(cgRect: fromBounds), to: NSValue(cgRect: toBounds), duration: duration, beginTime: statusValue.generationTimestamp, offset: timestamp, speed: statusValue.status == .playing ? 1.0 : 0.0), forKey: "playback-bounds") - node.foregroundNode.layer.add(self.preparedAnimation(keyPath: "position", from: NSValue(cgPoint: CGPoint(x: fromRect.midX, y: fromRect.midY)), to: NSValue(cgPoint: CGPoint(x: toRect.midX, y: toRect.midY)), duration: duration, beginTime: statusValue.generationTimestamp, offset: timestamp, speed: statusValue.status == .playing ? 1.0 : 0.0), forKey: "playback-position") + node.foregroundNode.layer.add(self.preparedAnimation(keyPath: "bounds", from: NSValue(cgRect: fromBounds), to: NSValue(cgRect: toBounds), duration: duration, beginTime: statusValue.generationTimestamp, offset: timestamp, speed: statusValue.status == .playing ? Float(statusValue.baseRate) : 0.0), forKey: "playback-bounds") + node.foregroundNode.layer.add(self.preparedAnimation(keyPath: "position", from: NSValue(cgPoint: CGPoint(x: fromRect.midX, y: fromRect.midY)), to: NSValue(cgPoint: CGPoint(x: toRect.midX, y: toRect.midY)), duration: duration, beginTime: statusValue.generationTimestamp, offset: timestamp, speed: statusValue.status == .playing ? Float(statusValue.baseRate) : 0.0), forKey: "playback-position") if let handleNodeContainer = node.handleNodeContainer { let fromBounds = bounds let toBounds = bounds.offsetBy(dx: -bounds.size.width, dy: 0.0) handleNodeContainer.isHidden = false - handleNodeContainer.layer.add(self.preparedAnimation(keyPath: "bounds", from: NSValue(cgRect: fromBounds), to: NSValue(cgRect: toBounds), duration: statusValue.duration, beginTime: statusValue.generationTimestamp, offset: statusValue.timestamp, speed: statusValue.status == .playing ? 1.0 : 0.0), forKey: "playback-bounds") + handleNodeContainer.layer.add(self.preparedAnimation(keyPath: "bounds", from: NSValue(cgRect: fromBounds), to: NSValue(cgRect: toBounds), duration: statusValue.duration, beginTime: statusValue.generationTimestamp, offset: statusValue.timestamp, speed: statusValue.status == .playing ? Float(statusValue.baseRate) : 0.0), forKey: "playback-bounds") } if let handleNode = node.handleNode { let fromPosition = handleNode.position let toPosition = CGPoint(x: fromPosition.x - 1.0, y: fromPosition.y) - handleNode.layer.add(self.preparedAnimation(keyPath: "position", from: NSValue(cgPoint: fromPosition), to: NSValue(cgPoint: toPosition), duration: duration / Double(bounds.size.width), beginTime: statusValue.generationTimestamp, offset: timestamp, speed: statusValue.status == .playing ? 1.0 : 0.0, repeatForever: true), forKey: "playback-position") + handleNode.layer.add(self.preparedAnimation(keyPath: "position", from: NSValue(cgPoint: fromPosition), to: NSValue(cgPoint: toPosition), duration: duration / Double(bounds.size.width), beginTime: statusValue.generationTimestamp, offset: timestamp, speed: statusValue.status == .playing ? Float(statusValue.baseRate) : 0.0, repeatForever: true), forKey: "playback-position") } } } else { @@ -679,8 +679,8 @@ final class MediaPlayerScrubbingNode: ASDisplayNode { let toBounds = CGRect(origin: CGPoint(), size: toRect.size) node.foregroundNode.frame = toRect - node.foregroundNode.layer.add(self.preparedAnimation(keyPath: "bounds", from: NSValue(cgRect: fromBounds), to: NSValue(cgRect: toBounds), duration: duration, beginTime: statusValue.generationTimestamp, offset: timestamp, speed: statusValue.status == .playing ? 1.0 : 0.0), forKey: "playback-bounds") - node.foregroundNode.layer.add(self.preparedAnimation(keyPath: "position", from: NSValue(cgPoint: CGPoint(x: fromRect.midX, y: fromRect.midY)), to: NSValue(cgPoint: CGPoint(x: toRect.midX, y: toRect.midY)), duration: duration, beginTime: statusValue.generationTimestamp, offset: timestamp, speed: statusValue.status == .playing ? 1.0 : 0.0), forKey: "playback-position") + node.foregroundNode.layer.add(self.preparedAnimation(keyPath: "bounds", from: NSValue(cgRect: fromBounds), to: NSValue(cgRect: toBounds), duration: duration, beginTime: statusValue.generationTimestamp, offset: timestamp, speed: statusValue.status == .playing ? Float(statusValue.baseRate) : 0.0), forKey: "playback-bounds") + node.foregroundNode.layer.add(self.preparedAnimation(keyPath: "position", from: NSValue(cgPoint: CGPoint(x: fromRect.midX, y: fromRect.midY)), to: NSValue(cgPoint: CGPoint(x: toRect.midX, y: toRect.midY)), duration: duration, beginTime: statusValue.generationTimestamp, offset: timestamp, speed: statusValue.status == .playing ? Float(statusValue.baseRate) : 0.0), forKey: "playback-position") } } else { node.foregroundNode.frame = CGRect(origin: backgroundFrame.origin, size: CGSize(width: 0.0, height: backgroundFrame.size.height)) diff --git a/TelegramUI/MentionChatInputContextPanelNode.swift b/TelegramUI/MentionChatInputContextPanelNode.swift index 335ade95ad..f19e217f79 100644 --- a/TelegramUI/MentionChatInputContextPanelNode.swift +++ b/TelegramUI/MentionChatInputContextPanelNode.swift @@ -32,8 +32,8 @@ private struct CommandChatInputContextPanelTransition { let updates: [ListViewUpdateItem] } -private func preparedTransition(from fromEntries: [MentionChatInputContextPanelEntry], to toEntries: [MentionChatInputContextPanelEntry], account: Account, inverted: Bool, peerSelected: @escaping (Peer) -> Void) -> CommandChatInputContextPanelTransition { - let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) +private func preparedTransition(from fromEntries: [MentionChatInputContextPanelEntry], to toEntries: [MentionChatInputContextPanelEntry], account: Account, inverted: Bool, forceUpdate: Bool, peerSelected: @escaping (Peer) -> Void) -> CommandChatInputContextPanelTransition { + let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries, allUpdated: forceUpdate) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, inverted: inverted, peerSelected: peerSelected), directionHint: nil) } @@ -94,9 +94,12 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { entries.append(MentionChatInputContextPanelEntry(index: index, peer: peer, theme: self.theme)) index += 1 } - + self.updateToEntries(entries: entries, forceUpdate: false) + } + + private func updateToEntries(entries: [MentionChatInputContextPanelEntry], forceUpdate: Bool) { let firstTime = self.currentEntries == nil - let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, account: self.account, inverted: self.mode == .search, peerSelected: { [weak self] peer in + let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, account: self.account, inverted: self.mode == .search, forceUpdate: forceUpdate, peerSelected: { [weak self] peer in if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { switch strongSelf.mode { case .input: @@ -203,6 +206,15 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { let hadValidLayout = self.validLayout != nil self.validLayout = (size, leftInset, rightInset) + if self.theme !== interfaceState.theme { + self.theme = interfaceState.theme + self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor + + if let currentEntries = self.currentEntries { + self.updateToEntries(entries: currentEntries, forceUpdate: true) + } + } + var insets = UIEdgeInsets() insets.top = topInsetForLayout(size: size) insets.left = leftInset @@ -241,6 +253,8 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { self.dequeueTransition() } } + + } override func animateOut(completion: @escaping () -> Void) { diff --git a/TelegramUI/MergeLists.swift b/TelegramUI/MergeLists.swift index 493812cf21..173644a412 100644 --- a/TelegramUI/MergeLists.swift +++ b/TelegramUI/MergeLists.swift @@ -5,7 +5,7 @@ public protocol Identifiable { var stableId: T { get } } -public func mergeListsStableWithUpdates(leftList: [T], rightList: [T]) -> ([Int], [(Int, T, Int?)], [(Int, T, Int)]) where T: Comparable, T: Identifiable { +public func mergeListsStableWithUpdates(leftList: [T], rightList: [T], allUpdated: Bool = false) -> ([Int], [(Int, T, Int?)], [(Int, T, Int)]) where T: Comparable, T: Identifiable { var removeIndices: [Int] = [] var insertItems: [(Int, T, Int?)] = [] var updatedIndices: [(Int, T, Int)] = [] @@ -46,12 +46,12 @@ public func mergeListsStableWithUpdates(leftList: [T], rightList: [T]) -> ([I let right: T? = j < rightList.count ? rightList[j] : nil if let left = left, let right = right { - if left.stableId == right.stableId && left != right { + if left.stableId == right.stableId && (left != right || allUpdated) { updatedIndices.append((i, right, previousIndices[left.stableId]!)) i += 1 j += 1 } else { - if left == right { + if left == right && !allUpdated { i += 1 j += 1 } else if left < right { @@ -181,7 +181,7 @@ public func mergeListsStableWithUpdates(leftList: [T], rightList: [T]) -> ([I return (removeIndices, insertItems, updatedIndices) } -public func mergeListsStableWithUpdatesReversed(leftList: [T], rightList: [T]) -> ([Int], [(Int, T, Int?)], [(Int, T, Int)]) where T: Comparable, T: Identifiable { +public func mergeListsStableWithUpdatesReversed(leftList: [T], rightList: [T], allUpdated: Bool = false) -> ([Int], [(Int, T, Int?)], [(Int, T, Int)]) where T: Comparable, T: Identifiable { var removeIndices: [Int] = [] var insertItems: [(Int, T, Int?)] = [] var updatedIndices: [(Int, T, Int)] = [] @@ -222,12 +222,12 @@ public func mergeListsStableWithUpdatesReversed(leftList: [T], rightList: [T] let right: T? = j < rightList.count ? rightList[j] : nil if let left = left, let right = right { - if left.stableId == right.stableId && left != right { + if left.stableId == right.stableId && (left != right || allUpdated) { updatedIndices.append((i, right, previousIndices[left.stableId]!)) i += 1 j += 1 } else { - if left == right { + if left == right && !allUpdated { i += 1 j += 1 } else if left > right { diff --git a/TelegramUI/MultiplexedVideoNode.swift b/TelegramUI/MultiplexedVideoNode.swift index af6a04352b..931a4e9cba 100644 --- a/TelegramUI/MultiplexedVideoNode.swift +++ b/TelegramUI/MultiplexedVideoNode.swift @@ -1,7 +1,5 @@ import Foundation import UIKit -import GLKit -import OpenGLES import Display import SwiftSignalKit import AsyncDisplayKit @@ -38,6 +36,8 @@ private final class VisibleVideoItem { final class MultiplexedVideoNode: UIScrollView, UIScrollViewDelegate { private let account: Account private let trackingNode: MultiplexedVideoTrackingNode + var didScroll: ((CGFloat, CGFloat) -> Void)? + var didEndScrolling: (() -> Void)? var topInset: CGFloat = 0.0 { didSet { @@ -158,6 +158,17 @@ final class MultiplexedVideoNode: UIScrollView, UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { self.updateImmediatelyVisibleItems() + self.didScroll?(scrollView.contentOffset.y, scrollView.contentSize.height) + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + self.didEndScrolling?() + } + + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + if !decelerate { + self.didEndScrolling?() + } } private var validVisibleItemsOffset: CGFloat? diff --git a/TelegramUI/MusicPlaybackSettings.swift b/TelegramUI/MusicPlaybackSettings.swift index d2e27d20dc..9ab00a1824 100644 --- a/TelegramUI/MusicPlaybackSettings.swift +++ b/TelegramUI/MusicPlaybackSettings.swift @@ -14,27 +14,40 @@ public enum MusicPlaybackSettingsLooping: Int32 { case all = 2 } +public enum AudioPlaybackRate: Int32 { + case x1 = 1000 + case x2 = 2000 + + var doubleValue: Double { + return Double(self.rawValue) / 1000.0 + } +} + public struct MusicPlaybackSettings: PreferencesEntry, Equatable { public let order: MusicPlaybackSettingsOrder public let looping: MusicPlaybackSettingsLooping + public let voicePlaybackRate: AudioPlaybackRate public static var defaultSettings: MusicPlaybackSettings { - return MusicPlaybackSettings(order: .regular, looping: .none) + return MusicPlaybackSettings(order: .regular, looping: .none, voicePlaybackRate: .x1) } - public init(order: MusicPlaybackSettingsOrder, looping: MusicPlaybackSettingsLooping) { + public init(order: MusicPlaybackSettingsOrder, looping: MusicPlaybackSettingsLooping, voicePlaybackRate: AudioPlaybackRate) { self.order = order self.looping = looping + self.voicePlaybackRate = voicePlaybackRate } public init(decoder: PostboxDecoder) { self.order = MusicPlaybackSettingsOrder(rawValue: decoder.decodeInt32ForKey("order", orElse: 0)) ?? .regular self.looping = MusicPlaybackSettingsLooping(rawValue: decoder.decodeInt32ForKey("looping", orElse: 0)) ?? .none + self.voicePlaybackRate = AudioPlaybackRate(rawValue: decoder.decodeInt32ForKey("voicePlaybackRate", orElse: AudioPlaybackRate.x1.rawValue)) ?? .x1 } public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.order.rawValue, forKey: "order") encoder.encodeInt32(self.looping.rawValue, forKey: "looping") + encoder.encodeInt32(self.voicePlaybackRate.rawValue, forKey: "voicePlaybackRate") } public func isEqual(to: PreferencesEntry) -> Bool { @@ -46,15 +59,19 @@ public struct MusicPlaybackSettings: PreferencesEntry, Equatable { } public static func ==(lhs: MusicPlaybackSettings, rhs: MusicPlaybackSettings) -> Bool { - return lhs.order == rhs.order && lhs.looping == rhs.looping + return lhs.order == rhs.order && lhs.looping == rhs.looping && lhs.voicePlaybackRate == rhs.voicePlaybackRate } func withUpdatedOrder(_ order: MusicPlaybackSettingsOrder) -> MusicPlaybackSettings { - return MusicPlaybackSettings(order: order, looping: self.looping) + return MusicPlaybackSettings(order: order, looping: self.looping, voicePlaybackRate: self.voicePlaybackRate) } func withUpdatedLooping(_ looping: MusicPlaybackSettingsLooping) -> MusicPlaybackSettings { - return MusicPlaybackSettings(order: self.order, looping: looping) + return MusicPlaybackSettings(order: self.order, looping: looping, voicePlaybackRate: self.voicePlaybackRate) + } + + func withUpdatedVoicePlaybackRate(_ voicePlaybackRate: AudioPlaybackRate) -> MusicPlaybackSettings { + return MusicPlaybackSettings(order: self.order, looping: self.looping, voicePlaybackRate: voicePlaybackRate) } } diff --git a/TelegramUI/NativeVideoContent.swift b/TelegramUI/NativeVideoContent.swift index 0efc754c22..2716049829 100644 --- a/TelegramUI/NativeVideoContent.swift +++ b/TelegramUI/NativeVideoContent.swift @@ -45,9 +45,10 @@ final class NativeVideoContent: UniversalVideoContent { let streamVideo: Bool let loopVideo: Bool let enableSound: Bool + let baseRate: Double let fetchAutomatically: Bool - init(id: NativeVideoContentId, fileReference: FileMediaReference, streamVideo: Bool = false, loopVideo: Bool = false, enableSound: Bool = true, fetchAutomatically: Bool = true) { + init(id: NativeVideoContentId, fileReference: FileMediaReference, streamVideo: Bool = false, loopVideo: Bool = false, enableSound: Bool = true, baseRate: Double = 1.0, fetchAutomatically: Bool = true) { self.id = id self.nativeId = id self.fileReference = fileReference @@ -56,11 +57,12 @@ final class NativeVideoContent: UniversalVideoContent { self.streamVideo = streamVideo self.loopVideo = loopVideo self.enableSound = enableSound + self.baseRate = baseRate self.fetchAutomatically = fetchAutomatically } func makeContentNode(postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { - return NativeVideoContentNode(postbox: postbox, audioSessionManager: audioSession, fileReference: self.fileReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, fetchAutomatically: self.fetchAutomatically) + return NativeVideoContentNode(postbox: postbox, audioSessionManager: audioSession, fileReference: self.fileReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically) } func isEqual(to other: UniversalVideoContent) -> Bool { @@ -108,13 +110,13 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent private var validLayout: CGSize? - init(postbox: Postbox, audioSessionManager: ManagedAudioSession, fileReference: FileMediaReference, streamVideo: Bool, loopVideo: Bool, enableSound: Bool, fetchAutomatically: Bool) { + init(postbox: Postbox, audioSessionManager: ManagedAudioSession, fileReference: FileMediaReference, streamVideo: Bool, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool) { self.postbox = postbox self.fileReference = fileReference self.imageNode = TransformImageNode() - self.player = MediaPlayer(audioSessionManager: audioSessionManager, postbox: postbox, resourceReference: fileReference.resourceReference(fileReference.media.resource), streamable: streamVideo, video: true, preferSoftwareDecoding: false, playAutomatically: false, enableSound: enableSound, fetchAutomatically: fetchAutomatically) + self.player = MediaPlayer(audioSessionManager: audioSessionManager, postbox: postbox, resourceReference: fileReference.resourceReference(fileReference.media.resource), streamable: streamVideo, video: true, preferSoftwareDecoding: false, playAutomatically: false, enableSound: enableSound, baseRate: baseRate, fetchAutomatically: fetchAutomatically) var actionAtEndImpl: (() -> Void)? if enableSound && !loopVideo { self.player.actionAtEnd = .action({ @@ -153,8 +155,9 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent self.addSubnode(self.imageNode) self.addSubnode(self.playerNode) - self._status.set(combineLatest(self.dimensionsPromise.get(), self.player.status) |> map { dimensions, status in - return MediaPlayerStatus(generationTimestamp: status.generationTimestamp, duration: status.duration, dimensions: dimensions, timestamp: status.timestamp, seekId: status.seekId, status: status.status) + self._status.set(combineLatest(self.dimensionsPromise.get(), self.player.status) + |> map { dimensions, status in + return MediaPlayerStatus(generationTimestamp: status.generationTimestamp, duration: status.duration, dimensions: dimensions, timestamp: status.timestamp, baseRate: status.baseRate, seekId: status.seekId, status: status.status) }) if let size = fileReference.media.size { @@ -239,6 +242,10 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent self.player.setForceAudioToSpeaker(forceAudioToSpeaker) } + func setBaseRate(_ baseRate: Double) { + self.player.setBaseRate(baseRate) + } + func continuePlayingWithoutSound() { assert(Queue.mainQueue().isCurrent()) self.player.continuePlayingWithoutSound() diff --git a/TelegramUI/NavigateToChatController.swift b/TelegramUI/NavigateToChatController.swift index c4c1c2f8e9..16b13552e0 100644 --- a/TelegramUI/NavigateToChatController.swift +++ b/TelegramUI/NavigateToChatController.swift @@ -6,6 +6,7 @@ import Postbox public enum NavigateToChatKeepStack { case `default` case always + case never } public func navigateToChatController(navigationController: NavigationController, chatController: ChatController? = nil, account: Account, chatLocation: ChatLocation, messageId: MessageId? = nil, botStart: ChatControllerInitialBotStart? = nil, keepStack: NavigateToChatKeepStack = .default, animated: Bool = true) { @@ -41,6 +42,8 @@ public func navigateToChatController(navigationController: NavigationController, resolvedKeepStack = account.telegramApplicationContext.immediateExperimentalUISettings.keepChatNavigationStack case .always: resolvedKeepStack = true + case .never: + resolvedKeepStack = false } if resolvedKeepStack { navigationController.pushViewController(controller) diff --git a/TelegramUI/NetworkStatusTitleView.swift b/TelegramUI/NetworkStatusTitleView.swift index 1c3e24e4ae..6c5cd06bb3 100644 --- a/TelegramUI/NetworkStatusTitleView.swift +++ b/TelegramUI/NetworkStatusTitleView.swift @@ -229,4 +229,11 @@ final class NetworkStatusTitleView: UIView, NavigationBarTitleView, NavigationBa } return nil } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !self.proxyButton.isHidden { + return self.proxyButton.hitTest(point.offsetBy(dx: -self.proxyButton.frame.minX, dy: -self.proxyButton.frame.minY), with: event) + } + return super.hitTest(point, with: event) + } } diff --git a/TelegramUI/Notices.swift b/TelegramUI/Notices.swift index 438b70c403..9fe80f7cd5 100644 --- a/TelegramUI/Notices.swift +++ b/TelegramUI/Notices.swift @@ -48,6 +48,33 @@ final class ApplicationSpecificVariantNotice: NoticeEntry { } } +final class ApplicationSpecificCounterNotice: NoticeEntry { + let value: Int32 + + init(value: Int32) { + self.value = value + } + + init(decoder: PostboxDecoder) { + self.value = decoder.decodeInt32ForKey("v", orElse: 0) + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.value, forKey: "v") + } + + func isEqual(to: NoticeEntry) -> Bool { + if let to = to as? ApplicationSpecificCounterNotice { + if self.value != to.value { + return false + } + return true + } else { + return false + } + } +} + private func noticeNamespace(namespace: Int32) -> ValueBoxKey { let key = ValueBoxKey(length: 4) key.setInt32(0, value: namespace) @@ -65,6 +92,8 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case secretChatInlineBotUsage = 0 case secretChatLinkPreviews = 1 case proxyAdsAcknowledgment = 2 + case chatMediaMediaRecordingTips = 3 + case profileCallTips = 4 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -89,6 +118,14 @@ private struct ApplicationSpecificNoticeKeys { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.secretChatLinkPreviews.key) } + static func chatMediaMediaRecordingTips() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatMediaMediaRecordingTips.key) + } + + static func profileCallTips() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.profileCallTips.key) + } + static func proxyAdsAcknowledgment() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.proxyAdsAcknowledgment.key) } @@ -155,6 +192,50 @@ struct ApplicationSpecificNotice { return ApplicationSpecificNoticeKeys.secretChatLinkPreviews() } + static func getChatMediaMediaRecordingTips(postbox: Postbox) -> Signal { + return postbox.transaction { transaction -> Int32 in + if let value = transaction.getNoticeEntry(key: ApplicationSpecificNoticeKeys.chatMediaMediaRecordingTips()) as? ApplicationSpecificCounterNotice { + return value.value + } else { + return 0 + } + } + } + + static func incrementChatMediaMediaRecordingTips(postbox: Postbox, count: Int32 = 1) -> Signal { + return postbox.transaction { transaction -> Void in + var currentValue: Int32 = 0 + if let value = transaction.getNoticeEntry(key: ApplicationSpecificNoticeKeys.chatMediaMediaRecordingTips()) as? ApplicationSpecificCounterNotice { + currentValue = value.value + } + currentValue += count + + transaction.setNoticeEntry(key: ApplicationSpecificNoticeKeys.chatMediaMediaRecordingTips(), value: ApplicationSpecificCounterNotice(value: currentValue)) + } + } + + static func getProfileCallTips(postbox: Postbox) -> Signal { + return postbox.transaction { transaction -> Int32 in + if let value = transaction.getNoticeEntry(key: ApplicationSpecificNoticeKeys.profileCallTips()) as? ApplicationSpecificCounterNotice { + return value.value + } else { + return 0 + } + } + } + + static func incrementProfileCallTips(postbox: Postbox, count: Int32 = 1) -> Signal { + return postbox.transaction { transaction -> Void in + var currentValue: Int32 = 0 + if let value = transaction.getNoticeEntry(key: ApplicationSpecificNoticeKeys.profileCallTips()) as? ApplicationSpecificCounterNotice { + currentValue = value.value + } + currentValue += count + + transaction.setNoticeEntry(key: ApplicationSpecificNoticeKeys.profileCallTips(), value: ApplicationSpecificCounterNotice(value: currentValue)) + } + } + static func getProxyAdsAcknowledgment(postbox: Postbox) -> Signal { return postbox.transaction { transaction -> Bool in if let _ = transaction.getNoticeEntry(key: ApplicationSpecificNoticeKeys.proxyAdsAcknowledgment()) as? ApplicationSpecificBoolNotice { diff --git a/TelegramUI/OpenChatMessage.swift b/TelegramUI/OpenChatMessage.swift index 5e9986b41f..de9f03923e 100644 --- a/TelegramUI/OpenChatMessage.swift +++ b/TelegramUI/OpenChatMessage.swift @@ -149,24 +149,24 @@ func openChatMessage(account: Account, message: Message, standalone: Bool, rever return true case let .pass(file): let _ = (account.postbox.mediaBox.resourceData(file.resource, option: .complete(waitUntilFetchStatus: true)) - |> take(1) - |> deliverOnMainQueue).start(next: { data in - guard let navigationController = navigationController else { - return - } - if data.complete, let content = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { - var error: NSError? - let pass = PKPass(data: content, error: &error) - if error == nil { - let controller = PKAddPassesViewController(pass: pass) - if let window = navigationController.view.window { - controller.popoverPresentationController?.sourceView = window - controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0)) - window.rootViewController?.present(controller, animated: true) - } + |> take(1) + |> deliverOnMainQueue).start(next: { data in + guard let navigationController = navigationController else { + return + } + if data.complete, let content = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { + var error: NSError? + let pass = PKPass(data: content, error: &error) + if error == nil { + let controller = PKAddPassesViewController(pass: pass) + if let window = navigationController.view.window { + controller.popoverPresentationController?.sourceView = window + controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0)) + window.rootViewController?.present(controller, animated: true) } } - }) + } + }) return true case let .instantPage(gallery, centralIndex, galleryMedia): setupTemporaryHiddenMedia(gallery.hiddenMedia, centralIndex, galleryMedia) @@ -209,13 +209,25 @@ func openChatMessage(account: Account, message: Message, standalone: Bool, rever let location: PeerMessagesPlaylistLocation let playerType: MediaManagerPlayerType if (file.isVoice || file.isInstantVideo) && message.tags.contains(.voiceOrInstantVideo) { - location = .messages(peerId: message.id.peerId, tagMask: .voiceOrInstantVideo, at: message.id) + if standalone { + location = .recentActions(message) + } else { + location = .messages(peerId: message.id.peerId, tagMask: .voiceOrInstantVideo, at: message.id) + } playerType = .voice } else if file.isMusic && message.tags.contains(.music) { - location = .messages(peerId: message.id.peerId, tagMask: .music, at: message.id) + if standalone { + location = .recentActions(message) + } else { + location = .messages(peerId: message.id.peerId, tagMask: .music, at: message.id) + } playerType = .music } else { - location = .singleMessage(message.id) + if standalone { + location = .recentActions(message) + } else { + location = .singleMessage(message.id) + } playerType = (file.isVoice || file.isInstantVideo) ? .voice : .music } account.telegramApplicationContext.mediaManager.setPlaylist(PeerMessagesMediaPlaylist(postbox: account.postbox, network: account.network, location: location), type: playerType) diff --git a/TelegramUI/OpenResolvedUrl.swift b/TelegramUI/OpenResolvedUrl.swift index 3b385c1fa3..da6af9101d 100644 --- a/TelegramUI/OpenResolvedUrl.swift +++ b/TelegramUI/OpenResolvedUrl.swift @@ -49,9 +49,9 @@ func openResolvedUrl(_ resolvedUrl: ResolvedUrl, account: Account, navigationCon let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let server: ProxyServerSettings if let secret = secret { - server = ProxyServerSettings(host: host, port: port, connection: .mtp(secret: secret)) + server = ProxyServerSettings(host: host, port: abs(port), connection: .mtp(secret: secret)) } else { - server = ProxyServerSettings(host: host, port: port, connection: .socks5(username: username, password: password)) + server = ProxyServerSettings(host: host, port: abs(port), connection: .socks5(username: username, password: password)) } dismissInput() diff --git a/TelegramUI/OpenUrl.swift b/TelegramUI/OpenUrl.swift index 974346ac4d..d02a62a5c6 100644 --- a/TelegramUI/OpenUrl.swift +++ b/TelegramUI/OpenUrl.swift @@ -202,6 +202,7 @@ public func openExternalUrl(account: Account, url: String, presentationData: Pre var scope: String? var publicKey: String? var opaquePayload = Data() + var opaqueNonce = Data() if let queryItems = components.queryItems { for queryItem in queryItems { if let value = queryItem.value { @@ -217,6 +218,10 @@ public func openExternalUrl(account: Account, url: String, presentationData: Pre if let data = value.data(using: .utf8) { opaquePayload = data } + } else if queryItem.name == "nonce" { + if let data = value.data(using: .utf8) { + opaqueNonce = data + } } } } @@ -235,7 +240,15 @@ public func openExternalUrl(account: Account, url: String, presentationData: Pre if valid && GlobalExperimentalSettings.enablePassport { if let botId = botId, let scope = scope, let publicKey = publicKey { - let controller = SecureIdAuthController(account: account, mode: .form(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: botId), scope: scope, publicKey: publicKey, opaquePayload: opaquePayload)) + if scope.hasPrefix("{") && scope.hasSuffix("}") { + opaquePayload = Data() + if opaqueNonce.isEmpty { + return + } + } else if opaquePayload.isEmpty { + return + } + let controller = SecureIdAuthController(account: account, mode: .form(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: botId), scope: scope, publicKey: publicKey, opaquePayload: opaquePayload, opaqueNonce: opaqueNonce)) if let navigationController = navigationController { navigationController.view.window?.rootViewController?.dismiss(animated: true, completion: nil) diff --git a/TelegramUI/OverlayInstantVideoNode.swift b/TelegramUI/OverlayInstantVideoNode.swift index 0ad5a7f72e..4ec4176da7 100644 --- a/TelegramUI/OverlayInstantVideoNode.swift +++ b/TelegramUI/OverlayInstantVideoNode.swift @@ -122,9 +122,14 @@ final class OverlayInstantVideoNode: OverlayMediaItemNode { self.videoNode.playOnceWithSound(playAndRecord: true) } else { self.videoNode.continuePlayingWithoutSound() + self.videoNode.setBaseRate(1.0) } } + func setBaseRate(_ baseRate: Double) { + self.videoNode.setBaseRate(baseRate) + } + func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool) { self.videoNode.setForceAudioToSpeaker(forceAudioToSpeaker) } diff --git a/TelegramUI/OverlayPlayerControllerNode.swift b/TelegramUI/OverlayPlayerControllerNode.swift index b99e78e5eb..8c9320911a 100644 --- a/TelegramUI/OverlayPlayerControllerNode.swift +++ b/TelegramUI/OverlayPlayerControllerNode.swift @@ -54,7 +54,7 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec } else { return false } - }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in + }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in }, presentController: { _, _ in }, navigationController: { return nil }, presentGlobalOverlayController: { _, _ in diff --git a/TelegramUI/OverlayPlayerControlsNode.swift b/TelegramUI/OverlayPlayerControlsNode.swift index bb392d6d3f..e4de319cef 100644 --- a/TelegramUI/OverlayPlayerControlsNode.swift +++ b/TelegramUI/OverlayPlayerControlsNode.swift @@ -180,7 +180,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode { if let value = value { return value.status } else { - return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, seekId: 0, status: .paused) + return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused) } } self.scrubberNode.status = mappedStatus diff --git a/TelegramUI/OverlayStatusController.swift b/TelegramUI/OverlayStatusController.swift index 072a2cfe53..483dae5f49 100644 --- a/TelegramUI/OverlayStatusController.swift +++ b/TelegramUI/OverlayStatusController.swift @@ -8,29 +8,66 @@ enum OverlayStatusControllerType { case proxySettingSuccess } +private enum OverlayStatusContentController { + case progress(TGProgressWindowController) + case proxy(TGProxyWindowController) + + var view: UIView { + switch self { + case let .progress(controller): + return controller.view + case let .proxy(controller): + return controller.view + } + } + + func updateLayout() { + switch self { + case let .progress(controller): + controller.updateLayout() + case let .proxy(controller): + controller.updateLayout() + } + } + + func dismiss(success: @escaping () -> Void) { + switch self { + case let .progress(controller): + controller.dismiss(success: success) + case let .proxy(controller): + controller.dismiss(success: success) + } + } +} + private final class OverlayStatusControllerNode: ViewControllerTracingNode { private let dismissed: () -> Void - private let progressController: TGProgressWindowController + private let contentController: OverlayStatusContentController - init(theme: PresentationTheme, dismissed: @escaping () -> Void) { + init(theme: PresentationTheme, type: OverlayStatusControllerType, dismissed: @escaping () -> Void) { self.dismissed = dismissed - self.progressController = TGProgressWindowController(light: theme.actionSheet.backgroundType == .light) + switch type { + case .success: + self.contentController = .progress(TGProgressWindowController(light: theme.actionSheet.backgroundType == .light)) + case .proxySettingSuccess: + self.contentController = .proxy(TGProxyWindowController(light: theme.actionSheet.backgroundType == .light)) + } super.init() self.backgroundColor = nil self.isOpaque = false - self.view.addSubview(self.progressController.view) + self.view.addSubview(self.contentController.view) } func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - self.progressController.view.frame = CGRect(origin: CGPoint(), size: layout.size) - self.progressController.updateLayout() + self.contentController.view.frame = CGRect(origin: CGPoint(), size: layout.size) + self.contentController.updateLayout() } func begin() { - self.progressController.dismiss(success: { [weak self] in + self.contentController.dismiss(success: { [weak self] in self?.dismissed() }) } @@ -60,7 +97,7 @@ final class OverlayStatusController: ViewController { } override func loadDisplayNode() { - self.displayNode = OverlayStatusControllerNode(theme: self.theme, dismissed: { [weak self] in + self.displayNode = OverlayStatusControllerNode(theme: self.theme, type: self.type, dismissed: { [weak self] in self?.dismiss() }) diff --git a/TelegramUI/OverlayVideoDecoration.swift b/TelegramUI/OverlayVideoDecoration.swift index 7eb0156bd2..78b74bbceb 100644 --- a/TelegramUI/OverlayVideoDecoration.swift +++ b/TelegramUI/OverlayVideoDecoration.swift @@ -136,7 +136,7 @@ final class OverlayVideoDecoration: UniversalVideoDecoration { if let value = value { return value } else { - return MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: 0.0, dimensions: CGSize(), timestamp: 0.0, seekId: 0, status: .paused) + return MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused) } } } diff --git a/TelegramUI/PasscodeOptionsController.swift b/TelegramUI/PasscodeOptionsController.swift index 20ac05826d..59266f46bc 100644 --- a/TelegramUI/PasscodeOptionsController.swift +++ b/TelegramUI/PasscodeOptionsController.swift @@ -265,7 +265,7 @@ func passcodeOptionsController(account: Account) -> ViewController { } })! legacyController.bind(controller: controller) - legacyController.supportedOrientations = .portrait + legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) legacyController.statusBar.statusBarStyle = .White presentControllerImpl?(legacyController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) dismissImpl = { [weak legacyController] in @@ -294,35 +294,45 @@ func passcodeOptionsController(account: Account) -> ViewController { ])]) presentControllerImpl?(actionSheet, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }, changePasscode: { - var dismissImpl: (() -> Void)? - - let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - - let legacyController = LegacyController(presentation: LegacyControllerPresentation.modal(animateIn: true), theme: presentationData.theme) - let controller = TGPasscodeEntryController(context: legacyController.context, style: TGPasscodeEntryControllerStyleDefault, mode: TGPasscodeEntryControllerModeSetupSimple, cancelEnabled: true, allowTouchId: false, attemptData: nil, completion: { result in - if let result = result { - let _ = account.postbox.transaction({ transaction -> Void in - var data = transaction.getAccessChallengeData() - data = PostboxAccessChallengeData.numericalPassword(value: result, timeout: data.autolockDeadline, attempts: nil) - transaction.setAccessChallengeData(data) - }).start() - - let _ = (passcodeOptionsDataPromise.get() |> take(1)).start(next: { [weak passcodeOptionsDataPromise] data in - passcodeOptionsDataPromise?.set(.single(data.withUpdatedAccessChallenge(PostboxAccessChallengeData.numericalPassword(value: result, timeout: nil, attempts: nil)))) - }) - - dismissImpl?() - } else { - dismissImpl?() + let _ = (account.postbox.transaction({ transaction -> Bool in + switch transaction.getAccessChallengeData() { + case .none, .numericalPassword: + return true + case .plaintextPassword: + return false } - })! - legacyController.bind(controller: controller) - legacyController.supportedOrientations = .portrait - legacyController.statusBar.statusBarStyle = .White - presentControllerImpl?(legacyController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - dismissImpl = { [weak legacyController] in - legacyController?.dismiss() - } + }) + |> deliverOnMainQueue).start(next: { isSimple in + var dismissImpl: (() -> Void)? + + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + + let legacyController = LegacyController(presentation: LegacyControllerPresentation.modal(animateIn: true), theme: presentationData.theme) + let controller = TGPasscodeEntryController(context: legacyController.context, style: TGPasscodeEntryControllerStyleDefault, mode: isSimple ? TGPasscodeEntryControllerModeSetupSimple : TGPasscodeEntryControllerModeSetupComplex, cancelEnabled: true, allowTouchId: false, attemptData: nil, completion: { result in + if let result = result { + let _ = account.postbox.transaction({ transaction -> Void in + var data = transaction.getAccessChallengeData() + data = PostboxAccessChallengeData.numericalPassword(value: result, timeout: data.autolockDeadline, attempts: nil) + transaction.setAccessChallengeData(data) + }).start() + + let _ = (passcodeOptionsDataPromise.get() |> take(1)).start(next: { [weak passcodeOptionsDataPromise] data in + passcodeOptionsDataPromise?.set(.single(data.withUpdatedAccessChallenge(PostboxAccessChallengeData.numericalPassword(value: result, timeout: nil, attempts: nil)))) + }) + + dismissImpl?() + } else { + dismissImpl?() + } + })! + legacyController.bind(controller: controller) + legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) + legacyController.statusBar.statusBarStyle = .White + presentControllerImpl?(legacyController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + dismissImpl = { [weak legacyController] in + legacyController?.dismiss() + } + }) }, changePasscodeTimeout: { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let actionSheet = ActionSheetController(presentationTheme: presentationData.theme) @@ -395,7 +405,7 @@ func passcodeOptionsController(account: Account) -> ViewController { } })! legacyController.bind(controller: controller) - legacyController.supportedOrientations = .portrait + legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) legacyController.statusBar.statusBarStyle = .White presentControllerImpl?(legacyController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) dismissImpl = { [weak legacyController] in @@ -488,7 +498,7 @@ public func passcodeOptionsAccessController(account: Account, animateIn: Bool = }).start() } legacyController.bind(controller: controller) - legacyController.supportedOrientations = .portrait + legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) legacyController.statusBar.statusBarStyle = .White dismissImpl = { [weak legacyController] in legacyController?.dismiss() diff --git a/TelegramUI/PeerAvatarImageGalleryItem.swift b/TelegramUI/PeerAvatarImageGalleryItem.swift index 6d746a5190..c68c04b201 100644 --- a/TelegramUI/PeerAvatarImageGalleryItem.swift +++ b/TelegramUI/PeerAvatarImageGalleryItem.swift @@ -215,10 +215,10 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { if isActive { actualProgress = max(actualProgress, 0.027) } - strongSelf.statusNode.transitionToState(.progress(color: .white, value: CGFloat(actualProgress), cancelEnabled: true), completion: {}) + strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: CGFloat(actualProgress), cancelEnabled: true), completion: {}) case .Local: if let previousStatus = previousStatus, case .Fetching = previousStatus { - strongSelf.statusNode.transitionToState(.progress(color: .white, value: 1.0, cancelEnabled: true), completion: { + strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: 1.0, cancelEnabled: true), completion: { if let strongSelf = self { strongSelf.statusNode.alpha = 0.0 strongSelf.statusNodeContainer.isUserInteractionEnabled = false diff --git a/TelegramUI/PeerMediaCollectionController.swift b/TelegramUI/PeerMediaCollectionController.swift index 31976c3617..114dc59432 100644 --- a/TelegramUI/PeerMediaCollectionController.swift +++ b/TelegramUI/PeerMediaCollectionController.swift @@ -154,7 +154,7 @@ public class PeerMediaCollectionController: TelegramController { }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in - }, openUrl: { [weak self] url in + }, openUrl: { [weak self] url, _ in self?.openUrl(url) }, shareCurrentLocation: { }, shareAccountContact: { @@ -370,11 +370,14 @@ public class PeerMediaCollectionController: TelegramController { }, pinMessage: { _ in }, unpinMessage: { }, reportPeer: { + }, presentPeerContact: { }, dismissReportPeer: { }, deleteChat: { }, beginCall: { }, toggleMessageStickerStarred: { _ in }, presentController: { _, _ in + }, getNavigationController: { + return nil }, presentGlobalOverlayController: { _, _ in }, navigateFeed: { }, openGrouping: { diff --git a/TelegramUI/PeerMessagesMediaPlaylist.swift b/TelegramUI/PeerMessagesMediaPlaylist.swift index 213422db1a..8a9de2b485 100644 --- a/TelegramUI/PeerMessagesMediaPlaylist.swift +++ b/TelegramUI/PeerMessagesMediaPlaylist.swift @@ -203,13 +203,16 @@ private func navigatedMessageFromView(_ view: MessageHistoryView, anchorIndex: M enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation { case messages(peerId: PeerId, tagMask: MessageTags, at: MessageId) case singleMessage(MessageId) - - var peerId: PeerId { + case recentActions(Message) + + var playlistId: PeerMessagesMediaPlaylistId { switch self { case let .messages(peerId, _, _): - return peerId + return .peer(peerId) case let .singleMessage(id): - return id.peerId + return .peer(id.peerId) + case let .recentActions(message): + return .recentActions(message.id.peerId) } } @@ -235,16 +238,23 @@ enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation { } else { return false } + case let .recentActions(lhsMessage): + if case let .recentActions(rhsMessage) = rhs, lhsMessage.id == rhsMessage.id { + return true + } else { + return false + } } } } -struct PeerMessagesMediaPlaylistId: SharedMediaPlaylistId { - let peerId: PeerId +enum PeerMessagesMediaPlaylistId: Equatable, SharedMediaPlaylistId { + case peer(PeerId) + case recentActions(PeerId) func isEqual(to: SharedMediaPlaylistId) -> Bool { if let to = to as? PeerMessagesMediaPlaylistId { - return self.peerId == to.peerId + return self == to } return false } @@ -261,8 +271,12 @@ func peerMessageMediaPlayerType(_ message: Message) -> MediaManagerPlayerType? { return nil } -func peerMessagesMediaPlaylistAndItemId(_ message: Message) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? { - return (PeerMessagesMediaPlaylistId(peerId: message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id)) +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)) + } } final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { @@ -292,7 +306,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { init(postbox: Postbox, network: Network, location: PeerMessagesPlaylistLocation) { assert(Queue.mainQueue().isCurrent()) - self.id = PeerMessagesMediaPlaylistId(peerId: location.peerId) + self.id = location.playlistId self.postbox = postbox self.network = network @@ -303,6 +317,10 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { 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 + self.currentItem = message + self.updateState() } } @@ -315,6 +333,15 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { switch action { case .next, .previous: + switch self.messagesLocation { + case let .recentActions(message): + self.loadingItem = false + self.currentItem = nil + self.updateState() + return + default: + break + } if !self.loadingItem { if let currentItem = self.currentItem { let navigation: PeerMessagesMediaPlaylistNavigation @@ -468,12 +495,22 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { strongSelf.updateState() } })) + case let .recentActions(message): + self.loadingItem = false + self.currentItem = message + self.updateState() } } } func onItemPlaybackStarted(_ item: SharedMediaPlaylistItem) { if let item = item as? MessageMediaPlaylistItem { + switch self.messagesLocation { + case .recentActions: + return + default: + break + } let _ = markMessageContentAsConsumedInteractively(postbox: self.postbox, messageId: item.message.id).start() } } diff --git a/TelegramUI/PeerNotificationSoundStrings.swift b/TelegramUI/PeerNotificationSoundStrings.swift index 8a14daeb47..615c8867a4 100644 --- a/TelegramUI/PeerNotificationSoundStrings.swift +++ b/TelegramUI/PeerNotificationSoundStrings.swift @@ -1,46 +1,46 @@ import Foundation import TelegramCore -private let modernSounds: [String] = [ - "Note", - "Aurora", - "Bamboo", - "Chord", - "Circles", - "Complete", - "Hello", - "Input", - "Keys", - "Popcorn", - "Pulse", - "Synth" +private let modernSoundsNamePaths: [KeyPath] = [ + \.NotificationsSound_Note, + \.NotificationsSound_Aurora, + \.NotificationsSound_Bamboo, + \.NotificationsSound_Chord, + \.NotificationsSound_Circles, + \.NotificationsSound_Complete, + \.NotificationsSound_Hello, + \.NotificationsSound_Input, + \.NotificationsSound_Keys, + \.NotificationsSound_Popcorn, + \.NotificationsSound_Pulse, + \.NotificationsSound_Synth ] -private let classicSounds: [String] = [ - "Tri-tone", - "Tremolo", - "Alert", - "Bell", - "Calypso", - "Chime", - "Glass", - "Telegraph" +private let classicSoundNamePaths: [KeyPath] = [ + \.NotificationsSound_Tritone, + \.NotificationsSound_Tremolo, + \.NotificationsSound_Alert, + \.NotificationsSound_Bell, + \.NotificationsSound_Calypso, + \.NotificationsSound_Chime, + \.NotificationsSound_Glass, + \.NotificationsSound_Telegraph ] private func soundName(strings: PresentationStrings, sound: PeerMessageSound) -> String { switch sound { case .none: - return "None" + return strings.NotificationsSound_None case .default: return "" case let .bundledModern(id): - if id >= 0 && Int(id) < modernSounds.count { - return modernSounds[Int(id)] + if id >= 0 && Int(id) < modernSoundsNamePaths.count { + return strings[keyPath: modernSoundsNamePaths[Int(id)]] } return "Sound \(id)" case let .bundledClassic(id): - if id >= 0 && Int(id) < classicSounds.count { - return classicSounds[Int(id)] + if id >= 0 && Int(id) < classicSoundNamePaths.count { + return strings[keyPath: classicSoundNamePaths[Int(id)]] } return "Sound \(id)" } @@ -57,9 +57,9 @@ func localizedPeerNotificationSoundString(strings: PresentationStrings, sound: P } else { actualName = name } - return "Default (\(actualName))" + return strings.UserInfo_NotificationsDefaultSound(actualName).0 } else { - return "Default" + return strings.UserInfo_NotificationsDefault } default: return soundName(strings: strings, sound: sound) diff --git a/TelegramUI/PhotoResources.swift b/TelegramUI/PhotoResources.swift index 0b09053341..daed1dc7f1 100644 --- a/TelegramUI/PhotoResources.swift +++ b/TelegramUI/PhotoResources.swift @@ -217,7 +217,9 @@ private func chatMessageVideoDatas(postbox: Postbox, fileReference: FileMediaRef let maybeFullSize = postbox.mediaBox.cachedResourceRepresentation(fullSizeResource, representation: thumbnailSize ? CachedScaledVideoFirstFrameRepresentation(size: CGSize(width: 160.0, height: 160.0)) : CachedVideoFirstFrameRepresentation(), complete: false) - let signal = maybeFullSize |> take(1) |> mapToSignal { maybeData -> Signal<(Data?, (Data, String)?, Bool), NoError> in + let signal = maybeFullSize + |> take(1) + |> mapToSignal { maybeData -> Signal<(Data?, (Data, String)?, Bool), NoError> in if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) @@ -250,12 +252,12 @@ private func chatMessageVideoDatas(postbox: Postbox, fileReference: FileMediaRef } } } |> filter({ - return $0.0 != nil || $0.1 != nil + return $0.0 != nil || $0.1 != nil || $0.2 }) return signal } else { - return .never() + return .single((nil, nil, true)) } } diff --git a/TelegramUI/PreferencesKeys.swift b/TelegramUI/PreferencesKeys.swift index 278f49da8c..b8a1997b5c 100644 --- a/TelegramUI/PreferencesKeys.swift +++ b/TelegramUI/PreferencesKeys.swift @@ -16,6 +16,7 @@ private enum ApplicationSpecificPreferencesKeyValues: Int32 { case mediaInputSettings = 10 case experimentalUISettings = 11 case contactSynchronizationSettings = 12 + case stickerSettings = 13 } public struct ApplicationSpecificPreferencesKeys { @@ -32,4 +33,5 @@ public struct ApplicationSpecificPreferencesKeys { public static let mediaInputSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.mediaInputSettings.rawValue) public static let experimentalUISettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.experimentalUISettings.rawValue) public static let contactSynchronizationSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.contactSynchronizationSettings.rawValue) + public static let stickerSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.stickerSettings.rawValue) } diff --git a/TelegramUI/PreparedChatHistoryViewTransition.swift b/TelegramUI/PreparedChatHistoryViewTransition.swift index 7a601e2e5b..7572131bf4 100644 --- a/TelegramUI/PreparedChatHistoryViewTransition.swift +++ b/TelegramUI/PreparedChatHistoryViewTransition.swift @@ -7,10 +7,11 @@ import Display func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toView: ChatHistoryView, reason: ChatHistoryViewTransitionReason, reverse: Bool, account: Account, chatLocation: ChatLocation, controllerInteraction: ChatControllerInteraction, scrollPosition: ChatHistoryViewScrollPosition?, initialData: InitialMessageHistoryData?, keyboardButtonsMessage: Message?, cachedData: CachedPeerData?, cachedDataMessages: [MessageId: Message]?, readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?) -> Signal { return Signal { subscriber in let mergeResult: (deleteIndices: [Int], indicesAndItems: [(Int, ChatHistoryEntry, Int?)], updateIndices: [(Int, ChatHistoryEntry, Int)]) + let allUpdated = fromView?.associatedData != toView.associatedData if reverse { - mergeResult = mergeListsStableWithUpdatesReversed(leftList: fromView?.filteredEntries ?? [], rightList: toView.filteredEntries) + mergeResult = mergeListsStableWithUpdatesReversed(leftList: fromView?.filteredEntries ?? [], rightList: toView.filteredEntries, allUpdated: allUpdated) } else { - mergeResult = mergeListsStableWithUpdates(leftList: fromView?.filteredEntries ?? [], rightList: toView.filteredEntries) + mergeResult = mergeListsStableWithUpdates(leftList: fromView?.filteredEntries ?? [], rightList: toView.filteredEntries, allUpdated: allUpdated) } var adjustedDeleteIndices: [ListViewDeleteItem] = [] diff --git a/TelegramUI/PresentationCall.swift b/TelegramUI/PresentationCall.swift index 7a0784a185..eb404471fd 100644 --- a/TelegramUI/PresentationCall.swift +++ b/TelegramUI/PresentationCall.swift @@ -81,7 +81,7 @@ private final class PresentationCallToneRenderer { self.toneRenderer = MediaPlayerAudioRenderer(audioSession: .custom({ control in return controlImpl?(control) ?? EmptyDisposable - }), playAndRecord: false, forceAudioToSpeaker: false, updatedRate: {}, audioPaused: {}) + }), playAndRecord: false, forceAudioToSpeaker: false, baseRate: 1.0, updatedRate: {}, audioPaused: {}) controlImpl = { [weak self] control in queue.async { @@ -201,10 +201,11 @@ public final class PresentationCall { return self.isMutedPromise.get() } - private let speakerModePromise = ValuePromise(false) - private var speakerModeValue = false - public var speakerMode: Signal { - return self.speakerModePromise.get() + private let audioOutputStatePromise = Promise<([AudioSessionOutput], AudioSessionOutput?)>(([], nil)) + private var audioOutputStateValue: ([AudioSessionOutput], AudioSessionOutput?) = ([], nil) + private var currentAudioOutputValue: AudioSessionOutput = .builtin + public var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { + return self.audioOutputStatePromise.get() } private let canBeRemovedPromise = Promise(false) @@ -242,6 +243,7 @@ public final class PresentationCall { self.ongoingGontext = OngoingCallContext(callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer) + var didReceiveAudioOutputs = false self.sessionStateDisposable = (callSessionManager.callState(internalId: internalId) |> deliverOnMainQueue).start(next: { [weak self] sessionState in if let strongSelf = self { @@ -285,6 +287,26 @@ public final class PresentationCall { } return EmptyDisposable } + }, availableOutputsChanged: { [weak self] availableOutputs, currentOutput in + Queue.mainQueue().async { + guard let strongSelf = self else { + return + } + strongSelf.audioOutputStateValue = (availableOutputs, currentOutput) + + var signal: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> = .single((availableOutputs, currentOutput)) + if !didReceiveAudioOutputs { + didReceiveAudioOutputs = true + if currentOutput == .speaker { + signal = .single((availableOutputs, .builtin)) + |> then( + signal + |> delay(1.0, queue: Queue.mainQueue()) + ) + } + } + strongSelf.audioOutputStatePromise.set(signal) + } }) self.audioSessionShouldBeActiveDisposable = (self.audioSessionShouldBeActive.get() @@ -366,7 +388,7 @@ public final class PresentationCall { } if let audioSessionControl = audioSessionControl, previous == nil || previousControl == nil { - audioSessionControl.setOutputMode(self.speakerModeValue ? .custom(.speaker) : .system) + audioSessionControl.setOutputMode(.custom(self.currentAudioOutputValue)) audioSessionControl.setup(synchronous: true) } @@ -542,11 +564,20 @@ public final class PresentationCall { self.ongoingGontext.setIsMuted(self.isMutedValue) } - func toggleSpeaker() { - self.speakerModeValue = !self.speakerModeValue - self.speakerModePromise.set(self.speakerModeValue) + func setCurrentAudioOutput(_ output: AudioSessionOutput) { + guard self.currentAudioOutputValue != output else { + return + } + self.currentAudioOutputValue = output + + self.audioOutputStatePromise.set(.single((self.audioOutputStateValue.0, output)) + |> then( + .single(self.audioOutputStateValue) + |> delay(1.0, queue: Queue.mainQueue()) + )) + if let audioSessionControl = self.audioSessionControl { - audioSessionControl.setOutputMode(self.speakerModeValue ? .speakerIfNoHeadphones : .system) + audioSessionControl.setOutputMode(.custom(output)) } } } diff --git a/TelegramUI/PresentationData.swift b/TelegramUI/PresentationData.swift index 0877cefb05..64c318da8c 100644 --- a/TelegramUI/PresentationData.swift +++ b/TelegramUI/PresentationData.swift @@ -145,9 +145,28 @@ public func currentPresentationDataAndSettings(postbox: Postbox) -> Signal map { (themeSettings, localizationSettings, automaticMediaDownloadSettings, loggingSettings, callListSettings, inAppNotificationSettings, mediaInputSettings, experimentalUISettings) -> InitialPresentationDataAndSettings in + } + |> map { (themeSettings, localizationSettings, automaticMediaDownloadSettings, loggingSettings, callListSettings, inAppNotificationSettings, mediaInputSettings, experimentalUISettings) -> InitialPresentationDataAndSettings in let themeValue: PresentationTheme - switch themeSettings.theme { + + let effectiveTheme: PresentationThemeReference + var effectiveChatWallpaper: TelegramWallpaper = themeSettings.chatWallpaper + + if automaticThemeShouldSwitchNow(themeSettings.automaticThemeSwitchSetting, currentTheme: themeSettings.theme) { + effectiveTheme = .builtin(themeSettings.automaticThemeSwitchSetting.theme) + switch themeSettings.automaticThemeSwitchSetting.theme { + case .nightAccent: + effectiveChatWallpaper = .color(0x18222D) + case .nightGrayscale: + effectiveChatWallpaper = .color(0x000000) + default: + break + } + } else { + effectiveTheme = themeSettings.theme + } + + switch effectiveTheme { case let .builtin(reference): switch reference { case .dayClassic: @@ -157,7 +176,7 @@ public func currentPresentationDataAndSettings(postbox: Postbox) -> Signal Signal Int32 { + let calendar = Calendar.current + let offset = 0 + let components = calendar.dateComponents([.hour, .minute, .second], from: Date(timeIntervalSince1970: Double(timestamp + Int32(offset)))) + return Int32(components.hour! * 60 * 60 + components.minute! * 60 + components.second!) +} + +private func automaticThemeShouldSwitchNow(_ settings: AutomaticThemeSwitchSetting, currentTheme: PresentationThemeReference) -> Bool { + switch currentTheme { + case let .builtin(builtin): + switch builtin { + case .nightAccent, .nightGrayscale: + return false + default: + break + } + } + switch settings.trigger { + case .none: + return false + case let .timeBased(setting): + let fromValue: Int32 + let toValue: Int32 + switch setting { + case let .automatic(automatic): + fromValue = automatic.sunset + toValue = automatic.sunrise + case let .manual(fromSeconds, toSeconds): + fromValue = fromSeconds + toValue = toSeconds + } + let roundedTimestamp = roundTimeToDay(Int32(Date().timeIntervalSince1970)) + if roundedTimestamp >= fromValue || roundedTimestamp <= toValue { + return true + } else { + return false + } + case let .brightness(threshold): + return UIScreen.main.brightness <= CGFloat(threshold) + } +} + +private func automaticThemeShouldSwitch(_ settings: AutomaticThemeSwitchSetting, currentTheme: PresentationThemeReference) -> Signal { + if case .none = settings.trigger { + return .single(false) + } else { + return Signal { subscriber in + subscriber.putNext(automaticThemeShouldSwitchNow(settings, currentTheme: currentTheme)) + + let timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { + subscriber.putNext(automaticThemeShouldSwitchNow(settings, currentTheme: currentTheme)) + }, queue: Queue.mainQueue()) + timer.start() + + return ActionDisposable { + timer.invalidate() + } + } + |> runOn(Queue.mainQueue()) + |> distinctUntilChanged + } +} + public func updatedPresentationData(postbox: Postbox) -> Signal { let preferencesKey = PostboxViewKey.preferences(keys: Set([ApplicationSpecificPreferencesKeys.presentationThemeSettings, PreferencesKeys.localizationSettings])) return postbox.combinedView(keys: [preferencesKey]) - |> map { view -> PresentationData in + |> mapToSignal { view -> Signal in let themeSettings: PresentationThemeSettings if let current = (view.views[preferencesKey] as! PreferencesView).values[ApplicationSpecificPreferencesKeys.presentationThemeSettings] as? PresentationThemeSettings { themeSettings = current } else { themeSettings = PresentationThemeSettings.defaultSettings } - let themeValue: PresentationTheme - switch themeSettings.theme { - case let .builtin(reference): - switch reference { - case .dayClassic: - themeValue = defaultPresentationTheme - case .nightGrayscale: - themeValue = defaultDarkPresentationTheme + + return automaticThemeShouldSwitch(themeSettings.automaticThemeSwitchSetting, currentTheme: themeSettings.theme) + |> distinctUntilChanged + |> map { shouldSwitch in + let themeValue: PresentationTheme + let effectiveTheme: PresentationThemeReference + var effectiveChatWallpaper: TelegramWallpaper = themeSettings.chatWallpaper + if shouldSwitch { + effectiveTheme = .builtin(themeSettings.automaticThemeSwitchSetting.theme) + switch themeSettings.automaticThemeSwitchSetting.theme { case .nightAccent: - themeValue = defaultDarkAccentPresentationTheme - case .day: - themeValue = defaultDayPresentationTheme + effectiveChatWallpaper = .color(0x18222D) + case .nightGrayscale: + effectiveChatWallpaper = .color(0x000000) + default: + break } + } else { + effectiveTheme = themeSettings.theme + } + switch effectiveTheme { + case let .builtin(reference): + switch reference { + case .dayClassic: + themeValue = defaultPresentationTheme + case .nightGrayscale: + themeValue = defaultDarkPresentationTheme + case .nightAccent: + themeValue = defaultDarkAccentPresentationTheme + case .day: + themeValue = makeDefaultDayPresentationTheme(accentColor: themeSettings.themeAccentColor ?? defaultDayAccentColor) + } + } + + let localizationSettings: LocalizationSettings? + if let current = (view.views[preferencesKey] as! PreferencesView).values[PreferencesKeys.localizationSettings] as? LocalizationSettings { + localizationSettings = current + } else { + localizationSettings = nil + } + + let stringsValue: PresentationStrings + if let localizationSettings = localizationSettings { + stringsValue = PresentationStrings(languageCode: localizationSettings.languageCode, dict: dictFromLocalization(localizationSettings.localization)) + } else { + stringsValue = defaultPresentationStrings + } + + let timeFormat: PresentationTimeFormat = currentTimeFormat() + + return PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, fontSize: themeSettings.fontSize, timeFormat: timeFormat) } - - let localizationSettings: LocalizationSettings? - if let current = (view.views[preferencesKey] as! PreferencesView).values[PreferencesKeys.localizationSettings] as? LocalizationSettings { - localizationSettings = current - } else { - localizationSettings = nil - } - - let stringsValue: PresentationStrings - if let localizationSettings = localizationSettings { - stringsValue = PresentationStrings(languageCode: localizationSettings.languageCode, dict: dictFromLocalization(localizationSettings.localization)) - } else { - stringsValue = defaultPresentationStrings - } - - let timeFormat: PresentationTimeFormat = currentTimeFormat() - - return PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: themeSettings.chatWallpaper, fontSize: themeSettings.fontSize, timeFormat: timeFormat) } } diff --git a/TelegramUI/PresentationPasscodeSettings.swift b/TelegramUI/PresentationPasscodeSettings.swift index b2fdd127ce..b0845d79d9 100644 --- a/TelegramUI/PresentationPasscodeSettings.swift +++ b/TelegramUI/PresentationPasscodeSettings.swift @@ -3,8 +3,8 @@ import Postbox import SwiftSignalKit public struct PresentationPasscodeSettings: PreferencesEntry, Equatable { - public let enableBiometrics: Bool - public let autolockTimeout: Int32? + public var enableBiometrics: Bool + public var autolockTimeout: Int32? public static var defaultSettings: PresentationPasscodeSettings { return PresentationPasscodeSettings(enableBiometrics: false, autolockTimeout: nil) diff --git a/TelegramUI/PresentationResourceKey.swift b/TelegramUI/PresentationResourceKey.swift index 69861b7132..28b0023823 100644 --- a/TelegramUI/PresentationResourceKey.swift +++ b/TelegramUI/PresentationResourceKey.swift @@ -32,6 +32,8 @@ enum PresentationResourceKey: Int32 { case navigationPlayerMaximizedShuffleIcon case navigationPlayerMaximizedRepeatIcon case navigationPlayerHandleIcon + case navigationPlayerRateActiveIcon + case navigationPlayerRateInactiveIcon case itemListDisclosureArrow case itemListCheckIcon @@ -64,7 +66,8 @@ enum PresentationResourceKey: Int32 { case chatTitleLockIcon case chatTitleMuteIcon - case chatPrincipalThemeEssentialGraphics + case chatPrincipalThemeEssentialGraphicsWithWallpaper + case chatPrincipalThemeEssentialGraphicsWithoutWallpaper case chatBubbleVerticalLineIncomingImage case chatBubbleVerticalLineOutgoingImage case chatServiceVerticalLineImage @@ -74,9 +77,6 @@ enum PresentationResourceKey: Int32 { case checkBubbleMediaFullImage case checkBubbleMediaPartialImage - case chatBubbleRadialIndicatorFileIconIncoming - case chatBubbleRadialIndicatorFileIconOutgoing - case chatBubbleConsumableContentIncomingIcon case chatBubbleConsumableContentOutgoingIcon case chatMediaConsumableContentIcon @@ -93,7 +93,9 @@ enum PresentationResourceKey: Int32 { case chatFreeformContentAdditionalInfoBackgroundImage - case chatInstantVideoBackgroundImage + case chatInstantVideoWithWallpaperBackgroundImage + case chatInstantVideoWithoutWallpaperBackgroundImage + case chatUnreadBarBackgroundImage case chatBubbleActionButtonIncomingMiddleImage @@ -108,7 +110,9 @@ enum PresentationResourceKey: Int32 { case chatBubbleReplyThumbnailPlayImage - case chatInfoItemBackgroundImage + case chatInfoItemBackgroundImageWithWallpaper + case chatInfoItemBackgroundImageWithoutWallpaper + case chatEmptyItemBackgroundImage case chatEmptyItemIconImage @@ -123,6 +127,7 @@ enum PresentationResourceKey: Int32 { case chatInputMediaPanelSettingsIconImage case chatInputMediaPanelAddPackButtonImage case chatInputMediaPanelGridSetupImage + case chatInputMediaPanelGridDismissImage case chatInputButtonPanelButtonImage case chatInputButtonPanelButtonHighlightedImage @@ -173,9 +178,11 @@ enum PresentationResourceKey: Int32 { case chatMessageAttachedContentHighlightedButtonOutgoing case chatMessageAttachedContentButtonIconInstantIncoming - case chatMessageAttachedContentHighlightedButtonIconInstantIncoming + case chatMessageAttachedContentHighlightedButtonIconInstantIncomingWithWallpaper + case chatMessageAttachedContentHighlightedButtonIconInstantIncomingWithoutWallpaper case chatMessageAttachedContentButtonIconInstantOutgoing - case chatMessageAttachedContentHighlightedButtonIconInstantOutgoing + case chatMessageAttachedContentHighlightedButtonIconInstantOutgoingWithWallpaper + case chatMessageAttachedContentHighlightedButtonIconInstantOutgoingWithoutWallpaper case chatCommandPanelArrowImage diff --git a/TelegramUI/PresentationResourcesChat.swift b/TelegramUI/PresentationResourcesChat.swift index dd44e727d0..8a70a4609b 100644 --- a/TelegramUI/PresentationResourcesChat.swift +++ b/TelegramUI/PresentationResourcesChat.swift @@ -1,5 +1,6 @@ import Foundation import Display +import TelegramCore private func generateLineImage(color: UIColor) -> UIImage? { return generateImage(CGSize(width: 2.0, height: 3.0), contextGenerator: { size, context in @@ -64,9 +65,10 @@ struct PresentationResourcesChat { }) } - static func principalGraphics(_ theme: PresentationTheme) -> PrincipalThemeEssentialGraphics { - return theme.object(PresentationResourceKey.chatPrincipalThemeEssentialGraphics.rawValue, { theme in - return PrincipalThemeEssentialGraphics(theme.chat) + static func principalGraphics(_ theme: PresentationTheme, wallpaper: Bool) -> PrincipalThemeEssentialGraphics { + let key: PresentationResourceKey = !wallpaper ? PresentationResourceKey.chatPrincipalThemeEssentialGraphicsWithoutWallpaper : PresentationResourceKey.chatPrincipalThemeEssentialGraphicsWithWallpaper + return theme.object(key.rawValue, { theme in + return PrincipalThemeEssentialGraphics(theme.chat, wallpaper: wallpaper) }) as! PrincipalThemeEssentialGraphics } @@ -88,18 +90,6 @@ struct PresentationResourcesChat { }) } - static func chatBubbleRadialIndicatorFileIconIncoming(_ theme: PresentationTheme) -> UIImage? { - return theme.image(PresentationResourceKey.chatBubbleRadialIndicatorFileIconIncoming.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocumentIncoming"), color: theme.chat.bubble.incomingFillColor) - }) - } - - static func chatBubbleRadialIndicatorFileIconOutgoing(_ theme: PresentationTheme) -> UIImage? { - return theme.image(PresentationResourceKey.chatBubbleRadialIndicatorFileIconOutgoing.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocumentIncoming"), color: theme.chat.bubble.outgoingFillColor) - }) - } - static func chatBubbleConsumableContentIncomingIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatBubbleConsumableContentIncomingIcon.rawValue, { theme in return generateFilledCircleImage(diameter: 4.0, color: theme.chat.bubble.incomingAccentControlColor) @@ -200,9 +190,10 @@ struct PresentationResourcesChat { }) } - static func chatInstantVideoBackgroundImage(_ theme: PresentationTheme) -> UIImage? { - return theme.image(PresentationResourceKey.chatInstantVideoBackgroundImage.rawValue, { theme in - return generateInstantVideoBackground(fillColor: theme.chat.bubble.freeformFillColor, strokeColor: theme.chat.bubble.freeformStrokeColor) + static func chatInstantVideoBackgroundImage(_ theme: PresentationTheme, wallpaper: Bool) -> UIImage? { + let key: PresentationResourceKey = !wallpaper ? PresentationResourceKey.chatInstantVideoWithoutWallpaperBackgroundImage : PresentationResourceKey.chatInstantVideoWithWallpaperBackgroundImage + return theme.image(key.rawValue, { theme in + return generateInstantVideoBackground(fillColor: theme.chat.bubble.freeform.withWallpaper.fill, strokeColor: theme.chat.bubble.freeform.withWallpaper.stroke) }) } @@ -267,9 +258,17 @@ struct PresentationResourcesChat { }) } - static func chatInfoItemBackgroundImage(_ theme: PresentationTheme) -> UIImage? { - return theme.image(PresentationResourceKey.chatInfoItemBackgroundImage.rawValue, { theme in - return messageSingleBubbleLikeImage(fillColor: theme.chat.bubble.infoFillColor, strokeColor: theme.chat.bubble.infoStrokeColor) + static func chatInfoItemBackgroundImageWithoutWallpaper(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatInfoItemBackgroundImageWithoutWallpaper.rawValue, { theme in + return messageSingleBubbleLikeImage(fillColor: theme.chat.bubble.incoming.withoutWallpaper.fill, strokeColor: theme.chat.bubble.incoming.withoutWallpaper.stroke) + }) + } + + static func chatInfoItemBackgroundImage(_ theme: PresentationTheme, wallpaper: Bool) -> UIImage? { + let key: PresentationResourceKey = !wallpaper ? PresentationResourceKey.chatInfoItemBackgroundImageWithoutWallpaper : PresentationResourceKey.chatInfoItemBackgroundImageWithWallpaper + return theme.image(key.rawValue, { theme in + let components: PresentationThemeBubbleColorComponents = wallpaper ? theme.chat.bubble.incoming.withWallpaper : theme.chat.bubble.incoming.withoutWallpaper + return messageSingleBubbleLikeImage(fillColor: components.fill, strokeColor: components.stroke) }) } @@ -289,6 +288,7 @@ struct PresentationResourcesChat { return theme.image(PresentationResourceKey.chatInputPanelCloseIconImage.rawValue, { theme in return generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) + context.setBlendMode(.copy) context.setStrokeColor(theme.chat.inputPanel.panelControlColor.cgColor) context.setLineWidth(2.0) context.setLineCap(.round) @@ -371,6 +371,12 @@ struct PresentationResourcesChat { }) } + static func chatInputMediaPanelGridDismissImage(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatInputMediaPanelGridDismissImage.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/GridDismissIcon"), color: theme.chat.inputMediaPanel.panelIconColor) + }) + } + static func chatInputButtonPanelButtonImage(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatInputButtonPanelButtonImage.rawValue, { theme in return generateInputPanelButtonBackgroundImage(fillColor: theme.chat.inputButtonPanel.buttonFillColor, strokeColor: theme.chat.inputButtonPanel.buttonStrokeColor) @@ -498,35 +504,6 @@ struct PresentationResourcesChat { }) } - /* - - - - - Created with Sketch. - - - - - - - - - - - - - - Created with Sketch. - - - - - - - - */ - static func chatInputPanelEditAttachmentButtonImage(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatInputPanelEditAttachmentButtonImage.rawValue, { theme in if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconAttachment"), color: theme.chat.inputPanel.panelControlColor) { @@ -883,9 +860,10 @@ struct PresentationResourcesChat { }) } - static func chatMessageAttachedContentHighlightedButtonIconInstantIncoming(_ theme: PresentationTheme) -> UIImage? { - return theme.image(PresentationResourceKey.chatMessageAttachedContentHighlightedButtonIconInstantIncoming.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/AttachedContentInstantIcon"), color: theme.chat.bubble.incomingFillColor) + static func chatMessageAttachedContentHighlightedButtonIconInstantIncoming(_ theme: PresentationTheme, wallpaper: Bool) -> UIImage? { + let key: PresentationResourceKey = !wallpaper ? PresentationResourceKey.chatMessageAttachedContentHighlightedButtonIconInstantIncomingWithoutWallpaper : PresentationResourceKey.chatMessageAttachedContentHighlightedButtonIconInstantIncomingWithWallpaper + return theme.image(key.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/AttachedContentInstantIcon"), color: bubbleColorComponents(theme: theme, incoming: true, wallpaper: wallpaper).fill) }) } @@ -895,9 +873,10 @@ struct PresentationResourcesChat { }) } - static func chatMessageAttachedContentHighlightedButtonIconInstantOutgoing(_ theme: PresentationTheme) -> UIImage? { - return theme.image(PresentationResourceKey.chatMessageAttachedContentHighlightedButtonIconInstantOutgoing.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/AttachedContentInstantIcon"), color: theme.chat.bubble.outgoingFillColor) + static func chatMessageAttachedContentHighlightedButtonIconInstantOutgoing(_ theme: PresentationTheme, wallpaper: Bool) -> UIImage? { + let key: PresentationResourceKey = !wallpaper ? PresentationResourceKey.chatMessageAttachedContentHighlightedButtonIconInstantOutgoingWithoutWallpaper : PresentationResourceKey.chatMessageAttachedContentHighlightedButtonIconInstantOutgoingWithWallpaper + return theme.image(key.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/AttachedContentInstantIcon"), color: bubbleColorComponents(theme: theme, incoming: false, wallpaper: wallpaper).fill) }) } diff --git a/TelegramUI/PresentationResourcesRootController.swift b/TelegramUI/PresentationResourcesRootController.swift index 698dddb81d..7439eeb032 100644 --- a/TelegramUI/PresentationResourcesRootController.swift +++ b/TelegramUI/PresentationResourcesRootController.swift @@ -17,6 +17,17 @@ func generateIndefiniteActivityIndicatorImage(color: UIColor, diameter: CGFloat }) } +func generatePlayerRateIcon(_ color: UIColor) -> UIImage? { + return generateImage(CGSize(width: 19.0, height: 16.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.cgColor) + context.setStrokeColor(color.cgColor) + context.setLineWidth(4.0) + context.scaleBy(x: 0.3333, y: 0.3333) + let _ = try? drawSvgPath(context, path: "M15.3637695,32.1972656 L23.7749023,32.1972656 C24.6127972,32.1972656 25.2519509,32.3691389 25.6923828,32.7128906 C26.1328147,33.0566423 26.3530273,33.5239228 26.3530273,34.1147461 C26.3530273,34.6411159 26.1784685,35.0869122 25.8293457,35.4521484 C25.4802229,35.8173846 24.9511754,36 24.2421875,36 L12.3828125,36 C11.5771444,36 10.9487327,35.7771018 10.4975586,35.3312988 C10.0463845,34.8854958 9.82080078,34.3618194 9.82080078,33.7602539 C9.82080078,33.3735332 9.96581886,32.8605989 10.2558594,32.2214355 C10.5458999,31.5822722 10.8627913,31.08008 11.206543,30.7148438 C12.635261,29.2324145 13.9243107,27.9621635 15.0737305,26.9040527 C16.2231503,25.845942 17.0449194,25.1503923 17.5390625,24.8173828 C18.4199263,24.1943328 19.1530732,23.5686067 19.7385254,22.9401855 C20.3239775,22.3117644 20.7697739,21.6672396 21.0759277,21.0065918 C21.3820816,20.345944 21.5351562,19.6987336 21.5351562,19.0649414 C21.5351562,18.377438 21.3713395,17.7624539 21.0437012,17.2199707 C20.7160628,16.6774875 20.2702665,16.2558609 19.7062988,15.9550781 C19.1423312,15.6542954 18.5273471,15.5039062 17.8613281,15.5039062 C16.4540945,15.5039062 15.3476603,16.1215759 14.5419922,17.3569336 C14.4345698,17.5180672 14.2546399,17.9584925 14.0021973,18.6782227 C13.7497546,19.3979528 13.4650895,19.9511699 13.1481934,20.3378906 C12.8312972,20.7246113 12.3667023,20.9179688 11.7543945,20.9179688 C11.2172825,20.9179688 10.7714861,20.7407244 10.4169922,20.3862305 C10.0624982,20.0317365 9.88525391,19.5483429 9.88525391,18.9360352 C9.88525391,18.1948205 10.0517561,17.4213907 10.3847656,16.6157227 C10.7177751,15.8100546 11.2145963,15.0795931 11.8752441,14.4243164 C12.535892,13.7690397 13.3737742,13.2399922 14.388916,12.8371582 C15.4040578,12.4343242 16.5937432,12.2329102 17.9580078,12.2329102 C19.6015707,12.2329102 21.0034122,12.4907201 22.1635742,13.0063477 C22.9155311,13.3500994 23.576169,13.8227509 24.1455078,14.4243164 C24.7148466,15.0258819 25.1579574,15.7214316 25.4748535,16.5109863 C25.7917496,17.3005411 25.9501953,18.1196247 25.9501953,18.9682617 C25.9501953,20.3002996 25.6198764,21.5114692 24.9592285,22.6018066 C24.2985807,23.6921441 23.6245152,24.5461395 22.9370117,25.1638184 C22.2495083,25.7814972 21.0974202,26.75097 19.4807129,28.0722656 C17.8640056,29.3935613 16.7548858,30.4194299 16.1533203,31.1499023 C15.8955065,31.4399429 15.6323256,31.7890605 15.3637695,32.1972656 Z M28.8464425,31.4077148 L34.1315987,23.6894531 L29.6843331,16.8251953 C29.2653857,16.1591764 28.9511799,15.5871606 28.7417062,15.1091309 C28.5322325,14.6311011 28.4274972,14.1718772 28.4274972,13.7314453 C28.4274972,13.2802712 28.6289112,12.8747577 29.0317452,12.5148926 C29.4345793,12.1550275 29.9260294,11.9750977 30.5061105,11.9750977 C31.1721294,11.9750977 31.6904348,12.1711406 32.0610421,12.5632324 C32.4316494,12.9553242 32.9445837,13.6831002 33.5998605,14.746582 L37.1447823,20.4829102 L40.9314034,14.746582 C41.2429284,14.2631812 41.5087949,13.8496111 41.7290109,13.5058594 C41.9492268,13.1621077 42.1613829,12.8774425 42.3654855,12.6518555 C42.569588,12.4262684 42.7978572,12.2570806 43.0502999,12.1442871 C43.3027426,12.0314936 43.5954643,11.9750977 43.9284737,11.9750977 C44.5300392,11.9750977 45.0214894,12.1550275 45.402839,12.5148926 C45.7841885,12.8747577 45.9748605,13.3017553 45.9748605,13.7958984 C45.9748605,14.5156286 45.5612904,15.4931579 44.7341378,16.7285156 L40.0773995,23.6894531 L45.08863,31.4077148 C45.5398041,32.084476 45.8674376,32.6457497 46.0715401,33.0915527 C46.2756427,33.5373557 46.3776925,33.9589824 46.3776925,34.3564453 C46.3776925,34.7324238 46.2863848,35.0761703 46.1037667,35.3876953 C45.9211486,35.6992203 45.6633387,35.9462881 45.3303292,36.1289062 C44.9973197,36.3115244 44.6213469,36.402832 44.2023995,36.402832 C43.7512254,36.402832 43.3698815,36.3088388 43.0583566,36.1208496 C42.7468316,35.9328604 42.4943927,35.6992201 42.3010323,35.4199219 C42.107672,35.1406236 41.7478123,34.5981486 41.2214425,33.7924805 L37.0642159,27.2504883 L32.6491769,33.9858398 C32.3054251,34.5229519 32.0610428,34.8989247 31.9160226,35.1137695 C31.7710023,35.3286144 31.5964435,35.5380849 31.3923409,35.7421875 C31.1882383,35.9462901 30.9465415,36.1074213 30.6672433,36.2255859 C30.387945,36.3437506 30.0603116,36.402832 29.6843331,36.402832 C29.1042521,36.402832 28.623544,36.2255877 28.2421944,35.8710938 C27.8608449,35.5165998 27.670173,35.0009799 27.670173,34.3242188 C27.670173,33.5292929 28.0622589,32.5571347 28.8464425,31.4077148 Z M8,2 C4.6862915,2 2,4.6862915 2,8 L2,40 C2,43.3137085 4.6862915,46 8,46 L48,46 C51.3137085,46 54,43.3137085 54,40 L54,8 C54,4.6862915 51.3137085,2 48,2 L8,2 S ") + }) +} + struct PresentationResourcesRootController { static func navigationIndefiniteActivityImage(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.rootNavigationIndefiniteActivity.rawValue, { theme in @@ -124,6 +135,18 @@ struct PresentationResourcesRootController { }) } + static func navigationPlayerRateActiveIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.navigationPlayerRateActiveIcon.rawValue, { theme in + return generatePlayerRateIcon(theme.rootController.navigationBar.accentTextColor) + }) + } + + static func navigationPlayerRateInactiveIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.navigationPlayerRateInactiveIcon.rawValue, { theme in + return generatePlayerRateIcon(theme.rootController.navigationBar.controlColor) + }) + } + static func navigationPlayerPauseIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.navigationPlayerPauseIcon.rawValue, { theme in return generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/MinimizedPause"), color: theme.rootController.navigationBar.accentTextColor) diff --git a/TelegramUI/PresentationStrings.swift b/TelegramUI/PresentationStrings.swift index 64aa63ffb9..8fef993e4f 100644 --- a/TelegramUI/PresentationStrings.swift +++ b/TelegramUI/PresentationStrings.swift @@ -101,8 +101,11 @@ public final class PresentationStrings { public let Channel_BanUser_Title: String public let Notification_SecretChatMessageScreenshotSelf: String public let Preview_SaveGif: String + public let Passport_ScanPassportHelp: String public let EnterPasscode_EnterNewPasscodeNew: String + public let Passport_Identity_TypeInternalPassport: String public let Privacy_Calls_WhoCanCallMe: String + public let Passport_DeletePassport: String public let Watch_NoConnection: String public let Activity_UploadingPhoto: String public let PrivacySettings_PrivacyTitle: String @@ -113,9 +116,9 @@ public final class PresentationStrings { } public let FastTwoStepSetup_PasswordSection: String public let FastTwoStepSetup_EmailSection: String - public let ChatList_MarkAsRead: String public let Cache_ClearCache: String public let Common_Close: String + public let Passport_PasswordDescription: String public let ChangePhoneNumberCode_Called: String public let Login_PhoneTitle: String private let _Cache_Clear: String @@ -127,6 +130,9 @@ public final class PresentationStrings { public let Watch_ChatList_Compose: String public let DialogList_SearchSectionDialogs: String public let Contacts_TabTitle: String + public let NotificationsSound_Pulse: String + public let Passport_Language_el: String + public let Passport_Identity_DateOfBirth: String public let TwoStepAuth_SetupPasswordConfirmPassword: String public let ChannelIntro_Text: String public let PrivacySettings_SecurityTitle: String @@ -136,12 +142,14 @@ public final class PresentationStrings { public func Login_SmsRequestState1(_ _0: Int, _ _1: Int) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Login_SmsRequestState1, self._Login_SmsRequestState1_r, ["\(_0)", String(format: "%.2d", _1)]) } + public let Update_Skip: String private let _Call_StatusOngoing: String private let _Call_StatusOngoing_r: [(Int, NSRange)] public func Call_StatusOngoing(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Call_StatusOngoing, self._Call_StatusOngoing_r, [_0]) } public let Settings_LogoutConfirmationText: String + public let Passport_Identity_ResidenceCountry: String public let AutoNightTheme_ScheduledTo: String public let SocksProxySetup_RequiredCredentials: String public let BlockedUsers_Info: String @@ -167,6 +175,8 @@ public final class PresentationStrings { public func Profile_CreateEncryptedChatOutdatedError(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Profile_CreateEncryptedChatOutdatedError, self._Profile_CreateEncryptedChatOutdatedError_r, [_0, _1]) } + public let PrivacyPolicy_DeclineLastWarning: String + public let Passport_FieldEmail: String public let ContactInfo_PhoneLabelPager: String private let _PINNED_STICKER: String private let _PINNED_STICKER_r: [(Int, NSRange)] @@ -181,13 +191,13 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Channel_AdminLog_MessageEdited, self._Channel_AdminLog_MessageEdited_r, [_0]) } public let Group_Setup_HistoryHidden: String + public let Your_cards_expiration_year_is_invalid: String + public let AccessDenied_MicrophoneRestricted: String private let _PHONE_CALL_REQUEST: String private let _PHONE_CALL_REQUEST_r: [(Int, NSRange)] public func PHONE_CALL_REQUEST(_ _1: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_PHONE_CALL_REQUEST, self._PHONE_CALL_REQUEST_r, [_1]) } - public let AccessDenied_MicrophoneRestricted: String - public let Your_cards_expiration_year_is_invalid: String public let GroupInfo_InviteByLink: String private let _Notification_LeftChat: String private let _Notification_LeftChat_r: [(Int, NSRange)] @@ -201,10 +211,18 @@ public final class PresentationStrings { } public let PrivacyLastSeenSettings_NeverShareWith_Placeholder: String public let Appearance_AutoNightThemeDisabled: String + public let Notifications_ExceptionsMessagePlaceholder: String + public let NotificationsSound_Alert: String public let TwoStepAuth_SetupEmail: String public let Checkout_PayWithFaceId: String public let Login_ResetAccountProtected_Reset: String public let SocksProxySetup_Hostname: String + private let _PrivacyPolicy_AgeVerificationMessage: String + private let _PrivacyPolicy_AgeVerificationMessage_r: [(Int, NSRange)] + public func PrivacyPolicy_AgeVerificationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_PrivacyPolicy_AgeVerificationMessage, self._PrivacyPolicy_AgeVerificationMessage_r, [_0]) + } + public let NotificationsSound_None: String public let Channel_AdminLog_CanEditMessages: String private let _MESSAGE_CONTACT: String private let _MESSAGE_CONTACT_r: [(Int, NSRange)] @@ -227,8 +245,13 @@ public final class PresentationStrings { public func Notification_CallTimeFormat(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Notification_CallTimeFormat, self._Notification_CallTimeFormat_r, [_1, _2]) } + public let Passport_Identity_Selfie: String + public let Passport_Identity_GenderMale: String public let Paint_Delete: String + public let Passport_Identity_AddDriversLicense: String + public let Passport_Language_ne: String public let Channel_MessagePhotoUpdated: String + public let Passport_Address_OneOfTypePassportRegistration: String public let Cache_Help: String public let SocksProxySetup_ProxyStatusConnected: String private let _Login_EmailPhoneBody: String @@ -240,6 +263,7 @@ public final class PresentationStrings { public let Channel_BanList_RestrictedTitle: String public let Checkout_TotalAmount: String public let Appearance_TextSize: String + public let Passport_Address_TypeResidentialAddress: String public let Conversation_MessageEditedLabel: String public let SharedMedia_EmptyLinksText: String private let _Conversation_RestrictedTextTimed: String @@ -247,7 +271,9 @@ public final class PresentationStrings { public func Conversation_RestrictedTextTimed(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Conversation_RestrictedTextTimed, self._Conversation_RestrictedTextTimed_r, [_0]) } + public let Passport_Address_AddResidentialAddress: String public let Calls_NoCallsPlaceholder: String + public let Passport_Address_AddPassportRegistration: String public let Conversation_PinMessageAlert_OnlyPin: String public let PasscodeSettings_UnlockWithFaceId: String public let ContactInfo_Title: String @@ -261,6 +287,7 @@ public final class PresentationStrings { } public let GroupInfo_Title: String public let State_Updating: String + public let PrivacyPolicy_AgeVerificationAgree: String public let Map_GetDirections: String private let _TwoStepAuth_PendingEmailHelp: String private let _TwoStepAuth_PendingEmailHelp_r: [(Int, NSRange)] @@ -268,10 +295,12 @@ public final class PresentationStrings { return formatWithArgumentRanges(_TwoStepAuth_PendingEmailHelp, self._TwoStepAuth_PendingEmailHelp_r, [_0]) } public let UserInfo_PhoneCall: String + public let Passport_Language_bn: String public let MusicPlayer_VoiceNote: String public let Paint_Duplicate: String public let Channel_Username_InvalidTaken: String public let Conversation_ClearGroupHistory: String + public let Passport_Address_OneOfTypeRentalAgreement: String public let Stickers_GroupStickersHelp: String public let SecretChat_Title: String public let Group_UpgradeConfirmation: String @@ -282,7 +311,13 @@ public final class PresentationStrings { public func Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Time_PreciseDate_m11, self._Time_PreciseDate_m11_r, [_1, _2, _3]) } - public let TermsOfService_DeclineAuthorized: String + public let Passport_DeletePersonalDetailsConfirmation: String + private let _UserInfo_NotificationsDefaultSound: String + private let _UserInfo_NotificationsDefaultSound_r: [(Int, NSRange)] + public func UserInfo_NotificationsDefaultSound(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_UserInfo_NotificationsDefaultSound, self._UserInfo_NotificationsDefaultSound_r, [_0]) + } + public let Passport_Email_Help: String private let _MESSAGE_GEOLIVE: String private let _MESSAGE_GEOLIVE_r: [(Int, NSRange)] public func MESSAGE_GEOLIVE(_ _1: String) -> (String, [(Int, NSRange)]) { @@ -315,8 +350,8 @@ public final class PresentationStrings { } public let Month_ShortDecember: String public let Channel_SignMessages: String - public let ReportPeer_ReasonCopyright: String public let Appearance_Title: String + public let ReportPeer_ReasonCopyright: String public let Conversation_Moderate_Delete: String public let Conversation_CloudStorage_ChatStatus: String public let Login_InfoTitle: String @@ -340,7 +375,7 @@ public final class PresentationStrings { public func DialogList_SingleRecordingAudioSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_DialogList_SingleRecordingAudioSuffix, self._DialogList_SingleRecordingAudioSuffix_r, [_0]) } - public let PrivacySettings_SyncContactsInfo: String + public let Privacy_TopPeersDelete: String public let Checkout_NewCard_CardholderNameTitle: String public let Settings_FAQ_Button: String private let _GroupInfo_AddParticipantConfirmation: String @@ -356,20 +391,24 @@ public final class PresentationStrings { public let AccessDenied_PhotosRestricted: String public let Map_Locating: String public let AutoDownloadSettings_Unlimited: String + public let Passport_Language_km: String public let MediaPicker_LivePhotoDescription: String + public let Passport_DiscardMessageDescription: String public let SocksProxySetup_Title: String public let SharedMedia_EmptyMusicText: String public let Cache_ByPeerHeader: String public let Bot_GroupStatusReadsHistory: String public let TwoStepAuth_ResetAccountConfirmation: String - public let TermsOfService_Decline: String public let CallSettings_Always: String public let Message_ImageExpired: String public let Channel_BanUser_Unban: String public let Stickers_GroupChooseStickerPack: String public let Group_Setup_TypePrivate: String + public let Passport_Language_cs: String public let Settings_LogoutConfirmationTitle: String public let UserInfo_FirstNamePlaceholder: String + public let Passport_Identity_SurnamePlaceholder: String + public let Passport_Identity_FilesView: String public let LoginPassword_ResetAccount: String public let Privacy_GroupsAndChannels_AlwaysAllow: String private let _Notification_JoinedChat: String @@ -377,9 +416,12 @@ public final class PresentationStrings { public func Notification_JoinedChat(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Notification_JoinedChat, self._Notification_JoinedChat_r, [_0]) } + public let Notifications_ExceptionsUnmuted: String public let ChannelInfo_DeleteChannel: String + public let Passport_Title: String public let NetworkUsageSettings_BytesReceived: String public let BlockedUsers_BlockTitle: String + public let Update_Title: String public let AccessDenied_PhotosAndVideos: String public let Channel_Username_Title: String private let _Channel_AdminLog_MessageToggleSignaturesOn: String @@ -393,6 +435,7 @@ public final class PresentationStrings { public func Conversation_EncryptionWaiting(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Conversation_EncryptionWaiting, self._Conversation_EncryptionWaiting_r, [_0]) } + public let Passport_Language_ka: String public let InfoPlist_NSSiriUsageDescription: String public let Calls_NotNow: String public let Conversation_Report: String @@ -403,7 +446,9 @@ public final class PresentationStrings { } public let Channel_AdminLogFilter_EventsAll: String public let InfoPlist_NSLocationWhenInUseUsageDescription: String + public let Passport_Address_TypeTemporaryRegistration: String public let Call_ConnectionErrorTitle: String + public let Passport_Language_tr: String public let Settings_ApplyProxyAlertEnable: String public let Settings_ChatSettings: String public let Group_About_Help: String @@ -426,6 +471,7 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Notification_PinnedRoundMessage, self._Notification_PinnedRoundMessage_r, [_0]) } public let Conversation_ViewMessage: String + public let Passport_FieldEmailHelp: String public let Settings_SaveEditedPhotos: String public let Channel_Management_LabelCreator: String private let _Notification_PinnedStickerMessage: String @@ -438,6 +484,7 @@ public final class PresentationStrings { public func AutoNightTheme_AutomaticHelp(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_AutoNightTheme_AutomaticHelp, self._AutoNightTheme_AutomaticHelp_r, [_0]) } + public let Passport_Address_EditPassportRegistration: String public let PhotoEditor_QualityTool: String public let Login_NetworkError: String public let TwoStepAuth_EnterPasswordForgot: String @@ -454,6 +501,7 @@ public final class PresentationStrings { public let GroupInfo_AddParticipantTitle: String public let Map_LiveLocationShowAll: String public let Settings_SavedMessages: String + public let Passport_FieldIdentitySelfieHelp: String private let _CHANNEL_MESSAGE_TEXT: String private let _CHANNEL_MESSAGE_TEXT_r: [(Int, NSRange)] public func CHANNEL_MESSAGE_TEXT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { @@ -465,6 +513,7 @@ public final class PresentationStrings { public let Settings_Username: String public let Notification_CallMissedShort: String public let Call_CallInProgressTitle: String + public let Passport_Scans: String public let PhotoEditor_Skip: String public let AuthSessions_TerminateOtherSessionsHelp: String public let Call_AudioRouteHeadphones: String @@ -483,7 +532,9 @@ public final class PresentationStrings { } public let Privacy_Calls: String public let DialogList_AdLabel: String + public let Passport_Identity_ScansHelp: String public let Channel_AdminLogFilter_EventsInfo: String + public let Passport_Language_hu: String private let _Channel_AdminLog_MessagePinned: String private let _Channel_AdminLog_MessagePinned_r: [(Int, NSRange)] public func Channel_AdminLog_MessagePinned(_ _0: String) -> (String, [(Int, NSRange)]) { @@ -511,8 +562,10 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Date_ChatDateHeaderYear, self._Date_ChatDateHeaderYear_r, [_1, _2, _3]) } public let Privacy_Calls_P2PContacts: String + public let Passport_Email_Delete: String public let CheckoutInfo_ShippingInfoCountry: String public let Map_ShowPlaces: String + public let Passport_Identity_GenderFemale: String public let Camera_VideoMode: String private let _Watch_Time_ShortFullAt: String private let _Watch_Time_ShortFullAt_r: [(Int, NSRange)] @@ -521,6 +574,7 @@ public final class PresentationStrings { } public let UserInfo_TelegramCall: String public let PrivacyLastSeenSettings_CustomShareSettingsHelp: String + public let Passport_UpdateRequiredError: String public let Channel_AdminLog_InfoPanelAlertText: String private let _Channel_AdminLog_MessageUnpinned: String private let _Channel_AdminLog_MessageUnpinned_r: [(Int, NSRange)] @@ -532,6 +586,7 @@ public final class PresentationStrings { public let PhotoEditor_QualityMedium: String public let Privacy_PaymentsClearInfo: String public let PhotoEditor_CurvesRed: String + public let Passport_Identity_AddPersonalDetails: String public let ContactInfo_PhoneLabelWorkFax: String public let Privacy_PaymentsTitle: String public let SocksProxySetup_ProxyType: String @@ -568,6 +623,7 @@ public final class PresentationStrings { public let ChatAdmins_AdminLabel: String public let Contacts_FailedToSendInvitesMessage: String public let Login_Code: String + public let Passport_Identity_ExpiryDateNone: String public let Channel_Username_InvalidCharacters: String public let FeatureDisabled_Oops: String public let Calls_CallTabTitle: String @@ -575,17 +631,23 @@ public final class PresentationStrings { public let WatchRemote_AlertTitle: String public let Channel_Members_AddBannedErrorAdmin: String public let Conversation_InfoGroup: String + public let Passport_Identity_TypePersonalDetails: String + public let Passport_Identity_OneOfTypePassport: String public let Checkout_Phone: String public let Channel_SignMessages_Help: String + public let Passport_PasswordNext: String public let Calls_SubmitRating: String public let Camera_FlashOn: String public let Watch_MessageView_Forward: String + public let Passport_DiscardMessageTitle: String + public let Passport_Language_uk: String public let GroupInfo_ActionPromote: String public let DialogList_You: String + public let Passport_Identity_SelfieHelp: String + public let Passport_Identity_MiddleName: String public let AccessDenied_Camera: String public let WatchRemote_NotificationText: String public let SharedMedia_ViewInChat: String - public let SecureId_FormRequestedTitle: String public let Activity_RecordingAudio: String public let Watch_Stickers_StickerPacks: String private let _Target_ShareGameConfirmationPrivate: String @@ -594,8 +656,8 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Target_ShareGameConfirmationPrivate, self._Target_ShareGameConfirmationPrivate_r, [_0]) } public let Checkout_NewCard_PostcodePlaceholder: String + public let Passport_Identity_OneOfTypeInternalPassport: String public let DialogList_DeleteConversationConfirmation: String - public let PrivacySettings_DeleteContactsSuccess: String public let AttachmentMenu_SendAsFile: String public let Watch_Conversation_Unblock: String public let Channel_AdminLog_MessagePreviousLink: String @@ -604,6 +666,8 @@ public final class PresentationStrings { public let PrivacyLastSeenSettings_NeverShareWith: String public let ConvertToSupergroup_HelpText: String public let MediaPicker_VideoMuteDescription: String + public let Passport_Address_TypeRentalAgreement: String + public let Passport_Language_it: String public let UserInfo_ShareMyContactInfo: String public let Channel_Info_Stickers: String public let Appearance_ColorTheme: String @@ -612,9 +676,15 @@ public final class PresentationStrings { public func FileSize_GB(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_FileSize_GB, self._FileSize_GB_r, [_0]) } + private let _Passport_FieldOneOf_Or: String + private let _Passport_FieldOneOf_Or_r: [(Int, NSRange)] + public func Passport_FieldOneOf_Or(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_Passport_FieldOneOf_Or, self._Passport_FieldOneOf_Or_r, [_1, _2]) + } public let Month_ShortJanuary: String public let Channel_BanUser_PermissionsHeader: String public let PhotoEditor_QualityVeryHigh: String + public let Passport_Language_mk: String public let Login_TermsOfServiceLabel: String private let _MESSAGE_TEXT: String private let _MESSAGE_TEXT_r: [(Int, NSRange)] @@ -622,6 +692,8 @@ public final class PresentationStrings { return formatWithArgumentRanges(_MESSAGE_TEXT, self._MESSAGE_TEXT_r, [_1, _2]) } public let DialogList_NoMessagesTitle: String + public let Passport_DeletePassportConfirmation: String + public let Passport_Language_az: String public let AccessDenied_Contacts: String public let Your_cards_security_code_is_invalid: String public let Contacts_InviteSearchLabel: String @@ -646,8 +718,10 @@ public final class PresentationStrings { public let Calls_AddTab: String public let DialogList_AdNoticeAlert: String public let PhotoEditor_TiltShift: String + public let Passport_Identity_TypeDriversLicenseUploadScan: String public let ChannelMembers_WhoCanAddMembers_Admins: String public let Tour_Text5: String + public let Notifications_ExceptionsGroupPlaceholder: String public let Watch_Stickers_RecentPlaceholder: String public let Common_Select: String private let _Notification_MessageLifetimeRemoved: String @@ -665,15 +739,18 @@ public final class PresentationStrings { public let FastTwoStepSetup_EmailHelp: String public let Month_GenOctober: String public let CheckoutInfo_ErrorPhoneInvalid: String + public let Passport_Identity_DocumentNumberPlaceholder: String public let AutoNightTheme_UpdateLocation: String public let Group_Setup_TypePublic: String public let Checkout_PaymentMethod_New: String public let ShareMenu_Comment: String + public let Passport_FloodError: String public let Channel_Management_LabelEditor: String public let TwoStepAuth_SetPasswordHelp: String public let Channel_AdminLogFilter_EventsTitle: String public let NotificationSettings_ContactJoined: String public let ChatSettings_AutoDownloadVideos: String + public let Passport_Identity_TypeIdentityCard: String public let Username_LinkCopied: String private let _Time_MonthOfYear_m9: String private let _Time_MonthOfYear_m9_r: [(Int, NSRange)] @@ -681,9 +758,9 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Time_MonthOfYear_m9, self._Time_MonthOfYear_m9_r, [_0]) } public let Channel_EditAdmin_PermissionAddAdmins: String + public let Passport_FieldPhoneHelp: String public let Conversation_SendMessage: String public let Notification_CallIncoming: String - public let PrivacySettings_SuggestFrequentContacts: String private let _MESSAGE_FWDS: String private let _MESSAGE_FWDS_r: [(Int, NSRange)] public func MESSAGE_FWDS(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { @@ -705,9 +782,9 @@ public final class PresentationStrings { public let Checkout_ErrorPaymentFailed: String public let Compose_NewMessage: String public let Conversation_LiveLocationYou: String + public let Privacy_TopPeersHelp: String public let Map_OpenInWaze: String public let Checkout_ShippingMethod: String - public let SecureId_FormFieldIdentity: String public let Login_InfoFirstNamePlaceholder: String public let Checkout_ErrorProviderAccountInvalid: String public let CallSettings_TabIconDescription: String @@ -716,6 +793,7 @@ public final class PresentationStrings { public let PasscodeSettings_AutoLock: String public let Notifications_MessageNotificationsPreview: String public let Conversation_BlockUser: String + public let Passport_Identity_EditPassport: String public let MessageTimer_Custom: String public let Conversation_SilentBroadcastTooltipOff: String public let Conversation_Mute: String @@ -738,7 +816,9 @@ public final class PresentationStrings { public let Common_TakePhotoOrVideo: String public let Notification_MessageLifetime2s: String public let Checkout_ErrorGeneric: String + public let DialogList_Unread: String public let AutoNightTheme_Automatic: String + public let Passport_Identity_Name: String public let Channel_AdminLog_CanBanUsers: String public let Cache_Indexing: String private let _ENCRYPTION_REQUEST: String @@ -750,6 +830,12 @@ public final class PresentationStrings { public let Channel_BanUser_PermissionEmbedLinks: String public let Map_Location: String public let GroupInfo_InviteLink_LinkSection: String + private let _Passport_Identity_UploadOneOfScan: String + private let _Passport_Identity_UploadOneOfScan_r: [(Int, NSRange)] + public func Passport_Identity_UploadOneOfScan(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_Passport_Identity_UploadOneOfScan, self._Passport_Identity_UploadOneOfScan_r, [_0]) + } + public let Notification_PassportValuePhone: String public let Privacy_Calls_AlwaysAllow_Placeholder: String public let CheckoutInfo_ShippingInfoPostcode: String public let Group_Setup_HistoryVisibleHelp: String @@ -759,13 +845,13 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Time_PreciseDate_m7, self._Time_PreciseDate_m7_r, [_1, _2, _3]) } public let PasscodeSettings_EncryptDataHelp: String + public let Passport_Language_ja: String public let KeyCommand_FocusOnInputField: String public let Channel_Members_AddAdminErrorBlacklisted: String public let Cache_KeepMedia: String public let SocksProxySetup_ProxyTelegram: String public let WebPreview_GettingLinkInfo: String public let Group_Setup_TypePublicHelp: String - public let Login_PRIVACY_URL: String public let Map_Satellite: String public let Username_InvalidTaken: String private let _Notification_PinnedAudioMessage: String @@ -795,6 +881,7 @@ public final class PresentationStrings { public func CHANNEL_MESSAGE_CONTACT(_ _1: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_CHANNEL_MESSAGE_CONTACT, self._CHANNEL_MESSAGE_CONTACT_r, [_1]) } + public let Passport_Language_bg: String public let PrivacySettings_DeleteAccountHelp: String public let Channel_Info_Banned: String public let Conversation_ShareBotContactConfirmationTitle: String @@ -809,6 +896,8 @@ public final class PresentationStrings { public func DialogList_MultipleTypingSuffix(_ _0: Int) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_DialogList_MultipleTypingSuffix, self._DialogList_MultipleTypingSuffix_r, ["\(_0)"]) } + public let Passport_Phone_Help: String + public let Passport_Language_sl: String public let Bot_GenericBotStatus: String public let PrivacySettings_PasscodeAndTouchId: String public let Common_edit: String @@ -820,26 +909,40 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Notification_Kicked, self._Notification_Kicked_r, [_0, _1]) } public let Channel_AdminLog_MessageRestrictedForever: String + public let Passport_DeleteDocument: String public let ChannelInfo_DeleteChannelConfirmation: String + public let Passport_Address_OneOfTypeBankStatement: String public let Weekday_ShortSaturday: String + public let Settings_Passport: String public let Map_SendThisLocation: String private let _Notification_PinnedDocumentMessage: String private let _Notification_PinnedDocumentMessage_r: [(Int, NSRange)] public func Notification_PinnedDocumentMessage(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Notification_PinnedDocumentMessage, self._Notification_PinnedDocumentMessage_r, [_0]) } + public let Passport_Identity_Surname: String public let Conversation_ContextMenuReply: String public let Channel_BanUser_PermissionSendMedia: String public let NetworkUsageSettings_Wifi: String public let Call_Accept: String public let GroupInfo_SetGroupPhotoDelete: String public let Login_PhoneBannedError: String + public let Passport_Identity_DocumentDetails: String public let PhotoEditor_CropAuto: String public let PhotoEditor_ContrastTool: String public let CheckoutInfo_ReceiverInfoNamePlaceholder: String + public let Passport_InfoLearnMore: String public let Channel_AdminLog_MessagePreviousCaption: String + private let _Passport_Email_UseTelegramEmail: String + private let _Passport_Email_UseTelegramEmail_r: [(Int, NSRange)] + public func Passport_Email_UseTelegramEmail(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_Passport_Email_UseTelegramEmail, self._Passport_Email_UseTelegramEmail_r, [_0]) + } public let Privacy_PaymentsClear_ShippingInfo: String + public let Passport_Email_UseTelegramEmailHelp: String + public let UserInfo_NotificationsDefaultDisabled: String public let Date_DialogDateFormat: String + public let Passport_Address_EditTemporaryRegistration: String public let ReportPeer_ReasonSpam: String public let Privacy_Calls_P2P: String public let Compose_TokenListPlaceholder: String @@ -851,6 +954,8 @@ public final class PresentationStrings { public let StickerPacksSettings_Title: String public let Privacy_PaymentsClearInfoDoneHelp: String public let Privacy_Calls_NeverAllow_Placeholder: String + public let Passport_PassportInformation: String + public let Passport_Identity_OneOfTypeDriversLicense: String public let Settings_Support: String public let Notification_GroupInviterSelf: String private let _SecretImage_NotViewedYet: String @@ -860,10 +965,16 @@ public final class PresentationStrings { } public let MaskStickerSettings_Title: String public let TwoStepAuth_SetPassword: String + private let _Passport_AcceptHelp: String + private let _Passport_AcceptHelp_r: [(Int, NSRange)] + public func Passport_AcceptHelp(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_Passport_AcceptHelp, self._Passport_AcceptHelp_r, [_1, _2]) + } public let SocksProxySetup_SavedProxies: String public let GroupInfo_InviteLink_ShareLink: String public let Common_Cancel: String public let UserInfo_About_Placeholder: String + public let Passport_Identity_NativeNameGenericTitle: String public let Camera_Discard: String public let ChangePhoneNumberCode_RequestingACall: String public let PrivacyLastSeenSettings_NeverShareWith_Title: String @@ -876,19 +987,24 @@ public final class PresentationStrings { public let Tour_Text1: String public let Privacy_SecretChatsTitle: String public let Conversation_HoldForVideo: String + public let Passport_Language_pt: String public let Checkout_NewCard_Title: String public let Channel_TitleInfo: String public let State_ConnectingToProxy: String public let Settings_About_Help: String public let AutoNightTheme_ScheduledFrom: String + public let Passport_Language_tk: String public let Watch_Conversation_Reply: String public let ShareMenu_CopyShareLink: String public let Stickers_Search: String + public let Notifications_GroupNotificationsExceptions: String public let Channel_Setup_TypePrivateHelp: String public let PhotoEditor_GrainTool: String public let Conversation_SearchByName_Placeholder: String public let Watch_Suggestion_TalkLater: String public let TwoStepAuth_ChangeEmail: String + public let Passport_Identity_EditPersonalDetails: String + public let Passport_FieldPhone: String private let _ENCRYPTION_ACCEPT: String private let _ENCRYPTION_ACCEPT_r: [(Int, NSRange)] public func ENCRYPTION_ACCEPT(_ _1: String) -> (String, [(Int, NSRange)]) { @@ -907,7 +1023,6 @@ public final class PresentationStrings { public func MESSAGE_VIDEO(_ _1: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_MESSAGE_VIDEO, self._MESSAGE_VIDEO_r, [_1]) } - public let TermsOfService_Title: String private let _Checkout_PayPrice: String private let _Checkout_PayPrice_r: [(Int, NSRange)] public func Checkout_PayPrice(_ _0: String) -> (String, [(Int, NSRange)]) { @@ -926,6 +1041,7 @@ public final class PresentationStrings { public let ChatSettings_ConnectionType_UseProxy: String public let Message_Audio: String public let Conversation_SearchNoResults: String + public let PrivacyPolicy_Accept: String public let ReportPeer_ReasonViolence: String public let Group_Username_RemoveExistingUsernamesInfo: String public let Message_InvoiceLabel: String @@ -954,6 +1070,7 @@ public final class PresentationStrings { public func Conversation_MessageViaUser(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Conversation_MessageViaUser, self._Conversation_MessageViaUser_r, [_0]) } + public let Notification_PassportValueAddress: String public let Tour_Title4: String public let Call_StatusEnded: String public let LiveLocationUpdated_JustNow: String @@ -962,12 +1079,15 @@ public final class PresentationStrings { public func Login_BannedPhoneSubject(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Login_BannedPhoneSubject, self._Login_BannedPhoneSubject_r, [_0]) } + public let Passport_Address_EditResidentialAddress: String private let _Channel_Management_RestrictedBy: String private let _Channel_Management_RestrictedBy_r: [(Int, NSRange)] public func Channel_Management_RestrictedBy(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Channel_Management_RestrictedBy, self._Channel_Management_RestrictedBy_r, [_0]) } public let Conversation_UnpinMessageAlert: String + public let NotificationsSound_Glass: String + public let Passport_Address_Street1Placeholder: String private let _Conversation_MessageDialogRetryAll: String private let _Conversation_MessageDialogRetryAll_r: [(Int, NSRange)] public func Conversation_MessageDialogRetryAll(_ _1: Int) -> (String, [(Int, NSRange)]) { @@ -1010,27 +1130,25 @@ public final class PresentationStrings { } public let AttachmentMenu_SendAsFiles: String public let Profile_MessageLifetime1m: String + public let Passport_PasswordReset: String public let Settings_AppleWatch: String + public let Notifications_ExceptionsTitle: String + public let Passport_Language_de: String public let Channel_AdminLog_MessagePreviousDescription: String - private let _SecureId_FormPolicyLink: String - private let _SecureId_FormPolicyLink_r: [(Int, NSRange)] - public func SecureId_FormPolicyLink(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(_SecureId_FormPolicyLink, self._SecureId_FormPolicyLink_r, [_0]) - } public let Your_card_was_declined: String public let PhoneNumberHelp_ChangeNumber: String public let ReportPeer_ReasonPornography: String public let Notification_CreatedChannel: String public let PhotoEditor_Original: String - public let TermsOfService_DeclineAndDelete: String + public let NotificationsSound_Chord: String public let Target_SelectGroup: String public let Stickers_SuggestAdded: String public let Channel_AdminLog_InfoPanelAlertTitle: String public let Notifications_GroupNotificationsPreview: String public let ChatSettings_AutoDownloadPhotos: String - public let SecureId_FormFieldEmail: String public let Message_PinnedLocationMessage: String public let Appearance_PreviewReplyText: String + public let Passport_Address_Street2Placeholder: String public let Settings_Logout: String private let _UserInfo_BlockConfirmation: String private let _UserInfo_BlockConfirmation_r: [(Int, NSRange)] @@ -1042,19 +1160,28 @@ public final class PresentationStrings { public let Appearance_AutoNightTheme: String public let AuthSessions_TerminateOtherSessions: String public let PasscodeSettings_TryAgainIn1Minute: String + public let Privacy_TopPeers: String + public let Passport_Phone_EnterOtherNumber: String + public let NotificationsSound_Hello: String public let Notifications_InAppNotifications: String + private let _Notification_PassportValuesSentMessage: String + private let _Notification_PassportValuesSentMessage_r: [(Int, NSRange)] + public func Notification_PassportValuesSentMessage(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_Notification_PassportValuesSentMessage, self._Notification_PassportValuesSentMessage_r, [_1, _2]) + } + public let Passport_Language_is: String public let StickerPack_ViewPack: String public let EnterPasscode_ChangeTitle: String public let Call_Decline: String public let UserInfo_AddPhone: String public let AutoNightTheme_Title: String - public let PrivacySettings_LinkPreviews: String public let Activity_PlayingGame: String public let CheckoutInfo_ShippingInfoStatePlaceholder: String public let SaveIncomingPhotosSettings_From: String + public let Passport_Address_TypeBankStatementUploadScan: String public let Notifications_MessageNotificationsSound: String public let Call_StatusWaiting: String - public let SecureId_FormFieldIdentityPlaceholder: String + public let Passport_Identity_MainPageHelp: String public let Weekday_ShortWednesday: String public let Notifications_Title: String public let PasscodeSettings_AutoLock_IfAwayFor_5hours: String @@ -1067,10 +1194,13 @@ public final class PresentationStrings { } public let ConversationProfile_LeaveDeleteAndExit: String public let State_connecting: String + public let Passport_Scans_Upload: String + public let Passport_Identity_FrontSideHelp: String public let AutoDownloadSettings_PhotosTitle: String public let Map_OpenInHereMaps: String public let Stickers_FavoriteStickers: String public let CheckoutInfo_Pay: String + public let Update_UpdateApp: String public let Login_CountryCode: String public let PasscodeSettings_AutoLock_IfAwayFor_1hour: String public let CheckoutInfo_ShippingInfoState: String @@ -1096,13 +1226,17 @@ public final class PresentationStrings { public func Conversation_RestrictedMediaTimed(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Conversation_RestrictedMediaTimed, self._Conversation_RestrictedMediaTimed_r, [_0]) } + public let NotificationsSound_Complete: String + public let NotificationsSound_Chime: String public let Login_InfoDeletePhoto: String public let ContactInfo_BirthdayLabel: String public let TwoStepAuth_RecoveryCodeExpired: String public let AutoDownloadSettings_Channels: String public let AutoDownloadSettings_Contacts: String public let TwoStepAuth_EmailTitle: String + public let Passport_Email_EmailPlaceholder: String public let Channel_AdminLog_ChannelEmptyText: String + public let Passport_Address_EditUtilityBill: String public let Privacy_GroupsAndChannels_NeverAllow: String public let Conversation_RestrictedStickers: String public let Conversation_AddContact: String @@ -1115,8 +1249,11 @@ public final class PresentationStrings { public let Paint_Outlined: String public let State_ConnectingToProxyInfo: String public let Checkout_PasswordEntry_Title: String + public let Conversation_InputTextCaptionPlaceholder: String public let Common_Done: String + public let Passport_Identity_FilesUploadNew: String public let PrivacySettings_LastSeenContacts: String + public let Passport_Language_vi: String public let CheckoutInfo_ShippingInfoAddress1: String public let UserInfo_LastNamePlaceholder: String public let Conversation_StatusKickedFromChannel: String @@ -1137,7 +1274,6 @@ public final class PresentationStrings { public let Privacy_Calls_NeverAllow: String public let Settings_About_Title: String public let PhoneNumberHelp_Help: String - public let PrivacySettings_SecretChats: String public let Channel_LinkItem: String public let Camera_Retake: String public let StickerPack_ShowStickers: String @@ -1156,8 +1292,14 @@ public final class PresentationStrings { } public let ChangePhoneNumberNumber_NewNumber: String public let Compose_NewChannel: String + public let NotificationsSound_Circles: String public let Login_TermsOfServiceAgree: String public let Channel_AdminLog_CanChangeInviteLink: String + private let _Passport_RequestHeader: String + private let _Passport_RequestHeader_r: [(Int, NSRange)] + public func Passport_RequestHeader(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_Passport_RequestHeader, self._Passport_RequestHeader_r, [_0]) + } private let _Call_CallInProgressMessage: String private let _Call_CallInProgressMessage_r: [(Int, NSRange)] public func Call_CallInProgressMessage(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { @@ -1232,6 +1374,7 @@ public final class PresentationStrings { public let KeyCommand_ChatInfo: String public let Channel_AdminLog_EmptyFilterTitle: String public let PhotoEditor_HighlightsTint: String + public let Passport_Address_Region: String public let Watch_Compose_AddContact: String private let _Time_PreciseDate_m5: String private let _Time_PreciseDate_m5_r: [(Int, NSRange)] @@ -1247,11 +1390,13 @@ public final class PresentationStrings { public let Compose_NewEncryptedChat: String public let PhotoEditor_CropReset: String public let Privacy_Calls_P2PAlways: String + public let Passport_Address_TypeTemporaryRegistrationUploadScan: String public let Login_InvalidLastNameError: String public let Channel_Members_AddMembers: String public let Tour_Title2: String public let Login_TermsOfServiceHeader: String public let Channel_AdminLog_BanSendGifs: String + public let Login_TermsOfServiceSignupDecline: String public let InfoPlist_NSMicrophoneUsageDescription: String public let AuthSessions_OtherSessions: String public let Watch_UserInfo_Title: String @@ -1265,6 +1410,7 @@ public final class PresentationStrings { public let NetworkUsageSettings_GeneralDataSection: String public let EnterPasscode_RepeatNewPasscode: String public let Conversation_ContextMenuCopyLink: String + public let Passport_Language_sk: String public let InstantPage_AutoNightTheme: String public let CloudStorage_Title: String public let Month_ShortOctober: String @@ -1275,6 +1421,7 @@ public final class PresentationStrings { public let Conversation_ContextMenuDelete: String public let Tour_Text6: String public let PhotoEditor_WarmthTool: String + public let Passport_Address_TypePassportRegistrationUploadScan: String public let Common_TakePhoto: String public let SocksProxySetup_AdNoticeHelp: String public let UserInfo_CreateNewContact: String @@ -1290,15 +1437,17 @@ public final class PresentationStrings { public let Group_ErrorSendRestrictedMedia: String public let Group_Setup_HistoryVisible: String public let Channel_EditAdmin_PermissinAddAdminOff: String + public let DialogList_ProxyConnectionIssuesTooltip: String public let Cache_Files: String public let PhotoEditor_EnhanceTool: String public let Conversation_SearchPlaceholder: String public let Channel_Stickers_NotFound: String + public let UserInfo_NotificationsDefaultEnabled: String public let WatchRemote_AlertText: String - public let SecureId_Title: String public let Channel_AdminLog_CanInviteUsers: String public let Channel_BanUser_PermissionReadMessages: String public let AttachmentMenu_PhotoOrVideo: String + public let Passport_Identity_GenderPlaceholder: String public let Month_ShortMarch: String public let GroupInfo_InviteLink_Title: String public let Watch_LastSeen_JustNow: String @@ -1316,6 +1465,7 @@ public final class PresentationStrings { public let Weekday_ShortThursday: String public let UserInfo_ShareContact: String public let LoginPassword_InvalidPasswordError: String + public let NotificationsSound_Calypso: String private let _MESSAGE_PHOTO_SECRET: String private let _MESSAGE_PHOTO_SECRET_r: [(Int, NSRange)] public func MESSAGE_PHOTO_SECRET(_ _1: String) -> (String, [(Int, NSRange)]) { @@ -1323,6 +1473,7 @@ public final class PresentationStrings { } public let Login_PhoneAndCountryHelp: String public let CheckoutInfo_ReceiverInfoName: String + public let NotificationsSound_Popcorn: String private let _Time_YesterdayAt: String private let _Time_YesterdayAt_r: [(Int, NSRange)] public func Time_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { @@ -1360,6 +1511,11 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Notification_LeftChannel, self._Notification_LeftChannel_r, [_0]) } public let Compose_Create: String + private let _Passport_Identity_NativeNameGenericHelp: String + private let _Passport_Identity_NativeNameGenericHelp_r: [(Int, NSRange)] + public func Passport_Identity_NativeNameGenericHelp(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_Passport_Identity_NativeNameGenericHelp, self._Passport_Identity_NativeNameGenericHelp_r, [_0]) + } private let _LOCKED_MESSAGE: String private let _LOCKED_MESSAGE_r: [(Int, NSRange)] public func LOCKED_MESSAGE(_ _1: String) -> (String, [(Int, NSRange)]) { @@ -1367,6 +1523,7 @@ public final class PresentationStrings { } public let Conversation_ClearPrivateHistory: String public let Conversation_ContextMenuShare: String + public let Notifications_ExceptionsNone: String private let _Time_MonthOfYear_m6: String private let _Time_MonthOfYear_m6_r: [(Int, NSRange)] public func Time_MonthOfYear_m6(_ _0: String) -> (String, [(Int, NSRange)]) { @@ -1379,8 +1536,15 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Call_GroupFormat, self._Call_GroupFormat_r, [_1, _2]) } public let Forward_ChannelReadOnly: String + public let Passport_InfoText: String public let Privacy_GroupsAndChannels_NeverAllow_Title: String + private let _Passport_Address_UploadOneOfScan: String + private let _Passport_Address_UploadOneOfScan_r: [(Int, NSRange)] + public func Passport_Address_UploadOneOfScan(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_Passport_Address_UploadOneOfScan, self._Passport_Address_UploadOneOfScan_r, [_0]) + } public let AutoDownloadSettings_Reset: String + public let NotificationsSound_Synth: String private let _Channel_AdminLog_MessageInvitedName: String private let _Channel_AdminLog_MessageInvitedName_r: [(Int, NSRange)] public func Channel_AdminLog_MessageInvitedName(_ _1: String) -> (String, [(Int, NSRange)]) { @@ -1388,9 +1552,15 @@ public final class PresentationStrings { } public let Conversation_Moderate_Ban: String public let Group_Status: String - public let ContactInfo_PhoneLabelOther: String + public let SocksProxySetup_ShareProxyList: String + public let Passport_Phone_Delete: String public let Conversation_InputTextPlaceholder: String + public let ContactInfo_PhoneLabelOther: String + public let Passport_Language_lv: String public let TwoStepAuth_RecoveryCode: String + public let Conversation_EditingMessageMediaEditCurrentPhoto: String + public let Passport_DeleteDocumentConfirmation: String + public let Passport_Language_hy: String public let SharedMedia_CategoryDocs: String public let Channel_AdminLog_CanChangeInfo: String public let Channel_AdminLogFilter_EventsAdmins: String @@ -1400,7 +1570,7 @@ public final class PresentationStrings { public func AuthSessions_AppUnofficial(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_AuthSessions_AppUnofficial, self._AuthSessions_AppUnofficial_r, [_0]) } - public let SecureId_FormFieldEmailPlaceholder: String + public let NotificationsSound_Telegraph: String public let AutoNightTheme_Disabled: String public let Conversation_ContextMenuBan: String public let Channel_EditAdmin_PermissionsHeader: String @@ -1431,6 +1601,9 @@ public final class PresentationStrings { public let ShareFileTip_CloseTip: String public let KeyCommand_Find: String public let SecretVideo_Title: String + public let Passport_DeleteAddressConfirmation: String + public let Passport_DiscardMessageAction: String + public let Passport_Language_dv: String public let Checkout_NewCard_PostcodeTitle: String private let _Channel_AdminLog_MessageRestricted: String private let _Channel_AdminLog_MessageRestricted_r: [(Int, NSRange)] @@ -1454,11 +1627,14 @@ public final class PresentationStrings { public let UserInfo_TapToCall: String public let Common_Edit: String public let Conversation_OpenFile: String + public let PrivacyPolicy_Decline: String + public let Passport_Identity_ResidenceCountryPlaceholder: String public let Message_PinnedDocumentMessage: String public let AuthSessions_LogOut: String public let AutoDownloadSettings_PrivateChats: String public let Checkout_TotalPaidAmount: String public let Conversation_UnsupportedMedia: String + public let Passport_InvalidPasswordError: String private let _Message_ForwardedMessage: String private let _Message_ForwardedMessage_r: [(Int, NSRange)] public func Message_ForwardedMessage(_ _0: String) -> (String, [(Int, NSRange)]) { @@ -1473,16 +1649,18 @@ public final class PresentationStrings { public let Call_AudioRouteHide: String public let CallSettings_OnMobile: String public let Conversation_GifTooltip: String - public let SecureId_FormFieldPhonePlaceholder: String + public let Passport_Address_EditBankStatement: String public let CheckoutInfo_ErrorCityInvalid: String public let Profile_CreateEncryptedChatError: String public let Map_LocationTitle: String public let Call_RateCall: String + public let Passport_Address_City: String public let SocksProxySetup_PasswordPlaceholder: String public let Message_ReplyActionButtonShowReceipt: String public let PhotoEditor_ShadowsTool: String public let Checkout_NewCard_CardholderNamePlaceholder: String public let Cache_Title: String + public let Passport_Email_Title: String public let Month_GenMay: String public let PasscodeSettings_HelpBottom: String private let _Notification_CreatedChat: String @@ -1491,6 +1669,7 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Notification_CreatedChat, self._Notification_CreatedChat_r, [_0]) } public let Calls_NoMissedCallsPlacehoder: String + public let Passport_Address_RegionPlaceholder: String public let Channel_Stickers_NotFoundHelp: String public let Watch_UserInfo_Block: String public let Watch_LastSeen_ALongTimeAgo: String @@ -1503,6 +1682,7 @@ public final class PresentationStrings { public func Conversation_LiveLocationYouAnd(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Conversation_LiveLocationYouAnd, self._Conversation_LiveLocationYouAnd_r, [_0]) } + public let TwoStepAuth_PasswordRemovePassportConfirmation: String public let Checkout_NewCard_SaveInfo: String public let Notification_CallMissed: String public let Profile_ShareContactButton: String @@ -1510,6 +1690,7 @@ public final class PresentationStrings { public let Bot_GroupStatusDoesNotReadHistory: String public let Notification_Mute1h: String public let Settings_TabTitle: String + public let Passport_Identity_ExpiryDatePlaceholder: String public let NetworkUsageSettings_MediaAudioDataSection: String public let GroupInfo_DeactivatedStatus: String private let _CHAT_PHOTO_EDITED: String @@ -1549,8 +1730,10 @@ public final class PresentationStrings { return formatWithArgumentRanges(_MESSAGE_AUDIO, self._MESSAGE_AUDIO_r, [_1]) } public let Checkout_PasswordEntry_Pay: String + public let Conversation_EditingMessagePanelMedia: String public let Notifications_MessageNotificationsHelp: String public let EnterPasscode_EnterCurrentPasscode: String + public let Conversation_EditingMessageMediaEditCurrentVideo: String private let _MESSAGE_GAME: String private let _MESSAGE_GAME_r: [(Int, NSRange)] public func MESSAGE_GAME(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { @@ -1574,15 +1757,15 @@ public final class PresentationStrings { public func Call_ParticipantVersionOutdatedError(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Call_ParticipantVersionOutdatedError, self._Call_ParticipantVersionOutdatedError_r, [_0]) } + public let Passport_Identity_ReverseSideHelp: String public let Tour_Text2: String public let Call_StatusNoAnswer: String - private let _SecureId_FormPolicy: String - private let _SecureId_FormPolicy_r: [(Int, NSRange)] - public func SecureId_FormPolicy(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(_SecureId_FormPolicy, self._SecureId_FormPolicy_r, [_0, _1]) + private let _Passport_Phone_UseTelegramNumber: String + private let _Passport_Phone_UseTelegramNumber_r: [(Int, NSRange)] + public func Passport_Phone_UseTelegramNumber(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_Passport_Phone_UseTelegramNumber, self._Passport_Phone_UseTelegramNumber_r, [_0]) } public let Channel_AdminLogFilter_EventsLeavingSubscribers: String - public let TermsOfService_AgeVerificationTitle: String public let Conversation_MessageDialogDelete: String public let Appearance_PreviewOutgoingText: String public let Username_Placeholder: String @@ -1605,11 +1788,10 @@ public final class PresentationStrings { } public let EnterPasscode_TouchId: String public let AuthSessions_LoggedInWithTelegram: String - public let PrivacySettings_SuggestFrequentContactsDisableNotice: String public let Checkout_ErrorInvoiceAlreadyPaid: String public let ChatAdmins_Title: String public let ChannelMembers_WhoCanAddMembers: String - public let PrivacySettings_SuggestFrequentContactsInfo: String + public let Passport_Language_ar: String public let PasscodeSettings_Help: String public let Conversation_EditingMessagePanelTitle: String public let Settings_AboutEmpty: String @@ -1634,14 +1816,23 @@ public final class PresentationStrings { public let Checkout_PaymentMethod_Title: String public let Conversation_Unmute: String public let AutoDownloadSettings_DocumentsTitle: String + public let Passport_FieldOneOf_FinalDelimeter: String public let Notifications_MessageNotifications: String + public let Passport_ForgottenPassword: String public let ChannelMembers_WhoCanAddMembersAdminsHelp: String public let DialogList_DeleteBotConversationConfirmation: String + public let Passport_Identity_TranslationHelp: String + private let _Update_AppVersion: String + private let _Update_AppVersion_r: [(Int, NSRange)] + public func Update_AppVersion(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_Update_AppVersion, self._Update_AppVersion_r, [_0]) + } private let _DialogList_MultipleTyping: String private let _DialogList_MultipleTyping_r: [(Int, NSRange)] public func DialogList_MultipleTyping(_ _0: String, _ _1: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_DialogList_MultipleTyping, self._DialogList_MultipleTyping_r, [_0, _1]) } + public let Passport_Identity_OneOfTypeIdentityCard: String public let Conversation_ClousStorageInfo_Description2: String private let _Time_MonthOfYear_m5: String private let _Time_MonthOfYear_m5_r: [(Int, NSRange)] @@ -1661,6 +1852,7 @@ public final class PresentationStrings { public let PhotoEditor_QualityVeryLow: String public let Stickers_AddToFavorites: String public let Month_ShortFebruary: String + public let Notifications_AddExceptionTitle: String public let Conversation_ForwardTitle: String public let Settings_FAQ_URL: String public let Activity_RecordingVideoMessage: String @@ -1672,6 +1864,7 @@ public final class PresentationStrings { } public let PasscodeSettings_UnlockWithTouchId: String public let Contacts_AccessDeniedHelpON: String + public let Passport_Identity_AddInternalPassport: String public let NetworkUsageSettings_ResetStats: String private let _PrivacySettings_LastSeenContactsMinusPlus: String private let _PrivacySettings_LastSeenContactsMinusPlus_r: [(Int, NSRange)] @@ -1704,6 +1897,7 @@ public final class PresentationStrings { public let Channel_ErrorAddBlocked: String public let Conversation_Unpin: String public let Call_RecordingDisabledMessage: String + public let Passport_Address_TypeUtilityBill: String public let Conversation_UnblockUser: String public let Conversation_Unblock: String private let _CHANNEL_MESSAGE_GIF: String @@ -1714,11 +1908,17 @@ public final class PresentationStrings { public let Channel_AdminLogFilter_EventsEditedMessages: String public let AutoNightTheme_ScheduleSection: String public let Appearance_ThemeNightBlue: String + private let _Passport_Scans_ScanIndex: String + private let _Passport_Scans_ScanIndex_r: [(Int, NSRange)] + public func Passport_Scans_ScanIndex(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_Passport_Scans_ScanIndex, self._Passport_Scans_ScanIndex_r, [_0]) + } public let Channel_Username_InvalidTooShort: String public let Conversation_ViewGroup: String public let Watch_LastSeen_WithinAWeek: String public let BlockedUsers_SelectUserTitle: String public let Profile_MessageLifetime1w: String + public let Passport_Address_TypeRentalAgreementUploadScan: String public let DialogList_TabTitle: String public let UserInfo_GenericPhoneLabel: String private let _Channel_AdminLog_MessagePromotedName: String @@ -1765,6 +1965,7 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Watch_Time_ShortTodayAt, self._Watch_Time_ShortTodayAt_r, [_0]) } public let Channel_AdminLogFilter_EventsNewSubscribers: String + public let Passport_Identity_ExpiryDate: String public let UserInfo_GroupsInCommon: String public let Message_PinnedContactMessage: String public let AccessDenied_CameraDisabled: String @@ -1773,12 +1974,15 @@ public final class PresentationStrings { public func Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Time_PreciseDate_m3, self._Time_PreciseDate_m3_r, [_1, _2, _3]) } + public let Passport_Email_EnterOtherEmail: String private let _LiveLocationUpdated_YesterdayAt: String private let _LiveLocationUpdated_YesterdayAt_r: [(Int, NSRange)] public func LiveLocationUpdated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_LiveLocationUpdated_YesterdayAt, self._LiveLocationUpdated_YesterdayAt_r, [_0]) } - public let SecureId_FormFieldAddressPlaceholder: String + public let NotificationsSound_Note: String + public let Passport_Identity_MiddleNamePlaceholder: String + public let PrivacyPolicy_Title: String public let Month_GenMarch: String public let Watch_UserInfo_Unmute: String public let CheckoutInfo_ErrorPostcodeInvalid: String @@ -1813,6 +2017,8 @@ public final class PresentationStrings { return formatWithArgumentRanges(_UserInfo_UnblockConfirmation, self._UserInfo_UnblockConfirmation_r, [_0]) } public let Appearance_PickAccentColor: String + public let Passport_Identity_EditDriversLicense: String + public let Passport_Identity_AddPassport: String public let UserInfo_ShareBot: String public let Settings_ProxyConnected: String public let ChatSettings_AutoDownloadVoiceMessages: String @@ -1820,14 +2026,16 @@ public final class PresentationStrings { public let Conversation_ViewContactDetails: String public let Conversation_JumpToDate: String public let AutoDownloadSettings_VideoMessagesTitle: String + public let Passport_Address_OneOfTypeUtilityBill: String public let CheckoutInfo_ReceiverInfoEmailPlaceholder: String public let Message_Photo: String public let Conversation_ReportSpam: String public let Camera_FlashAuto: String - public let PrivacySettings_LinkPreviewsInfo: String + public let Passport_Identity_TypePassportUploadScan: String public let Call_ConnectionErrorMessage: String public let Stickers_FrequentlyUsed: String public let LastSeen_ALongTimeAgo: String + public let Passport_Identity_ReverseSide: String public let DialogList_SearchSectionGlobal: String public let ChangePhoneNumberNumber_NumberPlaceholder: String public let GroupInfo_AddUserLeftError: String @@ -1852,6 +2060,7 @@ public final class PresentationStrings { public func CONTACT_JOINED(_ _1: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_CONTACT_JOINED, self._CONTACT_JOINED_r, [_1]) } + public let NotificationsSound_Bamboo: String public let PrivacyLastSeenSettings_AlwaysShareWith_Title: String private let _Channel_AdminLog_MessageGroupPreHistoryVisible: String private let _Channel_AdminLog_MessageGroupPreHistoryVisible_r: [(Int, NSRange)] @@ -1877,6 +2086,7 @@ public final class PresentationStrings { public let Conversation_ApplyLocalization: String public let FastTwoStepSetup_Title: String public let SocksProxySetup_ProxyStatusUnavailable: String + public let Passport_Address_EditRentalAgreement: String public let Conversation_DeleteManyMessages: String public let CancelResetAccount_Title: String public let Notification_CallOutgoingShort: String @@ -1903,11 +2113,13 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Conversation_Moderate_DeleteAllMessages, self._Conversation_Moderate_DeleteAllMessages_r, [_0]) } public let SharedMedia_CategoryOther: String + public let Passport_Address_Address: String public let DialogList_SavedMessagesTooltip: String public let Preview_DeletePhoto: String public let GroupInfo_ChannelListNamePlaceholder: String public let PasscodeSettings_TurnPasscodeOn: String public let AuthSessions_LogOutApplicationsHelp: String + public let Passport_FieldOneOf_Delimeter: String private let _Channel_AdminLog_MessageChangedGroupStickerPack: String private let _Channel_AdminLog_MessageChangedGroupStickerPack_r: [(Int, NSRange)] public func Channel_AdminLog_MessageChangedGroupStickerPack(_ _0: String) -> (String, [(Int, NSRange)]) { @@ -1934,7 +2146,7 @@ public final class PresentationStrings { public func Channel_AdminLog_MessageRemovedGroupStickerPack(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Channel_AdminLog_MessageRemovedGroupStickerPack, self._Channel_AdminLog_MessageRemovedGroupStickerPack_r, [_0]) } - public let TermsOfService_Agree: String + public let PrivacyPolicy_DeclineTitle: String public let AccessDenied_VideoMessageCamera: String public let Privacy_ContactsSyncHelp: String public let Conversation_Search: String @@ -1990,7 +2202,9 @@ public final class PresentationStrings { public let Calls_Missed: String public let Conversation_ContextMenuForward: String public let AutoDownloadSettings_ResetHelp: String + public let Passport_Identity_NativeNameHelp: String public let Call_StatusRinging: String + public let Passport_Language_pl: String public let Invitation_JoinGroup: String public let Notification_PinnedMessage: String public let AutoDownloadSettings_WiFi: String @@ -2002,6 +2216,8 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Notification_MessageLifetimeChanged, self._Notification_MessageLifetimeChanged_r, [_1, _2]) } public let Message_Contact: String + public let Passport_Language_lo: String + public let UserInfo_BotPrivacy: String public let PasscodeSettings_AutoLock_IfAwayFor_1minute: String public let Common_More: String public let Preview_OpenInInstagram: String @@ -2017,6 +2233,7 @@ public final class PresentationStrings { return formatWithArgumentRanges(_PINNED_GAME, self._PINNED_GAME_r, [_1]) } public let Invite_LargeRecipientsCountWarning: String + public let Passport_Language_hr: String public let GroupInfo_BroadcastListNamePlaceholder: String public let Activity_UploadingVideoMessage: String public let Conversation_ShareBotContactConfirmation: String @@ -2032,7 +2249,6 @@ public final class PresentationStrings { return formatWithArgumentRanges(_TwoStepAuth_EnterPasswordHint, self._TwoStepAuth_EnterPasswordHint_r, [_0]) } public let CallSettings_TabIcon: String - public let TermsOfService_DeclineUnauthorized: String public let ConversationProfile_UnknownAddMemberError: String private let _Conversation_FileHowToText: String private let _Conversation_FileHowToText_r: [(Int, NSRange)] @@ -2040,12 +2256,15 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Conversation_FileHowToText, self._Conversation_FileHowToText_r, [_0]) } public let Channel_AdminLog_BanSendMedia: String + public let Passport_Language_uz: String public let Watch_UserInfo_Unblock: String public let ChatSettings_AutoDownloadVideoMessages: String + public let PrivacyPolicy_AgeVerificationTitle: String public let StickerPacksSettings_ArchivedMasks: String public let Message_Animation: String public let Checkout_PaymentMethod: String public let Channel_AdminLog_TitleSelectedEvents: String + public let PrivacyPolicy_DeclineDeleteNow: String public let Privacy_Calls_NeverAllow_Title: String public let Cache_Music: String private let _Login_CallRequestState1: String @@ -2102,6 +2321,7 @@ public final class PresentationStrings { public let Channel_BanUser_PermissionSendStickersAndGifs: String public let Conversation_CloudStorageInfo_Title: String public let Conversation_ClearSecretHistory: String + public let Passport_Identity_EditIdentityCard: String public let Notification_RenamedChannel: String public let BlockedUsers_BlockUser: String public let ChatSettings_TextSize: String @@ -2126,13 +2346,20 @@ public final class PresentationStrings { return formatWithArgumentRanges(_CHAT_MESSAGE_STICKER, self._CHAT_MESSAGE_STICKER_r, [_1, _2, _3]) } public let Map_ChooseAPlace: String + public let Passport_Identity_NamePlaceholder: String + public let Passport_ScanPassport: String public let Map_ShareLiveLocationHelp: String public let Watch_Bot_Restart: String + public let Passport_RequestedInformation: String public let Channel_About_Help: String public let Web_OpenExternal: String + public let Passport_Language_mn: String public let UserInfo_AddContact: String public let Privacy_ContactsSync: String public let SocksProxySetup_Connection: String + public let Passport_NotLoggedInMessage: String + public let Passport_PasswordPlaceholder: String + public let Passport_PasswordCreate: String public let SocksProxySetup_ProxyStatusChecking: String public let Call_EncryptionKey_Title: String public let PhotoEditor_BlurToolLinear: String @@ -2144,11 +2371,13 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Call_StatusBar, self._Call_StatusBar_r, [_0]) } public let EditProfile_NameAndPhotoHelp: String - public let SecureId_FormFieldPhone: String + public let NotificationsSound_Tritone: String + public let Passport_FieldAddressUploadHelp: String public let Month_ShortJuly: String public let CheckoutInfo_ShippingInfoAddress1Placeholder: String public let Watch_MessageView_ViewOnPhone: String public let CallSettings_Never: String + public let Passport_Identity_TypeInternalPassportUploadScan: String public let TwoStepAuth_EmailSent: String private let _Notification_PinnedAnimationMessage: String private let _Notification_PinnedAnimationMessage_r: [(Int, NSRange)] @@ -2156,8 +2385,10 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Notification_PinnedAnimationMessage, self._Notification_PinnedAnimationMessage_r, [_0]) } public let TwoStepAuth_RecoveryTitle: String + public let Notifications_MessageNotificationsExceptions: String public let WatchRemote_AlertOpen: String public let ExplicitContent_AlertChannel: String + public let Notification_PassportValueEmail: String public let ContactInfo_PhoneLabelMobile: String public let Widget_AuthRequired: String private let _ForwardedAuthors2: String @@ -2181,11 +2412,14 @@ public final class PresentationStrings { public let Map_LiveLocationFor1Hour: String public let AutoNightTheme_AutomaticSection: String public let Stickers_NoStickersFound: String + public let Passport_Identity_AddIdentityCard: String private let _Notification_JoinedChannel: String private let _Notification_JoinedChannel_r: [(Int, NSRange)] public func Notification_JoinedChannel(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Notification_JoinedChannel, self._Notification_JoinedChannel_r, [_0]) } + public let Passport_Language_et: String + public let Passport_Language_en: String public let GroupInfo_ActionRestrict: String public let Checkout_ShippingOption_Title: String public let Stickers_SuggestStickers: String @@ -2208,6 +2442,7 @@ public final class PresentationStrings { public let Month_GenApril: String public let StickerPacksSettings_ShowStickersButton: String public let CheckoutInfo_ShippingInfoTitle: String + public let Notification_PassportValueProofOfAddress: String public let StickerPacksSettings_ShowStickersButtonHelp: String private let _Compatibility_SecretMediaVersionTooLow: String private let _Compatibility_SecretMediaVersionTooLow_r: [(Int, NSRange)] @@ -2238,6 +2473,8 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Login_BannedPhoneBody, self._Login_BannedPhoneBody_r, [_0]) } public let Conversation_ClearAll: String + public let Conversation_EditingMessageMediaChange: String + public let Passport_FieldIdentityTranslationHelp: String public let Call_ReportIncludeLog: String private let _Time_MonthOfYear_m3: String private let _Time_MonthOfYear_m3_r: [(Int, NSRange)] @@ -2248,6 +2485,7 @@ public final class PresentationStrings { public let Call_PhoneCallInProgressMessage: String public let Notification_GroupActivated: String public let Checkout_Name: String + public let Passport_Address_PostcodePlaceholder: String private let _AUTH_REGION: String private let _AUTH_REGION_r: [(Int, NSRange)] public func AUTH_REGION(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { @@ -2262,19 +2500,21 @@ public final class PresentationStrings { } public let AccessDenied_SaveMedia: String public let InviteText_URL: String + public let Passport_CorrectErrors: String private let _Channel_AdminLog_MessageInvitedNameUsername: String private let _Channel_AdminLog_MessageInvitedNameUsername_r: [(Int, NSRange)] public func Channel_AdminLog_MessageInvitedNameUsername(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Channel_AdminLog_MessageInvitedNameUsername, self._Channel_AdminLog_MessageInvitedNameUsername_r, [_1, _2]) } public let Compose_GroupTokenListPlaceholder: String - public let PrivacySettings_FrequentContacts: String + public let Passport_Address_CityPlaceholder: String + public let Passport_InfoFAQ_URL: String public let Conversation_MessageDeliveryFailed: String public let Privacy_PaymentsClear_PaymentInfo: String public let Notifications_GroupNotifications: String public let CheckoutInfo_SaveInfoHelp: String public let Notification_Mute1hMin: String - public let PrivacySettings_SyncContacts: String + public let Privacy_TopPeersWarning: String public let StickerPacksSettings_ArchivedMasks_Info: String public let ChannelMembers_WhoCanAddMembers_AllMembers: String public let Channel_Edit_PrivatePublicLinkAlert: String @@ -2287,6 +2527,8 @@ public final class PresentationStrings { public let Profile_MessageLifetime1d: String public let CheckoutInfo_ShippingInfoCityPlaceholder: String public let Calls_CallTabDescription: String + public let Passport_DeletePersonalDetails: String + public let Passport_Address_AddBankStatement: String public let Resolve_ErrorNotFound: String public let PhotoEditor_FadeTool: String public let Channel_Setup_TypePublicHelp: String @@ -2330,6 +2572,7 @@ public final class PresentationStrings { } public let ChatSearch_SearchPlaceholder: String public let TwoStepAuth_ConfirmationAbort: String + public let FastTwoStepSetup_HintSection: String public let TwoStepAuth_SetupPasswordConfirmFailed: String private let _LastSeen_YesterdayAt: String private let _LastSeen_YesterdayAt_r: [(Int, NSRange)] @@ -2364,12 +2607,14 @@ public final class PresentationStrings { public let AuthSessions_LogOutApplications: String public let Map_LoadError: String public let Settings_ProxyConnecting: String + public let Passport_Language_fa: String public let AccessDenied_VoiceMicrophone: String private let _CHANNEL_MESSAGE_STICKER: String private let _CHANNEL_MESSAGE_STICKER_r: [(Int, NSRange)] public func CHANNEL_MESSAGE_STICKER(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_CHANNEL_MESSAGE_STICKER, self._CHANNEL_MESSAGE_STICKER_r, [_1, _2]) } + public let Passport_Address_TypeUtilityBillUploadScan: String public let PrivacySettings_Title: String public let PasscodeSettings_TurnPasscodeOff: String public let MediaPicker_AddCaption: String @@ -2385,7 +2630,9 @@ public final class PresentationStrings { public let PhotoEditor_SharpenTool: String public let Common_of: String public let AuthSessions_Title: String + public let Passport_Scans_UploadNew: String public let Message_PinnedLiveLocationMessage: String + public let Passport_FieldIdentityDetailsHelp: String public let PrivacyLastSeenSettings_AlwaysShareWith: String public let EnterPasscode_EnterPasscode: String public let Notifications_Reset: String @@ -2407,6 +2654,7 @@ public final class PresentationStrings { } public let Watch_AppName: String public let ConvertToSupergroup_HelpTitle: String + public let Conversation_TapAndHoldToRecord: String private let _MESSAGE_GIF: String private let _MESSAGE_GIF_r: [(Int, NSRange)] public func MESSAGE_GIF(_ _1: String) -> (String, [(Int, NSRange)]) { @@ -2418,6 +2666,7 @@ public final class PresentationStrings { return formatWithArgumentRanges(_DialogList_EncryptedChatStartedOutgoing, self._DialogList_EncryptedChatStartedOutgoing_r, [_0]) } public let Checkout_PayWithTouchId: String + public let Passport_Language_ko: String public let Conversation_DiscardVoiceMessageTitle: String private let _CHAT_ADD_YOU: String private let _CHAT_ADD_YOU_r: [(Int, NSRange)] @@ -2427,6 +2676,7 @@ public final class PresentationStrings { public let CheckoutInfo_ShippingInfoCity: String public let AutoDownloadSettings_GroupChats: String public let Conversation_ClousStorageInfo_Description3: String + public let Notifications_ExceptionsMuted: String public let Conversation_PinMessageAlertGroup: String public let Settings_FAQ_Intro: String public let PrivacySettings_AuthSessions: String @@ -2435,6 +2685,7 @@ public final class PresentationStrings { public func CHAT_MESSAGE_GEOLIVE(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_CHAT_MESSAGE_GEOLIVE, self._CHAT_MESSAGE_GEOLIVE_r, [_1, _2]) } + public let Passport_Address_Postcode: String public let Tour_Title5: String public let ChatAdmins_AllMembersAreAdmins: String public let Group_Management_AddModeratorHelp: String @@ -2470,11 +2721,6 @@ public final class PresentationStrings { } public let Profile_MessageLifetime5s: String public let Privacy_ContactsReset: String - private let _TermsOfService_AgeVerificationText: String - private let _TermsOfService_AgeVerificationText_r: [(Int, NSRange)] - public func TermsOfService_AgeVerificationText(_ _0: Int) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(_TermsOfService_AgeVerificationText, self._TermsOfService_AgeVerificationText_r, ["\(_0)"]) - } private let _PINNED_PHOTO: String private let _PINNED_PHOTO_r: [(Int, NSRange)] public func PINNED_PHOTO(_ _1: String) -> (String, [(Int, NSRange)]) { @@ -2483,11 +2729,15 @@ public final class PresentationStrings { public let Channel_AdminLog_CanAddAdmins: String public let TwoStepAuth_SetupHint: String public let Conversation_StatusLeftGroup: String + public let Settings_CopyUsername: String + public let Passport_Identity_CountryPlaceholder: String public let ChatSettings_AutoDownloadDocuments: String public let MediaPicker_TapToUngroupDescription: String public let Conversation_ShareBotLocationConfirmation: String public let Conversation_DeleteMessagesForMe: String + public let Notification_PassportValuePersonalDetails: String public let Message_PinnedAnimationMessage: String + public let Passport_FieldIdentityUploadHelp: String public let SocksProxySetup_ConnectAndSave: String public let SocksProxySetup_FailedToConnect: String public let Checkout_ErrorPrecheckoutFailed: String @@ -2508,13 +2758,17 @@ public final class PresentationStrings { public let Calls_RatingTitle: String public let SharedMedia_EmptyText: String public let Channel_Stickers_Searching: String + public let Passport_Address_AddUtilityBill: String public let Login_PadPhoneHelp: String public let StickerPacksSettings_ArchivedPacks: String + public let Passport_Language_th: String public let Channel_ErrorAccessDenied: String public let Generic_ErrorMoreInfo: String public let Channel_AdminLog_TitleAllEvents: String public let Settings_Proxy: String + public let Passport_Language_lt: String public let ChannelMembers_WhoCanAddMembersAllHelp: String + public let Passport_Address_CountryPlaceholder: String public let ChangePhoneNumberCode_CodePlaceholder: String public let Camera_SquareMode: String private let _Conversation_EncryptedPlaceholderTitleOutgoing: String @@ -2530,6 +2784,8 @@ public final class PresentationStrings { public let PhotoEditor_VignetteTool: String public let LastSeen_WithinAWeek: String public let Widget_NoUsers: String + public let Passport_Identity_DocumentNumber: String + public let Application_Update: String public let Calls_NewCall: String private let _CHANNEL_MESSAGE_AUDIO: String private let _CHANNEL_MESSAGE_AUDIO_r: [(Int, NSRange)] @@ -2539,6 +2795,8 @@ public final class PresentationStrings { public let DialogList_NoMessagesText: String public let MaskStickerSettings_Info: String public let ChatSettings_AutoDownloadTitle: String + public let Passport_FieldAddressHelp: String + public let Passport_Language_dz: String public let Conversation_FilePhotoOrVideo: String public let Channel_AdminLog_BanSendStickers: String public let Common_Next: String @@ -2549,10 +2807,13 @@ public final class PresentationStrings { public func Channel_AdminLog_MessageRestrictedNewSetting(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Channel_AdminLog_MessageRestrictedNewSetting, self._Channel_AdminLog_MessageRestrictedNewSetting_r, [_0]) } + public let Passport_DeleteAddress: String public let ContactInfo_PhoneLabelHome: String public let GroupInfo_DeleteAndExitConfirmation: String + public let NotificationsSound_Tremolo: String public let TwoStepAuth_EmailInvalid: String public let Privacy_ContactsTitle: String + public let Passport_Address_TypeBankStatement: String private let _CHAT_MESSAGE_VIDEO: String private let _CHAT_MESSAGE_VIDEO_r: [(Int, NSRange)] public func CHAT_MESSAGE_VIDEO(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { @@ -2595,6 +2856,7 @@ public final class PresentationStrings { public let Notifications_GroupNotificationsSound: String public let AuthSessions_EmptyTitle: String public let Privacy_GroupsAndChannels_AlwaysAllow_Title: String + public let Passport_Language_he: String private let _MediaPicker_Nof: String private let _MediaPicker_Nof_r: [(Int, NSRange)] public func MediaPicker_Nof(_ _0: String) -> (String, [(Int, NSRange)]) { @@ -2607,6 +2869,7 @@ public final class PresentationStrings { public func Map_DirectionsDriveEta(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Map_DirectionsDriveEta, self._Map_DirectionsDriveEta_r, [_0]) } + public let PrivacyPolicy_DeclineMessage: String public let Your_cards_number_is_invalid: String private let _MESSAGE_INVOICE: String private let _MESSAGE_INVOICE_r: [(Int, NSRange)] @@ -2621,6 +2884,7 @@ public final class PresentationStrings { } public let Group_MessagePhotoRemoved: String public let UserInfo_AddToExisting: String + public let NotificationsSound_Aurora: String private let _LastSeen_AtDate: String private let _LastSeen_AtDate_r: [(Int, NSRange)] public func LastSeen_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { @@ -2628,6 +2892,7 @@ public final class PresentationStrings { } public let Conversation_MessageDialogRetry: String public let Watch_ChatList_NoConversationsTitle: String + public let Passport_Language_my: String public let Stickers_GroupStickers: String public let BlockedUsers_Title: String private let _LiveLocationUpdated_TodayAt: String @@ -2637,16 +2902,19 @@ public final class PresentationStrings { } public let ContactInfo_PhoneLabelWork: String public let ChatSettings_ConnectionType_UseSocks5: String + public let Passport_FieldAddressTranslationHelp: String public let Cache_ClearNone: String public let SecretTimer_VideoDescription: String public let Login_InvalidCodeError: String public let Channel_BanList_BlockedTitle: String + public let Passport_PasswordHelp: String public let NetworkUsageSettings_Cellular: String public let Watch_Location_Access: String public let PrivacySettings_DeleteAccountIfAwayFor: String public let Channel_AdminLog_EmptyFilterText: String public let Channel_AdminLog_EmptyText: String public let PrivacySettings_DeleteAccountTitle: String + public let Passport_Language_ms: String public let PrivacyLastSeenSettings_CustomShareSettings_Delete: String private let _ENCRYPTED_MESSAGE: String private let _ENCRYPTED_MESSAGE_r: [(Int, NSRange)] @@ -2660,12 +2928,15 @@ public final class PresentationStrings { public let Privacy_GroupsAndChannels_AlwaysAllow_Placeholder: String public let UserInfo_BotSettings: String public let Your_cards_expiration_month_is_invalid: String + public let Passport_FieldIdentity: String public let PrivacyLastSeenSettings_EmpryUsersPlaceholder: String + public let Passport_Identity_EditInternalPassport: String private let _CHANNEL_MESSAGE_ROUND: String private let _CHANNEL_MESSAGE_ROUND_r: [(Int, NSRange)] public func CHANNEL_MESSAGE_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_CHANNEL_MESSAGE_ROUND, self._CHANNEL_MESSAGE_ROUND_r, [_1]) } + public let Passport_Identity_LatinNameHelp: String public let SocksProxySetup_Port: String public let Message_VideoMessage: String public let Conversation_ContextMenuStickerPackInfo: String @@ -2677,6 +2948,7 @@ public final class PresentationStrings { } public let Conversation_DiscardVoiceMessageAction: String public let Camera_Title: String + public let Passport_Identity_IssueDate: String public let PhotoEditor_CurvesBlue: String public let Message_PinnedVideoMessage: String private let _Login_EmailPhoneSubject: String @@ -2684,6 +2956,7 @@ public final class PresentationStrings { public func Login_EmailPhoneSubject(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Login_EmailPhoneSubject, self._Login_EmailPhoneSubject_r, [_0]) } + public let Passport_Phone_UseTelegramNumberHelp: String public let Group_EditAdmin_PermissionChangeInfo: String public let TwoStepAuth_Email: String public let Stickers_SuggestNone: String @@ -2693,22 +2966,28 @@ public final class PresentationStrings { public func MESSAGE_ROUND(_ _1: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_MESSAGE_ROUND, self._MESSAGE_ROUND_r, [_1]) } + public let Passport_Identity_IssueDatePlaceholder: String public let Map_Unknown: String public let Wallpaper_Set: String public let AccessDenied_Title: String public let SharedMedia_CategoryLinks: String public let Localization_LanguageOther: String public let SaveIncomingPhotosSettings_Title: String + public let Passport_Identity_TypeDriversLicense: String + public let FastTwoStepSetup_HintHelp: String + public let Notifications_ExceptionsDefaultSound: String + public let Passport_Language_es: String public let TwoStepAuth_EmailSkipAlert: String public let ChatSettings_Stickers: String public let Camera_FlashOff: String public let TwoStepAuth_Title: String - public let PrivacySettings_DataSettingsHelp: String + public let Passport_Identity_Translation: String public let Checkout_ErrorProviderAccountTimeout: String public let TwoStepAuth_SetupPasswordEnterPasswordChange: String public let WebSearch_Images: String public let Conversation_typing: String public let Common_Back: String + public let PrivacySettings_DataSettingsHelp: String public let Common_Search: String private let _CancelResetAccount_Success: String private let _CancelResetAccount_Success_r: [(Int, NSRange)] @@ -2719,6 +2998,11 @@ public final class PresentationStrings { public let Login_EmailNotConfiguredError: String public let Watch_Suggestion_OK: String public let Profile_AddToExisting: String + private let _Passport_Identity_NativeNameTitle: String + private let _Passport_Identity_NativeNameTitle_r: [(Int, NSRange)] + public func Passport_Identity_NativeNameTitle(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_Passport_Identity_NativeNameTitle, self._Passport_Identity_NativeNameTitle_r, [_0]) + } private let _PINNED_NOTEXT: String private let _PINNED_NOTEXT_r: [(Int, NSRange)] public func PINNED_NOTEXT(_ _1: String) -> (String, [(Int, NSRange)]) { @@ -2729,6 +3013,8 @@ public final class PresentationStrings { public func Login_EmailCodeBody(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Login_EmailCodeBody, self._Login_EmailCodeBody_r, [_0]) } + public let NotificationsSound_Keys: String + public let Passport_Phone_Title: String public let Profile_About: String private let _EncryptionKey_Description: String private let _EncryptionKey_Description_r: [(Int, NSRange)] @@ -2742,8 +3028,11 @@ public final class PresentationStrings { return formatWithArgumentRanges(_DialogList_LiveLocationSharingTo, self._DialogList_LiveLocationSharingTo_r, [_0]) } public let Tour_Title3: String + public let Passport_Identity_FrontSide: String public let PrivacyLastSeenSettings_GroupsAndChannelsHelp: String public let Watch_Contacts_NoResults: String + public let Passport_Language_id: String + public let Passport_Identity_TypeIdentityCardUploadScan: String public let Watch_UserInfo_MuteTitle: String private let _Privacy_GroupsAndChannels_InviteToGroupError: String private let _Privacy_GroupsAndChannels_InviteToGroupError_r: [(Int, NSRange)] @@ -2763,6 +3052,7 @@ public final class PresentationStrings { public let Conversation_EmptyGifPanelPlaceholder: String public let DialogList_Typing: String public let Notification_CallBack: String + public let Passport_Language_ru: String public let Map_LocatingError: String public let InfoPlist_NSFaceIDUsageDescription: String public let MediaPicker_Send: String @@ -2778,17 +3068,19 @@ public final class PresentationStrings { public func InviteText_SingleContact(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_InviteText_SingleContact, self._InviteText_SingleContact_r, [_0]) } + public let Passport_Address_TypePassportRegistration: String public let Channel_EditAdmin_CannotEdit: String - public let PrivacySettings_DeleteContacts: String public let LoginPassword_PasswordHelp: String public let BlockedUsers_Unblock: String public let AutoDownloadSettings_Cellular: String + public let Passport_Language_ro: String private let _Time_MonthOfYear_m1: String private let _Time_MonthOfYear_m1_r: [(Int, NSRange)] public func Time_MonthOfYear_m1(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Time_MonthOfYear_m1, self._Time_MonthOfYear_m1_r, [_0]) } public let Appearance_PreviewIncomingText: String + public let Passport_Identity_DateOfBirthPlaceholder: String public let Notifications_GroupNotificationsAlert: String public let Paint_Masks: String public let Appearance_ThemeDayClassic: String @@ -2824,6 +3116,7 @@ public final class PresentationStrings { public let SocksProxySetup_HostnamePlaceholder: String public let NetworkUsageSettings_MediaVideoDataSection: String public let Paint_Edit: String + public let Passport_Language_nl: String public let Conversation_EncryptedDescription3: String public let Login_CodeFloodError: String public let Conversation_EncryptedDescription4: String @@ -2832,13 +3125,9 @@ public final class PresentationStrings { public let Conversation_StatusTyping: String public let Share_Title: String public let TwoStepAuth_ConfirmationTitle: String + public let Passport_Identity_FilesTitle: String public let ChatSettings_Title: String public let AuthSessions_CurrentSession: String - private let _SecureId_RequestTitle: String - private let _SecureId_RequestTitle_r: [(Int, NSRange)] - public func SecureId_RequestTitle(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(_SecureId_RequestTitle, self._SecureId_RequestTitle_r, [_0]) - } public let Watch_Microphone_Access: String private let _Notification_RenamedChat: String private let _Notification_RenamedChat_r: [(Int, NSRange)] @@ -2847,7 +3136,9 @@ public final class PresentationStrings { } public let Conversation_LiveLocation: String public let Watch_Conversation_GroupInfo: String + public let Passport_Language_fr: String public let UserInfo_Title: String + public let Passport_Identity_DoesNotExpire: String public let Map_LiveLocationGroupDescription: String public let Login_InfoHelp: String public let ShareMenu_ShareTo: String @@ -2864,16 +3155,21 @@ public final class PresentationStrings { public func Call_PrivacyErrorMessage(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Call_PrivacyErrorMessage, self._Call_PrivacyErrorMessage_r, [_0]) } + public let Passport_Address_Street: String + public let FastTwoStepSetup_HintPlaceholder: String public let PrivacySettings_DataSettings: String public let ChangePhoneNumberNumber_Title: String + public let NotificationsSound_Bell: String public let TwoStepAuth_EnterPasswordInvalid: String public let DialogList_SearchSectionMessages: String public let Media_ShareThisVideo: String public let Call_ReportIncludeLogDescription: String public let Preview_DeleteGif: String + public let Passport_Address_OneOfTypeTemporaryRegistration: String public let UserInfo_DeleteContact: String public let Notifications_ResetAllNotifications: String public let SocksProxySetup_SaveProxy: String + public let Passport_Identity_Country: String public let Notification_MessageLifetimeRemovedOutgoing: String public let Login_ContinueWithLocalization: String public let GroupInfo_AddParticipant: String @@ -2900,14 +3196,15 @@ public final class PresentationStrings { } public let Privacy_GroupsAndChannels: String public let Conversation_DiscardVoiceMessageDescription: String + public let Passport_Address_ScansHelp: String private let _Notification_ChangedGroupPhoto: String private let _Notification_ChangedGroupPhoto_r: [(Int, NSRange)] public func Notification_ChangedGroupPhoto(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_Notification_ChangedGroupPhoto, self._Notification_ChangedGroupPhoto_r, [_0]) } - public let PrivacySettings_Contacts: String public let TwoStepAuth_RemovePassword: String public let Privacy_GroupsAndChannels_CustomHelp: String + public let Passport_Identity_Gender: String public let UserInfo_NotificationsDisable: String public let Watch_UserInfo_Service: String public let Privacy_Calls_CustomHelp: String @@ -2930,8 +3227,10 @@ public final class PresentationStrings { public let GroupInfo_LeftStatus: String public let Cache_ClearProgress: String public let Login_InvalidPhoneError: String + public let Passport_Authorize: String public let Cache_ClearEmpty: String public let Map_Search: String + public let Passport_Identity_Translations: String public let ChannelMembers_GroupAdminsTitle: String private let _Channel_AdminLog_MessageRemovedGroupUsername: String private let _Channel_AdminLog_MessageRemovedGroupUsername_r: [(Int, NSRange)] @@ -2942,6 +3241,7 @@ public final class PresentationStrings { public let Group_ErrorAddTooMuchAdmins: String public let SocksProxySetup_Password: String public let Login_SelectCountry_Title: String + public let UserInfo_NotificationsDefault: String public let Notifications_GroupNotificationsHelp: String public let PhotoEditor_CropAspectRatioSquare: String public let Notification_CallOutgoing: String @@ -2955,10 +3255,11 @@ public final class PresentationStrings { public func MESSAGE_VIDEO_SECRET(_ _1: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_MESSAGE_VIDEO_SECRET, self._MESSAGE_VIDEO_SECRET_r, [_1]) } + public let Settings_CopyPhoneNumber: String public let ReportPeer_Report: String public let Channel_EditMessageErrorGeneric: String + public let Passport_Identity_TranslationsHelp: String public let LoginPassword_FloodError: String - public let SecureId_FormFieldAddress: String public let TwoStepAuth_SetupPasswordTitle: String public let PhotoEditor_DiscardChanges: String public let Group_UpgradeNoticeText2: String @@ -3014,16 +3315,18 @@ public final class PresentationStrings { public let GroupInfo_Sound: String public let Channel_EditAdmin_PermissionBanUsers: String public let InfoPlist_NSCameraUsageDescription: String - public let ContactInfo_Job: String + public let Passport_Address_AddRentalAgreement: String public let Wallpaper_PhotoLibrary: String public let Settings_About: String public let Privacy_Calls_IntegrationHelp: String + public let ContactInfo_Job: String private let _CHAT_LEFT: String private let _CHAT_LEFT_r: [(Int, NSRange)] public func CHAT_LEFT(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_CHAT_LEFT, self._CHAT_LEFT_r, [_1, _2]) } public let LoginPassword_ForgotPassword: String + public let Passport_Address_AddTemporaryRegistration: String private let _Map_LiveLocationShortHour: String private let _Map_LiveLocationShortHour_r: [(Int, NSRange)] public func Map_LiveLocationShortHour(_ _0: String) -> (String, [(Int, NSRange)]) { @@ -3035,9 +3338,15 @@ public final class PresentationStrings { public func DialogList_AwaitingEncryption(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_DialogList_AwaitingEncryption, self._DialogList_AwaitingEncryption_r, [_0]) } + public let Passport_Identity_TypePassport: String public let ChatSettings_Appearance: String public let Tour_Title1: String public let Conversation_EditingCaptionPanelTitle: String + private let _Notifications_ExceptionsChangeSound: String + private let _Notifications_ExceptionsChangeSound_r: [(Int, NSRange)] + public func Notifications_ExceptionsChangeSound(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_Notifications_ExceptionsChangeSound, self._Notifications_ExceptionsChangeSound_r, [_0]) + } public let Conversation_LinkDialogCopy: String private let _Notification_PinnedLocationMessage: String private let _Notification_PinnedLocationMessage_r: [(Int, NSRange)] @@ -3081,8 +3390,14 @@ public final class PresentationStrings { } public let Settings_ViewPhoto: String public let Paint_RecentStickers: String + private let _Passport_PrivacyPolicy: String + private let _Passport_PrivacyPolicy_r: [(Int, NSRange)] + public func Passport_PrivacyPolicy(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_Passport_PrivacyPolicy, self._Passport_PrivacyPolicy_r, [_1, _2]) + } public let Login_CallRequestState3: String public let Channel_Edit_LinkItem: String + public let Passport_InfoTitle: String public let CallSettings_Title: String public let ChangePhoneNumberNumber_Help: String public let Watch_Suggestion_Thanks: String @@ -3100,12 +3415,13 @@ public final class PresentationStrings { public let Tour_Text4: String public let Channel_Info_Description: String public let AccessDenied_LocationTracking: String - public let TermsOfService_Disagree: String public let Watch_Compose_Send: String public let SocksProxySetup_UseForCallsHelp: String public let Preview_CopyAddress: String public let Settings_BlockedUsers: String public let Month_ShortAugust: String + public let Passport_Identity_MainPage: String + public let Passport_FieldAddress: String public let Channel_AdminLogFilter_AdminsTitle: String public let Channel_EditAdmin_PermissionChangeInfo: String public let Notifications_ResetAllNotificationsHelp: String @@ -3118,6 +3434,7 @@ public final class PresentationStrings { public let PhotoEditor_CurvesGreen: String public let Month_GenJuly: String public let ContactInfo_URLLabelHomepage: String + public let PrivacyPolicy_DeclineDeclineAndDelete: String private let _DialogList_SingleUploadingFileSuffix: String private let _DialogList_SingleUploadingFileSuffix_r: [(Int, NSRange)] public func DialogList_SingleUploadingFileSuffix(_ _0: String) -> (String, [(Int, NSRange)]) { @@ -3154,6 +3471,7 @@ public final class PresentationStrings { public let Notifications_ClassicTones: String public let Conversation_LinkDialogOpen: String public let Channel_Info_Subscribers: String + public let NotificationsSound_Input: String public let Conversation_ClousStorageInfo_Description4: String public let Privacy_Calls_AlwaysAllow: String public let Privacy_PaymentsClearInfoHelp: String @@ -3178,12 +3496,14 @@ public final class PresentationStrings { return formatWithArgumentRanges(_Conversation_Kilobytes, self._Conversation_Kilobytes_r, ["\(_0)"]) } public let Group_ErrorAddBlocked: String - public let ChatList_MarkAsUnread: String public let TwoStepAuth_AdditionalPassword: String public let MediaPicker_Videos: String + public let Notification_PassportValueProofOfIdentity: String public let BlockedUsers_AddNew: String public let StickerPacksSettings_StickerPacksSection: String public let Channel_NotificationLoading: String + public let Passport_Language_da: String + public let Passport_Address_Country: String private let _CHAT_RETURNED: String private let _CHAT_RETURNED_r: [(Int, NSRange)] public func CHAT_RETURNED(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) { @@ -3201,7 +3521,13 @@ public final class PresentationStrings { public let Weekday_ShortTuesday: String public let Notification_CallIncomingShort: String public let ConvertToSupergroup_Note: String + public let DialogList_Read: String public let Conversation_EmptyPlaceholder: String + private let _Passport_Email_CodeHelp: String + private let _Passport_Email_CodeHelp_r: [(Int, NSRange)] + public func Passport_Email_CodeHelp(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_Passport_Email_CodeHelp, self._Passport_Email_CodeHelp_r, [_0]) + } public let Username_Help: String public let StickerSettings_ContextHide: String public let Media_ShareThisPhoto: String @@ -3209,401 +3535,49 @@ public final class PresentationStrings { public let AutoNightTheme_Scheduled: String public let PrivacySettings_PasscodeAndFaceId: String public let Settings_ChatBackground: String - public let TermsOfService_Confirm: String - private let _Media_ShareItem_zero: String - private let _Media_ShareItem_one: String - private let _Media_ShareItem_two: String - private let _Media_ShareItem_few: String - private let _Media_ShareItem_many: String - private let _Media_ShareItem_other: String - public func Media_ShareItem(_ value: Int32) -> String { + public let Login_TermsOfServiceDecline: String + private let _ForwardedFiles_zero: String + private let _ForwardedFiles_one: String + private let _ForwardedFiles_two: String + private let _ForwardedFiles_few: String + private let _ForwardedFiles_many: String + private let _ForwardedFiles_other: String + public func ForwardedFiles(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._Media_ShareItem_zero, "\(value)") + return String(format: self._ForwardedFiles_zero, "\(value)") case .one: - return String(format: self._Media_ShareItem_one, "\(value)") + return String(format: self._ForwardedFiles_one, "\(value)") case .two: - return String(format: self._Media_ShareItem_two, "\(value)") + return String(format: self._ForwardedFiles_two, "\(value)") case .few: - return String(format: self._Media_ShareItem_few, "\(value)") + return String(format: self._ForwardedFiles_few, "\(value)") case .many: - return String(format: self._Media_ShareItem_many, "\(value)") + return String(format: self._ForwardedFiles_many, "\(value)") case .other: - return String(format: self._Media_ShareItem_other, "\(value)") + return String(format: self._ForwardedFiles_other, "\(value)") } } - private let _Media_ShareVideo_zero: String - private let _Media_ShareVideo_one: String - private let _Media_ShareVideo_two: String - private let _Media_ShareVideo_few: String - private let _Media_ShareVideo_many: String - private let _Media_ShareVideo_other: String - public func Media_ShareVideo(_ value: Int32) -> String { + private let _Watch_LastSeen_HoursAgo_zero: String + private let _Watch_LastSeen_HoursAgo_one: String + private let _Watch_LastSeen_HoursAgo_two: String + private let _Watch_LastSeen_HoursAgo_few: String + private let _Watch_LastSeen_HoursAgo_many: String + private let _Watch_LastSeen_HoursAgo_other: String + public func Watch_LastSeen_HoursAgo(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._Media_ShareVideo_zero, "\(value)") + return String(format: self._Watch_LastSeen_HoursAgo_zero, "\(value)") case .one: - return String(format: self._Media_ShareVideo_one, "\(value)") + return String(format: self._Watch_LastSeen_HoursAgo_one, "\(value)") case .two: - return String(format: self._Media_ShareVideo_two, "\(value)") + return String(format: self._Watch_LastSeen_HoursAgo_two, "\(value)") case .few: - return String(format: self._Media_ShareVideo_few, "\(value)") + return String(format: self._Watch_LastSeen_HoursAgo_few, "\(value)") case .many: - return String(format: self._Media_ShareVideo_many, "\(value)") + return String(format: self._Watch_LastSeen_HoursAgo_many, "\(value)") case .other: - return String(format: self._Media_ShareVideo_other, "\(value)") - } - } - private let _Call_Minutes_zero: String - private let _Call_Minutes_one: String - private let _Call_Minutes_two: String - private let _Call_Minutes_few: String - private let _Call_Minutes_many: String - private let _Call_Minutes_other: String - public func Call_Minutes(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Call_Minutes_zero, "\(value)") - case .one: - return String(format: self._Call_Minutes_one, "\(value)") - case .two: - return String(format: self._Call_Minutes_two, "\(value)") - case .few: - return String(format: self._Call_Minutes_few, "\(value)") - case .many: - return String(format: self._Call_Minutes_many, "\(value)") - case .other: - return String(format: self._Call_Minutes_other, "\(value)") - } - } - private let _LiveLocation_MenuChatsCount_zero: String - private let _LiveLocation_MenuChatsCount_one: String - private let _LiveLocation_MenuChatsCount_two: String - private let _LiveLocation_MenuChatsCount_few: String - private let _LiveLocation_MenuChatsCount_many: String - private let _LiveLocation_MenuChatsCount_other: String - public func LiveLocation_MenuChatsCount(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._LiveLocation_MenuChatsCount_zero, "\(value)") - case .one: - return String(format: self._LiveLocation_MenuChatsCount_one, "\(value)") - case .two: - return String(format: self._LiveLocation_MenuChatsCount_two, "\(value)") - case .few: - return String(format: self._LiveLocation_MenuChatsCount_few, "\(value)") - case .many: - return String(format: self._LiveLocation_MenuChatsCount_many, "\(value)") - case .other: - return String(format: self._LiveLocation_MenuChatsCount_other, "\(value)") - } - } - private let _SharedMedia_Photo_zero: String - private let _SharedMedia_Photo_one: String - private let _SharedMedia_Photo_two: String - private let _SharedMedia_Photo_few: String - private let _SharedMedia_Photo_many: String - private let _SharedMedia_Photo_other: String - public func SharedMedia_Photo(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._SharedMedia_Photo_zero, "\(value)") - case .one: - return String(format: self._SharedMedia_Photo_one, "\(value)") - case .two: - return String(format: self._SharedMedia_Photo_two, "\(value)") - case .few: - return String(format: self._SharedMedia_Photo_few, "\(value)") - case .many: - return String(format: self._SharedMedia_Photo_many, "\(value)") - case .other: - return String(format: self._SharedMedia_Photo_other, "\(value)") - } - } - private let _AttachmentMenu_SendGif_zero: String - private let _AttachmentMenu_SendGif_one: String - private let _AttachmentMenu_SendGif_two: String - private let _AttachmentMenu_SendGif_few: String - private let _AttachmentMenu_SendGif_many: String - private let _AttachmentMenu_SendGif_other: String - public func AttachmentMenu_SendGif(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._AttachmentMenu_SendGif_zero, "\(value)") - case .one: - return String(format: self._AttachmentMenu_SendGif_one, "\(value)") - case .two: - return String(format: self._AttachmentMenu_SendGif_two, "\(value)") - case .few: - return String(format: self._AttachmentMenu_SendGif_few, "\(value)") - case .many: - return String(format: self._AttachmentMenu_SendGif_many, "\(value)") - case .other: - return String(format: self._AttachmentMenu_SendGif_other, "\(value)") - } - } - private let _MuteFor_Days_zero: String - private let _MuteFor_Days_one: String - private let _MuteFor_Days_two: String - private let _MuteFor_Days_few: String - private let _MuteFor_Days_many: String - private let _MuteFor_Days_other: String - public func MuteFor_Days(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MuteFor_Days_zero, "\(value)") - case .one: - return String(format: self._MuteFor_Days_one, "\(value)") - case .two: - return String(format: self._MuteFor_Days_two, "\(value)") - case .few: - return String(format: self._MuteFor_Days_few, "\(value)") - case .many: - return String(format: self._MuteFor_Days_many, "\(value)") - case .other: - return String(format: self._MuteFor_Days_other, "\(value)") - } - } - private let _ForwardedGifs_zero: String - private let _ForwardedGifs_one: String - private let _ForwardedGifs_two: String - private let _ForwardedGifs_few: String - private let _ForwardedGifs_many: String - private let _ForwardedGifs_other: String - public func ForwardedGifs(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ForwardedGifs_zero, "\(value)") - case .one: - return String(format: self._ForwardedGifs_one, "\(value)") - case .two: - return String(format: self._ForwardedGifs_two, "\(value)") - case .few: - return String(format: self._ForwardedGifs_few, "\(value)") - case .many: - return String(format: self._ForwardedGifs_many, "\(value)") - case .other: - return String(format: self._ForwardedGifs_other, "\(value)") - } - } - private let _ForwardedVideoMessages_zero: String - private let _ForwardedVideoMessages_one: String - private let _ForwardedVideoMessages_two: String - private let _ForwardedVideoMessages_few: String - private let _ForwardedVideoMessages_many: String - private let _ForwardedVideoMessages_other: String - public func ForwardedVideoMessages(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ForwardedVideoMessages_zero, "\(value)") - case .one: - return String(format: self._ForwardedVideoMessages_one, "\(value)") - case .two: - return String(format: self._ForwardedVideoMessages_two, "\(value)") - case .few: - return String(format: self._ForwardedVideoMessages_few, "\(value)") - case .many: - return String(format: self._ForwardedVideoMessages_many, "\(value)") - case .other: - return String(format: self._ForwardedVideoMessages_other, "\(value)") - } - } - private let _Forward_ConfirmMultipleFiles_zero: String - private let _Forward_ConfirmMultipleFiles_one: String - private let _Forward_ConfirmMultipleFiles_two: String - private let _Forward_ConfirmMultipleFiles_few: String - private let _Forward_ConfirmMultipleFiles_many: String - private let _Forward_ConfirmMultipleFiles_other: String - public func Forward_ConfirmMultipleFiles(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Forward_ConfirmMultipleFiles_zero, "\(value)") - case .one: - return String(format: self._Forward_ConfirmMultipleFiles_one, "\(value)") - case .two: - return String(format: self._Forward_ConfirmMultipleFiles_two, "\(value)") - case .few: - return String(format: self._Forward_ConfirmMultipleFiles_few, "\(value)") - case .many: - return String(format: self._Forward_ConfirmMultipleFiles_many, "\(value)") - case .other: - return String(format: self._Forward_ConfirmMultipleFiles_other, "\(value)") - } - } - private let _MessageTimer_Months_zero: String - private let _MessageTimer_Months_one: String - private let _MessageTimer_Months_two: String - private let _MessageTimer_Months_few: String - private let _MessageTimer_Months_many: String - private let _MessageTimer_Months_other: String - public func MessageTimer_Months(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MessageTimer_Months_zero, "\(value)") - case .one: - return String(format: self._MessageTimer_Months_one, "\(value)") - case .two: - return String(format: self._MessageTimer_Months_two, "\(value)") - case .few: - return String(format: self._MessageTimer_Months_few, "\(value)") - case .many: - return String(format: self._MessageTimer_Months_many, "\(value)") - case .other: - return String(format: self._MessageTimer_Months_other, "\(value)") - } - } - private let _Map_ETAHours_zero: String - private let _Map_ETAHours_one: String - private let _Map_ETAHours_two: String - private let _Map_ETAHours_few: String - private let _Map_ETAHours_many: String - private let _Map_ETAHours_other: String - public func Map_ETAHours(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Map_ETAHours_zero, "\(value)") - case .one: - return String(format: self._Map_ETAHours_one, "\(value)") - case .two: - return String(format: self._Map_ETAHours_two, "\(value)") - case .few: - return String(format: self._Map_ETAHours_few, "\(value)") - case .many: - return String(format: self._Map_ETAHours_many, "\(value)") - case .other: - return String(format: self._Map_ETAHours_other, "\(value)") - } - } - private let _StickerPack_RemoveStickerCount_zero: String - private let _StickerPack_RemoveStickerCount_one: String - private let _StickerPack_RemoveStickerCount_two: String - private let _StickerPack_RemoveStickerCount_few: String - private let _StickerPack_RemoveStickerCount_many: String - private let _StickerPack_RemoveStickerCount_other: String - public func StickerPack_RemoveStickerCount(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._StickerPack_RemoveStickerCount_zero, "\(value)") - case .one: - return String(format: self._StickerPack_RemoveStickerCount_one, "\(value)") - case .two: - return String(format: self._StickerPack_RemoveStickerCount_two, "\(value)") - case .few: - return String(format: self._StickerPack_RemoveStickerCount_few, "\(value)") - case .many: - return String(format: self._StickerPack_RemoveStickerCount_many, "\(value)") - case .other: - return String(format: self._StickerPack_RemoveStickerCount_other, "\(value)") - } - } - private let _MessageTimer_ShortWeeks_zero: String - private let _MessageTimer_ShortWeeks_one: String - private let _MessageTimer_ShortWeeks_two: String - private let _MessageTimer_ShortWeeks_few: String - private let _MessageTimer_ShortWeeks_many: String - private let _MessageTimer_ShortWeeks_other: String - public func MessageTimer_ShortWeeks(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MessageTimer_ShortWeeks_zero, "\(value)") - case .one: - return String(format: self._MessageTimer_ShortWeeks_one, "\(value)") - case .two: - return String(format: self._MessageTimer_ShortWeeks_two, "\(value)") - case .few: - return String(format: self._MessageTimer_ShortWeeks_few, "\(value)") - case .many: - return String(format: self._MessageTimer_ShortWeeks_many, "\(value)") - case .other: - return String(format: self._MessageTimer_ShortWeeks_other, "\(value)") - } - } - private let _Call_Seconds_zero: String - private let _Call_Seconds_one: String - private let _Call_Seconds_two: String - private let _Call_Seconds_few: String - private let _Call_Seconds_many: String - private let _Call_Seconds_other: String - public func Call_Seconds(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Call_Seconds_zero, "\(value)") - case .one: - return String(format: self._Call_Seconds_one, "\(value)") - case .two: - return String(format: self._Call_Seconds_two, "\(value)") - case .few: - return String(format: self._Call_Seconds_few, "\(value)") - case .many: - return String(format: self._Call_Seconds_many, "\(value)") - case .other: - return String(format: self._Call_Seconds_other, "\(value)") - } - } - private let _ForwardedAudios_zero: String - private let _ForwardedAudios_one: String - private let _ForwardedAudios_two: String - private let _ForwardedAudios_few: String - private let _ForwardedAudios_many: String - private let _ForwardedAudios_other: String - public func ForwardedAudios(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ForwardedAudios_zero, "\(value)") - case .one: - return String(format: self._ForwardedAudios_one, "\(value)") - case .two: - return String(format: self._ForwardedAudios_two, "\(value)") - case .few: - return String(format: self._ForwardedAudios_few, "\(value)") - case .many: - return String(format: self._ForwardedAudios_many, "\(value)") - case .other: - return String(format: self._ForwardedAudios_other, "\(value)") - } - } - private let _Conversation_StatusMembers_zero: String - private let _Conversation_StatusMembers_one: String - private let _Conversation_StatusMembers_two: String - private let _Conversation_StatusMembers_few: String - private let _Conversation_StatusMembers_many: String - private let _Conversation_StatusMembers_other: String - public func Conversation_StatusMembers(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Conversation_StatusMembers_zero, "\(value)") - case .one: - return String(format: self._Conversation_StatusMembers_one, "\(value)") - case .two: - return String(format: self._Conversation_StatusMembers_two, "\(value)") - case .few: - return String(format: self._Conversation_StatusMembers_few, "\(value)") - case .many: - return String(format: self._Conversation_StatusMembers_many, "\(value)") - case .other: - return String(format: self._Conversation_StatusMembers_other, "\(value)") - } - } - private let _PasscodeSettings_FailedAttempts_zero: String - private let _PasscodeSettings_FailedAttempts_one: String - private let _PasscodeSettings_FailedAttempts_two: String - private let _PasscodeSettings_FailedAttempts_few: String - private let _PasscodeSettings_FailedAttempts_many: String - private let _PasscodeSettings_FailedAttempts_other: String - public func PasscodeSettings_FailedAttempts(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._PasscodeSettings_FailedAttempts_zero, "\(value)") - case .one: - return String(format: self._PasscodeSettings_FailedAttempts_one, "\(value)") - case .two: - return String(format: self._PasscodeSettings_FailedAttempts_two, "\(value)") - case .few: - return String(format: self._PasscodeSettings_FailedAttempts_few, "\(value)") - case .many: - return String(format: self._PasscodeSettings_FailedAttempts_many, "\(value)") - case .other: - return String(format: self._PasscodeSettings_FailedAttempts_other, "\(value)") + return String(format: self._Watch_LastSeen_HoursAgo_other, "\(value)") } } private let _MessageTimer_ShortDays_zero: String @@ -3628,6 +3602,160 @@ public final class PresentationStrings { return String(format: self._MessageTimer_ShortDays_other, "\(value)") } } + private let _LastSeen_MinutesAgo_zero: String + private let _LastSeen_MinutesAgo_one: String + private let _LastSeen_MinutesAgo_two: String + private let _LastSeen_MinutesAgo_few: String + private let _LastSeen_MinutesAgo_many: String + private let _LastSeen_MinutesAgo_other: String + public func LastSeen_MinutesAgo(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._LastSeen_MinutesAgo_zero, "\(value)") + case .one: + return String(format: self._LastSeen_MinutesAgo_one, "\(value)") + case .two: + return String(format: self._LastSeen_MinutesAgo_two, "\(value)") + case .few: + return String(format: self._LastSeen_MinutesAgo_few, "\(value)") + case .many: + return String(format: self._LastSeen_MinutesAgo_many, "\(value)") + case .other: + return String(format: self._LastSeen_MinutesAgo_other, "\(value)") + } + } + private let _MuteFor_Days_zero: String + private let _MuteFor_Days_one: String + private let _MuteFor_Days_two: String + private let _MuteFor_Days_few: String + private let _MuteFor_Days_many: String + private let _MuteFor_Days_other: String + public func MuteFor_Days(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MuteFor_Days_zero, "\(value)") + case .one: + return String(format: self._MuteFor_Days_one, "\(value)") + case .two: + return String(format: self._MuteFor_Days_two, "\(value)") + case .few: + return String(format: self._MuteFor_Days_few, "\(value)") + case .many: + return String(format: self._MuteFor_Days_many, "\(value)") + case .other: + return String(format: self._MuteFor_Days_other, "\(value)") + } + } + private let _MessageTimer_Months_zero: String + private let _MessageTimer_Months_one: String + private let _MessageTimer_Months_two: String + private let _MessageTimer_Months_few: String + private let _MessageTimer_Months_many: String + private let _MessageTimer_Months_other: String + public func MessageTimer_Months(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MessageTimer_Months_zero, "\(value)") + case .one: + return String(format: self._MessageTimer_Months_one, "\(value)") + case .two: + return String(format: self._MessageTimer_Months_two, "\(value)") + case .few: + return String(format: self._MessageTimer_Months_few, "\(value)") + case .many: + return String(format: self._MessageTimer_Months_many, "\(value)") + case .other: + return String(format: self._MessageTimer_Months_other, "\(value)") + } + } + private let _PasscodeSettings_FailedAttempts_zero: String + private let _PasscodeSettings_FailedAttempts_one: String + private let _PasscodeSettings_FailedAttempts_two: String + private let _PasscodeSettings_FailedAttempts_few: String + private let _PasscodeSettings_FailedAttempts_many: String + private let _PasscodeSettings_FailedAttempts_other: String + public func PasscodeSettings_FailedAttempts(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._PasscodeSettings_FailedAttempts_zero, "\(value)") + case .one: + return String(format: self._PasscodeSettings_FailedAttempts_one, "\(value)") + case .two: + return String(format: self._PasscodeSettings_FailedAttempts_two, "\(value)") + case .few: + return String(format: self._PasscodeSettings_FailedAttempts_few, "\(value)") + case .many: + return String(format: self._PasscodeSettings_FailedAttempts_many, "\(value)") + case .other: + return String(format: self._PasscodeSettings_FailedAttempts_other, "\(value)") + } + } + private let _ServiceMessage_GameScoreSimple_zero: String + private let _ServiceMessage_GameScoreSimple_one: String + private let _ServiceMessage_GameScoreSimple_two: String + private let _ServiceMessage_GameScoreSimple_few: String + private let _ServiceMessage_GameScoreSimple_many: String + private let _ServiceMessage_GameScoreSimple_other: String + public func ServiceMessage_GameScoreSimple(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ServiceMessage_GameScoreSimple_zero, "\(value)") + case .one: + return String(format: self._ServiceMessage_GameScoreSimple_one, "\(value)") + case .two: + return String(format: self._ServiceMessage_GameScoreSimple_two, "\(value)") + case .few: + return String(format: self._ServiceMessage_GameScoreSimple_few, "\(value)") + case .many: + return String(format: self._ServiceMessage_GameScoreSimple_many, "\(value)") + case .other: + return String(format: self._ServiceMessage_GameScoreSimple_other, "\(value)") + } + } + private let _MessageTimer_ShortWeeks_zero: String + private let _MessageTimer_ShortWeeks_one: String + private let _MessageTimer_ShortWeeks_two: String + private let _MessageTimer_ShortWeeks_few: String + private let _MessageTimer_ShortWeeks_many: String + private let _MessageTimer_ShortWeeks_other: String + public func MessageTimer_ShortWeeks(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MessageTimer_ShortWeeks_zero, "\(value)") + case .one: + return String(format: self._MessageTimer_ShortWeeks_one, "\(value)") + case .two: + return String(format: self._MessageTimer_ShortWeeks_two, "\(value)") + case .few: + return String(format: self._MessageTimer_ShortWeeks_few, "\(value)") + case .many: + return String(format: self._MessageTimer_ShortWeeks_many, "\(value)") + case .other: + return String(format: self._MessageTimer_ShortWeeks_other, "\(value)") + } + } + private let _MuteExpires_Days_zero: String + private let _MuteExpires_Days_one: String + private let _MuteExpires_Days_two: String + private let _MuteExpires_Days_few: String + private let _MuteExpires_Days_many: String + private let _MuteExpires_Days_other: String + public func MuteExpires_Days(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MuteExpires_Days_zero, "\(value)") + case .one: + return String(format: self._MuteExpires_Days_one, "\(value)") + case .two: + return String(format: self._MuteExpires_Days_two, "\(value)") + case .few: + return String(format: self._MuteExpires_Days_few, "\(value)") + case .many: + return String(format: self._MuteExpires_Days_many, "\(value)") + case .other: + return String(format: self._MuteExpires_Days_other, "\(value)") + } + } private let _Notification_GameScoreExtended_zero: String private let _Notification_GameScoreExtended_one: String private let _Notification_GameScoreExtended_two: String @@ -3650,70 +3778,92 @@ public final class PresentationStrings { return String(format: self._Notification_GameScoreExtended_other, "\(value)") } } - private let _MuteExpires_Hours_zero: String - private let _MuteExpires_Hours_one: String - private let _MuteExpires_Hours_two: String - private let _MuteExpires_Hours_few: String - private let _MuteExpires_Hours_many: String - private let _MuteExpires_Hours_other: String - public func MuteExpires_Hours(_ value: Int32) -> String { + private let _Notifications_Exceptions_zero: String + private let _Notifications_Exceptions_one: String + private let _Notifications_Exceptions_two: String + private let _Notifications_Exceptions_few: String + private let _Notifications_Exceptions_many: String + private let _Notifications_Exceptions_other: String + public func Notifications_Exceptions(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._MuteExpires_Hours_zero, "\(value)") + return String(format: self._Notifications_Exceptions_zero, "\(value)") case .one: - return String(format: self._MuteExpires_Hours_one, "\(value)") + return String(format: self._Notifications_Exceptions_one, "\(value)") case .two: - return String(format: self._MuteExpires_Hours_two, "\(value)") + return String(format: self._Notifications_Exceptions_two, "\(value)") case .few: - return String(format: self._MuteExpires_Hours_few, "\(value)") + return String(format: self._Notifications_Exceptions_few, "\(value)") case .many: - return String(format: self._MuteExpires_Hours_many, "\(value)") + return String(format: self._Notifications_Exceptions_many, "\(value)") case .other: - return String(format: self._MuteExpires_Hours_other, "\(value)") + return String(format: self._Notifications_Exceptions_other, "\(value)") } } - private let _InviteText_ContactsCount_zero: String - private let _InviteText_ContactsCount_one: String - private let _InviteText_ContactsCount_two: String - private let _InviteText_ContactsCount_few: String - private let _InviteText_ContactsCount_many: String - private let _InviteText_ContactsCount_other: String - public func InviteText_ContactsCount(_ value: Int32) -> String { + private let _DialogList_LiveLocationChatsCount_zero: String + private let _DialogList_LiveLocationChatsCount_one: String + private let _DialogList_LiveLocationChatsCount_two: String + private let _DialogList_LiveLocationChatsCount_few: String + private let _DialogList_LiveLocationChatsCount_many: String + private let _DialogList_LiveLocationChatsCount_other: String + public func DialogList_LiveLocationChatsCount(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._InviteText_ContactsCount_zero, "\(value)") + return String(format: self._DialogList_LiveLocationChatsCount_zero, "\(value)") case .one: - return String(format: self._InviteText_ContactsCount_one, "\(value)") + return String(format: self._DialogList_LiveLocationChatsCount_one, "\(value)") case .two: - return String(format: self._InviteText_ContactsCount_two, "\(value)") + return String(format: self._DialogList_LiveLocationChatsCount_two, "\(value)") case .few: - return String(format: self._InviteText_ContactsCount_few, "\(value)") + return String(format: self._DialogList_LiveLocationChatsCount_few, "\(value)") case .many: - return String(format: self._InviteText_ContactsCount_many, "\(value)") + return String(format: self._DialogList_LiveLocationChatsCount_many, "\(value)") case .other: - return String(format: self._InviteText_ContactsCount_other, "\(value)") + return String(format: self._DialogList_LiveLocationChatsCount_other, "\(value)") } } - private let _StickerPack_AddStickerCount_zero: String - private let _StickerPack_AddStickerCount_one: String - private let _StickerPack_AddStickerCount_two: String - private let _StickerPack_AddStickerCount_few: String - private let _StickerPack_AddStickerCount_many: String - private let _StickerPack_AddStickerCount_other: String - public func StickerPack_AddStickerCount(_ value: Int32) -> String { + private let _ForwardedAuthorsOthers_zero: String + private let _ForwardedAuthorsOthers_one: String + private let _ForwardedAuthorsOthers_two: String + private let _ForwardedAuthorsOthers_few: String + private let _ForwardedAuthorsOthers_many: String + private let _ForwardedAuthorsOthers_other: String + public func ForwardedAuthorsOthers(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._StickerPack_AddStickerCount_zero, "\(value)") + return String(format: self._ForwardedAuthorsOthers_zero, "\(value)") case .one: - return String(format: self._StickerPack_AddStickerCount_one, "\(value)") + return String(format: self._ForwardedAuthorsOthers_one, "\(value)") case .two: - return String(format: self._StickerPack_AddStickerCount_two, "\(value)") + return String(format: self._ForwardedAuthorsOthers_two, "\(value)") case .few: - return String(format: self._StickerPack_AddStickerCount_few, "\(value)") + return String(format: self._ForwardedAuthorsOthers_few, "\(value)") case .many: - return String(format: self._StickerPack_AddStickerCount_many, "\(value)") + return String(format: self._ForwardedAuthorsOthers_many, "\(value)") case .other: - return String(format: self._StickerPack_AddStickerCount_other, "\(value)") + return String(format: self._ForwardedAuthorsOthers_other, "\(value)") + } + } + private let _StickerPack_AddMaskCount_zero: String + private let _StickerPack_AddMaskCount_one: String + private let _StickerPack_AddMaskCount_two: String + private let _StickerPack_AddMaskCount_few: String + private let _StickerPack_AddMaskCount_many: String + private let _StickerPack_AddMaskCount_other: String + public func StickerPack_AddMaskCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._StickerPack_AddMaskCount_zero, "\(value)") + case .one: + return String(format: self._StickerPack_AddMaskCount_one, "\(value)") + case .two: + return String(format: self._StickerPack_AddMaskCount_two, "\(value)") + case .few: + return String(format: self._StickerPack_AddMaskCount_few, "\(value)") + case .many: + return String(format: self._StickerPack_AddMaskCount_many, "\(value)") + case .other: + return String(format: self._StickerPack_AddMaskCount_other, "\(value)") } } private let _ServiceMessage_GameScoreSelfExtended_zero: String @@ -3738,48 +3888,312 @@ public final class PresentationStrings { return String(format: self._ServiceMessage_GameScoreSelfExtended_other, "\(value)") } } - private let _Notification_GameScoreSelfExtended_zero: String - private let _Notification_GameScoreSelfExtended_one: String - private let _Notification_GameScoreSelfExtended_two: String - private let _Notification_GameScoreSelfExtended_few: String - private let _Notification_GameScoreSelfExtended_many: String - private let _Notification_GameScoreSelfExtended_other: String - public func Notification_GameScoreSelfExtended(_ value: Int32) -> String { + private let _Map_ETAHours_zero: String + private let _Map_ETAHours_one: String + private let _Map_ETAHours_two: String + private let _Map_ETAHours_few: String + private let _Map_ETAHours_many: String + private let _Map_ETAHours_other: String + public func Map_ETAHours(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._Notification_GameScoreSelfExtended_zero, "\(value)") + return String(format: self._Map_ETAHours_zero, "\(value)") case .one: - return String(format: self._Notification_GameScoreSelfExtended_one, "\(value)") + return String(format: self._Map_ETAHours_one, "\(value)") case .two: - return String(format: self._Notification_GameScoreSelfExtended_two, "\(value)") + return String(format: self._Map_ETAHours_two, "\(value)") case .few: - return String(format: self._Notification_GameScoreSelfExtended_few, "\(value)") + return String(format: self._Map_ETAHours_few, "\(value)") case .many: - return String(format: self._Notification_GameScoreSelfExtended_many, "\(value)") + return String(format: self._Map_ETAHours_many, "\(value)") case .other: - return String(format: self._Notification_GameScoreSelfExtended_other, "\(value)") + return String(format: self._Map_ETAHours_other, "\(value)") } } - private let _AttachmentMenu_SendItem_zero: String - private let _AttachmentMenu_SendItem_one: String - private let _AttachmentMenu_SendItem_two: String - private let _AttachmentMenu_SendItem_few: String - private let _AttachmentMenu_SendItem_many: String - private let _AttachmentMenu_SendItem_other: String - public func AttachmentMenu_SendItem(_ value: Int32) -> String { + private let _SharedMedia_File_zero: String + private let _SharedMedia_File_one: String + private let _SharedMedia_File_two: String + private let _SharedMedia_File_few: String + private let _SharedMedia_File_many: String + private let _SharedMedia_File_other: String + public func SharedMedia_File(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._AttachmentMenu_SendItem_zero, "\(value)") + return String(format: self._SharedMedia_File_zero, "\(value)") case .one: - return String(format: self._AttachmentMenu_SendItem_one, "\(value)") + return String(format: self._SharedMedia_File_one, "\(value)") case .two: - return String(format: self._AttachmentMenu_SendItem_two, "\(value)") + return String(format: self._SharedMedia_File_two, "\(value)") case .few: - return String(format: self._AttachmentMenu_SendItem_few, "\(value)") + return String(format: self._SharedMedia_File_few, "\(value)") case .many: - return String(format: self._AttachmentMenu_SendItem_many, "\(value)") + return String(format: self._SharedMedia_File_many, "\(value)") case .other: - return String(format: self._AttachmentMenu_SendItem_other, "\(value)") + return String(format: self._SharedMedia_File_other, "\(value)") + } + } + private let _MessageTimer_Days_zero: String + private let _MessageTimer_Days_one: String + private let _MessageTimer_Days_two: String + private let _MessageTimer_Days_few: String + private let _MessageTimer_Days_many: String + private let _MessageTimer_Days_other: String + public func MessageTimer_Days(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MessageTimer_Days_zero, "\(value)") + case .one: + return String(format: self._MessageTimer_Days_one, "\(value)") + case .two: + return String(format: self._MessageTimer_Days_two, "\(value)") + case .few: + return String(format: self._MessageTimer_Days_few, "\(value)") + case .many: + return String(format: self._MessageTimer_Days_many, "\(value)") + case .other: + return String(format: self._MessageTimer_Days_other, "\(value)") + } + } + private let _ForwardedStickers_zero: String + private let _ForwardedStickers_one: String + private let _ForwardedStickers_two: String + private let _ForwardedStickers_few: String + private let _ForwardedStickers_many: String + private let _ForwardedStickers_other: String + public func ForwardedStickers(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ForwardedStickers_zero, "\(value)") + case .one: + return String(format: self._ForwardedStickers_one, "\(value)") + case .two: + return String(format: self._ForwardedStickers_two, "\(value)") + case .few: + return String(format: self._ForwardedStickers_few, "\(value)") + case .many: + return String(format: self._ForwardedStickers_many, "\(value)") + case .other: + return String(format: self._ForwardedStickers_other, "\(value)") + } + } + private let _ServiceMessage_GameScoreExtended_zero: String + private let _ServiceMessage_GameScoreExtended_one: String + private let _ServiceMessage_GameScoreExtended_two: String + private let _ServiceMessage_GameScoreExtended_few: String + private let _ServiceMessage_GameScoreExtended_many: String + private let _ServiceMessage_GameScoreExtended_other: String + public func ServiceMessage_GameScoreExtended(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ServiceMessage_GameScoreExtended_zero, "\(value)") + case .one: + return String(format: self._ServiceMessage_GameScoreExtended_one, "\(value)") + case .two: + return String(format: self._ServiceMessage_GameScoreExtended_two, "\(value)") + case .few: + return String(format: self._ServiceMessage_GameScoreExtended_few, "\(value)") + case .many: + return String(format: self._ServiceMessage_GameScoreExtended_many, "\(value)") + case .other: + return String(format: self._ServiceMessage_GameScoreExtended_other, "\(value)") + } + } + private let _ServiceMessage_GameScoreSelfSimple_zero: String + private let _ServiceMessage_GameScoreSelfSimple_one: String + private let _ServiceMessage_GameScoreSelfSimple_two: String + private let _ServiceMessage_GameScoreSelfSimple_few: String + private let _ServiceMessage_GameScoreSelfSimple_many: String + private let _ServiceMessage_GameScoreSelfSimple_other: String + public func ServiceMessage_GameScoreSelfSimple(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ServiceMessage_GameScoreSelfSimple_zero, "\(value)") + case .one: + return String(format: self._ServiceMessage_GameScoreSelfSimple_one, "\(value)") + case .two: + return String(format: self._ServiceMessage_GameScoreSelfSimple_two, "\(value)") + case .few: + return String(format: self._ServiceMessage_GameScoreSelfSimple_few, "\(value)") + case .many: + return String(format: self._ServiceMessage_GameScoreSelfSimple_many, "\(value)") + case .other: + return String(format: self._ServiceMessage_GameScoreSelfSimple_other, "\(value)") + } + } + private let _LiveLocation_MenuChatsCount_zero: String + private let _LiveLocation_MenuChatsCount_one: String + private let _LiveLocation_MenuChatsCount_two: String + private let _LiveLocation_MenuChatsCount_few: String + private let _LiveLocation_MenuChatsCount_many: String + private let _LiveLocation_MenuChatsCount_other: String + public func LiveLocation_MenuChatsCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._LiveLocation_MenuChatsCount_zero, "\(value)") + case .one: + return String(format: self._LiveLocation_MenuChatsCount_one, "\(value)") + case .two: + return String(format: self._LiveLocation_MenuChatsCount_two, "\(value)") + case .few: + return String(format: self._LiveLocation_MenuChatsCount_few, "\(value)") + case .many: + return String(format: self._LiveLocation_MenuChatsCount_many, "\(value)") + case .other: + return String(format: self._LiveLocation_MenuChatsCount_other, "\(value)") + } + } + private let _MessageTimer_ShortSeconds_zero: String + private let _MessageTimer_ShortSeconds_one: String + private let _MessageTimer_ShortSeconds_two: String + private let _MessageTimer_ShortSeconds_few: String + private let _MessageTimer_ShortSeconds_many: String + private let _MessageTimer_ShortSeconds_other: String + public func MessageTimer_ShortSeconds(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MessageTimer_ShortSeconds_zero, "\(value)") + case .one: + return String(format: self._MessageTimer_ShortSeconds_one, "\(value)") + case .two: + return String(format: self._MessageTimer_ShortSeconds_two, "\(value)") + case .few: + return String(format: self._MessageTimer_ShortSeconds_few, "\(value)") + case .many: + return String(format: self._MessageTimer_ShortSeconds_many, "\(value)") + case .other: + return String(format: self._MessageTimer_ShortSeconds_other, "\(value)") + } + } + private let _ForwardedGifs_zero: String + private let _ForwardedGifs_one: String + private let _ForwardedGifs_two: String + private let _ForwardedGifs_few: String + private let _ForwardedGifs_many: String + private let _ForwardedGifs_other: String + public func ForwardedGifs(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ForwardedGifs_zero, "\(value)") + case .one: + return String(format: self._ForwardedGifs_one, "\(value)") + case .two: + return String(format: self._ForwardedGifs_two, "\(value)") + case .few: + return String(format: self._ForwardedGifs_few, "\(value)") + case .many: + return String(format: self._ForwardedGifs_many, "\(value)") + case .other: + return String(format: self._ForwardedGifs_other, "\(value)") + } + } + private let _MuteFor_Hours_zero: String + private let _MuteFor_Hours_one: String + private let _MuteFor_Hours_two: String + private let _MuteFor_Hours_few: String + private let _MuteFor_Hours_many: String + private let _MuteFor_Hours_other: String + public func MuteFor_Hours(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MuteFor_Hours_zero, "\(value)") + case .one: + return String(format: self._MuteFor_Hours_one, "\(value)") + case .two: + return String(format: self._MuteFor_Hours_two, "\(value)") + case .few: + return String(format: self._MuteFor_Hours_few, "\(value)") + case .many: + return String(format: self._MuteFor_Hours_many, "\(value)") + case .other: + return String(format: self._MuteFor_Hours_other, "\(value)") + } + } + private let _StickerPack_StickerCount_zero: String + private let _StickerPack_StickerCount_one: String + private let _StickerPack_StickerCount_two: String + private let _StickerPack_StickerCount_few: String + private let _StickerPack_StickerCount_many: String + private let _StickerPack_StickerCount_other: String + public func StickerPack_StickerCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._StickerPack_StickerCount_zero, "\(value)") + case .one: + return String(format: self._StickerPack_StickerCount_one, "\(value)") + case .two: + return String(format: self._StickerPack_StickerCount_two, "\(value)") + case .few: + return String(format: self._StickerPack_StickerCount_few, "\(value)") + case .many: + return String(format: self._StickerPack_StickerCount_many, "\(value)") + case .other: + return String(format: self._StickerPack_StickerCount_other, "\(value)") + } + } + private let _Passport_Scans_zero: String + private let _Passport_Scans_one: String + private let _Passport_Scans_two: String + private let _Passport_Scans_few: String + private let _Passport_Scans_many: String + private let _Passport_Scans_other: String + public func Passport_Scans(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Passport_Scans_zero, "\(value)") + case .one: + return String(format: self._Passport_Scans_one, "\(value)") + case .two: + return String(format: self._Passport_Scans_two, "\(value)") + case .few: + return String(format: self._Passport_Scans_few, "\(value)") + case .many: + return String(format: self._Passport_Scans_many, "\(value)") + case .other: + return String(format: self._Passport_Scans_other, "\(value)") + } + } + private let _Notification_GameScoreSelfSimple_zero: String + private let _Notification_GameScoreSelfSimple_one: String + private let _Notification_GameScoreSelfSimple_two: String + private let _Notification_GameScoreSelfSimple_few: String + private let _Notification_GameScoreSelfSimple_many: String + private let _Notification_GameScoreSelfSimple_other: String + public func Notification_GameScoreSelfSimple(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Notification_GameScoreSelfSimple_zero, "\(value)") + case .one: + return String(format: self._Notification_GameScoreSelfSimple_one, "\(value)") + case .two: + return String(format: self._Notification_GameScoreSelfSimple_two, "\(value)") + case .few: + return String(format: self._Notification_GameScoreSelfSimple_few, "\(value)") + case .many: + return String(format: self._Notification_GameScoreSelfSimple_many, "\(value)") + case .other: + return String(format: self._Notification_GameScoreSelfSimple_other, "\(value)") + } + } + private let _ForwardedVideos_zero: String + private let _ForwardedVideos_one: String + private let _ForwardedVideos_two: String + private let _ForwardedVideos_few: String + private let _ForwardedVideos_many: String + private let _ForwardedVideos_other: String + public func ForwardedVideos(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ForwardedVideos_zero, "\(value)") + case .one: + return String(format: self._ForwardedVideos_one, "\(value)") + case .two: + return String(format: self._ForwardedVideos_two, "\(value)") + case .few: + return String(format: self._ForwardedVideos_few, "\(value)") + case .many: + return String(format: self._ForwardedVideos_many, "\(value)") + case .other: + return String(format: self._ForwardedVideos_other, "\(value)") } } private let _MessageTimer_ShortHours_zero: String @@ -3826,6 +4240,72 @@ public final class PresentationStrings { return String(format: self._ForwardedContacts_other, "\(value)") } } + private let _ForwardedLocations_zero: String + private let _ForwardedLocations_one: String + private let _ForwardedLocations_two: String + private let _ForwardedLocations_few: String + private let _ForwardedLocations_many: String + private let _ForwardedLocations_other: String + public func ForwardedLocations(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ForwardedLocations_zero, "\(value)") + case .one: + return String(format: self._ForwardedLocations_one, "\(value)") + case .two: + return String(format: self._ForwardedLocations_two, "\(value)") + case .few: + return String(format: self._ForwardedLocations_few, "\(value)") + case .many: + return String(format: self._ForwardedLocations_many, "\(value)") + case .other: + return String(format: self._ForwardedLocations_other, "\(value)") + } + } + private let _Notification_GameScoreSelfExtended_zero: String + private let _Notification_GameScoreSelfExtended_one: String + private let _Notification_GameScoreSelfExtended_two: String + private let _Notification_GameScoreSelfExtended_few: String + private let _Notification_GameScoreSelfExtended_many: String + private let _Notification_GameScoreSelfExtended_other: String + public func Notification_GameScoreSelfExtended(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Notification_GameScoreSelfExtended_zero, "\(value)") + case .one: + return String(format: self._Notification_GameScoreSelfExtended_one, "\(value)") + case .two: + return String(format: self._Notification_GameScoreSelfExtended_two, "\(value)") + case .few: + return String(format: self._Notification_GameScoreSelfExtended_few, "\(value)") + case .many: + return String(format: self._Notification_GameScoreSelfExtended_many, "\(value)") + case .other: + return String(format: self._Notification_GameScoreSelfExtended_other, "\(value)") + } + } + private let _UserCount_zero: String + private let _UserCount_one: String + private let _UserCount_two: String + private let _UserCount_few: String + private let _UserCount_many: String + private let _UserCount_other: String + public func UserCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._UserCount_zero, "\(value)") + case .one: + return String(format: self._UserCount_one, "\(value)") + case .two: + return String(format: self._UserCount_two, "\(value)") + case .few: + return String(format: self._UserCount_few, "\(value)") + case .many: + return String(format: self._UserCount_many, "\(value)") + case .other: + return String(format: self._UserCount_other, "\(value)") + } + } private let _SharedMedia_Generic_zero: String private let _SharedMedia_Generic_one: String private let _SharedMedia_Generic_two: String @@ -3848,6 +4328,248 @@ public final class PresentationStrings { return String(format: self._SharedMedia_Generic_other, "\(value)") } } + private let _Call_Minutes_zero: String + private let _Call_Minutes_one: String + private let _Call_Minutes_two: String + private let _Call_Minutes_few: String + private let _Call_Minutes_many: String + private let _Call_Minutes_other: String + public func Call_Minutes(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Call_Minutes_zero, "\(value)") + case .one: + return String(format: self._Call_Minutes_one, "\(value)") + case .two: + return String(format: self._Call_Minutes_two, "\(value)") + case .few: + return String(format: self._Call_Minutes_few, "\(value)") + case .many: + return String(format: self._Call_Minutes_many, "\(value)") + case .other: + return String(format: self._Call_Minutes_other, "\(value)") + } + } + private let _MessageTimer_ShortMinutes_zero: String + private let _MessageTimer_ShortMinutes_one: String + private let _MessageTimer_ShortMinutes_two: String + private let _MessageTimer_ShortMinutes_few: String + private let _MessageTimer_ShortMinutes_many: String + private let _MessageTimer_ShortMinutes_other: String + public func MessageTimer_ShortMinutes(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MessageTimer_ShortMinutes_zero, "\(value)") + case .one: + return String(format: self._MessageTimer_ShortMinutes_one, "\(value)") + case .two: + return String(format: self._MessageTimer_ShortMinutes_two, "\(value)") + case .few: + return String(format: self._MessageTimer_ShortMinutes_few, "\(value)") + case .many: + return String(format: self._MessageTimer_ShortMinutes_many, "\(value)") + case .other: + return String(format: self._MessageTimer_ShortMinutes_other, "\(value)") + } + } + private let _AttachmentMenu_SendVideo_zero: String + private let _AttachmentMenu_SendVideo_one: String + private let _AttachmentMenu_SendVideo_two: String + private let _AttachmentMenu_SendVideo_few: String + private let _AttachmentMenu_SendVideo_many: String + private let _AttachmentMenu_SendVideo_other: String + public func AttachmentMenu_SendVideo(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._AttachmentMenu_SendVideo_zero, "\(value)") + case .one: + return String(format: self._AttachmentMenu_SendVideo_one, "\(value)") + case .two: + return String(format: self._AttachmentMenu_SendVideo_two, "\(value)") + case .few: + return String(format: self._AttachmentMenu_SendVideo_few, "\(value)") + case .many: + return String(format: self._AttachmentMenu_SendVideo_many, "\(value)") + case .other: + return String(format: self._AttachmentMenu_SendVideo_other, "\(value)") + } + } + private let _ForwardedMessages_zero: String + private let _ForwardedMessages_one: String + private let _ForwardedMessages_two: String + private let _ForwardedMessages_few: String + private let _ForwardedMessages_many: String + private let _ForwardedMessages_other: String + public func ForwardedMessages(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ForwardedMessages_zero, "\(value)") + case .one: + return String(format: self._ForwardedMessages_one, "\(value)") + case .two: + return String(format: self._ForwardedMessages_two, "\(value)") + case .few: + return String(format: self._ForwardedMessages_few, "\(value)") + case .many: + return String(format: self._ForwardedMessages_many, "\(value)") + case .other: + return String(format: self._ForwardedMessages_other, "\(value)") + } + } + private let _Conversation_StatusSubscribers_zero: String + private let _Conversation_StatusSubscribers_one: String + private let _Conversation_StatusSubscribers_two: String + private let _Conversation_StatusSubscribers_few: String + private let _Conversation_StatusSubscribers_many: String + private let _Conversation_StatusSubscribers_other: String + public func Conversation_StatusSubscribers(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Conversation_StatusSubscribers_zero, "\(value)") + case .one: + return String(format: self._Conversation_StatusSubscribers_one, "\(value)") + case .two: + return String(format: self._Conversation_StatusSubscribers_two, "\(value)") + case .few: + return String(format: self._Conversation_StatusSubscribers_few, "\(value)") + case .many: + return String(format: self._Conversation_StatusSubscribers_many, "\(value)") + case .other: + return String(format: self._Conversation_StatusSubscribers_other, "\(value)") + } + } + private let _SharedMedia_Photo_zero: String + private let _SharedMedia_Photo_one: String + private let _SharedMedia_Photo_two: String + private let _SharedMedia_Photo_few: String + private let _SharedMedia_Photo_many: String + private let _SharedMedia_Photo_other: String + public func SharedMedia_Photo(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._SharedMedia_Photo_zero, "\(value)") + case .one: + return String(format: self._SharedMedia_Photo_one, "\(value)") + case .two: + return String(format: self._SharedMedia_Photo_two, "\(value)") + case .few: + return String(format: self._SharedMedia_Photo_few, "\(value)") + case .many: + return String(format: self._SharedMedia_Photo_many, "\(value)") + case .other: + return String(format: self._SharedMedia_Photo_other, "\(value)") + } + } + private let _GroupInfo_ParticipantCount_zero: String + private let _GroupInfo_ParticipantCount_one: String + private let _GroupInfo_ParticipantCount_two: String + private let _GroupInfo_ParticipantCount_few: String + private let _GroupInfo_ParticipantCount_many: String + private let _GroupInfo_ParticipantCount_other: String + public func GroupInfo_ParticipantCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._GroupInfo_ParticipantCount_zero, "\(value)") + case .one: + return String(format: self._GroupInfo_ParticipantCount_one, "\(value)") + case .two: + return String(format: self._GroupInfo_ParticipantCount_two, "\(value)") + case .few: + return String(format: self._GroupInfo_ParticipantCount_few, "\(value)") + case .many: + return String(format: self._GroupInfo_ParticipantCount_many, "\(value)") + case .other: + return String(format: self._GroupInfo_ParticipantCount_other, "\(value)") + } + } + private let _Watch_UserInfo_Mute_zero: String + private let _Watch_UserInfo_Mute_one: String + private let _Watch_UserInfo_Mute_two: String + private let _Watch_UserInfo_Mute_few: String + private let _Watch_UserInfo_Mute_many: String + private let _Watch_UserInfo_Mute_other: String + public func Watch_UserInfo_Mute(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Watch_UserInfo_Mute_zero, "\(value)") + case .one: + return String(format: self._Watch_UserInfo_Mute_one, "\(value)") + case .two: + return String(format: self._Watch_UserInfo_Mute_two, "\(value)") + case .few: + return String(format: self._Watch_UserInfo_Mute_few, "\(value)") + case .many: + return String(format: self._Watch_UserInfo_Mute_many, "\(value)") + case .other: + return String(format: self._Watch_UserInfo_Mute_other, "\(value)") + } + } + private let _Forward_ConfirmMultipleFiles_zero: String + private let _Forward_ConfirmMultipleFiles_one: String + private let _Forward_ConfirmMultipleFiles_two: String + private let _Forward_ConfirmMultipleFiles_few: String + private let _Forward_ConfirmMultipleFiles_many: String + private let _Forward_ConfirmMultipleFiles_other: String + public func Forward_ConfirmMultipleFiles(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Forward_ConfirmMultipleFiles_zero, "\(value)") + case .one: + return String(format: self._Forward_ConfirmMultipleFiles_one, "\(value)") + case .two: + return String(format: self._Forward_ConfirmMultipleFiles_two, "\(value)") + case .few: + return String(format: self._Forward_ConfirmMultipleFiles_few, "\(value)") + case .many: + return String(format: self._Forward_ConfirmMultipleFiles_many, "\(value)") + case .other: + return String(format: self._Forward_ConfirmMultipleFiles_other, "\(value)") + } + } + private let _SharedMedia_Video_zero: String + private let _SharedMedia_Video_one: String + private let _SharedMedia_Video_two: String + private let _SharedMedia_Video_few: String + private let _SharedMedia_Video_many: String + private let _SharedMedia_Video_other: String + public func SharedMedia_Video(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._SharedMedia_Video_zero, "\(value)") + case .one: + return String(format: self._SharedMedia_Video_one, "\(value)") + case .two: + return String(format: self._SharedMedia_Video_two, "\(value)") + case .few: + return String(format: self._SharedMedia_Video_few, "\(value)") + case .many: + return String(format: self._SharedMedia_Video_many, "\(value)") + case .other: + return String(format: self._SharedMedia_Video_other, "\(value)") + } + } + private let _Call_ShortSeconds_zero: String + private let _Call_ShortSeconds_one: String + private let _Call_ShortSeconds_two: String + private let _Call_ShortSeconds_few: String + private let _Call_ShortSeconds_many: String + private let _Call_ShortSeconds_other: String + public func Call_ShortSeconds(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Call_ShortSeconds_zero, "\(value)") + case .one: + return String(format: self._Call_ShortSeconds_one, "\(value)") + case .two: + return String(format: self._Call_ShortSeconds_two, "\(value)") + case .few: + return String(format: self._Call_ShortSeconds_few, "\(value)") + case .many: + return String(format: self._Call_ShortSeconds_many, "\(value)") + case .other: + return String(format: self._Call_ShortSeconds_other, "\(value)") + } + } private let _LiveLocationUpdated_MinutesAgo_zero: String private let _LiveLocationUpdated_MinutesAgo_one: String private let _LiveLocationUpdated_MinutesAgo_two: String @@ -3870,48 +4592,158 @@ public final class PresentationStrings { return String(format: self._LiveLocationUpdated_MinutesAgo_other, "\(value)") } } - private let _Notification_GameScoreSelfSimple_zero: String - private let _Notification_GameScoreSelfSimple_one: String - private let _Notification_GameScoreSelfSimple_two: String - private let _Notification_GameScoreSelfSimple_few: String - private let _Notification_GameScoreSelfSimple_many: String - private let _Notification_GameScoreSelfSimple_other: String - public func Notification_GameScoreSelfSimple(_ value: Int32) -> String { + private let _MuteExpires_Hours_zero: String + private let _MuteExpires_Hours_one: String + private let _MuteExpires_Hours_two: String + private let _MuteExpires_Hours_few: String + private let _MuteExpires_Hours_many: String + private let _MuteExpires_Hours_other: String + public func MuteExpires_Hours(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._Notification_GameScoreSelfSimple_zero, "\(value)") + return String(format: self._MuteExpires_Hours_zero, "\(value)") case .one: - return String(format: self._Notification_GameScoreSelfSimple_one, "\(value)") + return String(format: self._MuteExpires_Hours_one, "\(value)") case .two: - return String(format: self._Notification_GameScoreSelfSimple_two, "\(value)") + return String(format: self._MuteExpires_Hours_two, "\(value)") case .few: - return String(format: self._Notification_GameScoreSelfSimple_few, "\(value)") + return String(format: self._MuteExpires_Hours_few, "\(value)") case .many: - return String(format: self._Notification_GameScoreSelfSimple_many, "\(value)") + return String(format: self._MuteExpires_Hours_many, "\(value)") case .other: - return String(format: self._Notification_GameScoreSelfSimple_other, "\(value)") + return String(format: self._MuteExpires_Hours_other, "\(value)") } } - private let _ServiceMessage_GameScoreSimple_zero: String - private let _ServiceMessage_GameScoreSimple_one: String - private let _ServiceMessage_GameScoreSimple_two: String - private let _ServiceMessage_GameScoreSimple_few: String - private let _ServiceMessage_GameScoreSimple_many: String - private let _ServiceMessage_GameScoreSimple_other: String - public func ServiceMessage_GameScoreSimple(_ value: Int32) -> String { + private let _SharedMedia_DeleteItemsConfirmation_zero: String + private let _SharedMedia_DeleteItemsConfirmation_one: String + private let _SharedMedia_DeleteItemsConfirmation_two: String + private let _SharedMedia_DeleteItemsConfirmation_few: String + private let _SharedMedia_DeleteItemsConfirmation_many: String + private let _SharedMedia_DeleteItemsConfirmation_other: String + public func SharedMedia_DeleteItemsConfirmation(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._ServiceMessage_GameScoreSimple_zero, "\(value)") + return String(format: self._SharedMedia_DeleteItemsConfirmation_zero, "\(value)") case .one: - return String(format: self._ServiceMessage_GameScoreSimple_one, "\(value)") + return String(format: self._SharedMedia_DeleteItemsConfirmation_one, "\(value)") case .two: - return String(format: self._ServiceMessage_GameScoreSimple_two, "\(value)") + return String(format: self._SharedMedia_DeleteItemsConfirmation_two, "\(value)") case .few: - return String(format: self._ServiceMessage_GameScoreSimple_few, "\(value)") + return String(format: self._SharedMedia_DeleteItemsConfirmation_few, "\(value)") case .many: - return String(format: self._ServiceMessage_GameScoreSimple_many, "\(value)") + return String(format: self._SharedMedia_DeleteItemsConfirmation_many, "\(value)") case .other: - return String(format: self._ServiceMessage_GameScoreSimple_other, "\(value)") + return String(format: self._SharedMedia_DeleteItemsConfirmation_other, "\(value)") + } + } + private let _Conversation_StatusMembers_zero: String + private let _Conversation_StatusMembers_one: String + private let _Conversation_StatusMembers_two: String + private let _Conversation_StatusMembers_few: String + private let _Conversation_StatusMembers_many: String + private let _Conversation_StatusMembers_other: String + public func Conversation_StatusMembers(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Conversation_StatusMembers_zero, "\(value)") + case .one: + return String(format: self._Conversation_StatusMembers_one, "\(value)") + case .two: + return String(format: self._Conversation_StatusMembers_two, "\(value)") + case .few: + return String(format: self._Conversation_StatusMembers_few, "\(value)") + case .many: + return String(format: self._Conversation_StatusMembers_many, "\(value)") + case .other: + return String(format: self._Conversation_StatusMembers_other, "\(value)") + } + } + private let _MessageTimer_Seconds_zero: String + private let _MessageTimer_Seconds_one: String + private let _MessageTimer_Seconds_two: String + private let _MessageTimer_Seconds_few: String + private let _MessageTimer_Seconds_many: String + private let _MessageTimer_Seconds_other: String + public func MessageTimer_Seconds(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MessageTimer_Seconds_zero, "\(value)") + case .one: + return String(format: self._MessageTimer_Seconds_one, "\(value)") + case .two: + return String(format: self._MessageTimer_Seconds_two, "\(value)") + case .few: + return String(format: self._MessageTimer_Seconds_few, "\(value)") + case .many: + return String(format: self._MessageTimer_Seconds_many, "\(value)") + case .other: + return String(format: self._MessageTimer_Seconds_other, "\(value)") + } + } + private let _Watch_LastSeen_MinutesAgo_zero: String + private let _Watch_LastSeen_MinutesAgo_one: String + private let _Watch_LastSeen_MinutesAgo_two: String + private let _Watch_LastSeen_MinutesAgo_few: String + private let _Watch_LastSeen_MinutesAgo_many: String + private let _Watch_LastSeen_MinutesAgo_other: String + public func Watch_LastSeen_MinutesAgo(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Watch_LastSeen_MinutesAgo_zero, "\(value)") + case .one: + return String(format: self._Watch_LastSeen_MinutesAgo_one, "\(value)") + case .two: + return String(format: self._Watch_LastSeen_MinutesAgo_two, "\(value)") + case .few: + return String(format: self._Watch_LastSeen_MinutesAgo_few, "\(value)") + case .many: + return String(format: self._Watch_LastSeen_MinutesAgo_many, "\(value)") + case .other: + return String(format: self._Watch_LastSeen_MinutesAgo_other, "\(value)") + } + } + private let _LastSeen_HoursAgo_zero: String + private let _LastSeen_HoursAgo_one: String + private let _LastSeen_HoursAgo_two: String + private let _LastSeen_HoursAgo_few: String + private let _LastSeen_HoursAgo_many: String + private let _LastSeen_HoursAgo_other: String + public func LastSeen_HoursAgo(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._LastSeen_HoursAgo_zero, "\(value)") + case .one: + return String(format: self._LastSeen_HoursAgo_one, "\(value)") + case .two: + return String(format: self._LastSeen_HoursAgo_two, "\(value)") + case .few: + return String(format: self._LastSeen_HoursAgo_few, "\(value)") + case .many: + return String(format: self._LastSeen_HoursAgo_many, "\(value)") + case .other: + return String(format: self._LastSeen_HoursAgo_other, "\(value)") + } + } + private let _Media_ShareItem_zero: String + private let _Media_ShareItem_one: String + private let _Media_ShareItem_two: String + private let _Media_ShareItem_few: String + private let _Media_ShareItem_many: String + private let _Media_ShareItem_other: String + public func Media_ShareItem(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Media_ShareItem_zero, "\(value)") + case .one: + return String(format: self._Media_ShareItem_one, "\(value)") + case .two: + return String(format: self._Media_ShareItem_two, "\(value)") + case .few: + return String(format: self._Media_ShareItem_few, "\(value)") + case .many: + return String(format: self._Media_ShareItem_many, "\(value)") + case .other: + return String(format: self._Media_ShareItem_other, "\(value)") } } private let _QuickSend_Photos_zero: String @@ -3980,334 +4812,48 @@ public final class PresentationStrings { return String(format: self._MuteExpires_Minutes_other, "\(value)") } } - private let _Conversation_LiveLocationMembersCount_zero: String - private let _Conversation_LiveLocationMembersCount_one: String - private let _Conversation_LiveLocationMembersCount_two: String - private let _Conversation_LiveLocationMembersCount_few: String - private let _Conversation_LiveLocationMembersCount_many: String - private let _Conversation_LiveLocationMembersCount_other: String - public func Conversation_LiveLocationMembersCount(_ value: Int32) -> String { + private let _Notification_GameScoreSimple_zero: String + private let _Notification_GameScoreSimple_one: String + private let _Notification_GameScoreSimple_two: String + private let _Notification_GameScoreSimple_few: String + private let _Notification_GameScoreSimple_many: String + private let _Notification_GameScoreSimple_other: String + public func Notification_GameScoreSimple(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._Conversation_LiveLocationMembersCount_zero, "\(value)") + return String(format: self._Notification_GameScoreSimple_zero, "\(value)") case .one: - return String(format: self._Conversation_LiveLocationMembersCount_one, "\(value)") + return String(format: self._Notification_GameScoreSimple_one, "\(value)") case .two: - return String(format: self._Conversation_LiveLocationMembersCount_two, "\(value)") + return String(format: self._Notification_GameScoreSimple_two, "\(value)") case .few: - return String(format: self._Conversation_LiveLocationMembersCount_few, "\(value)") + return String(format: self._Notification_GameScoreSimple_few, "\(value)") case .many: - return String(format: self._Conversation_LiveLocationMembersCount_many, "\(value)") + return String(format: self._Notification_GameScoreSimple_many, "\(value)") case .other: - return String(format: self._Conversation_LiveLocationMembersCount_other, "\(value)") + return String(format: self._Notification_GameScoreSimple_other, "\(value)") } } - private let _MessageTimer_Days_zero: String - private let _MessageTimer_Days_one: String - private let _MessageTimer_Days_two: String - private let _MessageTimer_Days_few: String - private let _MessageTimer_Days_many: String - private let _MessageTimer_Days_other: String - public func MessageTimer_Days(_ value: Int32) -> String { + private let _AttachmentMenu_SendItem_zero: String + private let _AttachmentMenu_SendItem_one: String + private let _AttachmentMenu_SendItem_two: String + private let _AttachmentMenu_SendItem_few: String + private let _AttachmentMenu_SendItem_many: String + private let _AttachmentMenu_SendItem_other: String + public func AttachmentMenu_SendItem(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._MessageTimer_Days_zero, "\(value)") + return String(format: self._AttachmentMenu_SendItem_zero, "\(value)") case .one: - return String(format: self._MessageTimer_Days_one, "\(value)") + return String(format: self._AttachmentMenu_SendItem_one, "\(value)") case .two: - return String(format: self._MessageTimer_Days_two, "\(value)") + return String(format: self._AttachmentMenu_SendItem_two, "\(value)") case .few: - return String(format: self._MessageTimer_Days_few, "\(value)") + return String(format: self._AttachmentMenu_SendItem_few, "\(value)") case .many: - return String(format: self._MessageTimer_Days_many, "\(value)") + return String(format: self._AttachmentMenu_SendItem_many, "\(value)") case .other: - return String(format: self._MessageTimer_Days_other, "\(value)") - } - } - private let _MessageTimer_ShortSeconds_zero: String - private let _MessageTimer_ShortSeconds_one: String - private let _MessageTimer_ShortSeconds_two: String - private let _MessageTimer_ShortSeconds_few: String - private let _MessageTimer_ShortSeconds_many: String - private let _MessageTimer_ShortSeconds_other: String - public func MessageTimer_ShortSeconds(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MessageTimer_ShortSeconds_zero, "\(value)") - case .one: - return String(format: self._MessageTimer_ShortSeconds_one, "\(value)") - case .two: - return String(format: self._MessageTimer_ShortSeconds_two, "\(value)") - case .few: - return String(format: self._MessageTimer_ShortSeconds_few, "\(value)") - case .many: - return String(format: self._MessageTimer_ShortSeconds_many, "\(value)") - case .other: - return String(format: self._MessageTimer_ShortSeconds_other, "\(value)") - } - } - private let _Conversation_StatusOnline_zero: String - private let _Conversation_StatusOnline_one: String - private let _Conversation_StatusOnline_two: String - private let _Conversation_StatusOnline_few: String - private let _Conversation_StatusOnline_many: String - private let _Conversation_StatusOnline_other: String - public func Conversation_StatusOnline(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Conversation_StatusOnline_zero, "\(value)") - case .one: - return String(format: self._Conversation_StatusOnline_one, "\(value)") - case .two: - return String(format: self._Conversation_StatusOnline_two, "\(value)") - case .few: - return String(format: self._Conversation_StatusOnline_few, "\(value)") - case .many: - return String(format: self._Conversation_StatusOnline_many, "\(value)") - case .other: - return String(format: self._Conversation_StatusOnline_other, "\(value)") - } - } - private let _ForwardedMessages_zero: String - private let _ForwardedMessages_one: String - private let _ForwardedMessages_two: String - private let _ForwardedMessages_few: String - private let _ForwardedMessages_many: String - private let _ForwardedMessages_other: String - public func ForwardedMessages(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ForwardedMessages_zero, "\(value)") - case .one: - return String(format: self._ForwardedMessages_one, "\(value)") - case .two: - return String(format: self._ForwardedMessages_two, "\(value)") - case .few: - return String(format: self._ForwardedMessages_few, "\(value)") - case .many: - return String(format: self._ForwardedMessages_many, "\(value)") - case .other: - return String(format: self._ForwardedMessages_other, "\(value)") - } - } - private let _MessageTimer_Seconds_zero: String - private let _MessageTimer_Seconds_one: String - private let _MessageTimer_Seconds_two: String - private let _MessageTimer_Seconds_few: String - private let _MessageTimer_Seconds_many: String - private let _MessageTimer_Seconds_other: String - public func MessageTimer_Seconds(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MessageTimer_Seconds_zero, "\(value)") - case .one: - return String(format: self._MessageTimer_Seconds_one, "\(value)") - case .two: - return String(format: self._MessageTimer_Seconds_two, "\(value)") - case .few: - return String(format: self._MessageTimer_Seconds_few, "\(value)") - case .many: - return String(format: self._MessageTimer_Seconds_many, "\(value)") - case .other: - return String(format: self._MessageTimer_Seconds_other, "\(value)") - } - } - private let _SharedMedia_Link_zero: String - private let _SharedMedia_Link_one: String - private let _SharedMedia_Link_two: String - private let _SharedMedia_Link_few: String - private let _SharedMedia_Link_many: String - private let _SharedMedia_Link_other: String - public func SharedMedia_Link(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._SharedMedia_Link_zero, "\(value)") - case .one: - return String(format: self._SharedMedia_Link_one, "\(value)") - case .two: - return String(format: self._SharedMedia_Link_two, "\(value)") - case .few: - return String(format: self._SharedMedia_Link_few, "\(value)") - case .many: - return String(format: self._SharedMedia_Link_many, "\(value)") - case .other: - return String(format: self._SharedMedia_Link_other, "\(value)") - } - } - private let _Invitation_Members_zero: String - private let _Invitation_Members_one: String - private let _Invitation_Members_two: String - private let _Invitation_Members_few: String - private let _Invitation_Members_many: String - private let _Invitation_Members_other: String - public func Invitation_Members(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Invitation_Members_zero, "\(value)") - case .one: - return String(format: self._Invitation_Members_one, "\(value)") - case .two: - return String(format: self._Invitation_Members_two, "\(value)") - case .few: - return String(format: self._Invitation_Members_few, "\(value)") - case .many: - return String(format: self._Invitation_Members_many, "\(value)") - case .other: - return String(format: self._Invitation_Members_other, "\(value)") - } - } - private let _Contacts_ImportersCount_zero: String - private let _Contacts_ImportersCount_one: String - private let _Contacts_ImportersCount_two: String - private let _Contacts_ImportersCount_few: String - private let _Contacts_ImportersCount_many: String - private let _Contacts_ImportersCount_other: String - public func Contacts_ImportersCount(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Contacts_ImportersCount_zero, "\(value)") - case .one: - return String(format: self._Contacts_ImportersCount_one, "\(value)") - case .two: - return String(format: self._Contacts_ImportersCount_two, "\(value)") - case .few: - return String(format: self._Contacts_ImportersCount_few, "\(value)") - case .many: - return String(format: self._Contacts_ImportersCount_many, "\(value)") - case .other: - return String(format: self._Contacts_ImportersCount_other, "\(value)") - } - } - private let _ForwardedFiles_zero: String - private let _ForwardedFiles_one: String - private let _ForwardedFiles_two: String - private let _ForwardedFiles_few: String - private let _ForwardedFiles_many: String - private let _ForwardedFiles_other: String - public func ForwardedFiles(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ForwardedFiles_zero, "\(value)") - case .one: - return String(format: self._ForwardedFiles_one, "\(value)") - case .two: - return String(format: self._ForwardedFiles_two, "\(value)") - case .few: - return String(format: self._ForwardedFiles_few, "\(value)") - case .many: - return String(format: self._ForwardedFiles_many, "\(value)") - case .other: - return String(format: self._ForwardedFiles_other, "\(value)") - } - } - private let _Watch_UserInfo_Mute_zero: String - private let _Watch_UserInfo_Mute_one: String - private let _Watch_UserInfo_Mute_two: String - private let _Watch_UserInfo_Mute_few: String - private let _Watch_UserInfo_Mute_many: String - private let _Watch_UserInfo_Mute_other: String - public func Watch_UserInfo_Mute(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Watch_UserInfo_Mute_zero, "\(value)") - case .one: - return String(format: self._Watch_UserInfo_Mute_one, "\(value)") - case .two: - return String(format: self._Watch_UserInfo_Mute_two, "\(value)") - case .few: - return String(format: self._Watch_UserInfo_Mute_few, "\(value)") - case .many: - return String(format: self._Watch_UserInfo_Mute_many, "\(value)") - case .other: - return String(format: self._Watch_UserInfo_Mute_other, "\(value)") - } - } - private let _Watch_LastSeen_HoursAgo_zero: String - private let _Watch_LastSeen_HoursAgo_one: String - private let _Watch_LastSeen_HoursAgo_two: String - private let _Watch_LastSeen_HoursAgo_few: String - private let _Watch_LastSeen_HoursAgo_many: String - private let _Watch_LastSeen_HoursAgo_other: String - public func Watch_LastSeen_HoursAgo(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Watch_LastSeen_HoursAgo_zero, "\(value)") - case .one: - return String(format: self._Watch_LastSeen_HoursAgo_one, "\(value)") - case .two: - return String(format: self._Watch_LastSeen_HoursAgo_two, "\(value)") - case .few: - return String(format: self._Watch_LastSeen_HoursAgo_few, "\(value)") - case .many: - return String(format: self._Watch_LastSeen_HoursAgo_many, "\(value)") - case .other: - return String(format: self._Watch_LastSeen_HoursAgo_other, "\(value)") - } - } - private let _SharedMedia_Video_zero: String - private let _SharedMedia_Video_one: String - private let _SharedMedia_Video_two: String - private let _SharedMedia_Video_few: String - private let _SharedMedia_Video_many: String - private let _SharedMedia_Video_other: String - public func SharedMedia_Video(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._SharedMedia_Video_zero, "\(value)") - case .one: - return String(format: self._SharedMedia_Video_one, "\(value)") - case .two: - return String(format: self._SharedMedia_Video_two, "\(value)") - case .few: - return String(format: self._SharedMedia_Video_few, "\(value)") - case .many: - return String(format: self._SharedMedia_Video_many, "\(value)") - case .other: - return String(format: self._SharedMedia_Video_other, "\(value)") - } - } - private let _AttachmentMenu_SendVideo_zero: String - private let _AttachmentMenu_SendVideo_one: String - private let _AttachmentMenu_SendVideo_two: String - private let _AttachmentMenu_SendVideo_few: String - private let _AttachmentMenu_SendVideo_many: String - private let _AttachmentMenu_SendVideo_other: String - public func AttachmentMenu_SendVideo(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._AttachmentMenu_SendVideo_zero, "\(value)") - case .one: - return String(format: self._AttachmentMenu_SendVideo_one, "\(value)") - case .two: - return String(format: self._AttachmentMenu_SendVideo_two, "\(value)") - case .few: - return String(format: self._AttachmentMenu_SendVideo_few, "\(value)") - case .many: - return String(format: self._AttachmentMenu_SendVideo_many, "\(value)") - case .other: - return String(format: self._AttachmentMenu_SendVideo_other, "\(value)") - } - } - private let _ForwardedStickers_zero: String - private let _ForwardedStickers_one: String - private let _ForwardedStickers_two: String - private let _ForwardedStickers_few: String - private let _ForwardedStickers_many: String - private let _ForwardedStickers_other: String - public func ForwardedStickers(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ForwardedStickers_zero, "\(value)") - case .one: - return String(format: self._ForwardedStickers_one, "\(value)") - case .two: - return String(format: self._ForwardedStickers_two, "\(value)") - case .few: - return String(format: self._ForwardedStickers_few, "\(value)") - case .many: - return String(format: self._ForwardedStickers_many, "\(value)") - case .other: - return String(format: self._ForwardedStickers_other, "\(value)") + return String(format: self._AttachmentMenu_SendItem_other, "\(value)") } } private let _MessageTimer_Years_zero: String @@ -4332,510 +4878,136 @@ public final class PresentationStrings { return String(format: self._MessageTimer_Years_other, "\(value)") } } - private let _SharedMedia_DeleteItemsConfirmation_zero: String - private let _SharedMedia_DeleteItemsConfirmation_one: String - private let _SharedMedia_DeleteItemsConfirmation_two: String - private let _SharedMedia_DeleteItemsConfirmation_few: String - private let _SharedMedia_DeleteItemsConfirmation_many: String - private let _SharedMedia_DeleteItemsConfirmation_other: String - public func SharedMedia_DeleteItemsConfirmation(_ value: Int32) -> String { + private let _PrivacyLastSeenSettings_AddUsers_zero: String + private let _PrivacyLastSeenSettings_AddUsers_one: String + private let _PrivacyLastSeenSettings_AddUsers_two: String + private let _PrivacyLastSeenSettings_AddUsers_few: String + private let _PrivacyLastSeenSettings_AddUsers_many: String + private let _PrivacyLastSeenSettings_AddUsers_other: String + public func PrivacyLastSeenSettings_AddUsers(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._SharedMedia_DeleteItemsConfirmation_zero, "\(value)") + return String(format: self._PrivacyLastSeenSettings_AddUsers_zero, "\(value)") case .one: - return String(format: self._SharedMedia_DeleteItemsConfirmation_one, "\(value)") + return String(format: self._PrivacyLastSeenSettings_AddUsers_one, "\(value)") case .two: - return String(format: self._SharedMedia_DeleteItemsConfirmation_two, "\(value)") + return String(format: self._PrivacyLastSeenSettings_AddUsers_two, "\(value)") case .few: - return String(format: self._SharedMedia_DeleteItemsConfirmation_few, "\(value)") + return String(format: self._PrivacyLastSeenSettings_AddUsers_few, "\(value)") case .many: - return String(format: self._SharedMedia_DeleteItemsConfirmation_many, "\(value)") + return String(format: self._PrivacyLastSeenSettings_AddUsers_many, "\(value)") case .other: - return String(format: self._SharedMedia_DeleteItemsConfirmation_other, "\(value)") + return String(format: self._PrivacyLastSeenSettings_AddUsers_other, "\(value)") } } - private let _MessageTimer_ShortMinutes_zero: String - private let _MessageTimer_ShortMinutes_one: String - private let _MessageTimer_ShortMinutes_two: String - private let _MessageTimer_ShortMinutes_few: String - private let _MessageTimer_ShortMinutes_many: String - private let _MessageTimer_ShortMinutes_other: String - public func MessageTimer_ShortMinutes(_ value: Int32) -> String { + private let _AttachmentMenu_SendGif_zero: String + private let _AttachmentMenu_SendGif_one: String + private let _AttachmentMenu_SendGif_two: String + private let _AttachmentMenu_SendGif_few: String + private let _AttachmentMenu_SendGif_many: String + private let _AttachmentMenu_SendGif_other: String + public func AttachmentMenu_SendGif(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._MessageTimer_ShortMinutes_zero, "\(value)") + return String(format: self._AttachmentMenu_SendGif_zero, "\(value)") case .one: - return String(format: self._MessageTimer_ShortMinutes_one, "\(value)") + return String(format: self._AttachmentMenu_SendGif_one, "\(value)") case .two: - return String(format: self._MessageTimer_ShortMinutes_two, "\(value)") + return String(format: self._AttachmentMenu_SendGif_two, "\(value)") case .few: - return String(format: self._MessageTimer_ShortMinutes_few, "\(value)") + return String(format: self._AttachmentMenu_SendGif_few, "\(value)") case .many: - return String(format: self._MessageTimer_ShortMinutes_many, "\(value)") + return String(format: self._AttachmentMenu_SendGif_many, "\(value)") case .other: - return String(format: self._MessageTimer_ShortMinutes_other, "\(value)") + return String(format: self._AttachmentMenu_SendGif_other, "\(value)") } } - private let _AttachmentMenu_SendPhoto_zero: String - private let _AttachmentMenu_SendPhoto_one: String - private let _AttachmentMenu_SendPhoto_two: String - private let _AttachmentMenu_SendPhoto_few: String - private let _AttachmentMenu_SendPhoto_many: String - private let _AttachmentMenu_SendPhoto_other: String - public func AttachmentMenu_SendPhoto(_ value: Int32) -> String { + private let _ForwardedVideoMessages_zero: String + private let _ForwardedVideoMessages_one: String + private let _ForwardedVideoMessages_two: String + private let _ForwardedVideoMessages_few: String + private let _ForwardedVideoMessages_many: String + private let _ForwardedVideoMessages_other: String + public func ForwardedVideoMessages(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._AttachmentMenu_SendPhoto_zero, "\(value)") + return String(format: self._ForwardedVideoMessages_zero, "\(value)") case .one: - return String(format: self._AttachmentMenu_SendPhoto_one, "\(value)") + return String(format: self._ForwardedVideoMessages_one, "\(value)") case .two: - return String(format: self._AttachmentMenu_SendPhoto_two, "\(value)") + return String(format: self._ForwardedVideoMessages_two, "\(value)") case .few: - return String(format: self._AttachmentMenu_SendPhoto_few, "\(value)") + return String(format: self._ForwardedVideoMessages_few, "\(value)") case .many: - return String(format: self._AttachmentMenu_SendPhoto_many, "\(value)") + return String(format: self._ForwardedVideoMessages_many, "\(value)") case .other: - return String(format: self._AttachmentMenu_SendPhoto_other, "\(value)") + return String(format: self._ForwardedVideoMessages_other, "\(value)") } } - private let _GroupInfo_ParticipantCount_zero: String - private let _GroupInfo_ParticipantCount_one: String - private let _GroupInfo_ParticipantCount_two: String - private let _GroupInfo_ParticipantCount_few: String - private let _GroupInfo_ParticipantCount_many: String - private let _GroupInfo_ParticipantCount_other: String - public func GroupInfo_ParticipantCount(_ value: Int32) -> String { + private let _Media_ShareVideo_zero: String + private let _Media_ShareVideo_one: String + private let _Media_ShareVideo_two: String + private let _Media_ShareVideo_few: String + private let _Media_ShareVideo_many: String + private let _Media_ShareVideo_other: String + public func Media_ShareVideo(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._GroupInfo_ParticipantCount_zero, "\(value)") + return String(format: self._Media_ShareVideo_zero, "\(value)") case .one: - return String(format: self._GroupInfo_ParticipantCount_one, "\(value)") + return String(format: self._Media_ShareVideo_one, "\(value)") case .two: - return String(format: self._GroupInfo_ParticipantCount_two, "\(value)") + return String(format: self._Media_ShareVideo_two, "\(value)") case .few: - return String(format: self._GroupInfo_ParticipantCount_few, "\(value)") + return String(format: self._Media_ShareVideo_few, "\(value)") case .many: - return String(format: self._GroupInfo_ParticipantCount_many, "\(value)") + return String(format: self._Media_ShareVideo_many, "\(value)") case .other: - return String(format: self._GroupInfo_ParticipantCount_other, "\(value)") + return String(format: self._Media_ShareVideo_other, "\(value)") } } - private let _Call_ShortSeconds_zero: String - private let _Call_ShortSeconds_one: String - private let _Call_ShortSeconds_two: String - private let _Call_ShortSeconds_few: String - private let _Call_ShortSeconds_many: String - private let _Call_ShortSeconds_other: String - public func Call_ShortSeconds(_ value: Int32) -> String { + private let _StickerPack_RemoveStickerCount_zero: String + private let _StickerPack_RemoveStickerCount_one: String + private let _StickerPack_RemoveStickerCount_two: String + private let _StickerPack_RemoveStickerCount_few: String + private let _StickerPack_RemoveStickerCount_many: String + private let _StickerPack_RemoveStickerCount_other: String + public func StickerPack_RemoveStickerCount(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._Call_ShortSeconds_zero, "\(value)") + return String(format: self._StickerPack_RemoveStickerCount_zero, "\(value)") case .one: - return String(format: self._Call_ShortSeconds_one, "\(value)") + return String(format: self._StickerPack_RemoveStickerCount_one, "\(value)") case .two: - return String(format: self._Call_ShortSeconds_two, "\(value)") + return String(format: self._StickerPack_RemoveStickerCount_two, "\(value)") case .few: - return String(format: self._Call_ShortSeconds_few, "\(value)") + return String(format: self._StickerPack_RemoveStickerCount_few, "\(value)") case .many: - return String(format: self._Call_ShortSeconds_many, "\(value)") + return String(format: self._StickerPack_RemoveStickerCount_many, "\(value)") case .other: - return String(format: self._Call_ShortSeconds_other, "\(value)") + return String(format: self._StickerPack_RemoveStickerCount_other, "\(value)") } } - private let _Notification_GameScoreSimple_zero: String - private let _Notification_GameScoreSimple_one: String - private let _Notification_GameScoreSimple_two: String - private let _Notification_GameScoreSimple_few: String - private let _Notification_GameScoreSimple_many: String - private let _Notification_GameScoreSimple_other: String - public func Notification_GameScoreSimple(_ value: Int32) -> String { + private let _Call_Seconds_zero: String + private let _Call_Seconds_one: String + private let _Call_Seconds_two: String + private let _Call_Seconds_few: String + private let _Call_Seconds_many: String + private let _Call_Seconds_other: String + public func Call_Seconds(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._Notification_GameScoreSimple_zero, "\(value)") + return String(format: self._Call_Seconds_zero, "\(value)") case .one: - return String(format: self._Notification_GameScoreSimple_one, "\(value)") + return String(format: self._Call_Seconds_one, "\(value)") case .two: - return String(format: self._Notification_GameScoreSimple_two, "\(value)") + return String(format: self._Call_Seconds_two, "\(value)") case .few: - return String(format: self._Notification_GameScoreSimple_few, "\(value)") + return String(format: self._Call_Seconds_few, "\(value)") case .many: - return String(format: self._Notification_GameScoreSimple_many, "\(value)") + return String(format: self._Call_Seconds_many, "\(value)") case .other: - return String(format: self._Notification_GameScoreSimple_other, "\(value)") - } - } - private let _StickerPack_StickerCount_zero: String - private let _StickerPack_StickerCount_one: String - private let _StickerPack_StickerCount_two: String - private let _StickerPack_StickerCount_few: String - private let _StickerPack_StickerCount_many: String - private let _StickerPack_StickerCount_other: String - public func StickerPack_StickerCount(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._StickerPack_StickerCount_zero, "\(value)") - case .one: - return String(format: self._StickerPack_StickerCount_one, "\(value)") - case .two: - return String(format: self._StickerPack_StickerCount_two, "\(value)") - case .few: - return String(format: self._StickerPack_StickerCount_few, "\(value)") - case .many: - return String(format: self._StickerPack_StickerCount_many, "\(value)") - case .other: - return String(format: self._StickerPack_StickerCount_other, "\(value)") - } - } - private let _StickerPack_RemoveMaskCount_zero: String - private let _StickerPack_RemoveMaskCount_one: String - private let _StickerPack_RemoveMaskCount_two: String - private let _StickerPack_RemoveMaskCount_few: String - private let _StickerPack_RemoveMaskCount_many: String - private let _StickerPack_RemoveMaskCount_other: String - public func StickerPack_RemoveMaskCount(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._StickerPack_RemoveMaskCount_zero, "\(value)") - case .one: - return String(format: self._StickerPack_RemoveMaskCount_one, "\(value)") - case .two: - return String(format: self._StickerPack_RemoveMaskCount_two, "\(value)") - case .few: - return String(format: self._StickerPack_RemoveMaskCount_few, "\(value)") - case .many: - return String(format: self._StickerPack_RemoveMaskCount_many, "\(value)") - case .other: - return String(format: self._StickerPack_RemoveMaskCount_other, "\(value)") - } - } - private let _UserCount_zero: String - private let _UserCount_one: String - private let _UserCount_two: String - private let _UserCount_few: String - private let _UserCount_many: String - private let _UserCount_other: String - public func UserCount(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._UserCount_zero, "\(value)") - case .one: - return String(format: self._UserCount_one, "\(value)") - case .two: - return String(format: self._UserCount_two, "\(value)") - case .few: - return String(format: self._UserCount_few, "\(value)") - case .many: - return String(format: self._UserCount_many, "\(value)") - case .other: - return String(format: self._UserCount_other, "\(value)") - } - } - private let _SharedMedia_File_zero: String - private let _SharedMedia_File_one: String - private let _SharedMedia_File_two: String - private let _SharedMedia_File_few: String - private let _SharedMedia_File_many: String - private let _SharedMedia_File_other: String - public func SharedMedia_File(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._SharedMedia_File_zero, "\(value)") - case .one: - return String(format: self._SharedMedia_File_one, "\(value)") - case .two: - return String(format: self._SharedMedia_File_two, "\(value)") - case .few: - return String(format: self._SharedMedia_File_few, "\(value)") - case .many: - return String(format: self._SharedMedia_File_many, "\(value)") - case .other: - return String(format: self._SharedMedia_File_other, "\(value)") - } - } - private let _Call_ShortMinutes_zero: String - private let _Call_ShortMinutes_one: String - private let _Call_ShortMinutes_two: String - private let _Call_ShortMinutes_few: String - private let _Call_ShortMinutes_many: String - private let _Call_ShortMinutes_other: String - public func Call_ShortMinutes(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Call_ShortMinutes_zero, "\(value)") - case .one: - return String(format: self._Call_ShortMinutes_one, "\(value)") - case .two: - return String(format: self._Call_ShortMinutes_two, "\(value)") - case .few: - return String(format: self._Call_ShortMinutes_few, "\(value)") - case .many: - return String(format: self._Call_ShortMinutes_many, "\(value)") - case .other: - return String(format: self._Call_ShortMinutes_other, "\(value)") - } - } - private let _MuteExpires_Days_zero: String - private let _MuteExpires_Days_one: String - private let _MuteExpires_Days_two: String - private let _MuteExpires_Days_few: String - private let _MuteExpires_Days_many: String - private let _MuteExpires_Days_other: String - public func MuteExpires_Days(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MuteExpires_Days_zero, "\(value)") - case .one: - return String(format: self._MuteExpires_Days_one, "\(value)") - case .two: - return String(format: self._MuteExpires_Days_two, "\(value)") - case .few: - return String(format: self._MuteExpires_Days_few, "\(value)") - case .many: - return String(format: self._MuteExpires_Days_many, "\(value)") - case .other: - return String(format: self._MuteExpires_Days_other, "\(value)") - } - } - private let _Watch_LastSeen_MinutesAgo_zero: String - private let _Watch_LastSeen_MinutesAgo_one: String - private let _Watch_LastSeen_MinutesAgo_two: String - private let _Watch_LastSeen_MinutesAgo_few: String - private let _Watch_LastSeen_MinutesAgo_many: String - private let _Watch_LastSeen_MinutesAgo_other: String - public func Watch_LastSeen_MinutesAgo(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Watch_LastSeen_MinutesAgo_zero, "\(value)") - case .one: - return String(format: self._Watch_LastSeen_MinutesAgo_one, "\(value)") - case .two: - return String(format: self._Watch_LastSeen_MinutesAgo_two, "\(value)") - case .few: - return String(format: self._Watch_LastSeen_MinutesAgo_few, "\(value)") - case .many: - return String(format: self._Watch_LastSeen_MinutesAgo_many, "\(value)") - case .other: - return String(format: self._Watch_LastSeen_MinutesAgo_other, "\(value)") - } - } - private let _LastSeen_MinutesAgo_zero: String - private let _LastSeen_MinutesAgo_one: String - private let _LastSeen_MinutesAgo_two: String - private let _LastSeen_MinutesAgo_few: String - private let _LastSeen_MinutesAgo_many: String - private let _LastSeen_MinutesAgo_other: String - public func LastSeen_MinutesAgo(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._LastSeen_MinutesAgo_zero, "\(value)") - case .one: - return String(format: self._LastSeen_MinutesAgo_one, "\(value)") - case .two: - return String(format: self._LastSeen_MinutesAgo_two, "\(value)") - case .few: - return String(format: self._LastSeen_MinutesAgo_few, "\(value)") - case .many: - return String(format: self._LastSeen_MinutesAgo_many, "\(value)") - case .other: - return String(format: self._LastSeen_MinutesAgo_other, "\(value)") - } - } - private let _ForwardedVideos_zero: String - private let _ForwardedVideos_one: String - private let _ForwardedVideos_two: String - private let _ForwardedVideos_few: String - private let _ForwardedVideos_many: String - private let _ForwardedVideos_other: String - public func ForwardedVideos(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ForwardedVideos_zero, "\(value)") - case .one: - return String(format: self._ForwardedVideos_one, "\(value)") - case .two: - return String(format: self._ForwardedVideos_two, "\(value)") - case .few: - return String(format: self._ForwardedVideos_few, "\(value)") - case .many: - return String(format: self._ForwardedVideos_many, "\(value)") - case .other: - return String(format: self._ForwardedVideos_other, "\(value)") - } - } - private let _DialogList_LiveLocationChatsCount_zero: String - private let _DialogList_LiveLocationChatsCount_one: String - private let _DialogList_LiveLocationChatsCount_two: String - private let _DialogList_LiveLocationChatsCount_few: String - private let _DialogList_LiveLocationChatsCount_many: String - private let _DialogList_LiveLocationChatsCount_other: String - public func DialogList_LiveLocationChatsCount(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._DialogList_LiveLocationChatsCount_zero, "\(value)") - case .one: - return String(format: self._DialogList_LiveLocationChatsCount_one, "\(value)") - case .two: - return String(format: self._DialogList_LiveLocationChatsCount_two, "\(value)") - case .few: - return String(format: self._DialogList_LiveLocationChatsCount_few, "\(value)") - case .many: - return String(format: self._DialogList_LiveLocationChatsCount_many, "\(value)") - case .other: - return String(format: self._DialogList_LiveLocationChatsCount_other, "\(value)") - } - } - private let _MessageTimer_Minutes_zero: String - private let _MessageTimer_Minutes_one: String - private let _MessageTimer_Minutes_two: String - private let _MessageTimer_Minutes_few: String - private let _MessageTimer_Minutes_many: String - private let _MessageTimer_Minutes_other: String - public func MessageTimer_Minutes(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MessageTimer_Minutes_zero, "\(value)") - case .one: - return String(format: self._MessageTimer_Minutes_one, "\(value)") - case .two: - return String(format: self._MessageTimer_Minutes_two, "\(value)") - case .few: - return String(format: self._MessageTimer_Minutes_few, "\(value)") - case .many: - return String(format: self._MessageTimer_Minutes_many, "\(value)") - case .other: - return String(format: self._MessageTimer_Minutes_other, "\(value)") - } - } - private let _Map_ETAMinutes_zero: String - private let _Map_ETAMinutes_one: String - private let _Map_ETAMinutes_two: String - private let _Map_ETAMinutes_few: String - private let _Map_ETAMinutes_many: String - private let _Map_ETAMinutes_other: String - public func Map_ETAMinutes(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._Map_ETAMinutes_zero, "\(value)") - case .one: - return String(format: self._Map_ETAMinutes_one, "\(value)") - case .two: - return String(format: self._Map_ETAMinutes_two, "\(value)") - case .few: - return String(format: self._Map_ETAMinutes_few, "\(value)") - case .many: - return String(format: self._Map_ETAMinutes_many, "\(value)") - case .other: - return String(format: self._Map_ETAMinutes_other, "\(value)") - } - } - private let _LastSeen_HoursAgo_zero: String - private let _LastSeen_HoursAgo_one: String - private let _LastSeen_HoursAgo_two: String - private let _LastSeen_HoursAgo_few: String - private let _LastSeen_HoursAgo_many: String - private let _LastSeen_HoursAgo_other: String - public func LastSeen_HoursAgo(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._LastSeen_HoursAgo_zero, "\(value)") - case .one: - return String(format: self._LastSeen_HoursAgo_one, "\(value)") - case .two: - return String(format: self._LastSeen_HoursAgo_two, "\(value)") - case .few: - return String(format: self._LastSeen_HoursAgo_few, "\(value)") - case .many: - return String(format: self._LastSeen_HoursAgo_many, "\(value)") - case .other: - return String(format: self._LastSeen_HoursAgo_other, "\(value)") - } - } - private let _StickerPack_AddMaskCount_zero: String - private let _StickerPack_AddMaskCount_one: String - private let _StickerPack_AddMaskCount_two: String - private let _StickerPack_AddMaskCount_few: String - private let _StickerPack_AddMaskCount_many: String - private let _StickerPack_AddMaskCount_other: String - public func StickerPack_AddMaskCount(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._StickerPack_AddMaskCount_zero, "\(value)") - case .one: - return String(format: self._StickerPack_AddMaskCount_one, "\(value)") - case .two: - return String(format: self._StickerPack_AddMaskCount_two, "\(value)") - case .few: - return String(format: self._StickerPack_AddMaskCount_few, "\(value)") - case .many: - return String(format: self._StickerPack_AddMaskCount_many, "\(value)") - case .other: - return String(format: self._StickerPack_AddMaskCount_other, "\(value)") - } - } - private let _MessageTimer_Weeks_zero: String - private let _MessageTimer_Weeks_one: String - private let _MessageTimer_Weeks_two: String - private let _MessageTimer_Weeks_few: String - private let _MessageTimer_Weeks_many: String - private let _MessageTimer_Weeks_other: String - public func MessageTimer_Weeks(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MessageTimer_Weeks_zero, "\(value)") - case .one: - return String(format: self._MessageTimer_Weeks_one, "\(value)") - case .two: - return String(format: self._MessageTimer_Weeks_two, "\(value)") - case .few: - return String(format: self._MessageTimer_Weeks_few, "\(value)") - case .many: - return String(format: self._MessageTimer_Weeks_many, "\(value)") - case .other: - return String(format: self._MessageTimer_Weeks_other, "\(value)") - } - } - private let _ForwardedAuthorsOthers_zero: String - private let _ForwardedAuthorsOthers_one: String - private let _ForwardedAuthorsOthers_two: String - private let _ForwardedAuthorsOthers_few: String - private let _ForwardedAuthorsOthers_many: String - private let _ForwardedAuthorsOthers_other: String - public func ForwardedAuthorsOthers(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ForwardedAuthorsOthers_zero, "\(value)") - case .one: - return String(format: self._ForwardedAuthorsOthers_one, "\(value)") - case .two: - return String(format: self._ForwardedAuthorsOthers_two, "\(value)") - case .few: - return String(format: self._ForwardedAuthorsOthers_few, "\(value)") - case .many: - return String(format: self._ForwardedAuthorsOthers_many, "\(value)") - case .other: - return String(format: self._ForwardedAuthorsOthers_other, "\(value)") - } - } - private let _ServiceMessage_GameScoreExtended_zero: String - private let _ServiceMessage_GameScoreExtended_one: String - private let _ServiceMessage_GameScoreExtended_two: String - private let _ServiceMessage_GameScoreExtended_few: String - private let _ServiceMessage_GameScoreExtended_many: String - private let _ServiceMessage_GameScoreExtended_other: String - public func ServiceMessage_GameScoreExtended(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._ServiceMessage_GameScoreExtended_zero, "\(value)") - case .one: - return String(format: self._ServiceMessage_GameScoreExtended_one, "\(value)") - case .two: - return String(format: self._ServiceMessage_GameScoreExtended_two, "\(value)") - case .few: - return String(format: self._ServiceMessage_GameScoreExtended_few, "\(value)") - case .many: - return String(format: self._ServiceMessage_GameScoreExtended_many, "\(value)") - case .other: - return String(format: self._ServiceMessage_GameScoreExtended_other, "\(value)") + return String(format: self._Call_Seconds_other, "\(value)") } } private let _MessageTimer_Hours_zero: String @@ -4860,70 +5032,26 @@ public final class PresentationStrings { return String(format: self._MessageTimer_Hours_other, "\(value)") } } - private let _ForwardedLocations_zero: String - private let _ForwardedLocations_one: String - private let _ForwardedLocations_two: String - private let _ForwardedLocations_few: String - private let _ForwardedLocations_many: String - private let _ForwardedLocations_other: String - public func ForwardedLocations(_ value: Int32) -> String { + private let _Conversation_StatusOnline_zero: String + private let _Conversation_StatusOnline_one: String + private let _Conversation_StatusOnline_two: String + private let _Conversation_StatusOnline_few: String + private let _Conversation_StatusOnline_many: String + private let _Conversation_StatusOnline_other: String + public func Conversation_StatusOnline(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._ForwardedLocations_zero, "\(value)") + return String(format: self._Conversation_StatusOnline_zero, "\(value)") case .one: - return String(format: self._ForwardedLocations_one, "\(value)") + return String(format: self._Conversation_StatusOnline_one, "\(value)") case .two: - return String(format: self._ForwardedLocations_two, "\(value)") + return String(format: self._Conversation_StatusOnline_two, "\(value)") case .few: - return String(format: self._ForwardedLocations_few, "\(value)") + return String(format: self._Conversation_StatusOnline_few, "\(value)") case .many: - return String(format: self._ForwardedLocations_many, "\(value)") + return String(format: self._Conversation_StatusOnline_many, "\(value)") case .other: - return String(format: self._ForwardedLocations_other, "\(value)") - } - } - private let _PrivacyLastSeenSettings_AddUsers_zero: String - private let _PrivacyLastSeenSettings_AddUsers_one: String - private let _PrivacyLastSeenSettings_AddUsers_two: String - private let _PrivacyLastSeenSettings_AddUsers_few: String - private let _PrivacyLastSeenSettings_AddUsers_many: String - private let _PrivacyLastSeenSettings_AddUsers_other: String - public func PrivacyLastSeenSettings_AddUsers(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._PrivacyLastSeenSettings_AddUsers_zero, "\(value)") - case .one: - return String(format: self._PrivacyLastSeenSettings_AddUsers_one, "\(value)") - case .two: - return String(format: self._PrivacyLastSeenSettings_AddUsers_two, "\(value)") - case .few: - return String(format: self._PrivacyLastSeenSettings_AddUsers_few, "\(value)") - case .many: - return String(format: self._PrivacyLastSeenSettings_AddUsers_many, "\(value)") - case .other: - return String(format: self._PrivacyLastSeenSettings_AddUsers_other, "\(value)") - } - } - private let _MuteFor_Hours_zero: String - private let _MuteFor_Hours_one: String - private let _MuteFor_Hours_two: String - private let _MuteFor_Hours_few: String - private let _MuteFor_Hours_many: String - private let _MuteFor_Hours_other: String - public func MuteFor_Hours(_ value: Int32) -> String { - switch presentationStringsPluralizationForm(self.lc, value) { - case .zero: - return String(format: self._MuteFor_Hours_zero, "\(value)") - case .one: - return String(format: self._MuteFor_Hours_one, "\(value)") - case .two: - return String(format: self._MuteFor_Hours_two, "\(value)") - case .few: - return String(format: self._MuteFor_Hours_few, "\(value)") - case .many: - return String(format: self._MuteFor_Hours_many, "\(value)") - case .other: - return String(format: self._MuteFor_Hours_other, "\(value)") + return String(format: self._Conversation_StatusOnline_other, "\(value)") } } private let _ForwardedPhotos_zero: String @@ -4948,48 +5076,356 @@ public final class PresentationStrings { return String(format: self._ForwardedPhotos_other, "\(value)") } } - private let _ServiceMessage_GameScoreSelfSimple_zero: String - private let _ServiceMessage_GameScoreSelfSimple_one: String - private let _ServiceMessage_GameScoreSelfSimple_two: String - private let _ServiceMessage_GameScoreSelfSimple_few: String - private let _ServiceMessage_GameScoreSelfSimple_many: String - private let _ServiceMessage_GameScoreSelfSimple_other: String - public func ServiceMessage_GameScoreSelfSimple(_ value: Int32) -> String { + private let _MessageTimer_Weeks_zero: String + private let _MessageTimer_Weeks_one: String + private let _MessageTimer_Weeks_two: String + private let _MessageTimer_Weeks_few: String + private let _MessageTimer_Weeks_many: String + private let _MessageTimer_Weeks_other: String + public func MessageTimer_Weeks(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._ServiceMessage_GameScoreSelfSimple_zero, "\(value)") + return String(format: self._MessageTimer_Weeks_zero, "\(value)") case .one: - return String(format: self._ServiceMessage_GameScoreSelfSimple_one, "\(value)") + return String(format: self._MessageTimer_Weeks_one, "\(value)") case .two: - return String(format: self._ServiceMessage_GameScoreSelfSimple_two, "\(value)") + return String(format: self._MessageTimer_Weeks_two, "\(value)") case .few: - return String(format: self._ServiceMessage_GameScoreSelfSimple_few, "\(value)") + return String(format: self._MessageTimer_Weeks_few, "\(value)") case .many: - return String(format: self._ServiceMessage_GameScoreSelfSimple_many, "\(value)") + return String(format: self._MessageTimer_Weeks_many, "\(value)") case .other: - return String(format: self._ServiceMessage_GameScoreSelfSimple_other, "\(value)") + return String(format: self._MessageTimer_Weeks_other, "\(value)") } } - private let _Conversation_StatusSubscribers_zero: String - private let _Conversation_StatusSubscribers_one: String - private let _Conversation_StatusSubscribers_two: String - private let _Conversation_StatusSubscribers_few: String - private let _Conversation_StatusSubscribers_many: String - private let _Conversation_StatusSubscribers_other: String - public func Conversation_StatusSubscribers(_ value: Int32) -> String { + private let _Call_ShortMinutes_zero: String + private let _Call_ShortMinutes_one: String + private let _Call_ShortMinutes_two: String + private let _Call_ShortMinutes_few: String + private let _Call_ShortMinutes_many: String + private let _Call_ShortMinutes_other: String + public func Call_ShortMinutes(_ value: Int32) -> String { switch presentationStringsPluralizationForm(self.lc, value) { case .zero: - return String(format: self._Conversation_StatusSubscribers_zero, "\(value)") + return String(format: self._Call_ShortMinutes_zero, "\(value)") case .one: - return String(format: self._Conversation_StatusSubscribers_one, "\(value)") + return String(format: self._Call_ShortMinutes_one, "\(value)") case .two: - return String(format: self._Conversation_StatusSubscribers_two, "\(value)") + return String(format: self._Call_ShortMinutes_two, "\(value)") case .few: - return String(format: self._Conversation_StatusSubscribers_few, "\(value)") + return String(format: self._Call_ShortMinutes_few, "\(value)") case .many: - return String(format: self._Conversation_StatusSubscribers_many, "\(value)") + return String(format: self._Call_ShortMinutes_many, "\(value)") case .other: - return String(format: self._Conversation_StatusSubscribers_other, "\(value)") + return String(format: self._Call_ShortMinutes_other, "\(value)") + } + } + private let _Notifications_ExceptionMuteExpires_Days_zero: String + private let _Notifications_ExceptionMuteExpires_Days_one: String + private let _Notifications_ExceptionMuteExpires_Days_two: String + private let _Notifications_ExceptionMuteExpires_Days_few: String + private let _Notifications_ExceptionMuteExpires_Days_many: String + private let _Notifications_ExceptionMuteExpires_Days_other: String + public func Notifications_ExceptionMuteExpires_Days(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Notifications_ExceptionMuteExpires_Days_zero, "\(value)") + case .one: + return String(format: self._Notifications_ExceptionMuteExpires_Days_one, "\(value)") + case .two: + return String(format: self._Notifications_ExceptionMuteExpires_Days_two, "\(value)") + case .few: + return String(format: self._Notifications_ExceptionMuteExpires_Days_few, "\(value)") + case .many: + return String(format: self._Notifications_ExceptionMuteExpires_Days_many, "\(value)") + case .other: + return String(format: self._Notifications_ExceptionMuteExpires_Days_other, "\(value)") + } + } + private let _ForwardedAudios_zero: String + private let _ForwardedAudios_one: String + private let _ForwardedAudios_two: String + private let _ForwardedAudios_few: String + private let _ForwardedAudios_many: String + private let _ForwardedAudios_other: String + public func ForwardedAudios(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._ForwardedAudios_zero, "\(value)") + case .one: + return String(format: self._ForwardedAudios_one, "\(value)") + case .two: + return String(format: self._ForwardedAudios_two, "\(value)") + case .few: + return String(format: self._ForwardedAudios_few, "\(value)") + case .many: + return String(format: self._ForwardedAudios_many, "\(value)") + case .other: + return String(format: self._ForwardedAudios_other, "\(value)") + } + } + private let _Conversation_LiveLocationMembersCount_zero: String + private let _Conversation_LiveLocationMembersCount_one: String + private let _Conversation_LiveLocationMembersCount_two: String + private let _Conversation_LiveLocationMembersCount_few: String + private let _Conversation_LiveLocationMembersCount_many: String + private let _Conversation_LiveLocationMembersCount_other: String + public func Conversation_LiveLocationMembersCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Conversation_LiveLocationMembersCount_zero, "\(value)") + case .one: + return String(format: self._Conversation_LiveLocationMembersCount_one, "\(value)") + case .two: + return String(format: self._Conversation_LiveLocationMembersCount_two, "\(value)") + case .few: + return String(format: self._Conversation_LiveLocationMembersCount_few, "\(value)") + case .many: + return String(format: self._Conversation_LiveLocationMembersCount_many, "\(value)") + case .other: + return String(format: self._Conversation_LiveLocationMembersCount_other, "\(value)") + } + } + private let _StickerPack_AddStickerCount_zero: String + private let _StickerPack_AddStickerCount_one: String + private let _StickerPack_AddStickerCount_two: String + private let _StickerPack_AddStickerCount_few: String + private let _StickerPack_AddStickerCount_many: String + private let _StickerPack_AddStickerCount_other: String + public func StickerPack_AddStickerCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._StickerPack_AddStickerCount_zero, "\(value)") + case .one: + return String(format: self._StickerPack_AddStickerCount_one, "\(value)") + case .two: + return String(format: self._StickerPack_AddStickerCount_two, "\(value)") + case .few: + return String(format: self._StickerPack_AddStickerCount_few, "\(value)") + case .many: + return String(format: self._StickerPack_AddStickerCount_many, "\(value)") + case .other: + return String(format: self._StickerPack_AddStickerCount_other, "\(value)") + } + } + private let _Contacts_ImportersCount_zero: String + private let _Contacts_ImportersCount_one: String + private let _Contacts_ImportersCount_two: String + private let _Contacts_ImportersCount_few: String + private let _Contacts_ImportersCount_many: String + private let _Contacts_ImportersCount_other: String + public func Contacts_ImportersCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Contacts_ImportersCount_zero, "\(value)") + case .one: + return String(format: self._Contacts_ImportersCount_one, "\(value)") + case .two: + return String(format: self._Contacts_ImportersCount_two, "\(value)") + case .few: + return String(format: self._Contacts_ImportersCount_few, "\(value)") + case .many: + return String(format: self._Contacts_ImportersCount_many, "\(value)") + case .other: + return String(format: self._Contacts_ImportersCount_other, "\(value)") + } + } + private let _StickerPack_RemoveMaskCount_zero: String + private let _StickerPack_RemoveMaskCount_one: String + private let _StickerPack_RemoveMaskCount_two: String + private let _StickerPack_RemoveMaskCount_few: String + private let _StickerPack_RemoveMaskCount_many: String + private let _StickerPack_RemoveMaskCount_other: String + public func StickerPack_RemoveMaskCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._StickerPack_RemoveMaskCount_zero, "\(value)") + case .one: + return String(format: self._StickerPack_RemoveMaskCount_one, "\(value)") + case .two: + return String(format: self._StickerPack_RemoveMaskCount_two, "\(value)") + case .few: + return String(format: self._StickerPack_RemoveMaskCount_few, "\(value)") + case .many: + return String(format: self._StickerPack_RemoveMaskCount_many, "\(value)") + case .other: + return String(format: self._StickerPack_RemoveMaskCount_other, "\(value)") + } + } + private let _Notifications_ExceptionMuteExpires_Minutes_zero: String + private let _Notifications_ExceptionMuteExpires_Minutes_one: String + private let _Notifications_ExceptionMuteExpires_Minutes_two: String + private let _Notifications_ExceptionMuteExpires_Minutes_few: String + private let _Notifications_ExceptionMuteExpires_Minutes_many: String + private let _Notifications_ExceptionMuteExpires_Minutes_other: String + public func Notifications_ExceptionMuteExpires_Minutes(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Notifications_ExceptionMuteExpires_Minutes_zero, "\(value)") + case .one: + return String(format: self._Notifications_ExceptionMuteExpires_Minutes_one, "\(value)") + case .two: + return String(format: self._Notifications_ExceptionMuteExpires_Minutes_two, "\(value)") + case .few: + return String(format: self._Notifications_ExceptionMuteExpires_Minutes_few, "\(value)") + case .many: + return String(format: self._Notifications_ExceptionMuteExpires_Minutes_many, "\(value)") + case .other: + return String(format: self._Notifications_ExceptionMuteExpires_Minutes_other, "\(value)") + } + } + private let _InviteText_ContactsCount_zero: String + private let _InviteText_ContactsCount_one: String + private let _InviteText_ContactsCount_two: String + private let _InviteText_ContactsCount_few: String + private let _InviteText_ContactsCount_many: String + private let _InviteText_ContactsCount_other: String + public func InviteText_ContactsCount(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._InviteText_ContactsCount_zero, "\(value)") + case .one: + return String(format: self._InviteText_ContactsCount_one, "\(value)") + case .two: + return String(format: self._InviteText_ContactsCount_two, "\(value)") + case .few: + return String(format: self._InviteText_ContactsCount_few, "\(value)") + case .many: + return String(format: self._InviteText_ContactsCount_many, "\(value)") + case .other: + return String(format: self._InviteText_ContactsCount_other, "\(value)") + } + } + private let _SharedMedia_Link_zero: String + private let _SharedMedia_Link_one: String + private let _SharedMedia_Link_two: String + private let _SharedMedia_Link_few: String + private let _SharedMedia_Link_many: String + private let _SharedMedia_Link_other: String + public func SharedMedia_Link(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._SharedMedia_Link_zero, "\(value)") + case .one: + return String(format: self._SharedMedia_Link_one, "\(value)") + case .two: + return String(format: self._SharedMedia_Link_two, "\(value)") + case .few: + return String(format: self._SharedMedia_Link_few, "\(value)") + case .many: + return String(format: self._SharedMedia_Link_many, "\(value)") + case .other: + return String(format: self._SharedMedia_Link_other, "\(value)") + } + } + private let _Map_ETAMinutes_zero: String + private let _Map_ETAMinutes_one: String + private let _Map_ETAMinutes_two: String + private let _Map_ETAMinutes_few: String + private let _Map_ETAMinutes_many: String + private let _Map_ETAMinutes_other: String + public func Map_ETAMinutes(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Map_ETAMinutes_zero, "\(value)") + case .one: + return String(format: self._Map_ETAMinutes_one, "\(value)") + case .two: + return String(format: self._Map_ETAMinutes_two, "\(value)") + case .few: + return String(format: self._Map_ETAMinutes_few, "\(value)") + case .many: + return String(format: self._Map_ETAMinutes_many, "\(value)") + case .other: + return String(format: self._Map_ETAMinutes_other, "\(value)") + } + } + private let _MessageTimer_Minutes_zero: String + private let _MessageTimer_Minutes_one: String + private let _MessageTimer_Minutes_two: String + private let _MessageTimer_Minutes_few: String + private let _MessageTimer_Minutes_many: String + private let _MessageTimer_Minutes_other: String + public func MessageTimer_Minutes(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._MessageTimer_Minutes_zero, "\(value)") + case .one: + return String(format: self._MessageTimer_Minutes_one, "\(value)") + case .two: + return String(format: self._MessageTimer_Minutes_two, "\(value)") + case .few: + return String(format: self._MessageTimer_Minutes_few, "\(value)") + case .many: + return String(format: self._MessageTimer_Minutes_many, "\(value)") + case .other: + return String(format: self._MessageTimer_Minutes_other, "\(value)") + } + } + private let _AttachmentMenu_SendPhoto_zero: String + private let _AttachmentMenu_SendPhoto_one: String + private let _AttachmentMenu_SendPhoto_two: String + private let _AttachmentMenu_SendPhoto_few: String + private let _AttachmentMenu_SendPhoto_many: String + private let _AttachmentMenu_SendPhoto_other: String + public func AttachmentMenu_SendPhoto(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._AttachmentMenu_SendPhoto_zero, "\(value)") + case .one: + return String(format: self._AttachmentMenu_SendPhoto_one, "\(value)") + case .two: + return String(format: self._AttachmentMenu_SendPhoto_two, "\(value)") + case .few: + return String(format: self._AttachmentMenu_SendPhoto_few, "\(value)") + case .many: + return String(format: self._AttachmentMenu_SendPhoto_many, "\(value)") + case .other: + return String(format: self._AttachmentMenu_SendPhoto_other, "\(value)") + } + } + private let _Notifications_ExceptionMuteExpires_Hours_zero: String + private let _Notifications_ExceptionMuteExpires_Hours_one: String + private let _Notifications_ExceptionMuteExpires_Hours_two: String + private let _Notifications_ExceptionMuteExpires_Hours_few: String + private let _Notifications_ExceptionMuteExpires_Hours_many: String + private let _Notifications_ExceptionMuteExpires_Hours_other: String + public func Notifications_ExceptionMuteExpires_Hours(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Notifications_ExceptionMuteExpires_Hours_zero, "\(value)") + case .one: + return String(format: self._Notifications_ExceptionMuteExpires_Hours_one, "\(value)") + case .two: + return String(format: self._Notifications_ExceptionMuteExpires_Hours_two, "\(value)") + case .few: + return String(format: self._Notifications_ExceptionMuteExpires_Hours_few, "\(value)") + case .many: + return String(format: self._Notifications_ExceptionMuteExpires_Hours_many, "\(value)") + case .other: + return String(format: self._Notifications_ExceptionMuteExpires_Hours_other, "\(value)") + } + } + private let _Invitation_Members_zero: String + private let _Invitation_Members_one: String + private let _Invitation_Members_two: String + private let _Invitation_Members_few: String + private let _Invitation_Members_many: String + private let _Invitation_Members_other: String + public func Invitation_Members(_ value: Int32) -> String { + switch presentationStringsPluralizationForm(self.lc, value) { + case .zero: + return String(format: self._Invitation_Members_zero, "\(value)") + case .one: + return String(format: self._Invitation_Members_one, "\(value)") + case .two: + return String(format: self._Invitation_Members_two, "\(value)") + case .few: + return String(format: self._Invitation_Members_few, "\(value)") + case .many: + return String(format: self._Invitation_Members_many, "\(value)") + case .other: + return String(format: self._Invitation_Members_other, "\(value)") } } @@ -5015,8 +5451,11 @@ public final class PresentationStrings { self.Channel_BanUser_Title = getValue(dict, "Channel.BanUser.Title") self.Notification_SecretChatMessageScreenshotSelf = getValue(dict, "Notification.SecretChatMessageScreenshotSelf") self.Preview_SaveGif = getValue(dict, "Preview.SaveGif") + self.Passport_ScanPassportHelp = getValue(dict, "Passport.ScanPassportHelp") self.EnterPasscode_EnterNewPasscodeNew = getValue(dict, "EnterPasscode.EnterNewPasscodeNew") + self.Passport_Identity_TypeInternalPassport = getValue(dict, "Passport.Identity.TypeInternalPassport") self.Privacy_Calls_WhoCanCallMe = getValue(dict, "Privacy.Calls.WhoCanCallMe") + self.Passport_DeletePassport = getValue(dict, "Passport.DeletePassport") self.Watch_NoConnection = getValue(dict, "Watch.NoConnection") self.Activity_UploadingPhoto = getValue(dict, "Activity.UploadingPhoto") self.PrivacySettings_PrivacyTitle = getValue(dict, "PrivacySettings.PrivacyTitle") @@ -5024,9 +5463,9 @@ public final class PresentationStrings { self._DialogList_PinLimitError_r = extractArgumentRanges(self._DialogList_PinLimitError) self.FastTwoStepSetup_PasswordSection = getValue(dict, "FastTwoStepSetup.PasswordSection") self.FastTwoStepSetup_EmailSection = getValue(dict, "FastTwoStepSetup.EmailSection") - self.ChatList_MarkAsRead = getValue(dict, "ChatList.MarkAsRead") self.Cache_ClearCache = getValue(dict, "Cache.ClearCache") self.Common_Close = getValue(dict, "Common.Close") + self.Passport_PasswordDescription = getValue(dict, "Passport.PasswordDescription") self.ChangePhoneNumberCode_Called = getValue(dict, "ChangePhoneNumberCode.Called") self.Login_PhoneTitle = getValue(dict, "Login.PhoneTitle") self._Cache_Clear = getValue(dict, "Cache.Clear") @@ -5035,15 +5474,20 @@ public final class PresentationStrings { self.Watch_ChatList_Compose = getValue(dict, "Watch.ChatList.Compose") self.DialogList_SearchSectionDialogs = getValue(dict, "DialogList.SearchSectionDialogs") self.Contacts_TabTitle = getValue(dict, "Contacts.TabTitle") + self.NotificationsSound_Pulse = getValue(dict, "NotificationsSound.Pulse") + self.Passport_Language_el = getValue(dict, "Passport.Language.el") + self.Passport_Identity_DateOfBirth = getValue(dict, "Passport.Identity.DateOfBirth") self.TwoStepAuth_SetupPasswordConfirmPassword = getValue(dict, "TwoStepAuth.SetupPasswordConfirmPassword") self.ChannelIntro_Text = getValue(dict, "ChannelIntro.Text") self.PrivacySettings_SecurityTitle = getValue(dict, "PrivacySettings.SecurityTitle") self.DialogList_SavedMessages = getValue(dict, "DialogList.SavedMessages") self._Login_SmsRequestState1 = getValue(dict, "Login.SmsRequestState1") self._Login_SmsRequestState1_r = extractArgumentRanges(self._Login_SmsRequestState1) + self.Update_Skip = getValue(dict, "Update.Skip") self._Call_StatusOngoing = getValue(dict, "Call.StatusOngoing") self._Call_StatusOngoing_r = extractArgumentRanges(self._Call_StatusOngoing) self.Settings_LogoutConfirmationText = getValue(dict, "Settings.LogoutConfirmationText") + self.Passport_Identity_ResidenceCountry = getValue(dict, "Passport.Identity.ResidenceCountry") self.AutoNightTheme_ScheduledTo = getValue(dict, "AutoNightTheme.ScheduledTo") self.SocksProxySetup_RequiredCredentials = getValue(dict, "SocksProxySetup.RequiredCredentials") self.BlockedUsers_Info = getValue(dict, "BlockedUsers.Info") @@ -5063,6 +5507,8 @@ public final class PresentationStrings { self.Channel_Username_Help = getValue(dict, "Channel.Username.Help") self._Profile_CreateEncryptedChatOutdatedError = getValue(dict, "Profile.CreateEncryptedChatOutdatedError") self._Profile_CreateEncryptedChatOutdatedError_r = extractArgumentRanges(self._Profile_CreateEncryptedChatOutdatedError) + self.PrivacyPolicy_DeclineLastWarning = getValue(dict, "PrivacyPolicy.DeclineLastWarning") + self.Passport_FieldEmail = getValue(dict, "Passport.FieldEmail") self.ContactInfo_PhoneLabelPager = getValue(dict, "ContactInfo.PhoneLabelPager") self._PINNED_STICKER = getValue(dict, "PINNED_STICKER") self._PINNED_STICKER_r = extractArgumentRanges(self._PINNED_STICKER) @@ -5071,10 +5517,10 @@ public final class PresentationStrings { self._Channel_AdminLog_MessageEdited = getValue(dict, "Channel.AdminLog.MessageEdited") self._Channel_AdminLog_MessageEdited_r = extractArgumentRanges(self._Channel_AdminLog_MessageEdited) self.Group_Setup_HistoryHidden = getValue(dict, "Group.Setup.HistoryHidden") + self.Your_cards_expiration_year_is_invalid = getValue(dict, "Your_cards_expiration_year_is_invalid") + self.AccessDenied_MicrophoneRestricted = getValue(dict, "AccessDenied.MicrophoneRestricted") self._PHONE_CALL_REQUEST = getValue(dict, "PHONE_CALL_REQUEST") self._PHONE_CALL_REQUEST_r = extractArgumentRanges(self._PHONE_CALL_REQUEST) - self.AccessDenied_MicrophoneRestricted = getValue(dict, "AccessDenied.MicrophoneRestricted") - self.Your_cards_expiration_year_is_invalid = getValue(dict, "Your_cards_expiration_year_is_invalid") self.GroupInfo_InviteByLink = getValue(dict, "GroupInfo.InviteByLink") self._Notification_LeftChat = getValue(dict, "Notification.LeftChat") self._Notification_LeftChat_r = extractArgumentRanges(self._Notification_LeftChat) @@ -5082,10 +5528,15 @@ public final class PresentationStrings { self._Channel_AdminLog_MessageAdmin_r = extractArgumentRanges(self._Channel_AdminLog_MessageAdmin) self.PrivacyLastSeenSettings_NeverShareWith_Placeholder = getValue(dict, "PrivacyLastSeenSettings.NeverShareWith.Placeholder") self.Appearance_AutoNightThemeDisabled = getValue(dict, "Appearance.AutoNightThemeDisabled") + self.Notifications_ExceptionsMessagePlaceholder = getValue(dict, "Notifications.ExceptionsMessagePlaceholder") + self.NotificationsSound_Alert = getValue(dict, "NotificationsSound.Alert") self.TwoStepAuth_SetupEmail = getValue(dict, "TwoStepAuth.SetupEmail") self.Checkout_PayWithFaceId = getValue(dict, "Checkout.PayWithFaceId") self.Login_ResetAccountProtected_Reset = getValue(dict, "Login.ResetAccountProtected.Reset") self.SocksProxySetup_Hostname = getValue(dict, "SocksProxySetup.Hostname") + self._PrivacyPolicy_AgeVerificationMessage = getValue(dict, "PrivacyPolicy.AgeVerificationMessage") + self._PrivacyPolicy_AgeVerificationMessage_r = extractArgumentRanges(self._PrivacyPolicy_AgeVerificationMessage) + self.NotificationsSound_None = getValue(dict, "NotificationsSound.None") self.Channel_AdminLog_CanEditMessages = getValue(dict, "Channel.AdminLog.CanEditMessages") self._MESSAGE_CONTACT = getValue(dict, "MESSAGE_CONTACT") self._MESSAGE_CONTACT_r = extractArgumentRanges(self._MESSAGE_CONTACT) @@ -5099,8 +5550,13 @@ public final class PresentationStrings { self.Watch_ConnectionDescription = getValue(dict, "Watch.ConnectionDescription") self._Notification_CallTimeFormat = getValue(dict, "Notification.CallTimeFormat") self._Notification_CallTimeFormat_r = extractArgumentRanges(self._Notification_CallTimeFormat) + self.Passport_Identity_Selfie = getValue(dict, "Passport.Identity.Selfie") + self.Passport_Identity_GenderMale = getValue(dict, "Passport.Identity.GenderMale") self.Paint_Delete = getValue(dict, "Paint.Delete") + self.Passport_Identity_AddDriversLicense = getValue(dict, "Passport.Identity.AddDriversLicense") + self.Passport_Language_ne = getValue(dict, "Passport.Language.ne") self.Channel_MessagePhotoUpdated = getValue(dict, "Channel.MessagePhotoUpdated") + self.Passport_Address_OneOfTypePassportRegistration = getValue(dict, "Passport.Address.OneOfTypePassportRegistration") self.Cache_Help = getValue(dict, "Cache.Help") self.SocksProxySetup_ProxyStatusConnected = getValue(dict, "SocksProxySetup.ProxyStatusConnected") self._Login_EmailPhoneBody = getValue(dict, "Login.EmailPhoneBody") @@ -5109,11 +5565,14 @@ public final class PresentationStrings { self.Channel_BanList_RestrictedTitle = getValue(dict, "Channel.BanList.RestrictedTitle") self.Checkout_TotalAmount = getValue(dict, "Checkout.TotalAmount") self.Appearance_TextSize = getValue(dict, "Appearance.TextSize") + self.Passport_Address_TypeResidentialAddress = getValue(dict, "Passport.Address.TypeResidentialAddress") self.Conversation_MessageEditedLabel = getValue(dict, "Conversation.MessageEditedLabel") self.SharedMedia_EmptyLinksText = getValue(dict, "SharedMedia.EmptyLinksText") self._Conversation_RestrictedTextTimed = getValue(dict, "Conversation.RestrictedTextTimed") self._Conversation_RestrictedTextTimed_r = extractArgumentRanges(self._Conversation_RestrictedTextTimed) + self.Passport_Address_AddResidentialAddress = getValue(dict, "Passport.Address.AddResidentialAddress") self.Calls_NoCallsPlaceholder = getValue(dict, "Calls.NoCallsPlaceholder") + self.Passport_Address_AddPassportRegistration = getValue(dict, "Passport.Address.AddPassportRegistration") self.Conversation_PinMessageAlert_OnlyPin = getValue(dict, "Conversation.PinMessageAlert.OnlyPin") self.PasscodeSettings_UnlockWithFaceId = getValue(dict, "PasscodeSettings.UnlockWithFaceId") self.ContactInfo_Title = getValue(dict, "ContactInfo.Title") @@ -5124,14 +5583,17 @@ public final class PresentationStrings { self._Time_PreciseDate_m9_r = extractArgumentRanges(self._Time_PreciseDate_m9) self.GroupInfo_Title = getValue(dict, "GroupInfo.Title") self.State_Updating = getValue(dict, "State.Updating") + self.PrivacyPolicy_AgeVerificationAgree = getValue(dict, "PrivacyPolicy.AgeVerificationAgree") self.Map_GetDirections = getValue(dict, "Map.GetDirections") self._TwoStepAuth_PendingEmailHelp = getValue(dict, "TwoStepAuth.PendingEmailHelp") self._TwoStepAuth_PendingEmailHelp_r = extractArgumentRanges(self._TwoStepAuth_PendingEmailHelp) self.UserInfo_PhoneCall = getValue(dict, "UserInfo.PhoneCall") + self.Passport_Language_bn = getValue(dict, "Passport.Language.bn") self.MusicPlayer_VoiceNote = getValue(dict, "MusicPlayer.VoiceNote") self.Paint_Duplicate = getValue(dict, "Paint.Duplicate") self.Channel_Username_InvalidTaken = getValue(dict, "Channel.Username.InvalidTaken") self.Conversation_ClearGroupHistory = getValue(dict, "Conversation.ClearGroupHistory") + self.Passport_Address_OneOfTypeRentalAgreement = getValue(dict, "Passport.Address.OneOfTypeRentalAgreement") self.Stickers_GroupStickersHelp = getValue(dict, "Stickers.GroupStickersHelp") self.SecretChat_Title = getValue(dict, "SecretChat.Title") self.Group_UpgradeConfirmation = getValue(dict, "Group.UpgradeConfirmation") @@ -5139,7 +5601,10 @@ public final class PresentationStrings { self.GroupInfo_GroupNamePlaceholder = getValue(dict, "GroupInfo.GroupNamePlaceholder") self._Time_PreciseDate_m11 = getValue(dict, "Time.PreciseDate_m11") self._Time_PreciseDate_m11_r = extractArgumentRanges(self._Time_PreciseDate_m11) - self.TermsOfService_DeclineAuthorized = getValue(dict, "TermsOfService.DeclineAuthorized") + self.Passport_DeletePersonalDetailsConfirmation = getValue(dict, "Passport.DeletePersonalDetailsConfirmation") + self._UserInfo_NotificationsDefaultSound = getValue(dict, "UserInfo.NotificationsDefaultSound") + self._UserInfo_NotificationsDefaultSound_r = extractArgumentRanges(self._UserInfo_NotificationsDefaultSound) + self.Passport_Email_Help = getValue(dict, "Passport.Email.Help") self._MESSAGE_GEOLIVE = getValue(dict, "MESSAGE_GEOLIVE") self._MESSAGE_GEOLIVE_r = extractArgumentRanges(self._MESSAGE_GEOLIVE) self._Notification_JoinedGroupByLink = getValue(dict, "Notification.JoinedGroupByLink") @@ -5160,8 +5625,8 @@ public final class PresentationStrings { self._Channel_AdminLog_MessageToggleSignaturesOff_r = extractArgumentRanges(self._Channel_AdminLog_MessageToggleSignaturesOff) self.Month_ShortDecember = getValue(dict, "Month.ShortDecember") self.Channel_SignMessages = getValue(dict, "Channel.SignMessages") - self.ReportPeer_ReasonCopyright = getValue(dict, "ReportPeer.ReasonCopyright") self.Appearance_Title = getValue(dict, "Appearance.Title") + self.ReportPeer_ReasonCopyright = getValue(dict, "ReportPeer.ReasonCopyright") self.Conversation_Moderate_Delete = getValue(dict, "Conversation.Moderate.Delete") self.Conversation_CloudStorage_ChatStatus = getValue(dict, "Conversation.CloudStorage.ChatStatus") self.Login_InfoTitle = getValue(dict, "Login.InfoTitle") @@ -5179,7 +5644,7 @@ public final class PresentationStrings { self.TwoStepAuth_GenericHelp = getValue(dict, "TwoStepAuth.GenericHelp") self._DialogList_SingleRecordingAudioSuffix = getValue(dict, "DialogList.SingleRecordingAudioSuffix") self._DialogList_SingleRecordingAudioSuffix_r = extractArgumentRanges(self._DialogList_SingleRecordingAudioSuffix) - self.PrivacySettings_SyncContactsInfo = getValue(dict, "PrivacySettings.SyncContactsInfo") + self.Privacy_TopPeersDelete = getValue(dict, "Privacy.TopPeersDelete") self.Checkout_NewCard_CardholderNameTitle = getValue(dict, "Checkout.NewCard.CardholderNameTitle") self.Settings_FAQ_Button = getValue(dict, "Settings.FAQ_Button") self._GroupInfo_AddParticipantConfirmation = getValue(dict, "GroupInfo.AddParticipantConfirmation") @@ -5189,27 +5654,34 @@ public final class PresentationStrings { self.AccessDenied_PhotosRestricted = getValue(dict, "AccessDenied.PhotosRestricted") self.Map_Locating = getValue(dict, "Map.Locating") self.AutoDownloadSettings_Unlimited = getValue(dict, "AutoDownloadSettings.Unlimited") + self.Passport_Language_km = getValue(dict, "Passport.Language.km") self.MediaPicker_LivePhotoDescription = getValue(dict, "MediaPicker.LivePhotoDescription") + self.Passport_DiscardMessageDescription = getValue(dict, "Passport.DiscardMessageDescription") self.SocksProxySetup_Title = getValue(dict, "SocksProxySetup.Title") self.SharedMedia_EmptyMusicText = getValue(dict, "SharedMedia.EmptyMusicText") self.Cache_ByPeerHeader = getValue(dict, "Cache.ByPeerHeader") self.Bot_GroupStatusReadsHistory = getValue(dict, "Bot.GroupStatusReadsHistory") self.TwoStepAuth_ResetAccountConfirmation = getValue(dict, "TwoStepAuth.ResetAccountConfirmation") - self.TermsOfService_Decline = getValue(dict, "TermsOfService.Decline") self.CallSettings_Always = getValue(dict, "CallSettings.Always") self.Message_ImageExpired = getValue(dict, "Message.ImageExpired") self.Channel_BanUser_Unban = getValue(dict, "Channel.BanUser.Unban") self.Stickers_GroupChooseStickerPack = getValue(dict, "Stickers.GroupChooseStickerPack") self.Group_Setup_TypePrivate = getValue(dict, "Group.Setup.TypePrivate") + self.Passport_Language_cs = getValue(dict, "Passport.Language.cs") self.Settings_LogoutConfirmationTitle = getValue(dict, "Settings.LogoutConfirmationTitle") self.UserInfo_FirstNamePlaceholder = getValue(dict, "UserInfo.FirstNamePlaceholder") + self.Passport_Identity_SurnamePlaceholder = getValue(dict, "Passport.Identity.SurnamePlaceholder") + self.Passport_Identity_FilesView = getValue(dict, "Passport.Identity.FilesView") self.LoginPassword_ResetAccount = getValue(dict, "LoginPassword.ResetAccount") self.Privacy_GroupsAndChannels_AlwaysAllow = getValue(dict, "Privacy.GroupsAndChannels.AlwaysAllow") self._Notification_JoinedChat = getValue(dict, "Notification.JoinedChat") self._Notification_JoinedChat_r = extractArgumentRanges(self._Notification_JoinedChat) + self.Notifications_ExceptionsUnmuted = getValue(dict, "Notifications.ExceptionsUnmuted") self.ChannelInfo_DeleteChannel = getValue(dict, "ChannelInfo.DeleteChannel") + self.Passport_Title = getValue(dict, "Passport.Title") self.NetworkUsageSettings_BytesReceived = getValue(dict, "NetworkUsageSettings.BytesReceived") self.BlockedUsers_BlockTitle = getValue(dict, "BlockedUsers.BlockTitle") + self.Update_Title = getValue(dict, "Update.Title") self.AccessDenied_PhotosAndVideos = getValue(dict, "AccessDenied.PhotosAndVideos") self.Channel_Username_Title = getValue(dict, "Channel.Username.Title") self._Channel_AdminLog_MessageToggleSignaturesOn = getValue(dict, "Channel.AdminLog.MessageToggleSignaturesOn") @@ -5217,6 +5689,7 @@ public final class PresentationStrings { self.Map_PullUpForPlaces = getValue(dict, "Map.PullUpForPlaces") self._Conversation_EncryptionWaiting = getValue(dict, "Conversation.EncryptionWaiting") self._Conversation_EncryptionWaiting_r = extractArgumentRanges(self._Conversation_EncryptionWaiting) + self.Passport_Language_ka = getValue(dict, "Passport.Language.ka") self.InfoPlist_NSSiriUsageDescription = getValue(dict, "InfoPlist.NSSiriUsageDescription") self.Calls_NotNow = getValue(dict, "Calls.NotNow") self.Conversation_Report = getValue(dict, "Conversation.Report") @@ -5224,7 +5697,9 @@ public final class PresentationStrings { self._CHANNEL_MESSAGE_DOC_r = extractArgumentRanges(self._CHANNEL_MESSAGE_DOC) self.Channel_AdminLogFilter_EventsAll = getValue(dict, "Channel.AdminLogFilter.EventsAll") self.InfoPlist_NSLocationWhenInUseUsageDescription = getValue(dict, "InfoPlist.NSLocationWhenInUseUsageDescription") + self.Passport_Address_TypeTemporaryRegistration = getValue(dict, "Passport.Address.TypeTemporaryRegistration") self.Call_ConnectionErrorTitle = getValue(dict, "Call.ConnectionErrorTitle") + self.Passport_Language_tr = getValue(dict, "Passport.Language.tr") self.Settings_ApplyProxyAlertEnable = getValue(dict, "Settings.ApplyProxyAlertEnable") self.Settings_ChatSettings = getValue(dict, "Settings.ChatSettings") self.Group_About_Help = getValue(dict, "Group.About.Help") @@ -5241,12 +5716,14 @@ public final class PresentationStrings { self._Notification_PinnedRoundMessage = getValue(dict, "Notification.PinnedRoundMessage") self._Notification_PinnedRoundMessage_r = extractArgumentRanges(self._Notification_PinnedRoundMessage) self.Conversation_ViewMessage = getValue(dict, "Conversation.ViewMessage") + self.Passport_FieldEmailHelp = getValue(dict, "Passport.FieldEmailHelp") self.Settings_SaveEditedPhotos = getValue(dict, "Settings.SaveEditedPhotos") self.Channel_Management_LabelCreator = getValue(dict, "Channel.Management.LabelCreator") self._Notification_PinnedStickerMessage = getValue(dict, "Notification.PinnedStickerMessage") self._Notification_PinnedStickerMessage_r = extractArgumentRanges(self._Notification_PinnedStickerMessage) self._AutoNightTheme_AutomaticHelp = getValue(dict, "AutoNightTheme.AutomaticHelp") self._AutoNightTheme_AutomaticHelp_r = extractArgumentRanges(self._AutoNightTheme_AutomaticHelp) + self.Passport_Address_EditPassportRegistration = getValue(dict, "Passport.Address.EditPassportRegistration") self.PhotoEditor_QualityTool = getValue(dict, "PhotoEditor.QualityTool") self.Login_NetworkError = getValue(dict, "Login.NetworkError") self.TwoStepAuth_EnterPasswordForgot = getValue(dict, "TwoStepAuth.EnterPasswordForgot") @@ -5260,6 +5737,7 @@ public final class PresentationStrings { self.GroupInfo_AddParticipantTitle = getValue(dict, "GroupInfo.AddParticipantTitle") self.Map_LiveLocationShowAll = getValue(dict, "Map.LiveLocationShowAll") self.Settings_SavedMessages = getValue(dict, "Settings.SavedMessages") + self.Passport_FieldIdentitySelfieHelp = getValue(dict, "Passport.FieldIdentitySelfieHelp") self._CHANNEL_MESSAGE_TEXT = getValue(dict, "CHANNEL_MESSAGE_TEXT") self._CHANNEL_MESSAGE_TEXT_r = extractArgumentRanges(self._CHANNEL_MESSAGE_TEXT) self.Checkout_PayNone = getValue(dict, "Checkout.PayNone") @@ -5268,6 +5746,7 @@ public final class PresentationStrings { self.Settings_Username = getValue(dict, "Settings.Username") self.Notification_CallMissedShort = getValue(dict, "Notification.CallMissedShort") self.Call_CallInProgressTitle = getValue(dict, "Call.CallInProgressTitle") + self.Passport_Scans = getValue(dict, "Passport.Scans") self.PhotoEditor_Skip = getValue(dict, "PhotoEditor.Skip") self.AuthSessions_TerminateOtherSessionsHelp = getValue(dict, "AuthSessions.TerminateOtherSessionsHelp") self.Call_AudioRouteHeadphones = getValue(dict, "Call.AudioRouteHeadphones") @@ -5283,7 +5762,9 @@ public final class PresentationStrings { self._MESSAGE_GEO_r = extractArgumentRanges(self._MESSAGE_GEO) self.Privacy_Calls = getValue(dict, "Privacy.Calls") self.DialogList_AdLabel = getValue(dict, "DialogList.AdLabel") + self.Passport_Identity_ScansHelp = getValue(dict, "Passport.Identity.ScansHelp") self.Channel_AdminLogFilter_EventsInfo = getValue(dict, "Channel.AdminLogFilter.EventsInfo") + self.Passport_Language_hu = getValue(dict, "Passport.Language.hu") self._Channel_AdminLog_MessagePinned = getValue(dict, "Channel.AdminLog.MessagePinned") self._Channel_AdminLog_MessagePinned_r = extractArgumentRanges(self._Channel_AdminLog_MessagePinned) self._Channel_AdminLog_MessageToggleInvitesOn = getValue(dict, "Channel.AdminLog.MessageToggleInvitesOn") @@ -5299,13 +5780,16 @@ public final class PresentationStrings { self._Date_ChatDateHeaderYear = getValue(dict, "Date.ChatDateHeaderYear") self._Date_ChatDateHeaderYear_r = extractArgumentRanges(self._Date_ChatDateHeaderYear) self.Privacy_Calls_P2PContacts = getValue(dict, "Privacy.Calls.P2PContacts") + self.Passport_Email_Delete = getValue(dict, "Passport.Email.Delete") self.CheckoutInfo_ShippingInfoCountry = getValue(dict, "CheckoutInfo.ShippingInfoCountry") self.Map_ShowPlaces = getValue(dict, "Map.ShowPlaces") + self.Passport_Identity_GenderFemale = getValue(dict, "Passport.Identity.GenderFemale") self.Camera_VideoMode = getValue(dict, "Camera.VideoMode") self._Watch_Time_ShortFullAt = getValue(dict, "Watch.Time.ShortFullAt") self._Watch_Time_ShortFullAt_r = extractArgumentRanges(self._Watch_Time_ShortFullAt) self.UserInfo_TelegramCall = getValue(dict, "UserInfo.TelegramCall") self.PrivacyLastSeenSettings_CustomShareSettingsHelp = getValue(dict, "PrivacyLastSeenSettings.CustomShareSettingsHelp") + self.Passport_UpdateRequiredError = getValue(dict, "Passport.UpdateRequiredError") self.Channel_AdminLog_InfoPanelAlertText = getValue(dict, "Channel.AdminLog.InfoPanelAlertText") self._Channel_AdminLog_MessageUnpinned = getValue(dict, "Channel.AdminLog.MessageUnpinned") self._Channel_AdminLog_MessageUnpinned_r = extractArgumentRanges(self._Channel_AdminLog_MessageUnpinned) @@ -5314,6 +5798,7 @@ public final class PresentationStrings { self.PhotoEditor_QualityMedium = getValue(dict, "PhotoEditor.QualityMedium") self.Privacy_PaymentsClearInfo = getValue(dict, "Privacy.PaymentsClearInfo") self.PhotoEditor_CurvesRed = getValue(dict, "PhotoEditor.CurvesRed") + self.Passport_Identity_AddPersonalDetails = getValue(dict, "Passport.Identity.AddPersonalDetails") self.ContactInfo_PhoneLabelWorkFax = getValue(dict, "ContactInfo.PhoneLabelWorkFax") self.Privacy_PaymentsTitle = getValue(dict, "Privacy.PaymentsTitle") self.SocksProxySetup_ProxyType = getValue(dict, "SocksProxySetup.ProxyType") @@ -5338,6 +5823,7 @@ public final class PresentationStrings { self.ChatAdmins_AdminLabel = getValue(dict, "ChatAdmins.AdminLabel") self.Contacts_FailedToSendInvitesMessage = getValue(dict, "Contacts.FailedToSendInvitesMessage") self.Login_Code = getValue(dict, "Login.Code") + self.Passport_Identity_ExpiryDateNone = getValue(dict, "Passport.Identity.ExpiryDateNone") self.Channel_Username_InvalidCharacters = getValue(dict, "Channel.Username.InvalidCharacters") self.FeatureDisabled_Oops = getValue(dict, "FeatureDisabled.Oops") self.Calls_CallTabTitle = getValue(dict, "Calls.CallTabTitle") @@ -5345,24 +5831,30 @@ public final class PresentationStrings { self.WatchRemote_AlertTitle = getValue(dict, "WatchRemote.AlertTitle") self.Channel_Members_AddBannedErrorAdmin = getValue(dict, "Channel.Members.AddBannedErrorAdmin") self.Conversation_InfoGroup = getValue(dict, "Conversation.InfoGroup") + self.Passport_Identity_TypePersonalDetails = getValue(dict, "Passport.Identity.TypePersonalDetails") + self.Passport_Identity_OneOfTypePassport = getValue(dict, "Passport.Identity.OneOfTypePassport") self.Checkout_Phone = getValue(dict, "Checkout.Phone") self.Channel_SignMessages_Help = getValue(dict, "Channel.SignMessages.Help") + self.Passport_PasswordNext = getValue(dict, "Passport.PasswordNext") self.Calls_SubmitRating = getValue(dict, "Calls.SubmitRating") self.Camera_FlashOn = getValue(dict, "Camera.FlashOn") self.Watch_MessageView_Forward = getValue(dict, "Watch.MessageView.Forward") + self.Passport_DiscardMessageTitle = getValue(dict, "Passport.DiscardMessageTitle") + self.Passport_Language_uk = getValue(dict, "Passport.Language.uk") self.GroupInfo_ActionPromote = getValue(dict, "GroupInfo.ActionPromote") self.DialogList_You = getValue(dict, "DialogList.You") + self.Passport_Identity_SelfieHelp = getValue(dict, "Passport.Identity.SelfieHelp") + self.Passport_Identity_MiddleName = getValue(dict, "Passport.Identity.MiddleName") self.AccessDenied_Camera = getValue(dict, "AccessDenied.Camera") self.WatchRemote_NotificationText = getValue(dict, "WatchRemote.NotificationText") self.SharedMedia_ViewInChat = getValue(dict, "SharedMedia.ViewInChat") - self.SecureId_FormRequestedTitle = getValue(dict, "SecureId.FormRequestedTitle") self.Activity_RecordingAudio = getValue(dict, "Activity.RecordingAudio") self.Watch_Stickers_StickerPacks = getValue(dict, "Watch.Stickers.StickerPacks") self._Target_ShareGameConfirmationPrivate = getValue(dict, "Target.ShareGameConfirmationPrivate") self._Target_ShareGameConfirmationPrivate_r = extractArgumentRanges(self._Target_ShareGameConfirmationPrivate) self.Checkout_NewCard_PostcodePlaceholder = getValue(dict, "Checkout.NewCard.PostcodePlaceholder") + self.Passport_Identity_OneOfTypeInternalPassport = getValue(dict, "Passport.Identity.OneOfTypeInternalPassport") self.DialogList_DeleteConversationConfirmation = getValue(dict, "DialogList.DeleteConversationConfirmation") - self.PrivacySettings_DeleteContactsSuccess = getValue(dict, "PrivacySettings.DeleteContactsSuccess") self.AttachmentMenu_SendAsFile = getValue(dict, "AttachmentMenu.SendAsFile") self.Watch_Conversation_Unblock = getValue(dict, "Watch.Conversation.Unblock") self.Channel_AdminLog_MessagePreviousLink = getValue(dict, "Channel.AdminLog.MessagePreviousLink") @@ -5371,18 +5863,25 @@ public final class PresentationStrings { self.PrivacyLastSeenSettings_NeverShareWith = getValue(dict, "PrivacyLastSeenSettings.NeverShareWith") self.ConvertToSupergroup_HelpText = getValue(dict, "ConvertToSupergroup.HelpText") self.MediaPicker_VideoMuteDescription = getValue(dict, "MediaPicker.VideoMuteDescription") + self.Passport_Address_TypeRentalAgreement = getValue(dict, "Passport.Address.TypeRentalAgreement") + self.Passport_Language_it = getValue(dict, "Passport.Language.it") self.UserInfo_ShareMyContactInfo = getValue(dict, "UserInfo.ShareMyContactInfo") self.Channel_Info_Stickers = getValue(dict, "Channel.Info.Stickers") self.Appearance_ColorTheme = getValue(dict, "Appearance.ColorTheme") self._FileSize_GB = getValue(dict, "FileSize.GB") self._FileSize_GB_r = extractArgumentRanges(self._FileSize_GB) + self._Passport_FieldOneOf_Or = getValue(dict, "Passport.FieldOneOf.Or") + self._Passport_FieldOneOf_Or_r = extractArgumentRanges(self._Passport_FieldOneOf_Or) self.Month_ShortJanuary = getValue(dict, "Month.ShortJanuary") self.Channel_BanUser_PermissionsHeader = getValue(dict, "Channel.BanUser.PermissionsHeader") self.PhotoEditor_QualityVeryHigh = getValue(dict, "PhotoEditor.QualityVeryHigh") + self.Passport_Language_mk = getValue(dict, "Passport.Language.mk") self.Login_TermsOfServiceLabel = getValue(dict, "Login.TermsOfServiceLabel") self._MESSAGE_TEXT = getValue(dict, "MESSAGE_TEXT") self._MESSAGE_TEXT_r = extractArgumentRanges(self._MESSAGE_TEXT) self.DialogList_NoMessagesTitle = getValue(dict, "DialogList.NoMessagesTitle") + self.Passport_DeletePassportConfirmation = getValue(dict, "Passport.DeletePassportConfirmation") + self.Passport_Language_az = getValue(dict, "Passport.Language.az") self.AccessDenied_Contacts = getValue(dict, "AccessDenied.Contacts") self.Your_cards_security_code_is_invalid = getValue(dict, "Your_cards_security_code_is_invalid") self.Contacts_InviteSearchLabel = getValue(dict, "Contacts.InviteSearchLabel") @@ -5401,8 +5900,10 @@ public final class PresentationStrings { self.Calls_AddTab = getValue(dict, "Calls.AddTab") self.DialogList_AdNoticeAlert = getValue(dict, "DialogList.AdNoticeAlert") self.PhotoEditor_TiltShift = getValue(dict, "PhotoEditor.TiltShift") + self.Passport_Identity_TypeDriversLicenseUploadScan = getValue(dict, "Passport.Identity.TypeDriversLicenseUploadScan") self.ChannelMembers_WhoCanAddMembers_Admins = getValue(dict, "ChannelMembers.WhoCanAddMembers.Admins") self.Tour_Text5 = getValue(dict, "Tour.Text5") + self.Notifications_ExceptionsGroupPlaceholder = getValue(dict, "Notifications.ExceptionsGroupPlaceholder") self.Watch_Stickers_RecentPlaceholder = getValue(dict, "Watch.Stickers.RecentPlaceholder") self.Common_Select = getValue(dict, "Common.Select") self._Notification_MessageLifetimeRemoved = getValue(dict, "Notification.MessageLifetimeRemoved") @@ -5414,22 +5915,25 @@ public final class PresentationStrings { self.FastTwoStepSetup_EmailHelp = getValue(dict, "FastTwoStepSetup.EmailHelp") self.Month_GenOctober = getValue(dict, "Month.GenOctober") self.CheckoutInfo_ErrorPhoneInvalid = getValue(dict, "CheckoutInfo.ErrorPhoneInvalid") + self.Passport_Identity_DocumentNumberPlaceholder = getValue(dict, "Passport.Identity.DocumentNumberPlaceholder") self.AutoNightTheme_UpdateLocation = getValue(dict, "AutoNightTheme.UpdateLocation") self.Group_Setup_TypePublic = getValue(dict, "Group.Setup.TypePublic") self.Checkout_PaymentMethod_New = getValue(dict, "Checkout.PaymentMethod.New") self.ShareMenu_Comment = getValue(dict, "ShareMenu.Comment") + self.Passport_FloodError = getValue(dict, "Passport.FloodError") self.Channel_Management_LabelEditor = getValue(dict, "Channel.Management.LabelEditor") self.TwoStepAuth_SetPasswordHelp = getValue(dict, "TwoStepAuth.SetPasswordHelp") self.Channel_AdminLogFilter_EventsTitle = getValue(dict, "Channel.AdminLogFilter.EventsTitle") self.NotificationSettings_ContactJoined = getValue(dict, "NotificationSettings.ContactJoined") self.ChatSettings_AutoDownloadVideos = getValue(dict, "ChatSettings.AutoDownloadVideos") + self.Passport_Identity_TypeIdentityCard = getValue(dict, "Passport.Identity.TypeIdentityCard") self.Username_LinkCopied = getValue(dict, "Username.LinkCopied") self._Time_MonthOfYear_m9 = getValue(dict, "Time.MonthOfYear_m9") self._Time_MonthOfYear_m9_r = extractArgumentRanges(self._Time_MonthOfYear_m9) self.Channel_EditAdmin_PermissionAddAdmins = getValue(dict, "Channel.EditAdmin.PermissionAddAdmins") + self.Passport_FieldPhoneHelp = getValue(dict, "Passport.FieldPhoneHelp") self.Conversation_SendMessage = getValue(dict, "Conversation.SendMessage") self.Notification_CallIncoming = getValue(dict, "Notification.CallIncoming") - self.PrivacySettings_SuggestFrequentContacts = getValue(dict, "PrivacySettings.SuggestFrequentContacts") self._MESSAGE_FWDS = getValue(dict, "MESSAGE_FWDS") self._MESSAGE_FWDS_r = extractArgumentRanges(self._MESSAGE_FWDS) self.Map_OpenInYandexMaps = getValue(dict, "Map.OpenInYandexMaps") @@ -5445,9 +5949,9 @@ public final class PresentationStrings { self.Checkout_ErrorPaymentFailed = getValue(dict, "Checkout.ErrorPaymentFailed") self.Compose_NewMessage = getValue(dict, "Compose.NewMessage") self.Conversation_LiveLocationYou = getValue(dict, "Conversation.LiveLocationYou") + self.Privacy_TopPeersHelp = getValue(dict, "Privacy.TopPeersHelp") self.Map_OpenInWaze = getValue(dict, "Map.OpenInWaze") self.Checkout_ShippingMethod = getValue(dict, "Checkout.ShippingMethod") - self.SecureId_FormFieldIdentity = getValue(dict, "SecureId.FormFieldIdentity") self.Login_InfoFirstNamePlaceholder = getValue(dict, "Login.InfoFirstNamePlaceholder") self.Checkout_ErrorProviderAccountInvalid = getValue(dict, "Checkout.ErrorProviderAccountInvalid") self.CallSettings_TabIconDescription = getValue(dict, "CallSettings.TabIconDescription") @@ -5456,6 +5960,7 @@ public final class PresentationStrings { self.PasscodeSettings_AutoLock = getValue(dict, "PasscodeSettings.AutoLock") self.Notifications_MessageNotificationsPreview = getValue(dict, "Notifications.MessageNotificationsPreview") self.Conversation_BlockUser = getValue(dict, "Conversation.BlockUser") + self.Passport_Identity_EditPassport = getValue(dict, "Passport.Identity.EditPassport") self.MessageTimer_Custom = getValue(dict, "MessageTimer.Custom") self.Conversation_SilentBroadcastTooltipOff = getValue(dict, "Conversation.SilentBroadcastTooltipOff") self.Conversation_Mute = getValue(dict, "Conversation.Mute") @@ -5475,7 +5980,9 @@ public final class PresentationStrings { self.Common_TakePhotoOrVideo = getValue(dict, "Common.TakePhotoOrVideo") self.Notification_MessageLifetime2s = getValue(dict, "Notification.MessageLifetime2s") self.Checkout_ErrorGeneric = getValue(dict, "Checkout.ErrorGeneric") + self.DialogList_Unread = getValue(dict, "DialogList.Unread") self.AutoNightTheme_Automatic = getValue(dict, "AutoNightTheme.Automatic") + self.Passport_Identity_Name = getValue(dict, "Passport.Identity.Name") self.Channel_AdminLog_CanBanUsers = getValue(dict, "Channel.AdminLog.CanBanUsers") self.Cache_Indexing = getValue(dict, "Cache.Indexing") self._ENCRYPTION_REQUEST = getValue(dict, "ENCRYPTION_REQUEST") @@ -5484,19 +5991,22 @@ public final class PresentationStrings { self.Channel_BanUser_PermissionEmbedLinks = getValue(dict, "Channel.BanUser.PermissionEmbedLinks") self.Map_Location = getValue(dict, "Map.Location") self.GroupInfo_InviteLink_LinkSection = getValue(dict, "GroupInfo.InviteLink.LinkSection") + self._Passport_Identity_UploadOneOfScan = getValue(dict, "Passport.Identity.UploadOneOfScan") + self._Passport_Identity_UploadOneOfScan_r = extractArgumentRanges(self._Passport_Identity_UploadOneOfScan) + self.Notification_PassportValuePhone = getValue(dict, "Notification.PassportValuePhone") self.Privacy_Calls_AlwaysAllow_Placeholder = getValue(dict, "Privacy.Calls.AlwaysAllow.Placeholder") self.CheckoutInfo_ShippingInfoPostcode = getValue(dict, "CheckoutInfo.ShippingInfoPostcode") self.Group_Setup_HistoryVisibleHelp = getValue(dict, "Group.Setup.HistoryVisibleHelp") self._Time_PreciseDate_m7 = getValue(dict, "Time.PreciseDate_m7") self._Time_PreciseDate_m7_r = extractArgumentRanges(self._Time_PreciseDate_m7) self.PasscodeSettings_EncryptDataHelp = getValue(dict, "PasscodeSettings.EncryptDataHelp") + self.Passport_Language_ja = getValue(dict, "Passport.Language.ja") self.KeyCommand_FocusOnInputField = getValue(dict, "KeyCommand.FocusOnInputField") self.Channel_Members_AddAdminErrorBlacklisted = getValue(dict, "Channel.Members.AddAdminErrorBlacklisted") self.Cache_KeepMedia = getValue(dict, "Cache.KeepMedia") self.SocksProxySetup_ProxyTelegram = getValue(dict, "SocksProxySetup.ProxyTelegram") self.WebPreview_GettingLinkInfo = getValue(dict, "WebPreview.GettingLinkInfo") self.Group_Setup_TypePublicHelp = getValue(dict, "Group.Setup.TypePublicHelp") - self.Login_PRIVACY_URL = getValue(dict, "Login.PRIVACY_URL") self.Map_Satellite = getValue(dict, "Map.Satellite") self.Username_InvalidTaken = getValue(dict, "Username.InvalidTaken") self._Notification_PinnedAudioMessage = getValue(dict, "Notification.PinnedAudioMessage") @@ -5514,6 +6024,7 @@ public final class PresentationStrings { self._Time_PreciseDate_m10_r = extractArgumentRanges(self._Time_PreciseDate_m10) self._CHANNEL_MESSAGE_CONTACT = getValue(dict, "CHANNEL_MESSAGE_CONTACT") self._CHANNEL_MESSAGE_CONTACT_r = extractArgumentRanges(self._CHANNEL_MESSAGE_CONTACT) + self.Passport_Language_bg = getValue(dict, "Passport.Language.bg") self.PrivacySettings_DeleteAccountHelp = getValue(dict, "PrivacySettings.DeleteAccountHelp") self.Channel_Info_Banned = getValue(dict, "Channel.Info.Banned") self.Conversation_ShareBotContactConfirmationTitle = getValue(dict, "Conversation.ShareBotContactConfirmationTitle") @@ -5525,6 +6036,8 @@ public final class PresentationStrings { self.FastTwoStepSetup_EmailPlaceholder = getValue(dict, "FastTwoStepSetup.EmailPlaceholder") self._DialogList_MultipleTypingSuffix = getValue(dict, "DialogList.MultipleTypingSuffix") self._DialogList_MultipleTypingSuffix_r = extractArgumentRanges(self._DialogList_MultipleTypingSuffix) + self.Passport_Phone_Help = getValue(dict, "Passport.Phone.Help") + self.Passport_Language_sl = getValue(dict, "Passport.Language.sl") self.Bot_GenericBotStatus = getValue(dict, "Bot.GenericBotStatus") self.PrivacySettings_PasscodeAndTouchId = getValue(dict, "PrivacySettings.PasscodeAndTouchId") self.Common_edit = getValue(dict, "Common.edit") @@ -5533,23 +6046,34 @@ public final class PresentationStrings { self._Notification_Kicked = getValue(dict, "Notification.Kicked") self._Notification_Kicked_r = extractArgumentRanges(self._Notification_Kicked) self.Channel_AdminLog_MessageRestrictedForever = getValue(dict, "Channel.AdminLog.MessageRestrictedForever") + self.Passport_DeleteDocument = getValue(dict, "Passport.DeleteDocument") self.ChannelInfo_DeleteChannelConfirmation = getValue(dict, "ChannelInfo.DeleteChannelConfirmation") + self.Passport_Address_OneOfTypeBankStatement = getValue(dict, "Passport.Address.OneOfTypeBankStatement") self.Weekday_ShortSaturday = getValue(dict, "Weekday.ShortSaturday") + self.Settings_Passport = getValue(dict, "Settings.Passport") self.Map_SendThisLocation = getValue(dict, "Map.SendThisLocation") self._Notification_PinnedDocumentMessage = getValue(dict, "Notification.PinnedDocumentMessage") self._Notification_PinnedDocumentMessage_r = extractArgumentRanges(self._Notification_PinnedDocumentMessage) + self.Passport_Identity_Surname = getValue(dict, "Passport.Identity.Surname") self.Conversation_ContextMenuReply = getValue(dict, "Conversation.ContextMenuReply") self.Channel_BanUser_PermissionSendMedia = getValue(dict, "Channel.BanUser.PermissionSendMedia") self.NetworkUsageSettings_Wifi = getValue(dict, "NetworkUsageSettings.Wifi") self.Call_Accept = getValue(dict, "Call.Accept") self.GroupInfo_SetGroupPhotoDelete = getValue(dict, "GroupInfo.SetGroupPhotoDelete") self.Login_PhoneBannedError = getValue(dict, "Login.PhoneBannedError") + self.Passport_Identity_DocumentDetails = getValue(dict, "Passport.Identity.DocumentDetails") self.PhotoEditor_CropAuto = getValue(dict, "PhotoEditor.CropAuto") self.PhotoEditor_ContrastTool = getValue(dict, "PhotoEditor.ContrastTool") self.CheckoutInfo_ReceiverInfoNamePlaceholder = getValue(dict, "CheckoutInfo.ReceiverInfoNamePlaceholder") + self.Passport_InfoLearnMore = getValue(dict, "Passport.InfoLearnMore") self.Channel_AdminLog_MessagePreviousCaption = getValue(dict, "Channel.AdminLog.MessagePreviousCaption") + self._Passport_Email_UseTelegramEmail = getValue(dict, "Passport.Email.UseTelegramEmail") + self._Passport_Email_UseTelegramEmail_r = extractArgumentRanges(self._Passport_Email_UseTelegramEmail) self.Privacy_PaymentsClear_ShippingInfo = getValue(dict, "Privacy.PaymentsClear.ShippingInfo") + self.Passport_Email_UseTelegramEmailHelp = getValue(dict, "Passport.Email.UseTelegramEmailHelp") + self.UserInfo_NotificationsDefaultDisabled = getValue(dict, "UserInfo.NotificationsDefaultDisabled") self.Date_DialogDateFormat = getValue(dict, "Date.DialogDateFormat") + self.Passport_Address_EditTemporaryRegistration = getValue(dict, "Passport.Address.EditTemporaryRegistration") self.ReportPeer_ReasonSpam = getValue(dict, "ReportPeer.ReasonSpam") self.Privacy_Calls_P2P = getValue(dict, "Privacy.Calls.P2P") self.Compose_TokenListPlaceholder = getValue(dict, "Compose.TokenListPlaceholder") @@ -5558,16 +6082,21 @@ public final class PresentationStrings { self.StickerPacksSettings_Title = getValue(dict, "StickerPacksSettings.Title") self.Privacy_PaymentsClearInfoDoneHelp = getValue(dict, "Privacy.PaymentsClearInfoDoneHelp") self.Privacy_Calls_NeverAllow_Placeholder = getValue(dict, "Privacy.Calls.NeverAllow.Placeholder") + self.Passport_PassportInformation = getValue(dict, "Passport.PassportInformation") + self.Passport_Identity_OneOfTypeDriversLicense = getValue(dict, "Passport.Identity.OneOfTypeDriversLicense") self.Settings_Support = getValue(dict, "Settings.Support") self.Notification_GroupInviterSelf = getValue(dict, "Notification.GroupInviterSelf") self._SecretImage_NotViewedYet = getValue(dict, "SecretImage.NotViewedYet") self._SecretImage_NotViewedYet_r = extractArgumentRanges(self._SecretImage_NotViewedYet) self.MaskStickerSettings_Title = getValue(dict, "MaskStickerSettings.Title") self.TwoStepAuth_SetPassword = getValue(dict, "TwoStepAuth.SetPassword") + self._Passport_AcceptHelp = getValue(dict, "Passport.AcceptHelp") + self._Passport_AcceptHelp_r = extractArgumentRanges(self._Passport_AcceptHelp) self.SocksProxySetup_SavedProxies = getValue(dict, "SocksProxySetup.SavedProxies") self.GroupInfo_InviteLink_ShareLink = getValue(dict, "GroupInfo.InviteLink.ShareLink") self.Common_Cancel = getValue(dict, "Common.Cancel") self.UserInfo_About_Placeholder = getValue(dict, "UserInfo.About.Placeholder") + self.Passport_Identity_NativeNameGenericTitle = getValue(dict, "Passport.Identity.NativeNameGenericTitle") self.Camera_Discard = getValue(dict, "Camera.Discard") self.ChangePhoneNumberCode_RequestingACall = getValue(dict, "ChangePhoneNumberCode.RequestingACall") self.PrivacyLastSeenSettings_NeverShareWith_Title = getValue(dict, "PrivacyLastSeenSettings.NeverShareWith.Title") @@ -5577,19 +6106,24 @@ public final class PresentationStrings { self.Tour_Text1 = getValue(dict, "Tour.Text1") self.Privacy_SecretChatsTitle = getValue(dict, "Privacy.SecretChatsTitle") self.Conversation_HoldForVideo = getValue(dict, "Conversation.HoldForVideo") + self.Passport_Language_pt = getValue(dict, "Passport.Language.pt") self.Checkout_NewCard_Title = getValue(dict, "Checkout.NewCard.Title") self.Channel_TitleInfo = getValue(dict, "Channel.TitleInfo") self.State_ConnectingToProxy = getValue(dict, "State.ConnectingToProxy") self.Settings_About_Help = getValue(dict, "Settings.About.Help") self.AutoNightTheme_ScheduledFrom = getValue(dict, "AutoNightTheme.ScheduledFrom") + self.Passport_Language_tk = getValue(dict, "Passport.Language.tk") self.Watch_Conversation_Reply = getValue(dict, "Watch.Conversation.Reply") self.ShareMenu_CopyShareLink = getValue(dict, "ShareMenu.CopyShareLink") self.Stickers_Search = getValue(dict, "Stickers.Search") + self.Notifications_GroupNotificationsExceptions = getValue(dict, "Notifications.GroupNotificationsExceptions") self.Channel_Setup_TypePrivateHelp = getValue(dict, "Channel.Setup.TypePrivateHelp") self.PhotoEditor_GrainTool = getValue(dict, "PhotoEditor.GrainTool") self.Conversation_SearchByName_Placeholder = getValue(dict, "Conversation.SearchByName.Placeholder") self.Watch_Suggestion_TalkLater = getValue(dict, "Watch.Suggestion.TalkLater") self.TwoStepAuth_ChangeEmail = getValue(dict, "TwoStepAuth.ChangeEmail") + self.Passport_Identity_EditPersonalDetails = getValue(dict, "Passport.Identity.EditPersonalDetails") + self.Passport_FieldPhone = getValue(dict, "Passport.FieldPhone") self._ENCRYPTION_ACCEPT = getValue(dict, "ENCRYPTION_ACCEPT") self._ENCRYPTION_ACCEPT_r = extractArgumentRanges(self._ENCRYPTION_ACCEPT) self.NetworkUsageSettings_BytesSent = getValue(dict, "NetworkUsageSettings.BytesSent") @@ -5599,7 +6133,6 @@ public final class PresentationStrings { self._Notification_ChangedGroupName_r = extractArgumentRanges(self._Notification_ChangedGroupName) self._MESSAGE_VIDEO = getValue(dict, "MESSAGE_VIDEO") self._MESSAGE_VIDEO_r = extractArgumentRanges(self._MESSAGE_VIDEO) - self.TermsOfService_Title = getValue(dict, "TermsOfService.Title") self._Checkout_PayPrice = getValue(dict, "Checkout.PayPrice") self._Checkout_PayPrice_r = extractArgumentRanges(self._Checkout_PayPrice) self._Notification_PinnedTextMessage = getValue(dict, "Notification.PinnedTextMessage") @@ -5612,6 +6145,7 @@ public final class PresentationStrings { self.ChatSettings_ConnectionType_UseProxy = getValue(dict, "ChatSettings.ConnectionType.UseProxy") self.Message_Audio = getValue(dict, "Message.Audio") self.Conversation_SearchNoResults = getValue(dict, "Conversation.SearchNoResults") + self.PrivacyPolicy_Accept = getValue(dict, "PrivacyPolicy.Accept") self.ReportPeer_ReasonViolence = getValue(dict, "ReportPeer.ReasonViolence") self.Group_Username_RemoveExistingUsernamesInfo = getValue(dict, "Group.Username.RemoveExistingUsernamesInfo") self.Message_InvoiceLabel = getValue(dict, "Message.InvoiceLabel") @@ -5637,14 +6171,18 @@ public final class PresentationStrings { self.ConversationMedia_Title = getValue(dict, "ConversationMedia.Title") self._Conversation_MessageViaUser = getValue(dict, "Conversation.MessageViaUser") self._Conversation_MessageViaUser_r = extractArgumentRanges(self._Conversation_MessageViaUser) + self.Notification_PassportValueAddress = getValue(dict, "Notification.PassportValueAddress") self.Tour_Title4 = getValue(dict, "Tour.Title4") self.Call_StatusEnded = getValue(dict, "Call.StatusEnded") self.LiveLocationUpdated_JustNow = getValue(dict, "LiveLocationUpdated.JustNow") self._Login_BannedPhoneSubject = getValue(dict, "Login.BannedPhoneSubject") self._Login_BannedPhoneSubject_r = extractArgumentRanges(self._Login_BannedPhoneSubject) + self.Passport_Address_EditResidentialAddress = getValue(dict, "Passport.Address.EditResidentialAddress") self._Channel_Management_RestrictedBy = getValue(dict, "Channel.Management.RestrictedBy") self._Channel_Management_RestrictedBy_r = extractArgumentRanges(self._Channel_Management_RestrictedBy) self.Conversation_UnpinMessageAlert = getValue(dict, "Conversation.UnpinMessageAlert") + self.NotificationsSound_Glass = getValue(dict, "NotificationsSound.Glass") + self.Passport_Address_Street1Placeholder = getValue(dict, "Passport.Address.Street1Placeholder") self._Conversation_MessageDialogRetryAll = getValue(dict, "Conversation.MessageDialogRetryAll") self._Conversation_MessageDialogRetryAll_r = extractArgumentRanges(self._Conversation_MessageDialogRetryAll) self._Checkout_PasswordEntry_Text = getValue(dict, "Checkout.PasswordEntry.Text") @@ -5669,24 +6207,25 @@ public final class PresentationStrings { self._DialogList_SinglePlayingGameSuffix_r = extractArgumentRanges(self._DialogList_SinglePlayingGameSuffix) self.AttachmentMenu_SendAsFiles = getValue(dict, "AttachmentMenu.SendAsFiles") self.Profile_MessageLifetime1m = getValue(dict, "Profile.MessageLifetime1m") + self.Passport_PasswordReset = getValue(dict, "Passport.PasswordReset") self.Settings_AppleWatch = getValue(dict, "Settings.AppleWatch") + self.Notifications_ExceptionsTitle = getValue(dict, "Notifications.ExceptionsTitle") + self.Passport_Language_de = getValue(dict, "Passport.Language.de") self.Channel_AdminLog_MessagePreviousDescription = getValue(dict, "Channel.AdminLog.MessagePreviousDescription") - self._SecureId_FormPolicyLink = getValue(dict, "SecureId.FormPolicyLink") - self._SecureId_FormPolicyLink_r = extractArgumentRanges(self._SecureId_FormPolicyLink) self.Your_card_was_declined = getValue(dict, "Your_card_was_declined") self.PhoneNumberHelp_ChangeNumber = getValue(dict, "PhoneNumberHelp.ChangeNumber") self.ReportPeer_ReasonPornography = getValue(dict, "ReportPeer.ReasonPornography") self.Notification_CreatedChannel = getValue(dict, "Notification.CreatedChannel") self.PhotoEditor_Original = getValue(dict, "PhotoEditor.Original") - self.TermsOfService_DeclineAndDelete = getValue(dict, "TermsOfService.DeclineAndDelete") + self.NotificationsSound_Chord = getValue(dict, "NotificationsSound.Chord") self.Target_SelectGroup = getValue(dict, "Target.SelectGroup") self.Stickers_SuggestAdded = getValue(dict, "Stickers.SuggestAdded") self.Channel_AdminLog_InfoPanelAlertTitle = getValue(dict, "Channel.AdminLog.InfoPanelAlertTitle") self.Notifications_GroupNotificationsPreview = getValue(dict, "Notifications.GroupNotificationsPreview") self.ChatSettings_AutoDownloadPhotos = getValue(dict, "ChatSettings.AutoDownloadPhotos") - self.SecureId_FormFieldEmail = getValue(dict, "SecureId.FormFieldEmail") self.Message_PinnedLocationMessage = getValue(dict, "Message.PinnedLocationMessage") self.Appearance_PreviewReplyText = getValue(dict, "Appearance.PreviewReplyText") + self.Passport_Address_Street2Placeholder = getValue(dict, "Passport.Address.Street2Placeholder") self.Settings_Logout = getValue(dict, "Settings.Logout") self._UserInfo_BlockConfirmation = getValue(dict, "UserInfo.BlockConfirmation") self._UserInfo_BlockConfirmation_r = extractArgumentRanges(self._UserInfo_BlockConfirmation) @@ -5695,19 +6234,25 @@ public final class PresentationStrings { self.Appearance_AutoNightTheme = getValue(dict, "Appearance.AutoNightTheme") self.AuthSessions_TerminateOtherSessions = getValue(dict, "AuthSessions.TerminateOtherSessions") self.PasscodeSettings_TryAgainIn1Minute = getValue(dict, "PasscodeSettings.TryAgainIn1Minute") + self.Privacy_TopPeers = getValue(dict, "Privacy.TopPeers") + self.Passport_Phone_EnterOtherNumber = getValue(dict, "Passport.Phone.EnterOtherNumber") + self.NotificationsSound_Hello = getValue(dict, "NotificationsSound.Hello") self.Notifications_InAppNotifications = getValue(dict, "Notifications.InAppNotifications") + self._Notification_PassportValuesSentMessage = getValue(dict, "Notification.PassportValuesSentMessage") + self._Notification_PassportValuesSentMessage_r = extractArgumentRanges(self._Notification_PassportValuesSentMessage) + self.Passport_Language_is = getValue(dict, "Passport.Language.is") self.StickerPack_ViewPack = getValue(dict, "StickerPack.ViewPack") self.EnterPasscode_ChangeTitle = getValue(dict, "EnterPasscode.ChangeTitle") self.Call_Decline = getValue(dict, "Call.Decline") self.UserInfo_AddPhone = getValue(dict, "UserInfo.AddPhone") self.AutoNightTheme_Title = getValue(dict, "AutoNightTheme.Title") - self.PrivacySettings_LinkPreviews = getValue(dict, "PrivacySettings.LinkPreviews") self.Activity_PlayingGame = getValue(dict, "Activity.PlayingGame") self.CheckoutInfo_ShippingInfoStatePlaceholder = getValue(dict, "CheckoutInfo.ShippingInfoStatePlaceholder") self.SaveIncomingPhotosSettings_From = getValue(dict, "SaveIncomingPhotosSettings.From") + self.Passport_Address_TypeBankStatementUploadScan = getValue(dict, "Passport.Address.TypeBankStatementUploadScan") self.Notifications_MessageNotificationsSound = getValue(dict, "Notifications.MessageNotificationsSound") self.Call_StatusWaiting = getValue(dict, "Call.StatusWaiting") - self.SecureId_FormFieldIdentityPlaceholder = getValue(dict, "SecureId.FormFieldIdentityPlaceholder") + self.Passport_Identity_MainPageHelp = getValue(dict, "Passport.Identity.MainPageHelp") self.Weekday_ShortWednesday = getValue(dict, "Weekday.ShortWednesday") self.Notifications_Title = getValue(dict, "Notifications.Title") self.PasscodeSettings_AutoLock_IfAwayFor_5hours = getValue(dict, "PasscodeSettings.AutoLock.IfAwayFor_5hours") @@ -5717,10 +6262,13 @@ public final class PresentationStrings { self._Time_MonthOfYear_m12_r = extractArgumentRanges(self._Time_MonthOfYear_m12) self.ConversationProfile_LeaveDeleteAndExit = getValue(dict, "ConversationProfile.LeaveDeleteAndExit") self.State_connecting = getValue(dict, "State.connecting") + self.Passport_Scans_Upload = getValue(dict, "Passport.Scans.Upload") + self.Passport_Identity_FrontSideHelp = getValue(dict, "Passport.Identity.FrontSideHelp") self.AutoDownloadSettings_PhotosTitle = getValue(dict, "AutoDownloadSettings.PhotosTitle") self.Map_OpenInHereMaps = getValue(dict, "Map.OpenInHereMaps") self.Stickers_FavoriteStickers = getValue(dict, "Stickers.FavoriteStickers") self.CheckoutInfo_Pay = getValue(dict, "CheckoutInfo.Pay") + self.Update_UpdateApp = getValue(dict, "Update.UpdateApp") self.Login_CountryCode = getValue(dict, "Login.CountryCode") self.PasscodeSettings_AutoLock_IfAwayFor_1hour = getValue(dict, "PasscodeSettings.AutoLock.IfAwayFor_1hour") self.CheckoutInfo_ShippingInfoState = getValue(dict, "CheckoutInfo.ShippingInfoState") @@ -5737,13 +6285,17 @@ public final class PresentationStrings { self.ChatSettings_ConnectionType_Title = getValue(dict, "ChatSettings.ConnectionType.Title") self._Conversation_RestrictedMediaTimed = getValue(dict, "Conversation.RestrictedMediaTimed") self._Conversation_RestrictedMediaTimed_r = extractArgumentRanges(self._Conversation_RestrictedMediaTimed) + self.NotificationsSound_Complete = getValue(dict, "NotificationsSound.Complete") + self.NotificationsSound_Chime = getValue(dict, "NotificationsSound.Chime") self.Login_InfoDeletePhoto = getValue(dict, "Login.InfoDeletePhoto") self.ContactInfo_BirthdayLabel = getValue(dict, "ContactInfo.BirthdayLabel") self.TwoStepAuth_RecoveryCodeExpired = getValue(dict, "TwoStepAuth.RecoveryCodeExpired") self.AutoDownloadSettings_Channels = getValue(dict, "AutoDownloadSettings.Channels") self.AutoDownloadSettings_Contacts = getValue(dict, "AutoDownloadSettings.Contacts") self.TwoStepAuth_EmailTitle = getValue(dict, "TwoStepAuth.EmailTitle") + self.Passport_Email_EmailPlaceholder = getValue(dict, "Passport.Email.EmailPlaceholder") self.Channel_AdminLog_ChannelEmptyText = getValue(dict, "Channel.AdminLog.ChannelEmptyText") + self.Passport_Address_EditUtilityBill = getValue(dict, "Passport.Address.EditUtilityBill") self.Privacy_GroupsAndChannels_NeverAllow = getValue(dict, "Privacy.GroupsAndChannels.NeverAllow") self.Conversation_RestrictedStickers = getValue(dict, "Conversation.RestrictedStickers") self.Conversation_AddContact = getValue(dict, "Conversation.AddContact") @@ -5753,8 +6305,11 @@ public final class PresentationStrings { self.Paint_Outlined = getValue(dict, "Paint.Outlined") self.State_ConnectingToProxyInfo = getValue(dict, "State.ConnectingToProxyInfo") self.Checkout_PasswordEntry_Title = getValue(dict, "Checkout.PasswordEntry.Title") + self.Conversation_InputTextCaptionPlaceholder = getValue(dict, "Conversation.InputTextCaptionPlaceholder") self.Common_Done = getValue(dict, "Common.Done") + self.Passport_Identity_FilesUploadNew = getValue(dict, "Passport.Identity.FilesUploadNew") self.PrivacySettings_LastSeenContacts = getValue(dict, "PrivacySettings.LastSeenContacts") + self.Passport_Language_vi = getValue(dict, "Passport.Language.vi") self.CheckoutInfo_ShippingInfoAddress1 = getValue(dict, "CheckoutInfo.ShippingInfoAddress1") self.UserInfo_LastNamePlaceholder = getValue(dict, "UserInfo.LastNamePlaceholder") self.Conversation_StatusKickedFromChannel = getValue(dict, "Conversation.StatusKickedFromChannel") @@ -5769,7 +6324,6 @@ public final class PresentationStrings { self.Privacy_Calls_NeverAllow = getValue(dict, "Privacy.Calls.NeverAllow") self.Settings_About_Title = getValue(dict, "Settings.About.Title") self.PhoneNumberHelp_Help = getValue(dict, "PhoneNumberHelp.Help") - self.PrivacySettings_SecretChats = getValue(dict, "PrivacySettings.SecretChats") self.Channel_LinkItem = getValue(dict, "Channel.LinkItem") self.Camera_Retake = getValue(dict, "Camera.Retake") self.StickerPack_ShowStickers = getValue(dict, "StickerPack.ShowStickers") @@ -5782,8 +6336,11 @@ public final class PresentationStrings { self._PrivacySettings_LastSeenContactsPlus_r = extractArgumentRanges(self._PrivacySettings_LastSeenContactsPlus) self.ChangePhoneNumberNumber_NewNumber = getValue(dict, "ChangePhoneNumberNumber.NewNumber") self.Compose_NewChannel = getValue(dict, "Compose.NewChannel") + self.NotificationsSound_Circles = getValue(dict, "NotificationsSound.Circles") self.Login_TermsOfServiceAgree = getValue(dict, "Login.TermsOfServiceAgree") self.Channel_AdminLog_CanChangeInviteLink = getValue(dict, "Channel.AdminLog.CanChangeInviteLink") + self._Passport_RequestHeader = getValue(dict, "Passport.RequestHeader") + self._Passport_RequestHeader_r = extractArgumentRanges(self._Passport_RequestHeader) self._Call_CallInProgressMessage = getValue(dict, "Call.CallInProgressMessage") self._Call_CallInProgressMessage_r = extractArgumentRanges(self._Call_CallInProgressMessage) self.Conversation_InputTextBroadcastPlaceholder = getValue(dict, "Conversation.InputTextBroadcastPlaceholder") @@ -5831,6 +6388,7 @@ public final class PresentationStrings { self.KeyCommand_ChatInfo = getValue(dict, "KeyCommand.ChatInfo") self.Channel_AdminLog_EmptyFilterTitle = getValue(dict, "Channel.AdminLog.EmptyFilterTitle") self.PhotoEditor_HighlightsTint = getValue(dict, "PhotoEditor.HighlightsTint") + self.Passport_Address_Region = getValue(dict, "Passport.Address.Region") self.Watch_Compose_AddContact = getValue(dict, "Watch.Compose.AddContact") self._Time_PreciseDate_m5 = getValue(dict, "Time.PreciseDate_m5") self._Time_PreciseDate_m5_r = extractArgumentRanges(self._Time_PreciseDate_m5) @@ -5840,11 +6398,13 @@ public final class PresentationStrings { self.Compose_NewEncryptedChat = getValue(dict, "Compose.NewEncryptedChat") self.PhotoEditor_CropReset = getValue(dict, "PhotoEditor.CropReset") self.Privacy_Calls_P2PAlways = getValue(dict, "Privacy.Calls.P2PAlways") + self.Passport_Address_TypeTemporaryRegistrationUploadScan = getValue(dict, "Passport.Address.TypeTemporaryRegistrationUploadScan") self.Login_InvalidLastNameError = getValue(dict, "Login.InvalidLastNameError") self.Channel_Members_AddMembers = getValue(dict, "Channel.Members.AddMembers") self.Tour_Title2 = getValue(dict, "Tour.Title2") self.Login_TermsOfServiceHeader = getValue(dict, "Login.TermsOfServiceHeader") self.Channel_AdminLog_BanSendGifs = getValue(dict, "Channel.AdminLog.BanSendGifs") + self.Login_TermsOfServiceSignupDecline = getValue(dict, "Login.TermsOfServiceSignupDecline") self.InfoPlist_NSMicrophoneUsageDescription = getValue(dict, "InfoPlist.NSMicrophoneUsageDescription") self.AuthSessions_OtherSessions = getValue(dict, "AuthSessions.OtherSessions") self.Watch_UserInfo_Title = getValue(dict, "Watch.UserInfo.Title") @@ -5855,6 +6415,7 @@ public final class PresentationStrings { self.NetworkUsageSettings_GeneralDataSection = getValue(dict, "NetworkUsageSettings.GeneralDataSection") self.EnterPasscode_RepeatNewPasscode = getValue(dict, "EnterPasscode.RepeatNewPasscode") self.Conversation_ContextMenuCopyLink = getValue(dict, "Conversation.ContextMenuCopyLink") + self.Passport_Language_sk = getValue(dict, "Passport.Language.sk") self.InstantPage_AutoNightTheme = getValue(dict, "InstantPage.AutoNightTheme") self.CloudStorage_Title = getValue(dict, "CloudStorage.Title") self.Month_ShortOctober = getValue(dict, "Month.ShortOctober") @@ -5865,6 +6426,7 @@ public final class PresentationStrings { self.Conversation_ContextMenuDelete = getValue(dict, "Conversation.ContextMenuDelete") self.Tour_Text6 = getValue(dict, "Tour.Text6") self.PhotoEditor_WarmthTool = getValue(dict, "PhotoEditor.WarmthTool") + self.Passport_Address_TypePassportRegistrationUploadScan = getValue(dict, "Passport.Address.TypePassportRegistrationUploadScan") self.Common_TakePhoto = getValue(dict, "Common.TakePhoto") self.SocksProxySetup_AdNoticeHelp = getValue(dict, "SocksProxySetup.AdNoticeHelp") self.UserInfo_CreateNewContact = getValue(dict, "UserInfo.CreateNewContact") @@ -5877,15 +6439,17 @@ public final class PresentationStrings { self.Group_ErrorSendRestrictedMedia = getValue(dict, "Group.ErrorSendRestrictedMedia") self.Group_Setup_HistoryVisible = getValue(dict, "Group.Setup.HistoryVisible") self.Channel_EditAdmin_PermissinAddAdminOff = getValue(dict, "Channel.EditAdmin.PermissinAddAdminOff") + self.DialogList_ProxyConnectionIssuesTooltip = getValue(dict, "DialogList.ProxyConnectionIssuesTooltip") self.Cache_Files = getValue(dict, "Cache.Files") self.PhotoEditor_EnhanceTool = getValue(dict, "PhotoEditor.EnhanceTool") self.Conversation_SearchPlaceholder = getValue(dict, "Conversation.SearchPlaceholder") self.Channel_Stickers_NotFound = getValue(dict, "Channel.Stickers.NotFound") + self.UserInfo_NotificationsDefaultEnabled = getValue(dict, "UserInfo.NotificationsDefaultEnabled") self.WatchRemote_AlertText = getValue(dict, "WatchRemote.AlertText") - self.SecureId_Title = getValue(dict, "SecureId.Title") self.Channel_AdminLog_CanInviteUsers = getValue(dict, "Channel.AdminLog.CanInviteUsers") self.Channel_BanUser_PermissionReadMessages = getValue(dict, "Channel.BanUser.PermissionReadMessages") self.AttachmentMenu_PhotoOrVideo = getValue(dict, "AttachmentMenu.PhotoOrVideo") + self.Passport_Identity_GenderPlaceholder = getValue(dict, "Passport.Identity.GenderPlaceholder") self.Month_ShortMarch = getValue(dict, "Month.ShortMarch") self.GroupInfo_InviteLink_Title = getValue(dict, "GroupInfo.InviteLink.Title") self.Watch_LastSeen_JustNow = getValue(dict, "Watch.LastSeen.JustNow") @@ -5900,10 +6464,12 @@ public final class PresentationStrings { self.Weekday_ShortThursday = getValue(dict, "Weekday.ShortThursday") self.UserInfo_ShareContact = getValue(dict, "UserInfo.ShareContact") self.LoginPassword_InvalidPasswordError = getValue(dict, "LoginPassword.InvalidPasswordError") + self.NotificationsSound_Calypso = getValue(dict, "NotificationsSound.Calypso") self._MESSAGE_PHOTO_SECRET = getValue(dict, "MESSAGE_PHOTO_SECRET") self._MESSAGE_PHOTO_SECRET_r = extractArgumentRanges(self._MESSAGE_PHOTO_SECRET) self.Login_PhoneAndCountryHelp = getValue(dict, "Login.PhoneAndCountryHelp") self.CheckoutInfo_ReceiverInfoName = getValue(dict, "CheckoutInfo.ReceiverInfoName") + self.NotificationsSound_Popcorn = getValue(dict, "NotificationsSound.Popcorn") self._Time_YesterdayAt = getValue(dict, "Time.YesterdayAt") self._Time_YesterdayAt_r = extractArgumentRanges(self._Time_YesterdayAt) self.Weekday_Yesterday = getValue(dict, "Weekday.Yesterday") @@ -5929,32 +6495,45 @@ public final class PresentationStrings { self._Notification_LeftChannel = getValue(dict, "Notification.LeftChannel") self._Notification_LeftChannel_r = extractArgumentRanges(self._Notification_LeftChannel) self.Compose_Create = getValue(dict, "Compose.Create") + self._Passport_Identity_NativeNameGenericHelp = getValue(dict, "Passport.Identity.NativeNameGenericHelp") + self._Passport_Identity_NativeNameGenericHelp_r = extractArgumentRanges(self._Passport_Identity_NativeNameGenericHelp) self._LOCKED_MESSAGE = getValue(dict, "LOCKED_MESSAGE") self._LOCKED_MESSAGE_r = extractArgumentRanges(self._LOCKED_MESSAGE) self.Conversation_ClearPrivateHistory = getValue(dict, "Conversation.ClearPrivateHistory") self.Conversation_ContextMenuShare = getValue(dict, "Conversation.ContextMenuShare") + self.Notifications_ExceptionsNone = getValue(dict, "Notifications.ExceptionsNone") self._Time_MonthOfYear_m6 = getValue(dict, "Time.MonthOfYear_m6") self._Time_MonthOfYear_m6_r = extractArgumentRanges(self._Time_MonthOfYear_m6) self.Conversation_ContextMenuReport = getValue(dict, "Conversation.ContextMenuReport") self._Call_GroupFormat = getValue(dict, "Call.GroupFormat") self._Call_GroupFormat_r = extractArgumentRanges(self._Call_GroupFormat) self.Forward_ChannelReadOnly = getValue(dict, "Forward.ChannelReadOnly") + self.Passport_InfoText = getValue(dict, "Passport.InfoText") self.Privacy_GroupsAndChannels_NeverAllow_Title = getValue(dict, "Privacy.GroupsAndChannels.NeverAllow.Title") + self._Passport_Address_UploadOneOfScan = getValue(dict, "Passport.Address.UploadOneOfScan") + self._Passport_Address_UploadOneOfScan_r = extractArgumentRanges(self._Passport_Address_UploadOneOfScan) self.AutoDownloadSettings_Reset = getValue(dict, "AutoDownloadSettings.Reset") + self.NotificationsSound_Synth = getValue(dict, "NotificationsSound.Synth") self._Channel_AdminLog_MessageInvitedName = getValue(dict, "Channel.AdminLog.MessageInvitedName") self._Channel_AdminLog_MessageInvitedName_r = extractArgumentRanges(self._Channel_AdminLog_MessageInvitedName) self.Conversation_Moderate_Ban = getValue(dict, "Conversation.Moderate.Ban") self.Group_Status = getValue(dict, "Group.Status") - self.ContactInfo_PhoneLabelOther = getValue(dict, "ContactInfo.PhoneLabelOther") + self.SocksProxySetup_ShareProxyList = getValue(dict, "SocksProxySetup.ShareProxyList") + self.Passport_Phone_Delete = getValue(dict, "Passport.Phone.Delete") self.Conversation_InputTextPlaceholder = getValue(dict, "Conversation.InputTextPlaceholder") + self.ContactInfo_PhoneLabelOther = getValue(dict, "ContactInfo.PhoneLabelOther") + self.Passport_Language_lv = getValue(dict, "Passport.Language.lv") self.TwoStepAuth_RecoveryCode = getValue(dict, "TwoStepAuth.RecoveryCode") + self.Conversation_EditingMessageMediaEditCurrentPhoto = getValue(dict, "Conversation.EditingMessageMediaEditCurrentPhoto") + self.Passport_DeleteDocumentConfirmation = getValue(dict, "Passport.DeleteDocumentConfirmation") + self.Passport_Language_hy = getValue(dict, "Passport.Language.hy") self.SharedMedia_CategoryDocs = getValue(dict, "SharedMedia.CategoryDocs") self.Channel_AdminLog_CanChangeInfo = getValue(dict, "Channel.AdminLog.CanChangeInfo") self.Channel_AdminLogFilter_EventsAdmins = getValue(dict, "Channel.AdminLogFilter.EventsAdmins") self.Group_Setup_HistoryHiddenHelp = getValue(dict, "Group.Setup.HistoryHiddenHelp") self._AuthSessions_AppUnofficial = getValue(dict, "AuthSessions.AppUnofficial") self._AuthSessions_AppUnofficial_r = extractArgumentRanges(self._AuthSessions_AppUnofficial) - self.SecureId_FormFieldEmailPlaceholder = getValue(dict, "SecureId.FormFieldEmailPlaceholder") + self.NotificationsSound_Telegraph = getValue(dict, "NotificationsSound.Telegraph") self.AutoNightTheme_Disabled = getValue(dict, "AutoNightTheme.Disabled") self.Conversation_ContextMenuBan = getValue(dict, "Conversation.ContextMenuBan") self.Channel_EditAdmin_PermissionsHeader = getValue(dict, "Channel.EditAdmin.PermissionsHeader") @@ -5973,6 +6552,9 @@ public final class PresentationStrings { self.ShareFileTip_CloseTip = getValue(dict, "ShareFileTip.CloseTip") self.KeyCommand_Find = getValue(dict, "KeyCommand.Find") self.SecretVideo_Title = getValue(dict, "SecretVideo.Title") + self.Passport_DeleteAddressConfirmation = getValue(dict, "Passport.DeleteAddressConfirmation") + self.Passport_DiscardMessageAction = getValue(dict, "Passport.DiscardMessageAction") + self.Passport_Language_dv = getValue(dict, "Passport.Language.dv") self.Checkout_NewCard_PostcodeTitle = getValue(dict, "Checkout.NewCard.PostcodeTitle") self._Channel_AdminLog_MessageRestricted = getValue(dict, "Channel.AdminLog.MessageRestricted") self._Channel_AdminLog_MessageRestricted_r = extractArgumentRanges(self._Channel_AdminLog_MessageRestricted) @@ -5990,11 +6572,14 @@ public final class PresentationStrings { self.UserInfo_TapToCall = getValue(dict, "UserInfo.TapToCall") self.Common_Edit = getValue(dict, "Common.Edit") self.Conversation_OpenFile = getValue(dict, "Conversation.OpenFile") + self.PrivacyPolicy_Decline = getValue(dict, "PrivacyPolicy.Decline") + self.Passport_Identity_ResidenceCountryPlaceholder = getValue(dict, "Passport.Identity.ResidenceCountryPlaceholder") self.Message_PinnedDocumentMessage = getValue(dict, "Message.PinnedDocumentMessage") self.AuthSessions_LogOut = getValue(dict, "AuthSessions.LogOut") self.AutoDownloadSettings_PrivateChats = getValue(dict, "AutoDownloadSettings.PrivateChats") self.Checkout_TotalPaidAmount = getValue(dict, "Checkout.TotalPaidAmount") self.Conversation_UnsupportedMedia = getValue(dict, "Conversation.UnsupportedMedia") + self.Passport_InvalidPasswordError = getValue(dict, "Passport.InvalidPasswordError") self._Message_ForwardedMessage = getValue(dict, "Message.ForwardedMessage") self._Message_ForwardedMessage_r = extractArgumentRanges(self._Message_ForwardedMessage) self._Time_PreciseDate_m4 = getValue(dict, "Time.PreciseDate_m4") @@ -6003,21 +6588,24 @@ public final class PresentationStrings { self.Call_AudioRouteHide = getValue(dict, "Call.AudioRouteHide") self.CallSettings_OnMobile = getValue(dict, "CallSettings.OnMobile") self.Conversation_GifTooltip = getValue(dict, "Conversation.GifTooltip") - self.SecureId_FormFieldPhonePlaceholder = getValue(dict, "SecureId.FormFieldPhonePlaceholder") + self.Passport_Address_EditBankStatement = getValue(dict, "Passport.Address.EditBankStatement") self.CheckoutInfo_ErrorCityInvalid = getValue(dict, "CheckoutInfo.ErrorCityInvalid") self.Profile_CreateEncryptedChatError = getValue(dict, "Profile.CreateEncryptedChatError") self.Map_LocationTitle = getValue(dict, "Map.LocationTitle") self.Call_RateCall = getValue(dict, "Call.RateCall") + self.Passport_Address_City = getValue(dict, "Passport.Address.City") self.SocksProxySetup_PasswordPlaceholder = getValue(dict, "SocksProxySetup.PasswordPlaceholder") self.Message_ReplyActionButtonShowReceipt = getValue(dict, "Message.ReplyActionButtonShowReceipt") self.PhotoEditor_ShadowsTool = getValue(dict, "PhotoEditor.ShadowsTool") self.Checkout_NewCard_CardholderNamePlaceholder = getValue(dict, "Checkout.NewCard.CardholderNamePlaceholder") self.Cache_Title = getValue(dict, "Cache.Title") + self.Passport_Email_Title = getValue(dict, "Passport.Email.Title") self.Month_GenMay = getValue(dict, "Month.GenMay") self.PasscodeSettings_HelpBottom = getValue(dict, "PasscodeSettings.HelpBottom") self._Notification_CreatedChat = getValue(dict, "Notification.CreatedChat") self._Notification_CreatedChat_r = extractArgumentRanges(self._Notification_CreatedChat) self.Calls_NoMissedCallsPlacehoder = getValue(dict, "Calls.NoMissedCallsPlacehoder") + self.Passport_Address_RegionPlaceholder = getValue(dict, "Passport.Address.RegionPlaceholder") self.Channel_Stickers_NotFoundHelp = getValue(dict, "Channel.Stickers.NotFoundHelp") self.Watch_UserInfo_Block = getValue(dict, "Watch.UserInfo.Block") self.Watch_LastSeen_ALongTimeAgo = getValue(dict, "Watch.LastSeen.ALongTimeAgo") @@ -6027,6 +6615,7 @@ public final class PresentationStrings { self.Channel_BlackList_Title = getValue(dict, "Channel.BlackList.Title") self._Conversation_LiveLocationYouAnd = getValue(dict, "Conversation.LiveLocationYouAnd") self._Conversation_LiveLocationYouAnd_r = extractArgumentRanges(self._Conversation_LiveLocationYouAnd) + self.TwoStepAuth_PasswordRemovePassportConfirmation = getValue(dict, "TwoStepAuth.PasswordRemovePassportConfirmation") self.Checkout_NewCard_SaveInfo = getValue(dict, "Checkout.NewCard.SaveInfo") self.Notification_CallMissed = getValue(dict, "Notification.CallMissed") self.Profile_ShareContactButton = getValue(dict, "Profile.ShareContactButton") @@ -6034,6 +6623,7 @@ public final class PresentationStrings { self.Bot_GroupStatusDoesNotReadHistory = getValue(dict, "Bot.GroupStatusDoesNotReadHistory") self.Notification_Mute1h = getValue(dict, "Notification.Mute1h") self.Settings_TabTitle = getValue(dict, "Settings.TabTitle") + self.Passport_Identity_ExpiryDatePlaceholder = getValue(dict, "Passport.Identity.ExpiryDatePlaceholder") self.NetworkUsageSettings_MediaAudioDataSection = getValue(dict, "NetworkUsageSettings.MediaAudioDataSection") self.GroupInfo_DeactivatedStatus = getValue(dict, "GroupInfo.DeactivatedStatus") self._CHAT_PHOTO_EDITED = getValue(dict, "CHAT_PHOTO_EDITED") @@ -6055,8 +6645,10 @@ public final class PresentationStrings { self._MESSAGE_AUDIO = getValue(dict, "MESSAGE_AUDIO") self._MESSAGE_AUDIO_r = extractArgumentRanges(self._MESSAGE_AUDIO) self.Checkout_PasswordEntry_Pay = getValue(dict, "Checkout.PasswordEntry.Pay") + self.Conversation_EditingMessagePanelMedia = getValue(dict, "Conversation.EditingMessagePanelMedia") self.Notifications_MessageNotificationsHelp = getValue(dict, "Notifications.MessageNotificationsHelp") self.EnterPasscode_EnterCurrentPasscode = getValue(dict, "EnterPasscode.EnterCurrentPasscode") + self.Conversation_EditingMessageMediaEditCurrentVideo = getValue(dict, "Conversation.EditingMessageMediaEditCurrentVideo") self._MESSAGE_GAME = getValue(dict, "MESSAGE_GAME") self._MESSAGE_GAME_r = extractArgumentRanges(self._MESSAGE_GAME) self.Conversation_Moderate_Report = getValue(dict, "Conversation.Moderate.Report") @@ -6068,12 +6660,12 @@ public final class PresentationStrings { self._Map_AccurateTo_r = extractArgumentRanges(self._Map_AccurateTo) self._Call_ParticipantVersionOutdatedError = getValue(dict, "Call.ParticipantVersionOutdatedError") self._Call_ParticipantVersionOutdatedError_r = extractArgumentRanges(self._Call_ParticipantVersionOutdatedError) + self.Passport_Identity_ReverseSideHelp = getValue(dict, "Passport.Identity.ReverseSideHelp") self.Tour_Text2 = getValue(dict, "Tour.Text2") self.Call_StatusNoAnswer = getValue(dict, "Call.StatusNoAnswer") - self._SecureId_FormPolicy = getValue(dict, "SecureId.FormPolicy") - self._SecureId_FormPolicy_r = extractArgumentRanges(self._SecureId_FormPolicy) + self._Passport_Phone_UseTelegramNumber = getValue(dict, "Passport.Phone.UseTelegramNumber") + self._Passport_Phone_UseTelegramNumber_r = extractArgumentRanges(self._Passport_Phone_UseTelegramNumber) self.Channel_AdminLogFilter_EventsLeavingSubscribers = getValue(dict, "Channel.AdminLogFilter.EventsLeavingSubscribers") - self.TermsOfService_AgeVerificationTitle = getValue(dict, "TermsOfService.AgeVerificationTitle") self.Conversation_MessageDialogDelete = getValue(dict, "Conversation.MessageDialogDelete") self.Appearance_PreviewOutgoingText = getValue(dict, "Appearance.PreviewOutgoingText") self.Username_Placeholder = getValue(dict, "Username.Placeholder") @@ -6087,11 +6679,10 @@ public final class PresentationStrings { self._CHANNEL_MESSAGE_VIDEO_r = extractArgumentRanges(self._CHANNEL_MESSAGE_VIDEO) self.EnterPasscode_TouchId = getValue(dict, "EnterPasscode.TouchId") self.AuthSessions_LoggedInWithTelegram = getValue(dict, "AuthSessions.LoggedInWithTelegram") - self.PrivacySettings_SuggestFrequentContactsDisableNotice = getValue(dict, "PrivacySettings.SuggestFrequentContactsDisableNotice") self.Checkout_ErrorInvoiceAlreadyPaid = getValue(dict, "Checkout.ErrorInvoiceAlreadyPaid") self.ChatAdmins_Title = getValue(dict, "ChatAdmins.Title") self.ChannelMembers_WhoCanAddMembers = getValue(dict, "ChannelMembers.WhoCanAddMembers") - self.PrivacySettings_SuggestFrequentContactsInfo = getValue(dict, "PrivacySettings.SuggestFrequentContactsInfo") + self.Passport_Language_ar = getValue(dict, "Passport.Language.ar") self.PasscodeSettings_Help = getValue(dict, "PasscodeSettings.Help") self.Conversation_EditingMessagePanelTitle = getValue(dict, "Conversation.EditingMessagePanelTitle") self.Settings_AboutEmpty = getValue(dict, "Settings.AboutEmpty") @@ -6110,11 +6701,17 @@ public final class PresentationStrings { self.Checkout_PaymentMethod_Title = getValue(dict, "Checkout.PaymentMethod.Title") self.Conversation_Unmute = getValue(dict, "Conversation.Unmute") self.AutoDownloadSettings_DocumentsTitle = getValue(dict, "AutoDownloadSettings.DocumentsTitle") + self.Passport_FieldOneOf_FinalDelimeter = getValue(dict, "Passport.FieldOneOf.FinalDelimeter") self.Notifications_MessageNotifications = getValue(dict, "Notifications.MessageNotifications") + self.Passport_ForgottenPassword = getValue(dict, "Passport.ForgottenPassword") self.ChannelMembers_WhoCanAddMembersAdminsHelp = getValue(dict, "ChannelMembers.WhoCanAddMembersAdminsHelp") self.DialogList_DeleteBotConversationConfirmation = getValue(dict, "DialogList.DeleteBotConversationConfirmation") + self.Passport_Identity_TranslationHelp = getValue(dict, "Passport.Identity.TranslationHelp") + self._Update_AppVersion = getValue(dict, "Update.AppVersion") + self._Update_AppVersion_r = extractArgumentRanges(self._Update_AppVersion) self._DialogList_MultipleTyping = getValue(dict, "DialogList.MultipleTyping") self._DialogList_MultipleTyping_r = extractArgumentRanges(self._DialogList_MultipleTyping) + self.Passport_Identity_OneOfTypeIdentityCard = getValue(dict, "Passport.Identity.OneOfTypeIdentityCard") self.Conversation_ClousStorageInfo_Description2 = getValue(dict, "Conversation.ClousStorageInfo.Description2") self._Time_MonthOfYear_m5 = getValue(dict, "Time.MonthOfYear_m5") self._Time_MonthOfYear_m5_r = extractArgumentRanges(self._Time_MonthOfYear_m5) @@ -6128,6 +6725,7 @@ public final class PresentationStrings { self.PhotoEditor_QualityVeryLow = getValue(dict, "PhotoEditor.QualityVeryLow") self.Stickers_AddToFavorites = getValue(dict, "Stickers.AddToFavorites") self.Month_ShortFebruary = getValue(dict, "Month.ShortFebruary") + self.Notifications_AddExceptionTitle = getValue(dict, "Notifications.AddExceptionTitle") self.Conversation_ForwardTitle = getValue(dict, "Conversation.ForwardTitle") self.Settings_FAQ_URL = getValue(dict, "Settings.FAQ_URL") self.Activity_RecordingVideoMessage = getValue(dict, "Activity.RecordingVideoMessage") @@ -6136,6 +6734,7 @@ public final class PresentationStrings { self._Contacts_AccessDeniedHelpLandscape_r = extractArgumentRanges(self._Contacts_AccessDeniedHelpLandscape) self.PasscodeSettings_UnlockWithTouchId = getValue(dict, "PasscodeSettings.UnlockWithTouchId") self.Contacts_AccessDeniedHelpON = getValue(dict, "Contacts.AccessDeniedHelpON") + self.Passport_Identity_AddInternalPassport = getValue(dict, "Passport.Identity.AddInternalPassport") self.NetworkUsageSettings_ResetStats = getValue(dict, "NetworkUsageSettings.ResetStats") self._PrivacySettings_LastSeenContactsMinusPlus = getValue(dict, "PrivacySettings.LastSeenContactsMinusPlus") self._PrivacySettings_LastSeenContactsMinusPlus_r = extractArgumentRanges(self._PrivacySettings_LastSeenContactsMinusPlus) @@ -6156,6 +6755,7 @@ public final class PresentationStrings { self.Channel_ErrorAddBlocked = getValue(dict, "Channel.ErrorAddBlocked") self.Conversation_Unpin = getValue(dict, "Conversation.Unpin") self.Call_RecordingDisabledMessage = getValue(dict, "Call.RecordingDisabledMessage") + self.Passport_Address_TypeUtilityBill = getValue(dict, "Passport.Address.TypeUtilityBill") self.Conversation_UnblockUser = getValue(dict, "Conversation.UnblockUser") self.Conversation_Unblock = getValue(dict, "Conversation.Unblock") self._CHANNEL_MESSAGE_GIF = getValue(dict, "CHANNEL_MESSAGE_GIF") @@ -6163,11 +6763,14 @@ public final class PresentationStrings { self.Channel_AdminLogFilter_EventsEditedMessages = getValue(dict, "Channel.AdminLogFilter.EventsEditedMessages") self.AutoNightTheme_ScheduleSection = getValue(dict, "AutoNightTheme.ScheduleSection") self.Appearance_ThemeNightBlue = getValue(dict, "Appearance.ThemeNightBlue") + self._Passport_Scans_ScanIndex = getValue(dict, "Passport.Scans.ScanIndex") + self._Passport_Scans_ScanIndex_r = extractArgumentRanges(self._Passport_Scans_ScanIndex) self.Channel_Username_InvalidTooShort = getValue(dict, "Channel.Username.InvalidTooShort") self.Conversation_ViewGroup = getValue(dict, "Conversation.ViewGroup") self.Watch_LastSeen_WithinAWeek = getValue(dict, "Watch.LastSeen.WithinAWeek") self.BlockedUsers_SelectUserTitle = getValue(dict, "BlockedUsers.SelectUserTitle") self.Profile_MessageLifetime1w = getValue(dict, "Profile.MessageLifetime1w") + self.Passport_Address_TypeRentalAgreementUploadScan = getValue(dict, "Passport.Address.TypeRentalAgreementUploadScan") self.DialogList_TabTitle = getValue(dict, "DialogList.TabTitle") self.UserInfo_GenericPhoneLabel = getValue(dict, "UserInfo.GenericPhoneLabel") self._Channel_AdminLog_MessagePromotedName = getValue(dict, "Channel.AdminLog.MessagePromotedName") @@ -6199,14 +6802,18 @@ public final class PresentationStrings { self._Watch_Time_ShortTodayAt = getValue(dict, "Watch.Time.ShortTodayAt") self._Watch_Time_ShortTodayAt_r = extractArgumentRanges(self._Watch_Time_ShortTodayAt) self.Channel_AdminLogFilter_EventsNewSubscribers = getValue(dict, "Channel.AdminLogFilter.EventsNewSubscribers") + self.Passport_Identity_ExpiryDate = getValue(dict, "Passport.Identity.ExpiryDate") self.UserInfo_GroupsInCommon = getValue(dict, "UserInfo.GroupsInCommon") self.Message_PinnedContactMessage = getValue(dict, "Message.PinnedContactMessage") self.AccessDenied_CameraDisabled = getValue(dict, "AccessDenied.CameraDisabled") self._Time_PreciseDate_m3 = getValue(dict, "Time.PreciseDate_m3") self._Time_PreciseDate_m3_r = extractArgumentRanges(self._Time_PreciseDate_m3) + self.Passport_Email_EnterOtherEmail = getValue(dict, "Passport.Email.EnterOtherEmail") self._LiveLocationUpdated_YesterdayAt = getValue(dict, "LiveLocationUpdated.YesterdayAt") self._LiveLocationUpdated_YesterdayAt_r = extractArgumentRanges(self._LiveLocationUpdated_YesterdayAt) - self.SecureId_FormFieldAddressPlaceholder = getValue(dict, "SecureId.FormFieldAddressPlaceholder") + self.NotificationsSound_Note = getValue(dict, "NotificationsSound.Note") + self.Passport_Identity_MiddleNamePlaceholder = getValue(dict, "Passport.Identity.MiddleNamePlaceholder") + self.PrivacyPolicy_Title = getValue(dict, "PrivacyPolicy.Title") self.Month_GenMarch = getValue(dict, "Month.GenMarch") self.Watch_UserInfo_Unmute = getValue(dict, "Watch.UserInfo.Unmute") self.CheckoutInfo_ErrorPostcodeInvalid = getValue(dict, "CheckoutInfo.ErrorPostcodeInvalid") @@ -6226,6 +6833,8 @@ public final class PresentationStrings { self._UserInfo_UnblockConfirmation = getValue(dict, "UserInfo.UnblockConfirmation") self._UserInfo_UnblockConfirmation_r = extractArgumentRanges(self._UserInfo_UnblockConfirmation) self.Appearance_PickAccentColor = getValue(dict, "Appearance.PickAccentColor") + self.Passport_Identity_EditDriversLicense = getValue(dict, "Passport.Identity.EditDriversLicense") + self.Passport_Identity_AddPassport = getValue(dict, "Passport.Identity.AddPassport") self.UserInfo_ShareBot = getValue(dict, "UserInfo.ShareBot") self.Settings_ProxyConnected = getValue(dict, "Settings.ProxyConnected") self.ChatSettings_AutoDownloadVoiceMessages = getValue(dict, "ChatSettings.AutoDownloadVoiceMessages") @@ -6233,14 +6842,16 @@ public final class PresentationStrings { self.Conversation_ViewContactDetails = getValue(dict, "Conversation.ViewContactDetails") self.Conversation_JumpToDate = getValue(dict, "Conversation.JumpToDate") self.AutoDownloadSettings_VideoMessagesTitle = getValue(dict, "AutoDownloadSettings.VideoMessagesTitle") + self.Passport_Address_OneOfTypeUtilityBill = getValue(dict, "Passport.Address.OneOfTypeUtilityBill") self.CheckoutInfo_ReceiverInfoEmailPlaceholder = getValue(dict, "CheckoutInfo.ReceiverInfoEmailPlaceholder") self.Message_Photo = getValue(dict, "Message.Photo") self.Conversation_ReportSpam = getValue(dict, "Conversation.ReportSpam") self.Camera_FlashAuto = getValue(dict, "Camera.FlashAuto") - self.PrivacySettings_LinkPreviewsInfo = getValue(dict, "PrivacySettings.LinkPreviewsInfo") + self.Passport_Identity_TypePassportUploadScan = getValue(dict, "Passport.Identity.TypePassportUploadScan") self.Call_ConnectionErrorMessage = getValue(dict, "Call.ConnectionErrorMessage") self.Stickers_FrequentlyUsed = getValue(dict, "Stickers.FrequentlyUsed") self.LastSeen_ALongTimeAgo = getValue(dict, "LastSeen.ALongTimeAgo") + self.Passport_Identity_ReverseSide = getValue(dict, "Passport.Identity.ReverseSide") self.DialogList_SearchSectionGlobal = getValue(dict, "DialogList.SearchSectionGlobal") self.ChangePhoneNumberNumber_NumberPlaceholder = getValue(dict, "ChangePhoneNumberNumber.NumberPlaceholder") self.GroupInfo_AddUserLeftError = getValue(dict, "GroupInfo.AddUserLeftError") @@ -6256,6 +6867,7 @@ public final class PresentationStrings { self.UserInfo_NotificationsDisabled = getValue(dict, "UserInfo.NotificationsDisabled") self._CONTACT_JOINED = getValue(dict, "CONTACT_JOINED") self._CONTACT_JOINED_r = extractArgumentRanges(self._CONTACT_JOINED) + self.NotificationsSound_Bamboo = getValue(dict, "NotificationsSound.Bamboo") self.PrivacyLastSeenSettings_AlwaysShareWith_Title = getValue(dict, "PrivacyLastSeenSettings.AlwaysShareWith.Title") self._Channel_AdminLog_MessageGroupPreHistoryVisible = getValue(dict, "Channel.AdminLog.MessageGroupPreHistoryVisible") self._Channel_AdminLog_MessageGroupPreHistoryVisible_r = extractArgumentRanges(self._Channel_AdminLog_MessageGroupPreHistoryVisible) @@ -6272,6 +6884,7 @@ public final class PresentationStrings { self.Conversation_ApplyLocalization = getValue(dict, "Conversation.ApplyLocalization") self.FastTwoStepSetup_Title = getValue(dict, "FastTwoStepSetup.Title") self.SocksProxySetup_ProxyStatusUnavailable = getValue(dict, "SocksProxySetup.ProxyStatusUnavailable") + self.Passport_Address_EditRentalAgreement = getValue(dict, "Passport.Address.EditRentalAgreement") self.Conversation_DeleteManyMessages = getValue(dict, "Conversation.DeleteManyMessages") self.CancelResetAccount_Title = getValue(dict, "CancelResetAccount.Title") self.Notification_CallOutgoingShort = getValue(dict, "Notification.CallOutgoingShort") @@ -6292,11 +6905,13 @@ public final class PresentationStrings { self._Conversation_Moderate_DeleteAllMessages = getValue(dict, "Conversation.Moderate.DeleteAllMessages") self._Conversation_Moderate_DeleteAllMessages_r = extractArgumentRanges(self._Conversation_Moderate_DeleteAllMessages) self.SharedMedia_CategoryOther = getValue(dict, "SharedMedia.CategoryOther") + self.Passport_Address_Address = getValue(dict, "Passport.Address.Address") self.DialogList_SavedMessagesTooltip = getValue(dict, "DialogList.SavedMessagesTooltip") self.Preview_DeletePhoto = getValue(dict, "Preview.DeletePhoto") self.GroupInfo_ChannelListNamePlaceholder = getValue(dict, "GroupInfo.ChannelListNamePlaceholder") self.PasscodeSettings_TurnPasscodeOn = getValue(dict, "PasscodeSettings.TurnPasscodeOn") self.AuthSessions_LogOutApplicationsHelp = getValue(dict, "AuthSessions.LogOutApplicationsHelp") + self.Passport_FieldOneOf_Delimeter = getValue(dict, "Passport.FieldOneOf.Delimeter") self._Channel_AdminLog_MessageChangedGroupStickerPack = getValue(dict, "Channel.AdminLog.MessageChangedGroupStickerPack") self._Channel_AdminLog_MessageChangedGroupStickerPack_r = extractArgumentRanges(self._Channel_AdminLog_MessageChangedGroupStickerPack) self.DialogList_Unpin = getValue(dict, "DialogList.Unpin") @@ -6311,7 +6926,7 @@ public final class PresentationStrings { self._Notification_NewAuthDetected_r = extractArgumentRanges(self._Notification_NewAuthDetected) self._Channel_AdminLog_MessageRemovedGroupStickerPack = getValue(dict, "Channel.AdminLog.MessageRemovedGroupStickerPack") self._Channel_AdminLog_MessageRemovedGroupStickerPack_r = extractArgumentRanges(self._Channel_AdminLog_MessageRemovedGroupStickerPack) - self.TermsOfService_Agree = getValue(dict, "TermsOfService.Agree") + self.PrivacyPolicy_DeclineTitle = getValue(dict, "PrivacyPolicy.DeclineTitle") self.AccessDenied_VideoMessageCamera = getValue(dict, "AccessDenied.VideoMessageCamera") self.Privacy_ContactsSyncHelp = getValue(dict, "Privacy.ContactsSyncHelp") self.Conversation_Search = getValue(dict, "Conversation.Search") @@ -6343,7 +6958,9 @@ public final class PresentationStrings { self.Calls_Missed = getValue(dict, "Calls.Missed") self.Conversation_ContextMenuForward = getValue(dict, "Conversation.ContextMenuForward") self.AutoDownloadSettings_ResetHelp = getValue(dict, "AutoDownloadSettings.ResetHelp") + self.Passport_Identity_NativeNameHelp = getValue(dict, "Passport.Identity.NativeNameHelp") self.Call_StatusRinging = getValue(dict, "Call.StatusRinging") + self.Passport_Language_pl = getValue(dict, "Passport.Language.pl") self.Invitation_JoinGroup = getValue(dict, "Invitation.JoinGroup") self.Notification_PinnedMessage = getValue(dict, "Notification.PinnedMessage") self.AutoDownloadSettings_WiFi = getValue(dict, "AutoDownloadSettings.WiFi") @@ -6352,6 +6969,8 @@ public final class PresentationStrings { self._Notification_MessageLifetimeChanged = getValue(dict, "Notification.MessageLifetimeChanged") self._Notification_MessageLifetimeChanged_r = extractArgumentRanges(self._Notification_MessageLifetimeChanged) self.Message_Contact = getValue(dict, "Message.Contact") + self.Passport_Language_lo = getValue(dict, "Passport.Language.lo") + self.UserInfo_BotPrivacy = getValue(dict, "UserInfo.BotPrivacy") self.PasscodeSettings_AutoLock_IfAwayFor_1minute = getValue(dict, "PasscodeSettings.AutoLock.IfAwayFor_1minute") self.Common_More = getValue(dict, "Common.More") self.Preview_OpenInInstagram = getValue(dict, "Preview.OpenInInstagram") @@ -6361,6 +6980,7 @@ public final class PresentationStrings { self._PINNED_GAME = getValue(dict, "PINNED_GAME") self._PINNED_GAME_r = extractArgumentRanges(self._PINNED_GAME) self.Invite_LargeRecipientsCountWarning = getValue(dict, "Invite.LargeRecipientsCountWarning") + self.Passport_Language_hr = getValue(dict, "Passport.Language.hr") self.GroupInfo_BroadcastListNamePlaceholder = getValue(dict, "GroupInfo.BroadcastListNamePlaceholder") self.Activity_UploadingVideoMessage = getValue(dict, "Activity.UploadingVideoMessage") self.Conversation_ShareBotContactConfirmation = getValue(dict, "Conversation.ShareBotContactConfirmation") @@ -6373,17 +6993,19 @@ public final class PresentationStrings { self._TwoStepAuth_EnterPasswordHint = getValue(dict, "TwoStepAuth.EnterPasswordHint") self._TwoStepAuth_EnterPasswordHint_r = extractArgumentRanges(self._TwoStepAuth_EnterPasswordHint) self.CallSettings_TabIcon = getValue(dict, "CallSettings.TabIcon") - self.TermsOfService_DeclineUnauthorized = getValue(dict, "TermsOfService.DeclineUnauthorized") self.ConversationProfile_UnknownAddMemberError = getValue(dict, "ConversationProfile.UnknownAddMemberError") self._Conversation_FileHowToText = getValue(dict, "Conversation.FileHowToText") self._Conversation_FileHowToText_r = extractArgumentRanges(self._Conversation_FileHowToText) self.Channel_AdminLog_BanSendMedia = getValue(dict, "Channel.AdminLog.BanSendMedia") + self.Passport_Language_uz = getValue(dict, "Passport.Language.uz") self.Watch_UserInfo_Unblock = getValue(dict, "Watch.UserInfo.Unblock") self.ChatSettings_AutoDownloadVideoMessages = getValue(dict, "ChatSettings.AutoDownloadVideoMessages") + self.PrivacyPolicy_AgeVerificationTitle = getValue(dict, "PrivacyPolicy.AgeVerificationTitle") self.StickerPacksSettings_ArchivedMasks = getValue(dict, "StickerPacksSettings.ArchivedMasks") self.Message_Animation = getValue(dict, "Message.Animation") self.Checkout_PaymentMethod = getValue(dict, "Checkout.PaymentMethod") self.Channel_AdminLog_TitleSelectedEvents = getValue(dict, "Channel.AdminLog.TitleSelectedEvents") + self.PrivacyPolicy_DeclineDeleteNow = getValue(dict, "PrivacyPolicy.DeclineDeleteNow") self.Privacy_Calls_NeverAllow_Title = getValue(dict, "Privacy.Calls.NeverAllow.Title") self.Cache_Music = getValue(dict, "Cache.Music") self._Login_CallRequestState1 = getValue(dict, "Login.CallRequestState1") @@ -6419,6 +7041,7 @@ public final class PresentationStrings { self.Channel_BanUser_PermissionSendStickersAndGifs = getValue(dict, "Channel.BanUser.PermissionSendStickersAndGifs") self.Conversation_CloudStorageInfo_Title = getValue(dict, "Conversation.CloudStorageInfo.Title") self.Conversation_ClearSecretHistory = getValue(dict, "Conversation.ClearSecretHistory") + self.Passport_Identity_EditIdentityCard = getValue(dict, "Passport.Identity.EditIdentityCard") self.Notification_RenamedChannel = getValue(dict, "Notification.RenamedChannel") self.BlockedUsers_BlockUser = getValue(dict, "BlockedUsers.BlockUser") self.ChatSettings_TextSize = getValue(dict, "ChatSettings.TextSize") @@ -6437,13 +7060,20 @@ public final class PresentationStrings { self._CHAT_MESSAGE_STICKER = getValue(dict, "CHAT_MESSAGE_STICKER") self._CHAT_MESSAGE_STICKER_r = extractArgumentRanges(self._CHAT_MESSAGE_STICKER) self.Map_ChooseAPlace = getValue(dict, "Map.ChooseAPlace") + self.Passport_Identity_NamePlaceholder = getValue(dict, "Passport.Identity.NamePlaceholder") + self.Passport_ScanPassport = getValue(dict, "Passport.ScanPassport") self.Map_ShareLiveLocationHelp = getValue(dict, "Map.ShareLiveLocationHelp") self.Watch_Bot_Restart = getValue(dict, "Watch.Bot.Restart") + self.Passport_RequestedInformation = getValue(dict, "Passport.RequestedInformation") self.Channel_About_Help = getValue(dict, "Channel.About.Help") self.Web_OpenExternal = getValue(dict, "Web.OpenExternal") + self.Passport_Language_mn = getValue(dict, "Passport.Language.mn") self.UserInfo_AddContact = getValue(dict, "UserInfo.AddContact") self.Privacy_ContactsSync = getValue(dict, "Privacy.ContactsSync") self.SocksProxySetup_Connection = getValue(dict, "SocksProxySetup.Connection") + self.Passport_NotLoggedInMessage = getValue(dict, "Passport.NotLoggedInMessage") + self.Passport_PasswordPlaceholder = getValue(dict, "Passport.PasswordPlaceholder") + self.Passport_PasswordCreate = getValue(dict, "Passport.PasswordCreate") self.SocksProxySetup_ProxyStatusChecking = getValue(dict, "SocksProxySetup.ProxyStatusChecking") self.Call_EncryptionKey_Title = getValue(dict, "Call.EncryptionKey.Title") self.PhotoEditor_BlurToolLinear = getValue(dict, "PhotoEditor.BlurToolLinear") @@ -6452,17 +7082,21 @@ public final class PresentationStrings { self._Call_StatusBar = getValue(dict, "Call.StatusBar") self._Call_StatusBar_r = extractArgumentRanges(self._Call_StatusBar) self.EditProfile_NameAndPhotoHelp = getValue(dict, "EditProfile.NameAndPhotoHelp") - self.SecureId_FormFieldPhone = getValue(dict, "SecureId.FormFieldPhone") + self.NotificationsSound_Tritone = getValue(dict, "NotificationsSound.Tritone") + self.Passport_FieldAddressUploadHelp = getValue(dict, "Passport.FieldAddressUploadHelp") self.Month_ShortJuly = getValue(dict, "Month.ShortJuly") self.CheckoutInfo_ShippingInfoAddress1Placeholder = getValue(dict, "CheckoutInfo.ShippingInfoAddress1Placeholder") self.Watch_MessageView_ViewOnPhone = getValue(dict, "Watch.MessageView.ViewOnPhone") self.CallSettings_Never = getValue(dict, "CallSettings.Never") + self.Passport_Identity_TypeInternalPassportUploadScan = getValue(dict, "Passport.Identity.TypeInternalPassportUploadScan") self.TwoStepAuth_EmailSent = getValue(dict, "TwoStepAuth.EmailSent") self._Notification_PinnedAnimationMessage = getValue(dict, "Notification.PinnedAnimationMessage") self._Notification_PinnedAnimationMessage_r = extractArgumentRanges(self._Notification_PinnedAnimationMessage) self.TwoStepAuth_RecoveryTitle = getValue(dict, "TwoStepAuth.RecoveryTitle") + self.Notifications_MessageNotificationsExceptions = getValue(dict, "Notifications.MessageNotificationsExceptions") self.WatchRemote_AlertOpen = getValue(dict, "WatchRemote.AlertOpen") self.ExplicitContent_AlertChannel = getValue(dict, "ExplicitContent.AlertChannel") + self.Notification_PassportValueEmail = getValue(dict, "Notification.PassportValueEmail") self.ContactInfo_PhoneLabelMobile = getValue(dict, "ContactInfo.PhoneLabelMobile") self.Widget_AuthRequired = getValue(dict, "Widget.AuthRequired") self._ForwardedAuthors2 = getValue(dict, "ForwardedAuthors2") @@ -6480,8 +7114,11 @@ public final class PresentationStrings { self.Map_LiveLocationFor1Hour = getValue(dict, "Map.LiveLocationFor1Hour") self.AutoNightTheme_AutomaticSection = getValue(dict, "AutoNightTheme.AutomaticSection") self.Stickers_NoStickersFound = getValue(dict, "Stickers.NoStickersFound") + self.Passport_Identity_AddIdentityCard = getValue(dict, "Passport.Identity.AddIdentityCard") self._Notification_JoinedChannel = getValue(dict, "Notification.JoinedChannel") self._Notification_JoinedChannel_r = extractArgumentRanges(self._Notification_JoinedChannel) + self.Passport_Language_et = getValue(dict, "Passport.Language.et") + self.Passport_Language_en = getValue(dict, "Passport.Language.en") self.GroupInfo_ActionRestrict = getValue(dict, "GroupInfo.ActionRestrict") self.Checkout_ShippingOption_Title = getValue(dict, "Checkout.ShippingOption.Title") self.Stickers_SuggestStickers = getValue(dict, "Stickers.SuggestStickers") @@ -6498,6 +7135,7 @@ public final class PresentationStrings { self.Month_GenApril = getValue(dict, "Month.GenApril") self.StickerPacksSettings_ShowStickersButton = getValue(dict, "StickerPacksSettings.ShowStickersButton") self.CheckoutInfo_ShippingInfoTitle = getValue(dict, "CheckoutInfo.ShippingInfoTitle") + self.Notification_PassportValueProofOfAddress = getValue(dict, "Notification.PassportValueProofOfAddress") self.StickerPacksSettings_ShowStickersButtonHelp = getValue(dict, "StickerPacksSettings.ShowStickersButtonHelp") self._Compatibility_SecretMediaVersionTooLow = getValue(dict, "Compatibility.SecretMediaVersionTooLow") self._Compatibility_SecretMediaVersionTooLow_r = extractArgumentRanges(self._Compatibility_SecretMediaVersionTooLow) @@ -6516,6 +7154,8 @@ public final class PresentationStrings { self._Login_BannedPhoneBody = getValue(dict, "Login.BannedPhoneBody") self._Login_BannedPhoneBody_r = extractArgumentRanges(self._Login_BannedPhoneBody) self.Conversation_ClearAll = getValue(dict, "Conversation.ClearAll") + self.Conversation_EditingMessageMediaChange = getValue(dict, "Conversation.EditingMessageMediaChange") + self.Passport_FieldIdentityTranslationHelp = getValue(dict, "Passport.FieldIdentityTranslationHelp") self.Call_ReportIncludeLog = getValue(dict, "Call.ReportIncludeLog") self._Time_MonthOfYear_m3 = getValue(dict, "Time.MonthOfYear_m3") self._Time_MonthOfYear_m3_r = extractArgumentRanges(self._Time_MonthOfYear_m3) @@ -6523,6 +7163,7 @@ public final class PresentationStrings { self.Call_PhoneCallInProgressMessage = getValue(dict, "Call.PhoneCallInProgressMessage") self.Notification_GroupActivated = getValue(dict, "Notification.GroupActivated") self.Checkout_Name = getValue(dict, "Checkout.Name") + self.Passport_Address_PostcodePlaceholder = getValue(dict, "Passport.Address.PostcodePlaceholder") self._AUTH_REGION = getValue(dict, "AUTH_REGION") self._AUTH_REGION_r = extractArgumentRanges(self._AUTH_REGION) self.Settings_NotificationsAndSounds = getValue(dict, "Settings.NotificationsAndSounds") @@ -6531,16 +7172,18 @@ public final class PresentationStrings { self._GroupInfo_InvitationLinkAcceptChannel_r = extractArgumentRanges(self._GroupInfo_InvitationLinkAcceptChannel) self.AccessDenied_SaveMedia = getValue(dict, "AccessDenied.SaveMedia") self.InviteText_URL = getValue(dict, "InviteText.URL") + self.Passport_CorrectErrors = getValue(dict, "Passport.CorrectErrors") self._Channel_AdminLog_MessageInvitedNameUsername = getValue(dict, "Channel.AdminLog.MessageInvitedNameUsername") self._Channel_AdminLog_MessageInvitedNameUsername_r = extractArgumentRanges(self._Channel_AdminLog_MessageInvitedNameUsername) self.Compose_GroupTokenListPlaceholder = getValue(dict, "Compose.GroupTokenListPlaceholder") - self.PrivacySettings_FrequentContacts = getValue(dict, "PrivacySettings.FrequentContacts") + self.Passport_Address_CityPlaceholder = getValue(dict, "Passport.Address.CityPlaceholder") + self.Passport_InfoFAQ_URL = getValue(dict, "Passport.InfoFAQ_URL") self.Conversation_MessageDeliveryFailed = getValue(dict, "Conversation.MessageDeliveryFailed") self.Privacy_PaymentsClear_PaymentInfo = getValue(dict, "Privacy.PaymentsClear.PaymentInfo") self.Notifications_GroupNotifications = getValue(dict, "Notifications.GroupNotifications") self.CheckoutInfo_SaveInfoHelp = getValue(dict, "CheckoutInfo.SaveInfoHelp") self.Notification_Mute1hMin = getValue(dict, "Notification.Mute1hMin") - self.PrivacySettings_SyncContacts = getValue(dict, "PrivacySettings.SyncContacts") + self.Privacy_TopPeersWarning = getValue(dict, "Privacy.TopPeersWarning") self.StickerPacksSettings_ArchivedMasks_Info = getValue(dict, "StickerPacksSettings.ArchivedMasks.Info") self.ChannelMembers_WhoCanAddMembers_AllMembers = getValue(dict, "ChannelMembers.WhoCanAddMembers.AllMembers") self.Channel_Edit_PrivatePublicLinkAlert = getValue(dict, "Channel.Edit.PrivatePublicLinkAlert") @@ -6553,6 +7196,8 @@ public final class PresentationStrings { self.Profile_MessageLifetime1d = getValue(dict, "Profile.MessageLifetime1d") self.CheckoutInfo_ShippingInfoCityPlaceholder = getValue(dict, "CheckoutInfo.ShippingInfoCityPlaceholder") self.Calls_CallTabDescription = getValue(dict, "Calls.CallTabDescription") + self.Passport_DeletePersonalDetails = getValue(dict, "Passport.DeletePersonalDetails") + self.Passport_Address_AddBankStatement = getValue(dict, "Passport.Address.AddBankStatement") self.Resolve_ErrorNotFound = getValue(dict, "Resolve.ErrorNotFound") self.PhotoEditor_FadeTool = getValue(dict, "PhotoEditor.FadeTool") self.Channel_Setup_TypePublicHelp = getValue(dict, "Channel.Setup.TypePublicHelp") @@ -6584,6 +7229,7 @@ public final class PresentationStrings { self._FileSize_MB_r = extractArgumentRanges(self._FileSize_MB) self.ChatSearch_SearchPlaceholder = getValue(dict, "ChatSearch.SearchPlaceholder") self.TwoStepAuth_ConfirmationAbort = getValue(dict, "TwoStepAuth.ConfirmationAbort") + self.FastTwoStepSetup_HintSection = getValue(dict, "FastTwoStepSetup.HintSection") self.TwoStepAuth_SetupPasswordConfirmFailed = getValue(dict, "TwoStepAuth.SetupPasswordConfirmFailed") self._LastSeen_YesterdayAt = getValue(dict, "LastSeen.YesterdayAt") self._LastSeen_YesterdayAt_r = extractArgumentRanges(self._LastSeen_YesterdayAt) @@ -6606,9 +7252,11 @@ public final class PresentationStrings { self.AuthSessions_LogOutApplications = getValue(dict, "AuthSessions.LogOutApplications") self.Map_LoadError = getValue(dict, "Map.LoadError") self.Settings_ProxyConnecting = getValue(dict, "Settings.ProxyConnecting") + self.Passport_Language_fa = getValue(dict, "Passport.Language.fa") self.AccessDenied_VoiceMicrophone = getValue(dict, "AccessDenied.VoiceMicrophone") self._CHANNEL_MESSAGE_STICKER = getValue(dict, "CHANNEL_MESSAGE_STICKER") self._CHANNEL_MESSAGE_STICKER_r = extractArgumentRanges(self._CHANNEL_MESSAGE_STICKER) + self.Passport_Address_TypeUtilityBillUploadScan = getValue(dict, "Passport.Address.TypeUtilityBillUploadScan") self.PrivacySettings_Title = getValue(dict, "PrivacySettings.Title") self.PasscodeSettings_TurnPasscodeOff = getValue(dict, "PasscodeSettings.TurnPasscodeOff") self.MediaPicker_AddCaption = getValue(dict, "MediaPicker.AddCaption") @@ -6624,7 +7272,9 @@ public final class PresentationStrings { self.PhotoEditor_SharpenTool = getValue(dict, "PhotoEditor.SharpenTool") self.Common_of = getValue(dict, "Common.of") self.AuthSessions_Title = getValue(dict, "AuthSessions.Title") + self.Passport_Scans_UploadNew = getValue(dict, "Passport.Scans.UploadNew") self.Message_PinnedLiveLocationMessage = getValue(dict, "Message.PinnedLiveLocationMessage") + self.Passport_FieldIdentityDetailsHelp = getValue(dict, "Passport.FieldIdentityDetailsHelp") self.PrivacyLastSeenSettings_AlwaysShareWith = getValue(dict, "PrivacyLastSeenSettings.AlwaysShareWith") self.EnterPasscode_EnterPasscode = getValue(dict, "EnterPasscode.EnterPasscode") self.Notifications_Reset = getValue(dict, "Notifications.Reset") @@ -6637,22 +7287,26 @@ public final class PresentationStrings { self._CHAT_MESSAGE_DOC_r = extractArgumentRanges(self._CHAT_MESSAGE_DOC) self.Watch_AppName = getValue(dict, "Watch.AppName") self.ConvertToSupergroup_HelpTitle = getValue(dict, "ConvertToSupergroup.HelpTitle") + self.Conversation_TapAndHoldToRecord = getValue(dict, "Conversation.TapAndHoldToRecord") self._MESSAGE_GIF = getValue(dict, "MESSAGE_GIF") self._MESSAGE_GIF_r = extractArgumentRanges(self._MESSAGE_GIF) self._DialogList_EncryptedChatStartedOutgoing = getValue(dict, "DialogList.EncryptedChatStartedOutgoing") self._DialogList_EncryptedChatStartedOutgoing_r = extractArgumentRanges(self._DialogList_EncryptedChatStartedOutgoing) self.Checkout_PayWithTouchId = getValue(dict, "Checkout.PayWithTouchId") + self.Passport_Language_ko = getValue(dict, "Passport.Language.ko") self.Conversation_DiscardVoiceMessageTitle = getValue(dict, "Conversation.DiscardVoiceMessageTitle") self._CHAT_ADD_YOU = getValue(dict, "CHAT_ADD_YOU") self._CHAT_ADD_YOU_r = extractArgumentRanges(self._CHAT_ADD_YOU) self.CheckoutInfo_ShippingInfoCity = getValue(dict, "CheckoutInfo.ShippingInfoCity") self.AutoDownloadSettings_GroupChats = getValue(dict, "AutoDownloadSettings.GroupChats") self.Conversation_ClousStorageInfo_Description3 = getValue(dict, "Conversation.ClousStorageInfo.Description3") + self.Notifications_ExceptionsMuted = getValue(dict, "Notifications.ExceptionsMuted") self.Conversation_PinMessageAlertGroup = getValue(dict, "Conversation.PinMessageAlertGroup") self.Settings_FAQ_Intro = getValue(dict, "Settings.FAQ_Intro") self.PrivacySettings_AuthSessions = getValue(dict, "PrivacySettings.AuthSessions") self._CHAT_MESSAGE_GEOLIVE = getValue(dict, "CHAT_MESSAGE_GEOLIVE") self._CHAT_MESSAGE_GEOLIVE_r = extractArgumentRanges(self._CHAT_MESSAGE_GEOLIVE) + self.Passport_Address_Postcode = getValue(dict, "Passport.Address.Postcode") self.Tour_Title5 = getValue(dict, "Tour.Title5") self.ChatAdmins_AllMembersAreAdmins = getValue(dict, "ChatAdmins.AllMembersAreAdmins") self.Group_Management_AddModeratorHelp = getValue(dict, "Group.Management.AddModeratorHelp") @@ -6676,18 +7330,20 @@ public final class PresentationStrings { self._MESSAGE_STICKER_r = extractArgumentRanges(self._MESSAGE_STICKER) self.Profile_MessageLifetime5s = getValue(dict, "Profile.MessageLifetime5s") self.Privacy_ContactsReset = getValue(dict, "Privacy.ContactsReset") - self._TermsOfService_AgeVerificationText = getValue(dict, "TermsOfService.AgeVerificationText") - self._TermsOfService_AgeVerificationText_r = extractArgumentRanges(self._TermsOfService_AgeVerificationText) self._PINNED_PHOTO = getValue(dict, "PINNED_PHOTO") self._PINNED_PHOTO_r = extractArgumentRanges(self._PINNED_PHOTO) self.Channel_AdminLog_CanAddAdmins = getValue(dict, "Channel.AdminLog.CanAddAdmins") self.TwoStepAuth_SetupHint = getValue(dict, "TwoStepAuth.SetupHint") self.Conversation_StatusLeftGroup = getValue(dict, "Conversation.StatusLeftGroup") + self.Settings_CopyUsername = getValue(dict, "Settings.CopyUsername") + self.Passport_Identity_CountryPlaceholder = getValue(dict, "Passport.Identity.CountryPlaceholder") self.ChatSettings_AutoDownloadDocuments = getValue(dict, "ChatSettings.AutoDownloadDocuments") self.MediaPicker_TapToUngroupDescription = getValue(dict, "MediaPicker.TapToUngroupDescription") self.Conversation_ShareBotLocationConfirmation = getValue(dict, "Conversation.ShareBotLocationConfirmation") self.Conversation_DeleteMessagesForMe = getValue(dict, "Conversation.DeleteMessagesForMe") + self.Notification_PassportValuePersonalDetails = getValue(dict, "Notification.PassportValuePersonalDetails") self.Message_PinnedAnimationMessage = getValue(dict, "Message.PinnedAnimationMessage") + self.Passport_FieldIdentityUploadHelp = getValue(dict, "Passport.FieldIdentityUploadHelp") self.SocksProxySetup_ConnectAndSave = getValue(dict, "SocksProxySetup.ConnectAndSave") self.SocksProxySetup_FailedToConnect = getValue(dict, "SocksProxySetup.FailedToConnect") self.Checkout_ErrorPrecheckoutFailed = getValue(dict, "Checkout.ErrorPrecheckoutFailed") @@ -6702,13 +7358,17 @@ public final class PresentationStrings { self.Calls_RatingTitle = getValue(dict, "Calls.RatingTitle") self.SharedMedia_EmptyText = getValue(dict, "SharedMedia.EmptyText") self.Channel_Stickers_Searching = getValue(dict, "Channel.Stickers.Searching") + self.Passport_Address_AddUtilityBill = getValue(dict, "Passport.Address.AddUtilityBill") self.Login_PadPhoneHelp = getValue(dict, "Login.PadPhoneHelp") self.StickerPacksSettings_ArchivedPacks = getValue(dict, "StickerPacksSettings.ArchivedPacks") + self.Passport_Language_th = getValue(dict, "Passport.Language.th") self.Channel_ErrorAccessDenied = getValue(dict, "Channel.ErrorAccessDenied") self.Generic_ErrorMoreInfo = getValue(dict, "Generic.ErrorMoreInfo") self.Channel_AdminLog_TitleAllEvents = getValue(dict, "Channel.AdminLog.TitleAllEvents") self.Settings_Proxy = getValue(dict, "Settings.Proxy") + self.Passport_Language_lt = getValue(dict, "Passport.Language.lt") self.ChannelMembers_WhoCanAddMembersAllHelp = getValue(dict, "ChannelMembers.WhoCanAddMembersAllHelp") + self.Passport_Address_CountryPlaceholder = getValue(dict, "Passport.Address.CountryPlaceholder") self.ChangePhoneNumberCode_CodePlaceholder = getValue(dict, "ChangePhoneNumberCode.CodePlaceholder") self.Camera_SquareMode = getValue(dict, "Camera.SquareMode") self._Conversation_EncryptedPlaceholderTitleOutgoing = getValue(dict, "Conversation.EncryptedPlaceholderTitleOutgoing") @@ -6721,12 +7381,16 @@ public final class PresentationStrings { self.PhotoEditor_VignetteTool = getValue(dict, "PhotoEditor.VignetteTool") self.LastSeen_WithinAWeek = getValue(dict, "LastSeen.WithinAWeek") self.Widget_NoUsers = getValue(dict, "Widget.NoUsers") + self.Passport_Identity_DocumentNumber = getValue(dict, "Passport.Identity.DocumentNumber") + self.Application_Update = getValue(dict, "Application.Update") self.Calls_NewCall = getValue(dict, "Calls.NewCall") self._CHANNEL_MESSAGE_AUDIO = getValue(dict, "CHANNEL_MESSAGE_AUDIO") self._CHANNEL_MESSAGE_AUDIO_r = extractArgumentRanges(self._CHANNEL_MESSAGE_AUDIO) self.DialogList_NoMessagesText = getValue(dict, "DialogList.NoMessagesText") self.MaskStickerSettings_Info = getValue(dict, "MaskStickerSettings.Info") self.ChatSettings_AutoDownloadTitle = getValue(dict, "ChatSettings.AutoDownloadTitle") + self.Passport_FieldAddressHelp = getValue(dict, "Passport.FieldAddressHelp") + self.Passport_Language_dz = getValue(dict, "Passport.Language.dz") self.Conversation_FilePhotoOrVideo = getValue(dict, "Conversation.FilePhotoOrVideo") self.Channel_AdminLog_BanSendStickers = getValue(dict, "Channel.AdminLog.BanSendStickers") self.Common_Next = getValue(dict, "Common.Next") @@ -6734,10 +7398,13 @@ public final class PresentationStrings { self.Watch_Notification_Joined = getValue(dict, "Watch.Notification.Joined") self._Channel_AdminLog_MessageRestrictedNewSetting = getValue(dict, "Channel.AdminLog.MessageRestrictedNewSetting") self._Channel_AdminLog_MessageRestrictedNewSetting_r = extractArgumentRanges(self._Channel_AdminLog_MessageRestrictedNewSetting) + self.Passport_DeleteAddress = getValue(dict, "Passport.DeleteAddress") self.ContactInfo_PhoneLabelHome = getValue(dict, "ContactInfo.PhoneLabelHome") self.GroupInfo_DeleteAndExitConfirmation = getValue(dict, "GroupInfo.DeleteAndExitConfirmation") + self.NotificationsSound_Tremolo = getValue(dict, "NotificationsSound.Tremolo") self.TwoStepAuth_EmailInvalid = getValue(dict, "TwoStepAuth.EmailInvalid") self.Privacy_ContactsTitle = getValue(dict, "Privacy.ContactsTitle") + self.Passport_Address_TypeBankStatement = getValue(dict, "Passport.Address.TypeBankStatement") self._CHAT_MESSAGE_VIDEO = getValue(dict, "CHAT_MESSAGE_VIDEO") self._CHAT_MESSAGE_VIDEO_r = extractArgumentRanges(self._CHAT_MESSAGE_VIDEO) self.Month_GenJune = getValue(dict, "Month.GenJune") @@ -6762,12 +7429,14 @@ public final class PresentationStrings { self.Notifications_GroupNotificationsSound = getValue(dict, "Notifications.GroupNotificationsSound") self.AuthSessions_EmptyTitle = getValue(dict, "AuthSessions.EmptyTitle") self.Privacy_GroupsAndChannels_AlwaysAllow_Title = getValue(dict, "Privacy.GroupsAndChannels.AlwaysAllow.Title") + self.Passport_Language_he = getValue(dict, "Passport.Language.he") self._MediaPicker_Nof = getValue(dict, "MediaPicker.Nof") self._MediaPicker_Nof_r = extractArgumentRanges(self._MediaPicker_Nof) self.Common_Create = getValue(dict, "Common.Create") self.Contacts_TopSection = getValue(dict, "Contacts.TopSection") self._Map_DirectionsDriveEta = getValue(dict, "Map.DirectionsDriveEta") self._Map_DirectionsDriveEta_r = extractArgumentRanges(self._Map_DirectionsDriveEta) + self.PrivacyPolicy_DeclineMessage = getValue(dict, "PrivacyPolicy.DeclineMessage") self.Your_cards_number_is_invalid = getValue(dict, "Your_cards_number_is_invalid") self._MESSAGE_INVOICE = getValue(dict, "MESSAGE_INVOICE") self._MESSAGE_INVOICE_r = extractArgumentRanges(self._MESSAGE_INVOICE) @@ -6776,26 +7445,31 @@ public final class PresentationStrings { self._Channel_AdminLog_MessageRemovedChannelUsername_r = extractArgumentRanges(self._Channel_AdminLog_MessageRemovedChannelUsername) self.Group_MessagePhotoRemoved = getValue(dict, "Group.MessagePhotoRemoved") self.UserInfo_AddToExisting = getValue(dict, "UserInfo.AddToExisting") + self.NotificationsSound_Aurora = getValue(dict, "NotificationsSound.Aurora") self._LastSeen_AtDate = getValue(dict, "LastSeen.AtDate") self._LastSeen_AtDate_r = extractArgumentRanges(self._LastSeen_AtDate) self.Conversation_MessageDialogRetry = getValue(dict, "Conversation.MessageDialogRetry") self.Watch_ChatList_NoConversationsTitle = getValue(dict, "Watch.ChatList.NoConversationsTitle") + self.Passport_Language_my = getValue(dict, "Passport.Language.my") self.Stickers_GroupStickers = getValue(dict, "Stickers.GroupStickers") self.BlockedUsers_Title = getValue(dict, "BlockedUsers.Title") self._LiveLocationUpdated_TodayAt = getValue(dict, "LiveLocationUpdated.TodayAt") self._LiveLocationUpdated_TodayAt_r = extractArgumentRanges(self._LiveLocationUpdated_TodayAt) self.ContactInfo_PhoneLabelWork = getValue(dict, "ContactInfo.PhoneLabelWork") self.ChatSettings_ConnectionType_UseSocks5 = getValue(dict, "ChatSettings.ConnectionType.UseSocks5") + self.Passport_FieldAddressTranslationHelp = getValue(dict, "Passport.FieldAddressTranslationHelp") self.Cache_ClearNone = getValue(dict, "Cache.ClearNone") self.SecretTimer_VideoDescription = getValue(dict, "SecretTimer.VideoDescription") self.Login_InvalidCodeError = getValue(dict, "Login.InvalidCodeError") self.Channel_BanList_BlockedTitle = getValue(dict, "Channel.BanList.BlockedTitle") + self.Passport_PasswordHelp = getValue(dict, "Passport.PasswordHelp") self.NetworkUsageSettings_Cellular = getValue(dict, "NetworkUsageSettings.Cellular") self.Watch_Location_Access = getValue(dict, "Watch.Location.Access") self.PrivacySettings_DeleteAccountIfAwayFor = getValue(dict, "PrivacySettings.DeleteAccountIfAwayFor") self.Channel_AdminLog_EmptyFilterText = getValue(dict, "Channel.AdminLog.EmptyFilterText") self.Channel_AdminLog_EmptyText = getValue(dict, "Channel.AdminLog.EmptyText") self.PrivacySettings_DeleteAccountTitle = getValue(dict, "PrivacySettings.DeleteAccountTitle") + self.Passport_Language_ms = getValue(dict, "Passport.Language.ms") self.PrivacyLastSeenSettings_CustomShareSettings_Delete = getValue(dict, "PrivacyLastSeenSettings.CustomShareSettings.Delete") self._ENCRYPTED_MESSAGE = getValue(dict, "ENCRYPTED_MESSAGE") self._ENCRYPTED_MESSAGE_r = extractArgumentRanges(self._ENCRYPTED_MESSAGE) @@ -6806,9 +7480,12 @@ public final class PresentationStrings { self.Privacy_GroupsAndChannels_AlwaysAllow_Placeholder = getValue(dict, "Privacy.GroupsAndChannels.AlwaysAllow.Placeholder") self.UserInfo_BotSettings = getValue(dict, "UserInfo.BotSettings") self.Your_cards_expiration_month_is_invalid = getValue(dict, "Your_cards_expiration_month_is_invalid") + self.Passport_FieldIdentity = getValue(dict, "Passport.FieldIdentity") self.PrivacyLastSeenSettings_EmpryUsersPlaceholder = getValue(dict, "PrivacyLastSeenSettings.EmpryUsersPlaceholder") + self.Passport_Identity_EditInternalPassport = getValue(dict, "Passport.Identity.EditInternalPassport") self._CHANNEL_MESSAGE_ROUND = getValue(dict, "CHANNEL_MESSAGE_ROUND") self._CHANNEL_MESSAGE_ROUND_r = extractArgumentRanges(self._CHANNEL_MESSAGE_ROUND) + self.Passport_Identity_LatinNameHelp = getValue(dict, "Passport.Identity.LatinNameHelp") self.SocksProxySetup_Port = getValue(dict, "SocksProxySetup.Port") self.Message_VideoMessage = getValue(dict, "Message.VideoMessage") self.Conversation_ContextMenuStickerPackInfo = getValue(dict, "Conversation.ContextMenuStickerPackInfo") @@ -6817,32 +7494,40 @@ public final class PresentationStrings { self._CHAT_DELETE_MEMBER_r = extractArgumentRanges(self._CHAT_DELETE_MEMBER) self.Conversation_DiscardVoiceMessageAction = getValue(dict, "Conversation.DiscardVoiceMessageAction") self.Camera_Title = getValue(dict, "Camera.Title") + self.Passport_Identity_IssueDate = getValue(dict, "Passport.Identity.IssueDate") self.PhotoEditor_CurvesBlue = getValue(dict, "PhotoEditor.CurvesBlue") self.Message_PinnedVideoMessage = getValue(dict, "Message.PinnedVideoMessage") self._Login_EmailPhoneSubject = getValue(dict, "Login.EmailPhoneSubject") self._Login_EmailPhoneSubject_r = extractArgumentRanges(self._Login_EmailPhoneSubject) + self.Passport_Phone_UseTelegramNumberHelp = getValue(dict, "Passport.Phone.UseTelegramNumberHelp") self.Group_EditAdmin_PermissionChangeInfo = getValue(dict, "Group.EditAdmin.PermissionChangeInfo") self.TwoStepAuth_Email = getValue(dict, "TwoStepAuth.Email") self.Stickers_SuggestNone = getValue(dict, "Stickers.SuggestNone") self.Map_SendMyCurrentLocation = getValue(dict, "Map.SendMyCurrentLocation") self._MESSAGE_ROUND = getValue(dict, "MESSAGE_ROUND") self._MESSAGE_ROUND_r = extractArgumentRanges(self._MESSAGE_ROUND) + self.Passport_Identity_IssueDatePlaceholder = getValue(dict, "Passport.Identity.IssueDatePlaceholder") self.Map_Unknown = getValue(dict, "Map.Unknown") self.Wallpaper_Set = getValue(dict, "Wallpaper.Set") self.AccessDenied_Title = getValue(dict, "AccessDenied.Title") self.SharedMedia_CategoryLinks = getValue(dict, "SharedMedia.CategoryLinks") self.Localization_LanguageOther = getValue(dict, "Localization.LanguageOther") self.SaveIncomingPhotosSettings_Title = getValue(dict, "SaveIncomingPhotosSettings.Title") + self.Passport_Identity_TypeDriversLicense = getValue(dict, "Passport.Identity.TypeDriversLicense") + self.FastTwoStepSetup_HintHelp = getValue(dict, "FastTwoStepSetup.HintHelp") + self.Notifications_ExceptionsDefaultSound = getValue(dict, "Notifications.ExceptionsDefaultSound") + self.Passport_Language_es = getValue(dict, "Passport.Language.es") self.TwoStepAuth_EmailSkipAlert = getValue(dict, "TwoStepAuth.EmailSkipAlert") self.ChatSettings_Stickers = getValue(dict, "ChatSettings.Stickers") self.Camera_FlashOff = getValue(dict, "Camera.FlashOff") self.TwoStepAuth_Title = getValue(dict, "TwoStepAuth.Title") - self.PrivacySettings_DataSettingsHelp = getValue(dict, "PrivacySettings.DataSettingsHelp") + self.Passport_Identity_Translation = getValue(dict, "Passport.Identity.Translation") self.Checkout_ErrorProviderAccountTimeout = getValue(dict, "Checkout.ErrorProviderAccountTimeout") self.TwoStepAuth_SetupPasswordEnterPasswordChange = getValue(dict, "TwoStepAuth.SetupPasswordEnterPasswordChange") self.WebSearch_Images = getValue(dict, "WebSearch.Images") self.Conversation_typing = getValue(dict, "Conversation.typing") self.Common_Back = getValue(dict, "Common.Back") + self.PrivacySettings_DataSettingsHelp = getValue(dict, "PrivacySettings.DataSettingsHelp") self.Common_Search = getValue(dict, "Common.Search") self._CancelResetAccount_Success = getValue(dict, "CancelResetAccount.Success") self._CancelResetAccount_Success_r = extractArgumentRanges(self._CancelResetAccount_Success) @@ -6850,10 +7535,14 @@ public final class PresentationStrings { self.Login_EmailNotConfiguredError = getValue(dict, "Login.EmailNotConfiguredError") self.Watch_Suggestion_OK = getValue(dict, "Watch.Suggestion.OK") self.Profile_AddToExisting = getValue(dict, "Profile.AddToExisting") + self._Passport_Identity_NativeNameTitle = getValue(dict, "Passport.Identity.NativeNameTitle") + self._Passport_Identity_NativeNameTitle_r = extractArgumentRanges(self._Passport_Identity_NativeNameTitle) self._PINNED_NOTEXT = getValue(dict, "PINNED_NOTEXT") self._PINNED_NOTEXT_r = extractArgumentRanges(self._PINNED_NOTEXT) self._Login_EmailCodeBody = getValue(dict, "Login.EmailCodeBody") self._Login_EmailCodeBody_r = extractArgumentRanges(self._Login_EmailCodeBody) + self.NotificationsSound_Keys = getValue(dict, "NotificationsSound.Keys") + self.Passport_Phone_Title = getValue(dict, "Passport.Phone.Title") self.Profile_About = getValue(dict, "Profile.About") self._EncryptionKey_Description = getValue(dict, "EncryptionKey.Description") self._EncryptionKey_Description_r = extractArgumentRanges(self._EncryptionKey_Description) @@ -6861,8 +7550,11 @@ public final class PresentationStrings { self._DialogList_LiveLocationSharingTo = getValue(dict, "DialogList.LiveLocationSharingTo") self._DialogList_LiveLocationSharingTo_r = extractArgumentRanges(self._DialogList_LiveLocationSharingTo) self.Tour_Title3 = getValue(dict, "Tour.Title3") + self.Passport_Identity_FrontSide = getValue(dict, "Passport.Identity.FrontSide") self.PrivacyLastSeenSettings_GroupsAndChannelsHelp = getValue(dict, "PrivacyLastSeenSettings.GroupsAndChannelsHelp") self.Watch_Contacts_NoResults = getValue(dict, "Watch.Contacts.NoResults") + self.Passport_Language_id = getValue(dict, "Passport.Language.id") + self.Passport_Identity_TypeIdentityCardUploadScan = getValue(dict, "Passport.Identity.TypeIdentityCardUploadScan") self.Watch_UserInfo_MuteTitle = getValue(dict, "Watch.UserInfo.MuteTitle") self._Privacy_GroupsAndChannels_InviteToGroupError = getValue(dict, "Privacy.GroupsAndChannels.InviteToGroupError") self._Privacy_GroupsAndChannels_InviteToGroupError_r = extractArgumentRanges(self._Privacy_GroupsAndChannels_InviteToGroupError) @@ -6873,6 +7565,7 @@ public final class PresentationStrings { self.Conversation_EmptyGifPanelPlaceholder = getValue(dict, "Conversation.EmptyGifPanelPlaceholder") self.DialogList_Typing = getValue(dict, "DialogList.Typing") self.Notification_CallBack = getValue(dict, "Notification.CallBack") + self.Passport_Language_ru = getValue(dict, "Passport.Language.ru") self.Map_LocatingError = getValue(dict, "Map.LocatingError") self.InfoPlist_NSFaceIDUsageDescription = getValue(dict, "InfoPlist.NSFaceIDUsageDescription") self.MediaPicker_Send = getValue(dict, "MediaPicker.Send") @@ -6882,14 +7575,16 @@ public final class PresentationStrings { self._PINNED_GIF_r = extractArgumentRanges(self._PINNED_GIF) self._InviteText_SingleContact = getValue(dict, "InviteText.SingleContact") self._InviteText_SingleContact_r = extractArgumentRanges(self._InviteText_SingleContact) + self.Passport_Address_TypePassportRegistration = getValue(dict, "Passport.Address.TypePassportRegistration") self.Channel_EditAdmin_CannotEdit = getValue(dict, "Channel.EditAdmin.CannotEdit") - self.PrivacySettings_DeleteContacts = getValue(dict, "PrivacySettings.DeleteContacts") self.LoginPassword_PasswordHelp = getValue(dict, "LoginPassword.PasswordHelp") self.BlockedUsers_Unblock = getValue(dict, "BlockedUsers.Unblock") self.AutoDownloadSettings_Cellular = getValue(dict, "AutoDownloadSettings.Cellular") + self.Passport_Language_ro = getValue(dict, "Passport.Language.ro") self._Time_MonthOfYear_m1 = getValue(dict, "Time.MonthOfYear_m1") self._Time_MonthOfYear_m1_r = extractArgumentRanges(self._Time_MonthOfYear_m1) self.Appearance_PreviewIncomingText = getValue(dict, "Appearance.PreviewIncomingText") + self.Passport_Identity_DateOfBirthPlaceholder = getValue(dict, "Passport.Identity.DateOfBirthPlaceholder") self.Notifications_GroupNotificationsAlert = getValue(dict, "Notifications.GroupNotificationsAlert") self.Paint_Masks = getValue(dict, "Paint.Masks") self.Appearance_ThemeDayClassic = getValue(dict, "Appearance.ThemeDayClassic") @@ -6916,6 +7611,7 @@ public final class PresentationStrings { self.SocksProxySetup_HostnamePlaceholder = getValue(dict, "SocksProxySetup.HostnamePlaceholder") self.NetworkUsageSettings_MediaVideoDataSection = getValue(dict, "NetworkUsageSettings.MediaVideoDataSection") self.Paint_Edit = getValue(dict, "Paint.Edit") + self.Passport_Language_nl = getValue(dict, "Passport.Language.nl") self.Conversation_EncryptedDescription3 = getValue(dict, "Conversation.EncryptedDescription3") self.Login_CodeFloodError = getValue(dict, "Login.CodeFloodError") self.Conversation_EncryptedDescription4 = getValue(dict, "Conversation.EncryptedDescription4") @@ -6924,16 +7620,17 @@ public final class PresentationStrings { self.Conversation_StatusTyping = getValue(dict, "Conversation.StatusTyping") self.Share_Title = getValue(dict, "Share.Title") self.TwoStepAuth_ConfirmationTitle = getValue(dict, "TwoStepAuth.ConfirmationTitle") + self.Passport_Identity_FilesTitle = getValue(dict, "Passport.Identity.FilesTitle") self.ChatSettings_Title = getValue(dict, "ChatSettings.Title") self.AuthSessions_CurrentSession = getValue(dict, "AuthSessions.CurrentSession") - self._SecureId_RequestTitle = getValue(dict, "SecureId.RequestTitle") - self._SecureId_RequestTitle_r = extractArgumentRanges(self._SecureId_RequestTitle) self.Watch_Microphone_Access = getValue(dict, "Watch.Microphone.Access") self._Notification_RenamedChat = getValue(dict, "Notification.RenamedChat") self._Notification_RenamedChat_r = extractArgumentRanges(self._Notification_RenamedChat) self.Conversation_LiveLocation = getValue(dict, "Conversation.LiveLocation") self.Watch_Conversation_GroupInfo = getValue(dict, "Watch.Conversation.GroupInfo") + self.Passport_Language_fr = getValue(dict, "Passport.Language.fr") self.UserInfo_Title = getValue(dict, "UserInfo.Title") + self.Passport_Identity_DoesNotExpire = getValue(dict, "Passport.Identity.DoesNotExpire") self.Map_LiveLocationGroupDescription = getValue(dict, "Map.LiveLocationGroupDescription") self.Login_InfoHelp = getValue(dict, "Login.InfoHelp") self.ShareMenu_ShareTo = getValue(dict, "ShareMenu.ShareTo") @@ -6944,16 +7641,21 @@ public final class PresentationStrings { self.Notification_RenamedGroup = getValue(dict, "Notification.RenamedGroup") self._Call_PrivacyErrorMessage = getValue(dict, "Call.PrivacyErrorMessage") self._Call_PrivacyErrorMessage_r = extractArgumentRanges(self._Call_PrivacyErrorMessage) + self.Passport_Address_Street = getValue(dict, "Passport.Address.Street") + self.FastTwoStepSetup_HintPlaceholder = getValue(dict, "FastTwoStepSetup.HintPlaceholder") self.PrivacySettings_DataSettings = getValue(dict, "PrivacySettings.DataSettings") self.ChangePhoneNumberNumber_Title = getValue(dict, "ChangePhoneNumberNumber.Title") + self.NotificationsSound_Bell = getValue(dict, "NotificationsSound.Bell") self.TwoStepAuth_EnterPasswordInvalid = getValue(dict, "TwoStepAuth.EnterPasswordInvalid") self.DialogList_SearchSectionMessages = getValue(dict, "DialogList.SearchSectionMessages") self.Media_ShareThisVideo = getValue(dict, "Media.ShareThisVideo") self.Call_ReportIncludeLogDescription = getValue(dict, "Call.ReportIncludeLogDescription") self.Preview_DeleteGif = getValue(dict, "Preview.DeleteGif") + self.Passport_Address_OneOfTypeTemporaryRegistration = getValue(dict, "Passport.Address.OneOfTypeTemporaryRegistration") self.UserInfo_DeleteContact = getValue(dict, "UserInfo.DeleteContact") self.Notifications_ResetAllNotifications = getValue(dict, "Notifications.ResetAllNotifications") self.SocksProxySetup_SaveProxy = getValue(dict, "SocksProxySetup.SaveProxy") + self.Passport_Identity_Country = getValue(dict, "Passport.Identity.Country") self.Notification_MessageLifetimeRemovedOutgoing = getValue(dict, "Notification.MessageLifetimeRemovedOutgoing") self.Login_ContinueWithLocalization = getValue(dict, "Login.ContinueWithLocalization") self.GroupInfo_AddParticipant = getValue(dict, "GroupInfo.AddParticipant") @@ -6971,11 +7673,12 @@ public final class PresentationStrings { self._PINNED_AUDIO_r = extractArgumentRanges(self._PINNED_AUDIO) self.Privacy_GroupsAndChannels = getValue(dict, "Privacy.GroupsAndChannels") self.Conversation_DiscardVoiceMessageDescription = getValue(dict, "Conversation.DiscardVoiceMessageDescription") + self.Passport_Address_ScansHelp = getValue(dict, "Passport.Address.ScansHelp") self._Notification_ChangedGroupPhoto = getValue(dict, "Notification.ChangedGroupPhoto") self._Notification_ChangedGroupPhoto_r = extractArgumentRanges(self._Notification_ChangedGroupPhoto) - self.PrivacySettings_Contacts = getValue(dict, "PrivacySettings.Contacts") self.TwoStepAuth_RemovePassword = getValue(dict, "TwoStepAuth.RemovePassword") self.Privacy_GroupsAndChannels_CustomHelp = getValue(dict, "Privacy.GroupsAndChannels.CustomHelp") + self.Passport_Identity_Gender = getValue(dict, "Passport.Identity.Gender") self.UserInfo_NotificationsDisable = getValue(dict, "UserInfo.NotificationsDisable") self.Watch_UserInfo_Service = getValue(dict, "Watch.UserInfo.Service") self.Privacy_Calls_CustomHelp = getValue(dict, "Privacy.Calls.CustomHelp") @@ -6995,8 +7698,10 @@ public final class PresentationStrings { self.GroupInfo_LeftStatus = getValue(dict, "GroupInfo.LeftStatus") self.Cache_ClearProgress = getValue(dict, "Cache.ClearProgress") self.Login_InvalidPhoneError = getValue(dict, "Login.InvalidPhoneError") + self.Passport_Authorize = getValue(dict, "Passport.Authorize") self.Cache_ClearEmpty = getValue(dict, "Cache.ClearEmpty") self.Map_Search = getValue(dict, "Map.Search") + self.Passport_Identity_Translations = getValue(dict, "Passport.Identity.Translations") self.ChannelMembers_GroupAdminsTitle = getValue(dict, "ChannelMembers.GroupAdminsTitle") self._Channel_AdminLog_MessageRemovedGroupUsername = getValue(dict, "Channel.AdminLog.MessageRemovedGroupUsername") self._Channel_AdminLog_MessageRemovedGroupUsername_r = extractArgumentRanges(self._Channel_AdminLog_MessageRemovedGroupUsername) @@ -7004,6 +7709,7 @@ public final class PresentationStrings { self.Group_ErrorAddTooMuchAdmins = getValue(dict, "Group.ErrorAddTooMuchAdmins") self.SocksProxySetup_Password = getValue(dict, "SocksProxySetup.Password") self.Login_SelectCountry_Title = getValue(dict, "Login.SelectCountry.Title") + self.UserInfo_NotificationsDefault = getValue(dict, "UserInfo.NotificationsDefault") self.Notifications_GroupNotificationsHelp = getValue(dict, "Notifications.GroupNotificationsHelp") self.PhotoEditor_CropAspectRatioSquare = getValue(dict, "PhotoEditor.CropAspectRatioSquare") self.Notification_CallOutgoing = getValue(dict, "Notification.CallOutgoing") @@ -7014,10 +7720,11 @@ public final class PresentationStrings { self.Channel_Members_AddMembersHelp = getValue(dict, "Channel.Members.AddMembersHelp") self._MESSAGE_VIDEO_SECRET = getValue(dict, "MESSAGE_VIDEO_SECRET") self._MESSAGE_VIDEO_SECRET_r = extractArgumentRanges(self._MESSAGE_VIDEO_SECRET) + self.Settings_CopyPhoneNumber = getValue(dict, "Settings.CopyPhoneNumber") self.ReportPeer_Report = getValue(dict, "ReportPeer.Report") self.Channel_EditMessageErrorGeneric = getValue(dict, "Channel.EditMessageErrorGeneric") + self.Passport_Identity_TranslationsHelp = getValue(dict, "Passport.Identity.TranslationsHelp") self.LoginPassword_FloodError = getValue(dict, "LoginPassword.FloodError") - self.SecureId_FormFieldAddress = getValue(dict, "SecureId.FormFieldAddress") self.TwoStepAuth_SetupPasswordTitle = getValue(dict, "TwoStepAuth.SetupPasswordTitle") self.PhotoEditor_DiscardChanges = getValue(dict, "PhotoEditor.DiscardChanges") self.Group_UpgradeNoticeText2 = getValue(dict, "Group.UpgradeNoticeText2") @@ -7058,21 +7765,26 @@ public final class PresentationStrings { self.GroupInfo_Sound = getValue(dict, "GroupInfo.Sound") self.Channel_EditAdmin_PermissionBanUsers = getValue(dict, "Channel.EditAdmin.PermissionBanUsers") self.InfoPlist_NSCameraUsageDescription = getValue(dict, "InfoPlist.NSCameraUsageDescription") - self.ContactInfo_Job = getValue(dict, "ContactInfo.Job") + self.Passport_Address_AddRentalAgreement = getValue(dict, "Passport.Address.AddRentalAgreement") self.Wallpaper_PhotoLibrary = getValue(dict, "Wallpaper.PhotoLibrary") self.Settings_About = getValue(dict, "Settings.About") self.Privacy_Calls_IntegrationHelp = getValue(dict, "Privacy.Calls.IntegrationHelp") + self.ContactInfo_Job = getValue(dict, "ContactInfo.Job") self._CHAT_LEFT = getValue(dict, "CHAT_LEFT") self._CHAT_LEFT_r = extractArgumentRanges(self._CHAT_LEFT) self.LoginPassword_ForgotPassword = getValue(dict, "LoginPassword.ForgotPassword") + self.Passport_Address_AddTemporaryRegistration = getValue(dict, "Passport.Address.AddTemporaryRegistration") self._Map_LiveLocationShortHour = getValue(dict, "Map.LiveLocationShortHour") self._Map_LiveLocationShortHour_r = extractArgumentRanges(self._Map_LiveLocationShortHour) self.Appearance_Preview = getValue(dict, "Appearance.Preview") self._DialogList_AwaitingEncryption = getValue(dict, "DialogList.AwaitingEncryption") self._DialogList_AwaitingEncryption_r = extractArgumentRanges(self._DialogList_AwaitingEncryption) + self.Passport_Identity_TypePassport = getValue(dict, "Passport.Identity.TypePassport") self.ChatSettings_Appearance = getValue(dict, "ChatSettings.Appearance") self.Tour_Title1 = getValue(dict, "Tour.Title1") self.Conversation_EditingCaptionPanelTitle = getValue(dict, "Conversation.EditingCaptionPanelTitle") + self._Notifications_ExceptionsChangeSound = getValue(dict, "Notifications.ExceptionsChangeSound") + self._Notifications_ExceptionsChangeSound_r = extractArgumentRanges(self._Notifications_ExceptionsChangeSound) self.Conversation_LinkDialogCopy = getValue(dict, "Conversation.LinkDialogCopy") self._Notification_PinnedLocationMessage = getValue(dict, "Notification.PinnedLocationMessage") self._Notification_PinnedLocationMessage_r = extractArgumentRanges(self._Notification_PinnedLocationMessage) @@ -7095,8 +7807,11 @@ public final class PresentationStrings { self._AutoDownloadSettings_UpTo_r = extractArgumentRanges(self._AutoDownloadSettings_UpTo) self.Settings_ViewPhoto = getValue(dict, "Settings.ViewPhoto") self.Paint_RecentStickers = getValue(dict, "Paint.RecentStickers") + self._Passport_PrivacyPolicy = getValue(dict, "Passport.PrivacyPolicy") + self._Passport_PrivacyPolicy_r = extractArgumentRanges(self._Passport_PrivacyPolicy) self.Login_CallRequestState3 = getValue(dict, "Login.CallRequestState3") self.Channel_Edit_LinkItem = getValue(dict, "Channel.Edit.LinkItem") + self.Passport_InfoTitle = getValue(dict, "Passport.InfoTitle") self.CallSettings_Title = getValue(dict, "CallSettings.Title") self.ChangePhoneNumberNumber_Help = getValue(dict, "ChangePhoneNumberNumber.Help") self.Watch_Suggestion_Thanks = getValue(dict, "Watch.Suggestion.Thanks") @@ -7111,12 +7826,13 @@ public final class PresentationStrings { self.Tour_Text4 = getValue(dict, "Tour.Text4") self.Channel_Info_Description = getValue(dict, "Channel.Info.Description") self.AccessDenied_LocationTracking = getValue(dict, "AccessDenied.LocationTracking") - self.TermsOfService_Disagree = getValue(dict, "TermsOfService.Disagree") self.Watch_Compose_Send = getValue(dict, "Watch.Compose.Send") self.SocksProxySetup_UseForCallsHelp = getValue(dict, "SocksProxySetup.UseForCallsHelp") self.Preview_CopyAddress = getValue(dict, "Preview.CopyAddress") self.Settings_BlockedUsers = getValue(dict, "Settings.BlockedUsers") self.Month_ShortAugust = getValue(dict, "Month.ShortAugust") + self.Passport_Identity_MainPage = getValue(dict, "Passport.Identity.MainPage") + self.Passport_FieldAddress = getValue(dict, "Passport.FieldAddress") self.Channel_AdminLogFilter_AdminsTitle = getValue(dict, "Channel.AdminLogFilter.AdminsTitle") self.Channel_EditAdmin_PermissionChangeInfo = getValue(dict, "Channel.EditAdmin.PermissionChangeInfo") self.Notifications_ResetAllNotificationsHelp = getValue(dict, "Notifications.ResetAllNotificationsHelp") @@ -7129,6 +7845,7 @@ public final class PresentationStrings { self.PhotoEditor_CurvesGreen = getValue(dict, "PhotoEditor.CurvesGreen") self.Month_GenJuly = getValue(dict, "Month.GenJuly") self.ContactInfo_URLLabelHomepage = getValue(dict, "ContactInfo.URLLabelHomepage") + self.PrivacyPolicy_DeclineDeclineAndDelete = getValue(dict, "PrivacyPolicy.DeclineDeclineAndDelete") self._DialogList_SingleUploadingFileSuffix = getValue(dict, "DialogList.SingleUploadingFileSuffix") self._DialogList_SingleUploadingFileSuffix_r = extractArgumentRanges(self._DialogList_SingleUploadingFileSuffix) self.ChannelIntro_CreateChannel = getValue(dict, "ChannelIntro.CreateChannel") @@ -7153,6 +7870,7 @@ public final class PresentationStrings { self.Notifications_ClassicTones = getValue(dict, "Notifications.ClassicTones") self.Conversation_LinkDialogOpen = getValue(dict, "Conversation.LinkDialogOpen") self.Channel_Info_Subscribers = getValue(dict, "Channel.Info.Subscribers") + self.NotificationsSound_Input = getValue(dict, "NotificationsSound.Input") self.Conversation_ClousStorageInfo_Description4 = getValue(dict, "Conversation.ClousStorageInfo.Description4") self.Privacy_Calls_AlwaysAllow = getValue(dict, "Privacy.Calls.AlwaysAllow") self.Privacy_PaymentsClearInfoHelp = getValue(dict, "Privacy.PaymentsClearInfoHelp") @@ -7168,12 +7886,14 @@ public final class PresentationStrings { self._Conversation_Kilobytes = getValue(dict, "Conversation.Kilobytes") self._Conversation_Kilobytes_r = extractArgumentRanges(self._Conversation_Kilobytes) self.Group_ErrorAddBlocked = getValue(dict, "Group.ErrorAddBlocked") - self.ChatList_MarkAsUnread = getValue(dict, "ChatList.MarkAsUnread") self.TwoStepAuth_AdditionalPassword = getValue(dict, "TwoStepAuth.AdditionalPassword") self.MediaPicker_Videos = getValue(dict, "MediaPicker.Videos") + self.Notification_PassportValueProofOfIdentity = getValue(dict, "Notification.PassportValueProofOfIdentity") self.BlockedUsers_AddNew = getValue(dict, "BlockedUsers.AddNew") self.StickerPacksSettings_StickerPacksSection = getValue(dict, "StickerPacksSettings.StickerPacksSection") self.Channel_NotificationLoading = getValue(dict, "Channel.NotificationLoading") + self.Passport_Language_da = getValue(dict, "Passport.Language.da") + self.Passport_Address_Country = getValue(dict, "Passport.Address.Country") self._CHAT_RETURNED = getValue(dict, "CHAT_RETURNED") self._CHAT_RETURNED_r = extractArgumentRanges(self._CHAT_RETURNED) self.PhotoEditor_ShadowsTint = getValue(dict, "PhotoEditor.ShadowsTint") @@ -7188,7 +7908,10 @@ public final class PresentationStrings { self.Weekday_ShortTuesday = getValue(dict, "Weekday.ShortTuesday") self.Notification_CallIncomingShort = getValue(dict, "Notification.CallIncomingShort") self.ConvertToSupergroup_Note = getValue(dict, "ConvertToSupergroup.Note") + self.DialogList_Read = getValue(dict, "DialogList.Read") self.Conversation_EmptyPlaceholder = getValue(dict, "Conversation.EmptyPlaceholder") + self._Passport_Email_CodeHelp = getValue(dict, "Passport.Email.CodeHelp") + self._Passport_Email_CodeHelp_r = extractArgumentRanges(self._Passport_Email_CodeHelp) self.Username_Help = getValue(dict, "Username.Help") self.StickerSettings_ContextHide = getValue(dict, "StickerSettings.ContextHide") self.Media_ShareThisPhoto = getValue(dict, "Media.ShareThisPhoto") @@ -7196,163 +7919,187 @@ public final class PresentationStrings { self.AutoNightTheme_Scheduled = getValue(dict, "AutoNightTheme.Scheduled") self.PrivacySettings_PasscodeAndFaceId = getValue(dict, "PrivacySettings.PasscodeAndFaceId") self.Settings_ChatBackground = getValue(dict, "Settings.ChatBackground") - self.TermsOfService_Confirm = getValue(dict, "TermsOfService.Confirm") - self._Media_ShareItem_zero = getValueWithForm(dict, "Media.ShareItem", .zero) - self._Media_ShareItem_one = getValueWithForm(dict, "Media.ShareItem", .one) - self._Media_ShareItem_two = getValueWithForm(dict, "Media.ShareItem", .two) - self._Media_ShareItem_few = getValueWithForm(dict, "Media.ShareItem", .few) - self._Media_ShareItem_many = getValueWithForm(dict, "Media.ShareItem", .many) - self._Media_ShareItem_other = getValueWithForm(dict, "Media.ShareItem", .other) - self._Media_ShareVideo_zero = getValueWithForm(dict, "Media.ShareVideo", .zero) - self._Media_ShareVideo_one = getValueWithForm(dict, "Media.ShareVideo", .one) - self._Media_ShareVideo_two = getValueWithForm(dict, "Media.ShareVideo", .two) - self._Media_ShareVideo_few = getValueWithForm(dict, "Media.ShareVideo", .few) - self._Media_ShareVideo_many = getValueWithForm(dict, "Media.ShareVideo", .many) - self._Media_ShareVideo_other = getValueWithForm(dict, "Media.ShareVideo", .other) - self._Call_Minutes_zero = getValueWithForm(dict, "Call.Minutes", .zero) - self._Call_Minutes_one = getValueWithForm(dict, "Call.Minutes", .one) - self._Call_Minutes_two = getValueWithForm(dict, "Call.Minutes", .two) - self._Call_Minutes_few = getValueWithForm(dict, "Call.Minutes", .few) - self._Call_Minutes_many = getValueWithForm(dict, "Call.Minutes", .many) - self._Call_Minutes_other = getValueWithForm(dict, "Call.Minutes", .other) - self._LiveLocation_MenuChatsCount_zero = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .zero) - self._LiveLocation_MenuChatsCount_one = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .one) - self._LiveLocation_MenuChatsCount_two = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .two) - self._LiveLocation_MenuChatsCount_few = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .few) - self._LiveLocation_MenuChatsCount_many = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .many) - self._LiveLocation_MenuChatsCount_other = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .other) - self._SharedMedia_Photo_zero = getValueWithForm(dict, "SharedMedia.Photo", .zero) - self._SharedMedia_Photo_one = getValueWithForm(dict, "SharedMedia.Photo", .one) - self._SharedMedia_Photo_two = getValueWithForm(dict, "SharedMedia.Photo", .two) - self._SharedMedia_Photo_few = getValueWithForm(dict, "SharedMedia.Photo", .few) - self._SharedMedia_Photo_many = getValueWithForm(dict, "SharedMedia.Photo", .many) - self._SharedMedia_Photo_other = getValueWithForm(dict, "SharedMedia.Photo", .other) - self._AttachmentMenu_SendGif_zero = getValueWithForm(dict, "AttachmentMenu.SendGif", .zero) - self._AttachmentMenu_SendGif_one = getValueWithForm(dict, "AttachmentMenu.SendGif", .one) - self._AttachmentMenu_SendGif_two = getValueWithForm(dict, "AttachmentMenu.SendGif", .two) - self._AttachmentMenu_SendGif_few = getValueWithForm(dict, "AttachmentMenu.SendGif", .few) - self._AttachmentMenu_SendGif_many = getValueWithForm(dict, "AttachmentMenu.SendGif", .many) - self._AttachmentMenu_SendGif_other = getValueWithForm(dict, "AttachmentMenu.SendGif", .other) - self._MuteFor_Days_zero = getValueWithForm(dict, "MuteFor.Days", .zero) - self._MuteFor_Days_one = getValueWithForm(dict, "MuteFor.Days", .one) - self._MuteFor_Days_two = getValueWithForm(dict, "MuteFor.Days", .two) - self._MuteFor_Days_few = getValueWithForm(dict, "MuteFor.Days", .few) - self._MuteFor_Days_many = getValueWithForm(dict, "MuteFor.Days", .many) - self._MuteFor_Days_other = getValueWithForm(dict, "MuteFor.Days", .other) - self._ForwardedGifs_zero = getValueWithForm(dict, "ForwardedGifs", .zero) - self._ForwardedGifs_one = getValueWithForm(dict, "ForwardedGifs", .one) - self._ForwardedGifs_two = getValueWithForm(dict, "ForwardedGifs", .two) - self._ForwardedGifs_few = getValueWithForm(dict, "ForwardedGifs", .few) - self._ForwardedGifs_many = getValueWithForm(dict, "ForwardedGifs", .many) - self._ForwardedGifs_other = getValueWithForm(dict, "ForwardedGifs", .other) - self._ForwardedVideoMessages_zero = getValueWithForm(dict, "ForwardedVideoMessages", .zero) - self._ForwardedVideoMessages_one = getValueWithForm(dict, "ForwardedVideoMessages", .one) - self._ForwardedVideoMessages_two = getValueWithForm(dict, "ForwardedVideoMessages", .two) - self._ForwardedVideoMessages_few = getValueWithForm(dict, "ForwardedVideoMessages", .few) - self._ForwardedVideoMessages_many = getValueWithForm(dict, "ForwardedVideoMessages", .many) - self._ForwardedVideoMessages_other = getValueWithForm(dict, "ForwardedVideoMessages", .other) - self._Forward_ConfirmMultipleFiles_zero = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .zero) - self._Forward_ConfirmMultipleFiles_one = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .one) - self._Forward_ConfirmMultipleFiles_two = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .two) - self._Forward_ConfirmMultipleFiles_few = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .few) - self._Forward_ConfirmMultipleFiles_many = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .many) - self._Forward_ConfirmMultipleFiles_other = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .other) - self._MessageTimer_Months_zero = getValueWithForm(dict, "MessageTimer.Months", .zero) - self._MessageTimer_Months_one = getValueWithForm(dict, "MessageTimer.Months", .one) - self._MessageTimer_Months_two = getValueWithForm(dict, "MessageTimer.Months", .two) - self._MessageTimer_Months_few = getValueWithForm(dict, "MessageTimer.Months", .few) - self._MessageTimer_Months_many = getValueWithForm(dict, "MessageTimer.Months", .many) - self._MessageTimer_Months_other = getValueWithForm(dict, "MessageTimer.Months", .other) - self._Map_ETAHours_zero = getValueWithForm(dict, "Map.ETAHours", .zero) - self._Map_ETAHours_one = getValueWithForm(dict, "Map.ETAHours", .one) - self._Map_ETAHours_two = getValueWithForm(dict, "Map.ETAHours", .two) - self._Map_ETAHours_few = getValueWithForm(dict, "Map.ETAHours", .few) - self._Map_ETAHours_many = getValueWithForm(dict, "Map.ETAHours", .many) - self._Map_ETAHours_other = getValueWithForm(dict, "Map.ETAHours", .other) - self._StickerPack_RemoveStickerCount_zero = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .zero) - self._StickerPack_RemoveStickerCount_one = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .one) - self._StickerPack_RemoveStickerCount_two = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .two) - self._StickerPack_RemoveStickerCount_few = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .few) - self._StickerPack_RemoveStickerCount_many = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .many) - self._StickerPack_RemoveStickerCount_other = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .other) - self._MessageTimer_ShortWeeks_zero = getValueWithForm(dict, "MessageTimer.ShortWeeks", .zero) - self._MessageTimer_ShortWeeks_one = getValueWithForm(dict, "MessageTimer.ShortWeeks", .one) - self._MessageTimer_ShortWeeks_two = getValueWithForm(dict, "MessageTimer.ShortWeeks", .two) - self._MessageTimer_ShortWeeks_few = getValueWithForm(dict, "MessageTimer.ShortWeeks", .few) - self._MessageTimer_ShortWeeks_many = getValueWithForm(dict, "MessageTimer.ShortWeeks", .many) - self._MessageTimer_ShortWeeks_other = getValueWithForm(dict, "MessageTimer.ShortWeeks", .other) - self._Call_Seconds_zero = getValueWithForm(dict, "Call.Seconds", .zero) - self._Call_Seconds_one = getValueWithForm(dict, "Call.Seconds", .one) - self._Call_Seconds_two = getValueWithForm(dict, "Call.Seconds", .two) - self._Call_Seconds_few = getValueWithForm(dict, "Call.Seconds", .few) - self._Call_Seconds_many = getValueWithForm(dict, "Call.Seconds", .many) - self._Call_Seconds_other = getValueWithForm(dict, "Call.Seconds", .other) - self._ForwardedAudios_zero = getValueWithForm(dict, "ForwardedAudios", .zero) - self._ForwardedAudios_one = getValueWithForm(dict, "ForwardedAudios", .one) - self._ForwardedAudios_two = getValueWithForm(dict, "ForwardedAudios", .two) - self._ForwardedAudios_few = getValueWithForm(dict, "ForwardedAudios", .few) - self._ForwardedAudios_many = getValueWithForm(dict, "ForwardedAudios", .many) - self._ForwardedAudios_other = getValueWithForm(dict, "ForwardedAudios", .other) - self._Conversation_StatusMembers_zero = getValueWithForm(dict, "Conversation.StatusMembers", .zero) - self._Conversation_StatusMembers_one = getValueWithForm(dict, "Conversation.StatusMembers", .one) - self._Conversation_StatusMembers_two = getValueWithForm(dict, "Conversation.StatusMembers", .two) - self._Conversation_StatusMembers_few = getValueWithForm(dict, "Conversation.StatusMembers", .few) - self._Conversation_StatusMembers_many = getValueWithForm(dict, "Conversation.StatusMembers", .many) - self._Conversation_StatusMembers_other = getValueWithForm(dict, "Conversation.StatusMembers", .other) - self._PasscodeSettings_FailedAttempts_zero = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .zero) - self._PasscodeSettings_FailedAttempts_one = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .one) - self._PasscodeSettings_FailedAttempts_two = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .two) - self._PasscodeSettings_FailedAttempts_few = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .few) - self._PasscodeSettings_FailedAttempts_many = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .many) - self._PasscodeSettings_FailedAttempts_other = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .other) + self.Login_TermsOfServiceDecline = getValue(dict, "Login.TermsOfServiceDecline") + self._ForwardedFiles_zero = getValueWithForm(dict, "ForwardedFiles", .zero) + self._ForwardedFiles_one = getValueWithForm(dict, "ForwardedFiles", .one) + self._ForwardedFiles_two = getValueWithForm(dict, "ForwardedFiles", .two) + self._ForwardedFiles_few = getValueWithForm(dict, "ForwardedFiles", .few) + self._ForwardedFiles_many = getValueWithForm(dict, "ForwardedFiles", .many) + self._ForwardedFiles_other = getValueWithForm(dict, "ForwardedFiles", .other) + self._Watch_LastSeen_HoursAgo_zero = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .zero) + self._Watch_LastSeen_HoursAgo_one = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .one) + self._Watch_LastSeen_HoursAgo_two = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .two) + self._Watch_LastSeen_HoursAgo_few = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .few) + self._Watch_LastSeen_HoursAgo_many = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .many) + self._Watch_LastSeen_HoursAgo_other = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .other) self._MessageTimer_ShortDays_zero = getValueWithForm(dict, "MessageTimer.ShortDays", .zero) self._MessageTimer_ShortDays_one = getValueWithForm(dict, "MessageTimer.ShortDays", .one) self._MessageTimer_ShortDays_two = getValueWithForm(dict, "MessageTimer.ShortDays", .two) self._MessageTimer_ShortDays_few = getValueWithForm(dict, "MessageTimer.ShortDays", .few) self._MessageTimer_ShortDays_many = getValueWithForm(dict, "MessageTimer.ShortDays", .many) self._MessageTimer_ShortDays_other = getValueWithForm(dict, "MessageTimer.ShortDays", .other) + self._LastSeen_MinutesAgo_zero = getValueWithForm(dict, "LastSeen.MinutesAgo", .zero) + self._LastSeen_MinutesAgo_one = getValueWithForm(dict, "LastSeen.MinutesAgo", .one) + self._LastSeen_MinutesAgo_two = getValueWithForm(dict, "LastSeen.MinutesAgo", .two) + self._LastSeen_MinutesAgo_few = getValueWithForm(dict, "LastSeen.MinutesAgo", .few) + self._LastSeen_MinutesAgo_many = getValueWithForm(dict, "LastSeen.MinutesAgo", .many) + self._LastSeen_MinutesAgo_other = getValueWithForm(dict, "LastSeen.MinutesAgo", .other) + self._MuteFor_Days_zero = getValueWithForm(dict, "MuteFor.Days", .zero) + self._MuteFor_Days_one = getValueWithForm(dict, "MuteFor.Days", .one) + self._MuteFor_Days_two = getValueWithForm(dict, "MuteFor.Days", .two) + self._MuteFor_Days_few = getValueWithForm(dict, "MuteFor.Days", .few) + self._MuteFor_Days_many = getValueWithForm(dict, "MuteFor.Days", .many) + self._MuteFor_Days_other = getValueWithForm(dict, "MuteFor.Days", .other) + self._MessageTimer_Months_zero = getValueWithForm(dict, "MessageTimer.Months", .zero) + self._MessageTimer_Months_one = getValueWithForm(dict, "MessageTimer.Months", .one) + self._MessageTimer_Months_two = getValueWithForm(dict, "MessageTimer.Months", .two) + self._MessageTimer_Months_few = getValueWithForm(dict, "MessageTimer.Months", .few) + self._MessageTimer_Months_many = getValueWithForm(dict, "MessageTimer.Months", .many) + self._MessageTimer_Months_other = getValueWithForm(dict, "MessageTimer.Months", .other) + self._PasscodeSettings_FailedAttempts_zero = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .zero) + self._PasscodeSettings_FailedAttempts_one = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .one) + self._PasscodeSettings_FailedAttempts_two = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .two) + self._PasscodeSettings_FailedAttempts_few = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .few) + self._PasscodeSettings_FailedAttempts_many = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .many) + self._PasscodeSettings_FailedAttempts_other = getValueWithForm(dict, "PasscodeSettings.FailedAttempts", .other) + self._ServiceMessage_GameScoreSimple_zero = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .zero) + self._ServiceMessage_GameScoreSimple_one = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .one) + self._ServiceMessage_GameScoreSimple_two = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .two) + self._ServiceMessage_GameScoreSimple_few = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .few) + self._ServiceMessage_GameScoreSimple_many = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .many) + self._ServiceMessage_GameScoreSimple_other = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .other) + self._MessageTimer_ShortWeeks_zero = getValueWithForm(dict, "MessageTimer.ShortWeeks", .zero) + self._MessageTimer_ShortWeeks_one = getValueWithForm(dict, "MessageTimer.ShortWeeks", .one) + self._MessageTimer_ShortWeeks_two = getValueWithForm(dict, "MessageTimer.ShortWeeks", .two) + self._MessageTimer_ShortWeeks_few = getValueWithForm(dict, "MessageTimer.ShortWeeks", .few) + self._MessageTimer_ShortWeeks_many = getValueWithForm(dict, "MessageTimer.ShortWeeks", .many) + self._MessageTimer_ShortWeeks_other = getValueWithForm(dict, "MessageTimer.ShortWeeks", .other) + self._MuteExpires_Days_zero = getValueWithForm(dict, "MuteExpires.Days", .zero) + self._MuteExpires_Days_one = getValueWithForm(dict, "MuteExpires.Days", .one) + self._MuteExpires_Days_two = getValueWithForm(dict, "MuteExpires.Days", .two) + self._MuteExpires_Days_few = getValueWithForm(dict, "MuteExpires.Days", .few) + self._MuteExpires_Days_many = getValueWithForm(dict, "MuteExpires.Days", .many) + self._MuteExpires_Days_other = getValueWithForm(dict, "MuteExpires.Days", .other) self._Notification_GameScoreExtended_zero = getValueWithForm(dict, "Notification.GameScoreExtended", .zero) self._Notification_GameScoreExtended_one = getValueWithForm(dict, "Notification.GameScoreExtended", .one) self._Notification_GameScoreExtended_two = getValueWithForm(dict, "Notification.GameScoreExtended", .two) self._Notification_GameScoreExtended_few = getValueWithForm(dict, "Notification.GameScoreExtended", .few) self._Notification_GameScoreExtended_many = getValueWithForm(dict, "Notification.GameScoreExtended", .many) self._Notification_GameScoreExtended_other = getValueWithForm(dict, "Notification.GameScoreExtended", .other) - self._MuteExpires_Hours_zero = getValueWithForm(dict, "MuteExpires.Hours", .zero) - self._MuteExpires_Hours_one = getValueWithForm(dict, "MuteExpires.Hours", .one) - self._MuteExpires_Hours_two = getValueWithForm(dict, "MuteExpires.Hours", .two) - self._MuteExpires_Hours_few = getValueWithForm(dict, "MuteExpires.Hours", .few) - self._MuteExpires_Hours_many = getValueWithForm(dict, "MuteExpires.Hours", .many) - self._MuteExpires_Hours_other = getValueWithForm(dict, "MuteExpires.Hours", .other) - self._InviteText_ContactsCount_zero = getValueWithForm(dict, "InviteText.ContactsCount", .zero) - self._InviteText_ContactsCount_one = getValueWithForm(dict, "InviteText.ContactsCount", .one) - self._InviteText_ContactsCount_two = getValueWithForm(dict, "InviteText.ContactsCount", .two) - self._InviteText_ContactsCount_few = getValueWithForm(dict, "InviteText.ContactsCount", .few) - self._InviteText_ContactsCount_many = getValueWithForm(dict, "InviteText.ContactsCount", .many) - self._InviteText_ContactsCount_other = getValueWithForm(dict, "InviteText.ContactsCount", .other) - self._StickerPack_AddStickerCount_zero = getValueWithForm(dict, "StickerPack.AddStickerCount", .zero) - self._StickerPack_AddStickerCount_one = getValueWithForm(dict, "StickerPack.AddStickerCount", .one) - self._StickerPack_AddStickerCount_two = getValueWithForm(dict, "StickerPack.AddStickerCount", .two) - self._StickerPack_AddStickerCount_few = getValueWithForm(dict, "StickerPack.AddStickerCount", .few) - self._StickerPack_AddStickerCount_many = getValueWithForm(dict, "StickerPack.AddStickerCount", .many) - self._StickerPack_AddStickerCount_other = getValueWithForm(dict, "StickerPack.AddStickerCount", .other) + self._Notifications_Exceptions_zero = getValueWithForm(dict, "Notifications.Exceptions", .zero) + self._Notifications_Exceptions_one = getValueWithForm(dict, "Notifications.Exceptions", .one) + self._Notifications_Exceptions_two = getValueWithForm(dict, "Notifications.Exceptions", .two) + self._Notifications_Exceptions_few = getValueWithForm(dict, "Notifications.Exceptions", .few) + self._Notifications_Exceptions_many = getValueWithForm(dict, "Notifications.Exceptions", .many) + self._Notifications_Exceptions_other = getValueWithForm(dict, "Notifications.Exceptions", .other) + self._DialogList_LiveLocationChatsCount_zero = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .zero) + self._DialogList_LiveLocationChatsCount_one = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .one) + self._DialogList_LiveLocationChatsCount_two = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .two) + self._DialogList_LiveLocationChatsCount_few = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .few) + self._DialogList_LiveLocationChatsCount_many = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .many) + self._DialogList_LiveLocationChatsCount_other = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .other) + self._ForwardedAuthorsOthers_zero = getValueWithForm(dict, "ForwardedAuthorsOthers", .zero) + self._ForwardedAuthorsOthers_one = getValueWithForm(dict, "ForwardedAuthorsOthers", .one) + self._ForwardedAuthorsOthers_two = getValueWithForm(dict, "ForwardedAuthorsOthers", .two) + self._ForwardedAuthorsOthers_few = getValueWithForm(dict, "ForwardedAuthorsOthers", .few) + self._ForwardedAuthorsOthers_many = getValueWithForm(dict, "ForwardedAuthorsOthers", .many) + self._ForwardedAuthorsOthers_other = getValueWithForm(dict, "ForwardedAuthorsOthers", .other) + self._StickerPack_AddMaskCount_zero = getValueWithForm(dict, "StickerPack.AddMaskCount", .zero) + self._StickerPack_AddMaskCount_one = getValueWithForm(dict, "StickerPack.AddMaskCount", .one) + self._StickerPack_AddMaskCount_two = getValueWithForm(dict, "StickerPack.AddMaskCount", .two) + self._StickerPack_AddMaskCount_few = getValueWithForm(dict, "StickerPack.AddMaskCount", .few) + self._StickerPack_AddMaskCount_many = getValueWithForm(dict, "StickerPack.AddMaskCount", .many) + self._StickerPack_AddMaskCount_other = getValueWithForm(dict, "StickerPack.AddMaskCount", .other) self._ServiceMessage_GameScoreSelfExtended_zero = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .zero) self._ServiceMessage_GameScoreSelfExtended_one = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .one) self._ServiceMessage_GameScoreSelfExtended_two = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .two) self._ServiceMessage_GameScoreSelfExtended_few = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .few) self._ServiceMessage_GameScoreSelfExtended_many = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .many) self._ServiceMessage_GameScoreSelfExtended_other = getValueWithForm(dict, "ServiceMessage.GameScoreSelfExtended", .other) - self._Notification_GameScoreSelfExtended_zero = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .zero) - self._Notification_GameScoreSelfExtended_one = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .one) - self._Notification_GameScoreSelfExtended_two = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .two) - self._Notification_GameScoreSelfExtended_few = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .few) - self._Notification_GameScoreSelfExtended_many = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .many) - self._Notification_GameScoreSelfExtended_other = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .other) - self._AttachmentMenu_SendItem_zero = getValueWithForm(dict, "AttachmentMenu.SendItem", .zero) - self._AttachmentMenu_SendItem_one = getValueWithForm(dict, "AttachmentMenu.SendItem", .one) - self._AttachmentMenu_SendItem_two = getValueWithForm(dict, "AttachmentMenu.SendItem", .two) - self._AttachmentMenu_SendItem_few = getValueWithForm(dict, "AttachmentMenu.SendItem", .few) - self._AttachmentMenu_SendItem_many = getValueWithForm(dict, "AttachmentMenu.SendItem", .many) - self._AttachmentMenu_SendItem_other = getValueWithForm(dict, "AttachmentMenu.SendItem", .other) + self._Map_ETAHours_zero = getValueWithForm(dict, "Map.ETAHours", .zero) + self._Map_ETAHours_one = getValueWithForm(dict, "Map.ETAHours", .one) + self._Map_ETAHours_two = getValueWithForm(dict, "Map.ETAHours", .two) + self._Map_ETAHours_few = getValueWithForm(dict, "Map.ETAHours", .few) + self._Map_ETAHours_many = getValueWithForm(dict, "Map.ETAHours", .many) + self._Map_ETAHours_other = getValueWithForm(dict, "Map.ETAHours", .other) + self._SharedMedia_File_zero = getValueWithForm(dict, "SharedMedia.File", .zero) + self._SharedMedia_File_one = getValueWithForm(dict, "SharedMedia.File", .one) + self._SharedMedia_File_two = getValueWithForm(dict, "SharedMedia.File", .two) + self._SharedMedia_File_few = getValueWithForm(dict, "SharedMedia.File", .few) + self._SharedMedia_File_many = getValueWithForm(dict, "SharedMedia.File", .many) + self._SharedMedia_File_other = getValueWithForm(dict, "SharedMedia.File", .other) + self._MessageTimer_Days_zero = getValueWithForm(dict, "MessageTimer.Days", .zero) + self._MessageTimer_Days_one = getValueWithForm(dict, "MessageTimer.Days", .one) + self._MessageTimer_Days_two = getValueWithForm(dict, "MessageTimer.Days", .two) + self._MessageTimer_Days_few = getValueWithForm(dict, "MessageTimer.Days", .few) + self._MessageTimer_Days_many = getValueWithForm(dict, "MessageTimer.Days", .many) + self._MessageTimer_Days_other = getValueWithForm(dict, "MessageTimer.Days", .other) + self._ForwardedStickers_zero = getValueWithForm(dict, "ForwardedStickers", .zero) + self._ForwardedStickers_one = getValueWithForm(dict, "ForwardedStickers", .one) + self._ForwardedStickers_two = getValueWithForm(dict, "ForwardedStickers", .two) + self._ForwardedStickers_few = getValueWithForm(dict, "ForwardedStickers", .few) + self._ForwardedStickers_many = getValueWithForm(dict, "ForwardedStickers", .many) + self._ForwardedStickers_other = getValueWithForm(dict, "ForwardedStickers", .other) + self._ServiceMessage_GameScoreExtended_zero = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .zero) + self._ServiceMessage_GameScoreExtended_one = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .one) + self._ServiceMessage_GameScoreExtended_two = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .two) + self._ServiceMessage_GameScoreExtended_few = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .few) + self._ServiceMessage_GameScoreExtended_many = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .many) + self._ServiceMessage_GameScoreExtended_other = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .other) + self._ServiceMessage_GameScoreSelfSimple_zero = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .zero) + self._ServiceMessage_GameScoreSelfSimple_one = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .one) + self._ServiceMessage_GameScoreSelfSimple_two = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .two) + self._ServiceMessage_GameScoreSelfSimple_few = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .few) + self._ServiceMessage_GameScoreSelfSimple_many = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .many) + self._ServiceMessage_GameScoreSelfSimple_other = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .other) + self._LiveLocation_MenuChatsCount_zero = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .zero) + self._LiveLocation_MenuChatsCount_one = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .one) + self._LiveLocation_MenuChatsCount_two = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .two) + self._LiveLocation_MenuChatsCount_few = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .few) + self._LiveLocation_MenuChatsCount_many = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .many) + self._LiveLocation_MenuChatsCount_other = getValueWithForm(dict, "LiveLocation.MenuChatsCount", .other) + self._MessageTimer_ShortSeconds_zero = getValueWithForm(dict, "MessageTimer.ShortSeconds", .zero) + self._MessageTimer_ShortSeconds_one = getValueWithForm(dict, "MessageTimer.ShortSeconds", .one) + self._MessageTimer_ShortSeconds_two = getValueWithForm(dict, "MessageTimer.ShortSeconds", .two) + self._MessageTimer_ShortSeconds_few = getValueWithForm(dict, "MessageTimer.ShortSeconds", .few) + self._MessageTimer_ShortSeconds_many = getValueWithForm(dict, "MessageTimer.ShortSeconds", .many) + self._MessageTimer_ShortSeconds_other = getValueWithForm(dict, "MessageTimer.ShortSeconds", .other) + self._ForwardedGifs_zero = getValueWithForm(dict, "ForwardedGifs", .zero) + self._ForwardedGifs_one = getValueWithForm(dict, "ForwardedGifs", .one) + self._ForwardedGifs_two = getValueWithForm(dict, "ForwardedGifs", .two) + self._ForwardedGifs_few = getValueWithForm(dict, "ForwardedGifs", .few) + self._ForwardedGifs_many = getValueWithForm(dict, "ForwardedGifs", .many) + self._ForwardedGifs_other = getValueWithForm(dict, "ForwardedGifs", .other) + self._MuteFor_Hours_zero = getValueWithForm(dict, "MuteFor.Hours", .zero) + self._MuteFor_Hours_one = getValueWithForm(dict, "MuteFor.Hours", .one) + self._MuteFor_Hours_two = getValueWithForm(dict, "MuteFor.Hours", .two) + self._MuteFor_Hours_few = getValueWithForm(dict, "MuteFor.Hours", .few) + self._MuteFor_Hours_many = getValueWithForm(dict, "MuteFor.Hours", .many) + self._MuteFor_Hours_other = getValueWithForm(dict, "MuteFor.Hours", .other) + self._StickerPack_StickerCount_zero = getValueWithForm(dict, "StickerPack.StickerCount", .zero) + self._StickerPack_StickerCount_one = getValueWithForm(dict, "StickerPack.StickerCount", .one) + self._StickerPack_StickerCount_two = getValueWithForm(dict, "StickerPack.StickerCount", .two) + self._StickerPack_StickerCount_few = getValueWithForm(dict, "StickerPack.StickerCount", .few) + self._StickerPack_StickerCount_many = getValueWithForm(dict, "StickerPack.StickerCount", .many) + self._StickerPack_StickerCount_other = getValueWithForm(dict, "StickerPack.StickerCount", .other) + self._Passport_Scans_zero = getValueWithForm(dict, "Passport.Scans", .zero) + self._Passport_Scans_one = getValueWithForm(dict, "Passport.Scans", .one) + self._Passport_Scans_two = getValueWithForm(dict, "Passport.Scans", .two) + self._Passport_Scans_few = getValueWithForm(dict, "Passport.Scans", .few) + self._Passport_Scans_many = getValueWithForm(dict, "Passport.Scans", .many) + self._Passport_Scans_other = getValueWithForm(dict, "Passport.Scans", .other) + self._Notification_GameScoreSelfSimple_zero = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .zero) + self._Notification_GameScoreSelfSimple_one = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .one) + self._Notification_GameScoreSelfSimple_two = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .two) + self._Notification_GameScoreSelfSimple_few = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .few) + self._Notification_GameScoreSelfSimple_many = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .many) + self._Notification_GameScoreSelfSimple_other = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .other) + self._ForwardedVideos_zero = getValueWithForm(dict, "ForwardedVideos", .zero) + self._ForwardedVideos_one = getValueWithForm(dict, "ForwardedVideos", .one) + self._ForwardedVideos_two = getValueWithForm(dict, "ForwardedVideos", .two) + self._ForwardedVideos_few = getValueWithForm(dict, "ForwardedVideos", .few) + self._ForwardedVideos_many = getValueWithForm(dict, "ForwardedVideos", .many) + self._ForwardedVideos_other = getValueWithForm(dict, "ForwardedVideos", .other) self._MessageTimer_ShortHours_zero = getValueWithForm(dict, "MessageTimer.ShortHours", .zero) self._MessageTimer_ShortHours_one = getValueWithForm(dict, "MessageTimer.ShortHours", .one) self._MessageTimer_ShortHours_two = getValueWithForm(dict, "MessageTimer.ShortHours", .two) @@ -7365,30 +8112,144 @@ public final class PresentationStrings { self._ForwardedContacts_few = getValueWithForm(dict, "ForwardedContacts", .few) self._ForwardedContacts_many = getValueWithForm(dict, "ForwardedContacts", .many) self._ForwardedContacts_other = getValueWithForm(dict, "ForwardedContacts", .other) + self._ForwardedLocations_zero = getValueWithForm(dict, "ForwardedLocations", .zero) + self._ForwardedLocations_one = getValueWithForm(dict, "ForwardedLocations", .one) + self._ForwardedLocations_two = getValueWithForm(dict, "ForwardedLocations", .two) + self._ForwardedLocations_few = getValueWithForm(dict, "ForwardedLocations", .few) + self._ForwardedLocations_many = getValueWithForm(dict, "ForwardedLocations", .many) + self._ForwardedLocations_other = getValueWithForm(dict, "ForwardedLocations", .other) + self._Notification_GameScoreSelfExtended_zero = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .zero) + self._Notification_GameScoreSelfExtended_one = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .one) + self._Notification_GameScoreSelfExtended_two = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .two) + self._Notification_GameScoreSelfExtended_few = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .few) + self._Notification_GameScoreSelfExtended_many = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .many) + self._Notification_GameScoreSelfExtended_other = getValueWithForm(dict, "Notification.GameScoreSelfExtended", .other) + self._UserCount_zero = getValueWithForm(dict, "UserCount", .zero) + self._UserCount_one = getValueWithForm(dict, "UserCount", .one) + self._UserCount_two = getValueWithForm(dict, "UserCount", .two) + self._UserCount_few = getValueWithForm(dict, "UserCount", .few) + self._UserCount_many = getValueWithForm(dict, "UserCount", .many) + self._UserCount_other = getValueWithForm(dict, "UserCount", .other) self._SharedMedia_Generic_zero = getValueWithForm(dict, "SharedMedia.Generic", .zero) self._SharedMedia_Generic_one = getValueWithForm(dict, "SharedMedia.Generic", .one) self._SharedMedia_Generic_two = getValueWithForm(dict, "SharedMedia.Generic", .two) self._SharedMedia_Generic_few = getValueWithForm(dict, "SharedMedia.Generic", .few) self._SharedMedia_Generic_many = getValueWithForm(dict, "SharedMedia.Generic", .many) self._SharedMedia_Generic_other = getValueWithForm(dict, "SharedMedia.Generic", .other) + self._Call_Minutes_zero = getValueWithForm(dict, "Call.Minutes", .zero) + self._Call_Minutes_one = getValueWithForm(dict, "Call.Minutes", .one) + self._Call_Minutes_two = getValueWithForm(dict, "Call.Minutes", .two) + self._Call_Minutes_few = getValueWithForm(dict, "Call.Minutes", .few) + self._Call_Minutes_many = getValueWithForm(dict, "Call.Minutes", .many) + self._Call_Minutes_other = getValueWithForm(dict, "Call.Minutes", .other) + self._MessageTimer_ShortMinutes_zero = getValueWithForm(dict, "MessageTimer.ShortMinutes", .zero) + self._MessageTimer_ShortMinutes_one = getValueWithForm(dict, "MessageTimer.ShortMinutes", .one) + self._MessageTimer_ShortMinutes_two = getValueWithForm(dict, "MessageTimer.ShortMinutes", .two) + self._MessageTimer_ShortMinutes_few = getValueWithForm(dict, "MessageTimer.ShortMinutes", .few) + self._MessageTimer_ShortMinutes_many = getValueWithForm(dict, "MessageTimer.ShortMinutes", .many) + self._MessageTimer_ShortMinutes_other = getValueWithForm(dict, "MessageTimer.ShortMinutes", .other) + self._AttachmentMenu_SendVideo_zero = getValueWithForm(dict, "AttachmentMenu.SendVideo", .zero) + self._AttachmentMenu_SendVideo_one = getValueWithForm(dict, "AttachmentMenu.SendVideo", .one) + self._AttachmentMenu_SendVideo_two = getValueWithForm(dict, "AttachmentMenu.SendVideo", .two) + self._AttachmentMenu_SendVideo_few = getValueWithForm(dict, "AttachmentMenu.SendVideo", .few) + self._AttachmentMenu_SendVideo_many = getValueWithForm(dict, "AttachmentMenu.SendVideo", .many) + self._AttachmentMenu_SendVideo_other = getValueWithForm(dict, "AttachmentMenu.SendVideo", .other) + self._ForwardedMessages_zero = getValueWithForm(dict, "ForwardedMessages", .zero) + self._ForwardedMessages_one = getValueWithForm(dict, "ForwardedMessages", .one) + self._ForwardedMessages_two = getValueWithForm(dict, "ForwardedMessages", .two) + self._ForwardedMessages_few = getValueWithForm(dict, "ForwardedMessages", .few) + self._ForwardedMessages_many = getValueWithForm(dict, "ForwardedMessages", .many) + self._ForwardedMessages_other = getValueWithForm(dict, "ForwardedMessages", .other) + self._Conversation_StatusSubscribers_zero = getValueWithForm(dict, "Conversation.StatusSubscribers", .zero) + self._Conversation_StatusSubscribers_one = getValueWithForm(dict, "Conversation.StatusSubscribers", .one) + self._Conversation_StatusSubscribers_two = getValueWithForm(dict, "Conversation.StatusSubscribers", .two) + self._Conversation_StatusSubscribers_few = getValueWithForm(dict, "Conversation.StatusSubscribers", .few) + self._Conversation_StatusSubscribers_many = getValueWithForm(dict, "Conversation.StatusSubscribers", .many) + self._Conversation_StatusSubscribers_other = getValueWithForm(dict, "Conversation.StatusSubscribers", .other) + self._SharedMedia_Photo_zero = getValueWithForm(dict, "SharedMedia.Photo", .zero) + self._SharedMedia_Photo_one = getValueWithForm(dict, "SharedMedia.Photo", .one) + self._SharedMedia_Photo_two = getValueWithForm(dict, "SharedMedia.Photo", .two) + self._SharedMedia_Photo_few = getValueWithForm(dict, "SharedMedia.Photo", .few) + self._SharedMedia_Photo_many = getValueWithForm(dict, "SharedMedia.Photo", .many) + self._SharedMedia_Photo_other = getValueWithForm(dict, "SharedMedia.Photo", .other) + self._GroupInfo_ParticipantCount_zero = getValueWithForm(dict, "GroupInfo.ParticipantCount", .zero) + self._GroupInfo_ParticipantCount_one = getValueWithForm(dict, "GroupInfo.ParticipantCount", .one) + self._GroupInfo_ParticipantCount_two = getValueWithForm(dict, "GroupInfo.ParticipantCount", .two) + self._GroupInfo_ParticipantCount_few = getValueWithForm(dict, "GroupInfo.ParticipantCount", .few) + self._GroupInfo_ParticipantCount_many = getValueWithForm(dict, "GroupInfo.ParticipantCount", .many) + self._GroupInfo_ParticipantCount_other = getValueWithForm(dict, "GroupInfo.ParticipantCount", .other) + self._Watch_UserInfo_Mute_zero = getValueWithForm(dict, "Watch.UserInfo.Mute", .zero) + self._Watch_UserInfo_Mute_one = getValueWithForm(dict, "Watch.UserInfo.Mute", .one) + self._Watch_UserInfo_Mute_two = getValueWithForm(dict, "Watch.UserInfo.Mute", .two) + self._Watch_UserInfo_Mute_few = getValueWithForm(dict, "Watch.UserInfo.Mute", .few) + self._Watch_UserInfo_Mute_many = getValueWithForm(dict, "Watch.UserInfo.Mute", .many) + self._Watch_UserInfo_Mute_other = getValueWithForm(dict, "Watch.UserInfo.Mute", .other) + self._Forward_ConfirmMultipleFiles_zero = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .zero) + self._Forward_ConfirmMultipleFiles_one = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .one) + self._Forward_ConfirmMultipleFiles_two = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .two) + self._Forward_ConfirmMultipleFiles_few = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .few) + self._Forward_ConfirmMultipleFiles_many = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .many) + self._Forward_ConfirmMultipleFiles_other = getValueWithForm(dict, "Forward.ConfirmMultipleFiles", .other) + self._SharedMedia_Video_zero = getValueWithForm(dict, "SharedMedia.Video", .zero) + self._SharedMedia_Video_one = getValueWithForm(dict, "SharedMedia.Video", .one) + self._SharedMedia_Video_two = getValueWithForm(dict, "SharedMedia.Video", .two) + self._SharedMedia_Video_few = getValueWithForm(dict, "SharedMedia.Video", .few) + self._SharedMedia_Video_many = getValueWithForm(dict, "SharedMedia.Video", .many) + self._SharedMedia_Video_other = getValueWithForm(dict, "SharedMedia.Video", .other) + self._Call_ShortSeconds_zero = getValueWithForm(dict, "Call.ShortSeconds", .zero) + self._Call_ShortSeconds_one = getValueWithForm(dict, "Call.ShortSeconds", .one) + self._Call_ShortSeconds_two = getValueWithForm(dict, "Call.ShortSeconds", .two) + self._Call_ShortSeconds_few = getValueWithForm(dict, "Call.ShortSeconds", .few) + self._Call_ShortSeconds_many = getValueWithForm(dict, "Call.ShortSeconds", .many) + self._Call_ShortSeconds_other = getValueWithForm(dict, "Call.ShortSeconds", .other) self._LiveLocationUpdated_MinutesAgo_zero = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .zero) self._LiveLocationUpdated_MinutesAgo_one = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .one) self._LiveLocationUpdated_MinutesAgo_two = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .two) self._LiveLocationUpdated_MinutesAgo_few = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .few) self._LiveLocationUpdated_MinutesAgo_many = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .many) self._LiveLocationUpdated_MinutesAgo_other = getValueWithForm(dict, "LiveLocationUpdated.MinutesAgo", .other) - self._Notification_GameScoreSelfSimple_zero = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .zero) - self._Notification_GameScoreSelfSimple_one = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .one) - self._Notification_GameScoreSelfSimple_two = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .two) - self._Notification_GameScoreSelfSimple_few = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .few) - self._Notification_GameScoreSelfSimple_many = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .many) - self._Notification_GameScoreSelfSimple_other = getValueWithForm(dict, "Notification.GameScoreSelfSimple", .other) - self._ServiceMessage_GameScoreSimple_zero = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .zero) - self._ServiceMessage_GameScoreSimple_one = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .one) - self._ServiceMessage_GameScoreSimple_two = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .two) - self._ServiceMessage_GameScoreSimple_few = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .few) - self._ServiceMessage_GameScoreSimple_many = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .many) - self._ServiceMessage_GameScoreSimple_other = getValueWithForm(dict, "ServiceMessage.GameScoreSimple", .other) + self._MuteExpires_Hours_zero = getValueWithForm(dict, "MuteExpires.Hours", .zero) + self._MuteExpires_Hours_one = getValueWithForm(dict, "MuteExpires.Hours", .one) + self._MuteExpires_Hours_two = getValueWithForm(dict, "MuteExpires.Hours", .two) + self._MuteExpires_Hours_few = getValueWithForm(dict, "MuteExpires.Hours", .few) + self._MuteExpires_Hours_many = getValueWithForm(dict, "MuteExpires.Hours", .many) + self._MuteExpires_Hours_other = getValueWithForm(dict, "MuteExpires.Hours", .other) + self._SharedMedia_DeleteItemsConfirmation_zero = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .zero) + self._SharedMedia_DeleteItemsConfirmation_one = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .one) + self._SharedMedia_DeleteItemsConfirmation_two = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .two) + self._SharedMedia_DeleteItemsConfirmation_few = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .few) + self._SharedMedia_DeleteItemsConfirmation_many = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .many) + self._SharedMedia_DeleteItemsConfirmation_other = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .other) + self._Conversation_StatusMembers_zero = getValueWithForm(dict, "Conversation.StatusMembers", .zero) + self._Conversation_StatusMembers_one = getValueWithForm(dict, "Conversation.StatusMembers", .one) + self._Conversation_StatusMembers_two = getValueWithForm(dict, "Conversation.StatusMembers", .two) + self._Conversation_StatusMembers_few = getValueWithForm(dict, "Conversation.StatusMembers", .few) + self._Conversation_StatusMembers_many = getValueWithForm(dict, "Conversation.StatusMembers", .many) + self._Conversation_StatusMembers_other = getValueWithForm(dict, "Conversation.StatusMembers", .other) + self._MessageTimer_Seconds_zero = getValueWithForm(dict, "MessageTimer.Seconds", .zero) + self._MessageTimer_Seconds_one = getValueWithForm(dict, "MessageTimer.Seconds", .one) + self._MessageTimer_Seconds_two = getValueWithForm(dict, "MessageTimer.Seconds", .two) + self._MessageTimer_Seconds_few = getValueWithForm(dict, "MessageTimer.Seconds", .few) + self._MessageTimer_Seconds_many = getValueWithForm(dict, "MessageTimer.Seconds", .many) + self._MessageTimer_Seconds_other = getValueWithForm(dict, "MessageTimer.Seconds", .other) + self._Watch_LastSeen_MinutesAgo_zero = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .zero) + self._Watch_LastSeen_MinutesAgo_one = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .one) + self._Watch_LastSeen_MinutesAgo_two = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .two) + self._Watch_LastSeen_MinutesAgo_few = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .few) + self._Watch_LastSeen_MinutesAgo_many = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .many) + self._Watch_LastSeen_MinutesAgo_other = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .other) + self._LastSeen_HoursAgo_zero = getValueWithForm(dict, "LastSeen.HoursAgo", .zero) + self._LastSeen_HoursAgo_one = getValueWithForm(dict, "LastSeen.HoursAgo", .one) + self._LastSeen_HoursAgo_two = getValueWithForm(dict, "LastSeen.HoursAgo", .two) + self._LastSeen_HoursAgo_few = getValueWithForm(dict, "LastSeen.HoursAgo", .few) + self._LastSeen_HoursAgo_many = getValueWithForm(dict, "LastSeen.HoursAgo", .many) + self._LastSeen_HoursAgo_other = getValueWithForm(dict, "LastSeen.HoursAgo", .other) + self._Media_ShareItem_zero = getValueWithForm(dict, "Media.ShareItem", .zero) + self._Media_ShareItem_one = getValueWithForm(dict, "Media.ShareItem", .one) + self._Media_ShareItem_two = getValueWithForm(dict, "Media.ShareItem", .two) + self._Media_ShareItem_few = getValueWithForm(dict, "Media.ShareItem", .few) + self._Media_ShareItem_many = getValueWithForm(dict, "Media.ShareItem", .many) + self._Media_ShareItem_other = getValueWithForm(dict, "Media.ShareItem", .other) self._QuickSend_Photos_zero = getValueWithForm(dict, "QuickSend.Photos", .zero) self._QuickSend_Photos_one = getValueWithForm(dict, "QuickSend.Photos", .one) self._QuickSend_Photos_two = getValueWithForm(dict, "QuickSend.Photos", .two) @@ -7407,282 +8268,174 @@ public final class PresentationStrings { self._MuteExpires_Minutes_few = getValueWithForm(dict, "MuteExpires.Minutes", .few) self._MuteExpires_Minutes_many = getValueWithForm(dict, "MuteExpires.Minutes", .many) self._MuteExpires_Minutes_other = getValueWithForm(dict, "MuteExpires.Minutes", .other) - self._Conversation_LiveLocationMembersCount_zero = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .zero) - self._Conversation_LiveLocationMembersCount_one = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .one) - self._Conversation_LiveLocationMembersCount_two = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .two) - self._Conversation_LiveLocationMembersCount_few = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .few) - self._Conversation_LiveLocationMembersCount_many = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .many) - self._Conversation_LiveLocationMembersCount_other = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .other) - self._MessageTimer_Days_zero = getValueWithForm(dict, "MessageTimer.Days", .zero) - self._MessageTimer_Days_one = getValueWithForm(dict, "MessageTimer.Days", .one) - self._MessageTimer_Days_two = getValueWithForm(dict, "MessageTimer.Days", .two) - self._MessageTimer_Days_few = getValueWithForm(dict, "MessageTimer.Days", .few) - self._MessageTimer_Days_many = getValueWithForm(dict, "MessageTimer.Days", .many) - self._MessageTimer_Days_other = getValueWithForm(dict, "MessageTimer.Days", .other) - self._MessageTimer_ShortSeconds_zero = getValueWithForm(dict, "MessageTimer.ShortSeconds", .zero) - self._MessageTimer_ShortSeconds_one = getValueWithForm(dict, "MessageTimer.ShortSeconds", .one) - self._MessageTimer_ShortSeconds_two = getValueWithForm(dict, "MessageTimer.ShortSeconds", .two) - self._MessageTimer_ShortSeconds_few = getValueWithForm(dict, "MessageTimer.ShortSeconds", .few) - self._MessageTimer_ShortSeconds_many = getValueWithForm(dict, "MessageTimer.ShortSeconds", .many) - self._MessageTimer_ShortSeconds_other = getValueWithForm(dict, "MessageTimer.ShortSeconds", .other) - self._Conversation_StatusOnline_zero = getValueWithForm(dict, "Conversation.StatusOnline", .zero) - self._Conversation_StatusOnline_one = getValueWithForm(dict, "Conversation.StatusOnline", .one) - self._Conversation_StatusOnline_two = getValueWithForm(dict, "Conversation.StatusOnline", .two) - self._Conversation_StatusOnline_few = getValueWithForm(dict, "Conversation.StatusOnline", .few) - self._Conversation_StatusOnline_many = getValueWithForm(dict, "Conversation.StatusOnline", .many) - self._Conversation_StatusOnline_other = getValueWithForm(dict, "Conversation.StatusOnline", .other) - self._ForwardedMessages_zero = getValueWithForm(dict, "ForwardedMessages", .zero) - self._ForwardedMessages_one = getValueWithForm(dict, "ForwardedMessages", .one) - self._ForwardedMessages_two = getValueWithForm(dict, "ForwardedMessages", .two) - self._ForwardedMessages_few = getValueWithForm(dict, "ForwardedMessages", .few) - self._ForwardedMessages_many = getValueWithForm(dict, "ForwardedMessages", .many) - self._ForwardedMessages_other = getValueWithForm(dict, "ForwardedMessages", .other) - self._MessageTimer_Seconds_zero = getValueWithForm(dict, "MessageTimer.Seconds", .zero) - self._MessageTimer_Seconds_one = getValueWithForm(dict, "MessageTimer.Seconds", .one) - self._MessageTimer_Seconds_two = getValueWithForm(dict, "MessageTimer.Seconds", .two) - self._MessageTimer_Seconds_few = getValueWithForm(dict, "MessageTimer.Seconds", .few) - self._MessageTimer_Seconds_many = getValueWithForm(dict, "MessageTimer.Seconds", .many) - self._MessageTimer_Seconds_other = getValueWithForm(dict, "MessageTimer.Seconds", .other) - self._SharedMedia_Link_zero = getValueWithForm(dict, "SharedMedia.Link", .zero) - self._SharedMedia_Link_one = getValueWithForm(dict, "SharedMedia.Link", .one) - self._SharedMedia_Link_two = getValueWithForm(dict, "SharedMedia.Link", .two) - self._SharedMedia_Link_few = getValueWithForm(dict, "SharedMedia.Link", .few) - self._SharedMedia_Link_many = getValueWithForm(dict, "SharedMedia.Link", .many) - self._SharedMedia_Link_other = getValueWithForm(dict, "SharedMedia.Link", .other) - self._Invitation_Members_zero = getValueWithForm(dict, "Invitation.Members", .zero) - self._Invitation_Members_one = getValueWithForm(dict, "Invitation.Members", .one) - self._Invitation_Members_two = getValueWithForm(dict, "Invitation.Members", .two) - self._Invitation_Members_few = getValueWithForm(dict, "Invitation.Members", .few) - self._Invitation_Members_many = getValueWithForm(dict, "Invitation.Members", .many) - self._Invitation_Members_other = getValueWithForm(dict, "Invitation.Members", .other) - self._Contacts_ImportersCount_zero = getValueWithForm(dict, "Contacts.ImportersCount", .zero) - self._Contacts_ImportersCount_one = getValueWithForm(dict, "Contacts.ImportersCount", .one) - self._Contacts_ImportersCount_two = getValueWithForm(dict, "Contacts.ImportersCount", .two) - self._Contacts_ImportersCount_few = getValueWithForm(dict, "Contacts.ImportersCount", .few) - self._Contacts_ImportersCount_many = getValueWithForm(dict, "Contacts.ImportersCount", .many) - self._Contacts_ImportersCount_other = getValueWithForm(dict, "Contacts.ImportersCount", .other) - self._ForwardedFiles_zero = getValueWithForm(dict, "ForwardedFiles", .zero) - self._ForwardedFiles_one = getValueWithForm(dict, "ForwardedFiles", .one) - self._ForwardedFiles_two = getValueWithForm(dict, "ForwardedFiles", .two) - self._ForwardedFiles_few = getValueWithForm(dict, "ForwardedFiles", .few) - self._ForwardedFiles_many = getValueWithForm(dict, "ForwardedFiles", .many) - self._ForwardedFiles_other = getValueWithForm(dict, "ForwardedFiles", .other) - self._Watch_UserInfo_Mute_zero = getValueWithForm(dict, "Watch.UserInfo.Mute", .zero) - self._Watch_UserInfo_Mute_one = getValueWithForm(dict, "Watch.UserInfo.Mute", .one) - self._Watch_UserInfo_Mute_two = getValueWithForm(dict, "Watch.UserInfo.Mute", .two) - self._Watch_UserInfo_Mute_few = getValueWithForm(dict, "Watch.UserInfo.Mute", .few) - self._Watch_UserInfo_Mute_many = getValueWithForm(dict, "Watch.UserInfo.Mute", .many) - self._Watch_UserInfo_Mute_other = getValueWithForm(dict, "Watch.UserInfo.Mute", .other) - self._Watch_LastSeen_HoursAgo_zero = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .zero) - self._Watch_LastSeen_HoursAgo_one = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .one) - self._Watch_LastSeen_HoursAgo_two = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .two) - self._Watch_LastSeen_HoursAgo_few = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .few) - self._Watch_LastSeen_HoursAgo_many = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .many) - self._Watch_LastSeen_HoursAgo_other = getValueWithForm(dict, "Watch.LastSeen.HoursAgo", .other) - self._SharedMedia_Video_zero = getValueWithForm(dict, "SharedMedia.Video", .zero) - self._SharedMedia_Video_one = getValueWithForm(dict, "SharedMedia.Video", .one) - self._SharedMedia_Video_two = getValueWithForm(dict, "SharedMedia.Video", .two) - self._SharedMedia_Video_few = getValueWithForm(dict, "SharedMedia.Video", .few) - self._SharedMedia_Video_many = getValueWithForm(dict, "SharedMedia.Video", .many) - self._SharedMedia_Video_other = getValueWithForm(dict, "SharedMedia.Video", .other) - self._AttachmentMenu_SendVideo_zero = getValueWithForm(dict, "AttachmentMenu.SendVideo", .zero) - self._AttachmentMenu_SendVideo_one = getValueWithForm(dict, "AttachmentMenu.SendVideo", .one) - self._AttachmentMenu_SendVideo_two = getValueWithForm(dict, "AttachmentMenu.SendVideo", .two) - self._AttachmentMenu_SendVideo_few = getValueWithForm(dict, "AttachmentMenu.SendVideo", .few) - self._AttachmentMenu_SendVideo_many = getValueWithForm(dict, "AttachmentMenu.SendVideo", .many) - self._AttachmentMenu_SendVideo_other = getValueWithForm(dict, "AttachmentMenu.SendVideo", .other) - self._ForwardedStickers_zero = getValueWithForm(dict, "ForwardedStickers", .zero) - self._ForwardedStickers_one = getValueWithForm(dict, "ForwardedStickers", .one) - self._ForwardedStickers_two = getValueWithForm(dict, "ForwardedStickers", .two) - self._ForwardedStickers_few = getValueWithForm(dict, "ForwardedStickers", .few) - self._ForwardedStickers_many = getValueWithForm(dict, "ForwardedStickers", .many) - self._ForwardedStickers_other = getValueWithForm(dict, "ForwardedStickers", .other) - self._MessageTimer_Years_zero = getValueWithForm(dict, "MessageTimer.Years", .zero) - self._MessageTimer_Years_one = getValueWithForm(dict, "MessageTimer.Years", .one) - self._MessageTimer_Years_two = getValueWithForm(dict, "MessageTimer.Years", .two) - self._MessageTimer_Years_few = getValueWithForm(dict, "MessageTimer.Years", .few) - self._MessageTimer_Years_many = getValueWithForm(dict, "MessageTimer.Years", .many) - self._MessageTimer_Years_other = getValueWithForm(dict, "MessageTimer.Years", .other) - self._SharedMedia_DeleteItemsConfirmation_zero = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .zero) - self._SharedMedia_DeleteItemsConfirmation_one = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .one) - self._SharedMedia_DeleteItemsConfirmation_two = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .two) - self._SharedMedia_DeleteItemsConfirmation_few = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .few) - self._SharedMedia_DeleteItemsConfirmation_many = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .many) - self._SharedMedia_DeleteItemsConfirmation_other = getValueWithForm(dict, "SharedMedia.DeleteItemsConfirmation", .other) - self._MessageTimer_ShortMinutes_zero = getValueWithForm(dict, "MessageTimer.ShortMinutes", .zero) - self._MessageTimer_ShortMinutes_one = getValueWithForm(dict, "MessageTimer.ShortMinutes", .one) - self._MessageTimer_ShortMinutes_two = getValueWithForm(dict, "MessageTimer.ShortMinutes", .two) - self._MessageTimer_ShortMinutes_few = getValueWithForm(dict, "MessageTimer.ShortMinutes", .few) - self._MessageTimer_ShortMinutes_many = getValueWithForm(dict, "MessageTimer.ShortMinutes", .many) - self._MessageTimer_ShortMinutes_other = getValueWithForm(dict, "MessageTimer.ShortMinutes", .other) - self._AttachmentMenu_SendPhoto_zero = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .zero) - self._AttachmentMenu_SendPhoto_one = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .one) - self._AttachmentMenu_SendPhoto_two = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .two) - self._AttachmentMenu_SendPhoto_few = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .few) - self._AttachmentMenu_SendPhoto_many = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .many) - self._AttachmentMenu_SendPhoto_other = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .other) - self._GroupInfo_ParticipantCount_zero = getValueWithForm(dict, "GroupInfo.ParticipantCount", .zero) - self._GroupInfo_ParticipantCount_one = getValueWithForm(dict, "GroupInfo.ParticipantCount", .one) - self._GroupInfo_ParticipantCount_two = getValueWithForm(dict, "GroupInfo.ParticipantCount", .two) - self._GroupInfo_ParticipantCount_few = getValueWithForm(dict, "GroupInfo.ParticipantCount", .few) - self._GroupInfo_ParticipantCount_many = getValueWithForm(dict, "GroupInfo.ParticipantCount", .many) - self._GroupInfo_ParticipantCount_other = getValueWithForm(dict, "GroupInfo.ParticipantCount", .other) - self._Call_ShortSeconds_zero = getValueWithForm(dict, "Call.ShortSeconds", .zero) - self._Call_ShortSeconds_one = getValueWithForm(dict, "Call.ShortSeconds", .one) - self._Call_ShortSeconds_two = getValueWithForm(dict, "Call.ShortSeconds", .two) - self._Call_ShortSeconds_few = getValueWithForm(dict, "Call.ShortSeconds", .few) - self._Call_ShortSeconds_many = getValueWithForm(dict, "Call.ShortSeconds", .many) - self._Call_ShortSeconds_other = getValueWithForm(dict, "Call.ShortSeconds", .other) self._Notification_GameScoreSimple_zero = getValueWithForm(dict, "Notification.GameScoreSimple", .zero) self._Notification_GameScoreSimple_one = getValueWithForm(dict, "Notification.GameScoreSimple", .one) self._Notification_GameScoreSimple_two = getValueWithForm(dict, "Notification.GameScoreSimple", .two) self._Notification_GameScoreSimple_few = getValueWithForm(dict, "Notification.GameScoreSimple", .few) self._Notification_GameScoreSimple_many = getValueWithForm(dict, "Notification.GameScoreSimple", .many) self._Notification_GameScoreSimple_other = getValueWithForm(dict, "Notification.GameScoreSimple", .other) - self._StickerPack_StickerCount_zero = getValueWithForm(dict, "StickerPack.StickerCount", .zero) - self._StickerPack_StickerCount_one = getValueWithForm(dict, "StickerPack.StickerCount", .one) - self._StickerPack_StickerCount_two = getValueWithForm(dict, "StickerPack.StickerCount", .two) - self._StickerPack_StickerCount_few = getValueWithForm(dict, "StickerPack.StickerCount", .few) - self._StickerPack_StickerCount_many = getValueWithForm(dict, "StickerPack.StickerCount", .many) - self._StickerPack_StickerCount_other = getValueWithForm(dict, "StickerPack.StickerCount", .other) - self._StickerPack_RemoveMaskCount_zero = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .zero) - self._StickerPack_RemoveMaskCount_one = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .one) - self._StickerPack_RemoveMaskCount_two = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .two) - self._StickerPack_RemoveMaskCount_few = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .few) - self._StickerPack_RemoveMaskCount_many = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .many) - self._StickerPack_RemoveMaskCount_other = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .other) - self._UserCount_zero = getValueWithForm(dict, "UserCount", .zero) - self._UserCount_one = getValueWithForm(dict, "UserCount", .one) - self._UserCount_two = getValueWithForm(dict, "UserCount", .two) - self._UserCount_few = getValueWithForm(dict, "UserCount", .few) - self._UserCount_many = getValueWithForm(dict, "UserCount", .many) - self._UserCount_other = getValueWithForm(dict, "UserCount", .other) - self._SharedMedia_File_zero = getValueWithForm(dict, "SharedMedia.File", .zero) - self._SharedMedia_File_one = getValueWithForm(dict, "SharedMedia.File", .one) - self._SharedMedia_File_two = getValueWithForm(dict, "SharedMedia.File", .two) - self._SharedMedia_File_few = getValueWithForm(dict, "SharedMedia.File", .few) - self._SharedMedia_File_many = getValueWithForm(dict, "SharedMedia.File", .many) - self._SharedMedia_File_other = getValueWithForm(dict, "SharedMedia.File", .other) - self._Call_ShortMinutes_zero = getValueWithForm(dict, "Call.ShortMinutes", .zero) - self._Call_ShortMinutes_one = getValueWithForm(dict, "Call.ShortMinutes", .one) - self._Call_ShortMinutes_two = getValueWithForm(dict, "Call.ShortMinutes", .two) - self._Call_ShortMinutes_few = getValueWithForm(dict, "Call.ShortMinutes", .few) - self._Call_ShortMinutes_many = getValueWithForm(dict, "Call.ShortMinutes", .many) - self._Call_ShortMinutes_other = getValueWithForm(dict, "Call.ShortMinutes", .other) - self._MuteExpires_Days_zero = getValueWithForm(dict, "MuteExpires.Days", .zero) - self._MuteExpires_Days_one = getValueWithForm(dict, "MuteExpires.Days", .one) - self._MuteExpires_Days_two = getValueWithForm(dict, "MuteExpires.Days", .two) - self._MuteExpires_Days_few = getValueWithForm(dict, "MuteExpires.Days", .few) - self._MuteExpires_Days_many = getValueWithForm(dict, "MuteExpires.Days", .many) - self._MuteExpires_Days_other = getValueWithForm(dict, "MuteExpires.Days", .other) - self._Watch_LastSeen_MinutesAgo_zero = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .zero) - self._Watch_LastSeen_MinutesAgo_one = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .one) - self._Watch_LastSeen_MinutesAgo_two = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .two) - self._Watch_LastSeen_MinutesAgo_few = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .few) - self._Watch_LastSeen_MinutesAgo_many = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .many) - self._Watch_LastSeen_MinutesAgo_other = getValueWithForm(dict, "Watch.LastSeen.MinutesAgo", .other) - self._LastSeen_MinutesAgo_zero = getValueWithForm(dict, "LastSeen.MinutesAgo", .zero) - self._LastSeen_MinutesAgo_one = getValueWithForm(dict, "LastSeen.MinutesAgo", .one) - self._LastSeen_MinutesAgo_two = getValueWithForm(dict, "LastSeen.MinutesAgo", .two) - self._LastSeen_MinutesAgo_few = getValueWithForm(dict, "LastSeen.MinutesAgo", .few) - self._LastSeen_MinutesAgo_many = getValueWithForm(dict, "LastSeen.MinutesAgo", .many) - self._LastSeen_MinutesAgo_other = getValueWithForm(dict, "LastSeen.MinutesAgo", .other) - self._ForwardedVideos_zero = getValueWithForm(dict, "ForwardedVideos", .zero) - self._ForwardedVideos_one = getValueWithForm(dict, "ForwardedVideos", .one) - self._ForwardedVideos_two = getValueWithForm(dict, "ForwardedVideos", .two) - self._ForwardedVideos_few = getValueWithForm(dict, "ForwardedVideos", .few) - self._ForwardedVideos_many = getValueWithForm(dict, "ForwardedVideos", .many) - self._ForwardedVideos_other = getValueWithForm(dict, "ForwardedVideos", .other) - self._DialogList_LiveLocationChatsCount_zero = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .zero) - self._DialogList_LiveLocationChatsCount_one = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .one) - self._DialogList_LiveLocationChatsCount_two = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .two) - self._DialogList_LiveLocationChatsCount_few = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .few) - self._DialogList_LiveLocationChatsCount_many = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .many) - self._DialogList_LiveLocationChatsCount_other = getValueWithForm(dict, "DialogList.LiveLocationChatsCount", .other) - self._MessageTimer_Minutes_zero = getValueWithForm(dict, "MessageTimer.Minutes", .zero) - self._MessageTimer_Minutes_one = getValueWithForm(dict, "MessageTimer.Minutes", .one) - self._MessageTimer_Minutes_two = getValueWithForm(dict, "MessageTimer.Minutes", .two) - self._MessageTimer_Minutes_few = getValueWithForm(dict, "MessageTimer.Minutes", .few) - self._MessageTimer_Minutes_many = getValueWithForm(dict, "MessageTimer.Minutes", .many) - self._MessageTimer_Minutes_other = getValueWithForm(dict, "MessageTimer.Minutes", .other) - self._Map_ETAMinutes_zero = getValueWithForm(dict, "Map.ETAMinutes", .zero) - self._Map_ETAMinutes_one = getValueWithForm(dict, "Map.ETAMinutes", .one) - self._Map_ETAMinutes_two = getValueWithForm(dict, "Map.ETAMinutes", .two) - self._Map_ETAMinutes_few = getValueWithForm(dict, "Map.ETAMinutes", .few) - self._Map_ETAMinutes_many = getValueWithForm(dict, "Map.ETAMinutes", .many) - self._Map_ETAMinutes_other = getValueWithForm(dict, "Map.ETAMinutes", .other) - self._LastSeen_HoursAgo_zero = getValueWithForm(dict, "LastSeen.HoursAgo", .zero) - self._LastSeen_HoursAgo_one = getValueWithForm(dict, "LastSeen.HoursAgo", .one) - self._LastSeen_HoursAgo_two = getValueWithForm(dict, "LastSeen.HoursAgo", .two) - self._LastSeen_HoursAgo_few = getValueWithForm(dict, "LastSeen.HoursAgo", .few) - self._LastSeen_HoursAgo_many = getValueWithForm(dict, "LastSeen.HoursAgo", .many) - self._LastSeen_HoursAgo_other = getValueWithForm(dict, "LastSeen.HoursAgo", .other) - self._StickerPack_AddMaskCount_zero = getValueWithForm(dict, "StickerPack.AddMaskCount", .zero) - self._StickerPack_AddMaskCount_one = getValueWithForm(dict, "StickerPack.AddMaskCount", .one) - self._StickerPack_AddMaskCount_two = getValueWithForm(dict, "StickerPack.AddMaskCount", .two) - self._StickerPack_AddMaskCount_few = getValueWithForm(dict, "StickerPack.AddMaskCount", .few) - self._StickerPack_AddMaskCount_many = getValueWithForm(dict, "StickerPack.AddMaskCount", .many) - self._StickerPack_AddMaskCount_other = getValueWithForm(dict, "StickerPack.AddMaskCount", .other) - self._MessageTimer_Weeks_zero = getValueWithForm(dict, "MessageTimer.Weeks", .zero) - self._MessageTimer_Weeks_one = getValueWithForm(dict, "MessageTimer.Weeks", .one) - self._MessageTimer_Weeks_two = getValueWithForm(dict, "MessageTimer.Weeks", .two) - self._MessageTimer_Weeks_few = getValueWithForm(dict, "MessageTimer.Weeks", .few) - self._MessageTimer_Weeks_many = getValueWithForm(dict, "MessageTimer.Weeks", .many) - self._MessageTimer_Weeks_other = getValueWithForm(dict, "MessageTimer.Weeks", .other) - self._ForwardedAuthorsOthers_zero = getValueWithForm(dict, "ForwardedAuthorsOthers", .zero) - self._ForwardedAuthorsOthers_one = getValueWithForm(dict, "ForwardedAuthorsOthers", .one) - self._ForwardedAuthorsOthers_two = getValueWithForm(dict, "ForwardedAuthorsOthers", .two) - self._ForwardedAuthorsOthers_few = getValueWithForm(dict, "ForwardedAuthorsOthers", .few) - self._ForwardedAuthorsOthers_many = getValueWithForm(dict, "ForwardedAuthorsOthers", .many) - self._ForwardedAuthorsOthers_other = getValueWithForm(dict, "ForwardedAuthorsOthers", .other) - self._ServiceMessage_GameScoreExtended_zero = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .zero) - self._ServiceMessage_GameScoreExtended_one = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .one) - self._ServiceMessage_GameScoreExtended_two = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .two) - self._ServiceMessage_GameScoreExtended_few = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .few) - self._ServiceMessage_GameScoreExtended_many = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .many) - self._ServiceMessage_GameScoreExtended_other = getValueWithForm(dict, "ServiceMessage.GameScoreExtended", .other) - self._MessageTimer_Hours_zero = getValueWithForm(dict, "MessageTimer.Hours", .zero) - self._MessageTimer_Hours_one = getValueWithForm(dict, "MessageTimer.Hours", .one) - self._MessageTimer_Hours_two = getValueWithForm(dict, "MessageTimer.Hours", .two) - self._MessageTimer_Hours_few = getValueWithForm(dict, "MessageTimer.Hours", .few) - self._MessageTimer_Hours_many = getValueWithForm(dict, "MessageTimer.Hours", .many) - self._MessageTimer_Hours_other = getValueWithForm(dict, "MessageTimer.Hours", .other) - self._ForwardedLocations_zero = getValueWithForm(dict, "ForwardedLocations", .zero) - self._ForwardedLocations_one = getValueWithForm(dict, "ForwardedLocations", .one) - self._ForwardedLocations_two = getValueWithForm(dict, "ForwardedLocations", .two) - self._ForwardedLocations_few = getValueWithForm(dict, "ForwardedLocations", .few) - self._ForwardedLocations_many = getValueWithForm(dict, "ForwardedLocations", .many) - self._ForwardedLocations_other = getValueWithForm(dict, "ForwardedLocations", .other) + self._AttachmentMenu_SendItem_zero = getValueWithForm(dict, "AttachmentMenu.SendItem", .zero) + self._AttachmentMenu_SendItem_one = getValueWithForm(dict, "AttachmentMenu.SendItem", .one) + self._AttachmentMenu_SendItem_two = getValueWithForm(dict, "AttachmentMenu.SendItem", .two) + self._AttachmentMenu_SendItem_few = getValueWithForm(dict, "AttachmentMenu.SendItem", .few) + self._AttachmentMenu_SendItem_many = getValueWithForm(dict, "AttachmentMenu.SendItem", .many) + self._AttachmentMenu_SendItem_other = getValueWithForm(dict, "AttachmentMenu.SendItem", .other) + self._MessageTimer_Years_zero = getValueWithForm(dict, "MessageTimer.Years", .zero) + self._MessageTimer_Years_one = getValueWithForm(dict, "MessageTimer.Years", .one) + self._MessageTimer_Years_two = getValueWithForm(dict, "MessageTimer.Years", .two) + self._MessageTimer_Years_few = getValueWithForm(dict, "MessageTimer.Years", .few) + self._MessageTimer_Years_many = getValueWithForm(dict, "MessageTimer.Years", .many) + self._MessageTimer_Years_other = getValueWithForm(dict, "MessageTimer.Years", .other) self._PrivacyLastSeenSettings_AddUsers_zero = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .zero) self._PrivacyLastSeenSettings_AddUsers_one = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .one) self._PrivacyLastSeenSettings_AddUsers_two = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .two) self._PrivacyLastSeenSettings_AddUsers_few = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .few) self._PrivacyLastSeenSettings_AddUsers_many = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .many) self._PrivacyLastSeenSettings_AddUsers_other = getValueWithForm(dict, "PrivacyLastSeenSettings.AddUsers", .other) - self._MuteFor_Hours_zero = getValueWithForm(dict, "MuteFor.Hours", .zero) - self._MuteFor_Hours_one = getValueWithForm(dict, "MuteFor.Hours", .one) - self._MuteFor_Hours_two = getValueWithForm(dict, "MuteFor.Hours", .two) - self._MuteFor_Hours_few = getValueWithForm(dict, "MuteFor.Hours", .few) - self._MuteFor_Hours_many = getValueWithForm(dict, "MuteFor.Hours", .many) - self._MuteFor_Hours_other = getValueWithForm(dict, "MuteFor.Hours", .other) + self._AttachmentMenu_SendGif_zero = getValueWithForm(dict, "AttachmentMenu.SendGif", .zero) + self._AttachmentMenu_SendGif_one = getValueWithForm(dict, "AttachmentMenu.SendGif", .one) + self._AttachmentMenu_SendGif_two = getValueWithForm(dict, "AttachmentMenu.SendGif", .two) + self._AttachmentMenu_SendGif_few = getValueWithForm(dict, "AttachmentMenu.SendGif", .few) + self._AttachmentMenu_SendGif_many = getValueWithForm(dict, "AttachmentMenu.SendGif", .many) + self._AttachmentMenu_SendGif_other = getValueWithForm(dict, "AttachmentMenu.SendGif", .other) + self._ForwardedVideoMessages_zero = getValueWithForm(dict, "ForwardedVideoMessages", .zero) + self._ForwardedVideoMessages_one = getValueWithForm(dict, "ForwardedVideoMessages", .one) + self._ForwardedVideoMessages_two = getValueWithForm(dict, "ForwardedVideoMessages", .two) + self._ForwardedVideoMessages_few = getValueWithForm(dict, "ForwardedVideoMessages", .few) + self._ForwardedVideoMessages_many = getValueWithForm(dict, "ForwardedVideoMessages", .many) + self._ForwardedVideoMessages_other = getValueWithForm(dict, "ForwardedVideoMessages", .other) + self._Media_ShareVideo_zero = getValueWithForm(dict, "Media.ShareVideo", .zero) + self._Media_ShareVideo_one = getValueWithForm(dict, "Media.ShareVideo", .one) + self._Media_ShareVideo_two = getValueWithForm(dict, "Media.ShareVideo", .two) + self._Media_ShareVideo_few = getValueWithForm(dict, "Media.ShareVideo", .few) + self._Media_ShareVideo_many = getValueWithForm(dict, "Media.ShareVideo", .many) + self._Media_ShareVideo_other = getValueWithForm(dict, "Media.ShareVideo", .other) + self._StickerPack_RemoveStickerCount_zero = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .zero) + self._StickerPack_RemoveStickerCount_one = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .one) + self._StickerPack_RemoveStickerCount_two = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .two) + self._StickerPack_RemoveStickerCount_few = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .few) + self._StickerPack_RemoveStickerCount_many = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .many) + self._StickerPack_RemoveStickerCount_other = getValueWithForm(dict, "StickerPack.RemoveStickerCount", .other) + self._Call_Seconds_zero = getValueWithForm(dict, "Call.Seconds", .zero) + self._Call_Seconds_one = getValueWithForm(dict, "Call.Seconds", .one) + self._Call_Seconds_two = getValueWithForm(dict, "Call.Seconds", .two) + self._Call_Seconds_few = getValueWithForm(dict, "Call.Seconds", .few) + self._Call_Seconds_many = getValueWithForm(dict, "Call.Seconds", .many) + self._Call_Seconds_other = getValueWithForm(dict, "Call.Seconds", .other) + self._MessageTimer_Hours_zero = getValueWithForm(dict, "MessageTimer.Hours", .zero) + self._MessageTimer_Hours_one = getValueWithForm(dict, "MessageTimer.Hours", .one) + self._MessageTimer_Hours_two = getValueWithForm(dict, "MessageTimer.Hours", .two) + self._MessageTimer_Hours_few = getValueWithForm(dict, "MessageTimer.Hours", .few) + self._MessageTimer_Hours_many = getValueWithForm(dict, "MessageTimer.Hours", .many) + self._MessageTimer_Hours_other = getValueWithForm(dict, "MessageTimer.Hours", .other) + self._Conversation_StatusOnline_zero = getValueWithForm(dict, "Conversation.StatusOnline", .zero) + self._Conversation_StatusOnline_one = getValueWithForm(dict, "Conversation.StatusOnline", .one) + self._Conversation_StatusOnline_two = getValueWithForm(dict, "Conversation.StatusOnline", .two) + self._Conversation_StatusOnline_few = getValueWithForm(dict, "Conversation.StatusOnline", .few) + self._Conversation_StatusOnline_many = getValueWithForm(dict, "Conversation.StatusOnline", .many) + self._Conversation_StatusOnline_other = getValueWithForm(dict, "Conversation.StatusOnline", .other) self._ForwardedPhotos_zero = getValueWithForm(dict, "ForwardedPhotos", .zero) self._ForwardedPhotos_one = getValueWithForm(dict, "ForwardedPhotos", .one) self._ForwardedPhotos_two = getValueWithForm(dict, "ForwardedPhotos", .two) self._ForwardedPhotos_few = getValueWithForm(dict, "ForwardedPhotos", .few) self._ForwardedPhotos_many = getValueWithForm(dict, "ForwardedPhotos", .many) self._ForwardedPhotos_other = getValueWithForm(dict, "ForwardedPhotos", .other) - self._ServiceMessage_GameScoreSelfSimple_zero = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .zero) - self._ServiceMessage_GameScoreSelfSimple_one = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .one) - self._ServiceMessage_GameScoreSelfSimple_two = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .two) - self._ServiceMessage_GameScoreSelfSimple_few = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .few) - self._ServiceMessage_GameScoreSelfSimple_many = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .many) - self._ServiceMessage_GameScoreSelfSimple_other = getValueWithForm(dict, "ServiceMessage.GameScoreSelfSimple", .other) - self._Conversation_StatusSubscribers_zero = getValueWithForm(dict, "Conversation.StatusSubscribers", .zero) - self._Conversation_StatusSubscribers_one = getValueWithForm(dict, "Conversation.StatusSubscribers", .one) - self._Conversation_StatusSubscribers_two = getValueWithForm(dict, "Conversation.StatusSubscribers", .two) - self._Conversation_StatusSubscribers_few = getValueWithForm(dict, "Conversation.StatusSubscribers", .few) - self._Conversation_StatusSubscribers_many = getValueWithForm(dict, "Conversation.StatusSubscribers", .many) - self._Conversation_StatusSubscribers_other = getValueWithForm(dict, "Conversation.StatusSubscribers", .other) + self._MessageTimer_Weeks_zero = getValueWithForm(dict, "MessageTimer.Weeks", .zero) + self._MessageTimer_Weeks_one = getValueWithForm(dict, "MessageTimer.Weeks", .one) + self._MessageTimer_Weeks_two = getValueWithForm(dict, "MessageTimer.Weeks", .two) + self._MessageTimer_Weeks_few = getValueWithForm(dict, "MessageTimer.Weeks", .few) + self._MessageTimer_Weeks_many = getValueWithForm(dict, "MessageTimer.Weeks", .many) + self._MessageTimer_Weeks_other = getValueWithForm(dict, "MessageTimer.Weeks", .other) + self._Call_ShortMinutes_zero = getValueWithForm(dict, "Call.ShortMinutes", .zero) + self._Call_ShortMinutes_one = getValueWithForm(dict, "Call.ShortMinutes", .one) + self._Call_ShortMinutes_two = getValueWithForm(dict, "Call.ShortMinutes", .two) + self._Call_ShortMinutes_few = getValueWithForm(dict, "Call.ShortMinutes", .few) + self._Call_ShortMinutes_many = getValueWithForm(dict, "Call.ShortMinutes", .many) + self._Call_ShortMinutes_other = getValueWithForm(dict, "Call.ShortMinutes", .other) + self._Notifications_ExceptionMuteExpires_Days_zero = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .zero) + self._Notifications_ExceptionMuteExpires_Days_one = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .one) + self._Notifications_ExceptionMuteExpires_Days_two = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .two) + self._Notifications_ExceptionMuteExpires_Days_few = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .few) + self._Notifications_ExceptionMuteExpires_Days_many = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .many) + self._Notifications_ExceptionMuteExpires_Days_other = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Days", .other) + self._ForwardedAudios_zero = getValueWithForm(dict, "ForwardedAudios", .zero) + self._ForwardedAudios_one = getValueWithForm(dict, "ForwardedAudios", .one) + self._ForwardedAudios_two = getValueWithForm(dict, "ForwardedAudios", .two) + self._ForwardedAudios_few = getValueWithForm(dict, "ForwardedAudios", .few) + self._ForwardedAudios_many = getValueWithForm(dict, "ForwardedAudios", .many) + self._ForwardedAudios_other = getValueWithForm(dict, "ForwardedAudios", .other) + self._Conversation_LiveLocationMembersCount_zero = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .zero) + self._Conversation_LiveLocationMembersCount_one = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .one) + self._Conversation_LiveLocationMembersCount_two = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .two) + self._Conversation_LiveLocationMembersCount_few = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .few) + self._Conversation_LiveLocationMembersCount_many = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .many) + self._Conversation_LiveLocationMembersCount_other = getValueWithForm(dict, "Conversation.LiveLocationMembersCount", .other) + self._StickerPack_AddStickerCount_zero = getValueWithForm(dict, "StickerPack.AddStickerCount", .zero) + self._StickerPack_AddStickerCount_one = getValueWithForm(dict, "StickerPack.AddStickerCount", .one) + self._StickerPack_AddStickerCount_two = getValueWithForm(dict, "StickerPack.AddStickerCount", .two) + self._StickerPack_AddStickerCount_few = getValueWithForm(dict, "StickerPack.AddStickerCount", .few) + self._StickerPack_AddStickerCount_many = getValueWithForm(dict, "StickerPack.AddStickerCount", .many) + self._StickerPack_AddStickerCount_other = getValueWithForm(dict, "StickerPack.AddStickerCount", .other) + self._Contacts_ImportersCount_zero = getValueWithForm(dict, "Contacts.ImportersCount", .zero) + self._Contacts_ImportersCount_one = getValueWithForm(dict, "Contacts.ImportersCount", .one) + self._Contacts_ImportersCount_two = getValueWithForm(dict, "Contacts.ImportersCount", .two) + self._Contacts_ImportersCount_few = getValueWithForm(dict, "Contacts.ImportersCount", .few) + self._Contacts_ImportersCount_many = getValueWithForm(dict, "Contacts.ImportersCount", .many) + self._Contacts_ImportersCount_other = getValueWithForm(dict, "Contacts.ImportersCount", .other) + self._StickerPack_RemoveMaskCount_zero = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .zero) + self._StickerPack_RemoveMaskCount_one = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .one) + self._StickerPack_RemoveMaskCount_two = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .two) + self._StickerPack_RemoveMaskCount_few = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .few) + self._StickerPack_RemoveMaskCount_many = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .many) + self._StickerPack_RemoveMaskCount_other = getValueWithForm(dict, "StickerPack.RemoveMaskCount", .other) + self._Notifications_ExceptionMuteExpires_Minutes_zero = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .zero) + self._Notifications_ExceptionMuteExpires_Minutes_one = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .one) + self._Notifications_ExceptionMuteExpires_Minutes_two = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .two) + self._Notifications_ExceptionMuteExpires_Minutes_few = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .few) + self._Notifications_ExceptionMuteExpires_Minutes_many = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .many) + self._Notifications_ExceptionMuteExpires_Minutes_other = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Minutes", .other) + self._InviteText_ContactsCount_zero = getValueWithForm(dict, "InviteText.ContactsCount", .zero) + self._InviteText_ContactsCount_one = getValueWithForm(dict, "InviteText.ContactsCount", .one) + self._InviteText_ContactsCount_two = getValueWithForm(dict, "InviteText.ContactsCount", .two) + self._InviteText_ContactsCount_few = getValueWithForm(dict, "InviteText.ContactsCount", .few) + self._InviteText_ContactsCount_many = getValueWithForm(dict, "InviteText.ContactsCount", .many) + self._InviteText_ContactsCount_other = getValueWithForm(dict, "InviteText.ContactsCount", .other) + self._SharedMedia_Link_zero = getValueWithForm(dict, "SharedMedia.Link", .zero) + self._SharedMedia_Link_one = getValueWithForm(dict, "SharedMedia.Link", .one) + self._SharedMedia_Link_two = getValueWithForm(dict, "SharedMedia.Link", .two) + self._SharedMedia_Link_few = getValueWithForm(dict, "SharedMedia.Link", .few) + self._SharedMedia_Link_many = getValueWithForm(dict, "SharedMedia.Link", .many) + self._SharedMedia_Link_other = getValueWithForm(dict, "SharedMedia.Link", .other) + self._Map_ETAMinutes_zero = getValueWithForm(dict, "Map.ETAMinutes", .zero) + self._Map_ETAMinutes_one = getValueWithForm(dict, "Map.ETAMinutes", .one) + self._Map_ETAMinutes_two = getValueWithForm(dict, "Map.ETAMinutes", .two) + self._Map_ETAMinutes_few = getValueWithForm(dict, "Map.ETAMinutes", .few) + self._Map_ETAMinutes_many = getValueWithForm(dict, "Map.ETAMinutes", .many) + self._Map_ETAMinutes_other = getValueWithForm(dict, "Map.ETAMinutes", .other) + self._MessageTimer_Minutes_zero = getValueWithForm(dict, "MessageTimer.Minutes", .zero) + self._MessageTimer_Minutes_one = getValueWithForm(dict, "MessageTimer.Minutes", .one) + self._MessageTimer_Minutes_two = getValueWithForm(dict, "MessageTimer.Minutes", .two) + self._MessageTimer_Minutes_few = getValueWithForm(dict, "MessageTimer.Minutes", .few) + self._MessageTimer_Minutes_many = getValueWithForm(dict, "MessageTimer.Minutes", .many) + self._MessageTimer_Minutes_other = getValueWithForm(dict, "MessageTimer.Minutes", .other) + self._AttachmentMenu_SendPhoto_zero = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .zero) + self._AttachmentMenu_SendPhoto_one = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .one) + self._AttachmentMenu_SendPhoto_two = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .two) + self._AttachmentMenu_SendPhoto_few = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .few) + self._AttachmentMenu_SendPhoto_many = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .many) + self._AttachmentMenu_SendPhoto_other = getValueWithForm(dict, "AttachmentMenu.SendPhoto", .other) + self._Notifications_ExceptionMuteExpires_Hours_zero = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .zero) + self._Notifications_ExceptionMuteExpires_Hours_one = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .one) + self._Notifications_ExceptionMuteExpires_Hours_two = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .two) + self._Notifications_ExceptionMuteExpires_Hours_few = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .few) + self._Notifications_ExceptionMuteExpires_Hours_many = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .many) + self._Notifications_ExceptionMuteExpires_Hours_other = getValueWithForm(dict, "Notifications.ExceptionMuteExpires.Hours", .other) + self._Invitation_Members_zero = getValueWithForm(dict, "Invitation.Members", .zero) + self._Invitation_Members_one = getValueWithForm(dict, "Invitation.Members", .one) + self._Invitation_Members_two = getValueWithForm(dict, "Invitation.Members", .two) + self._Invitation_Members_few = getValueWithForm(dict, "Invitation.Members", .few) + self._Invitation_Members_many = getValueWithForm(dict, "Invitation.Members", .many) + self._Invitation_Members_other = getValueWithForm(dict, "Invitation.Members", .other) } } diff --git a/TelegramUI/PresentationTheme.swift b/TelegramUI/PresentationTheme.swift index 93e1d83e79..b6f5c53ce1 100644 --- a/TelegramUI/PresentationTheme.swift +++ b/TelegramUI/PresentationTheme.swift @@ -379,21 +379,49 @@ public final class PresentationThemeChatList { } } +public final class PresentationThemeBubbleColorComponents { + public let fill: UIColor + public let highlightedFill: UIColor + public let stroke: UIColor + + public init(fill: UIColor, highlightedFill: UIColor, stroke: UIColor) { + self.fill = fill + self.highlightedFill = highlightedFill + self.stroke = stroke + } +} + +public final class PresentationThemeBubbleColor { + public let withWallpaper: PresentationThemeBubbleColorComponents + public let withoutWallpaper: PresentationThemeBubbleColorComponents + + public init(withWallpaper: PresentationThemeBubbleColorComponents, withoutWallpaper: PresentationThemeBubbleColorComponents) { + self.withWallpaper = withWallpaper + self.withoutWallpaper = withoutWallpaper + } +} + +public func bubbleColorComponents(theme: PresentationTheme, incoming: Bool, wallpaper: Bool) -> PresentationThemeBubbleColorComponents { + if incoming { + if wallpaper { + return theme.chat.bubble.incoming.withWallpaper + } else { + return theme.chat.bubble.incoming.withoutWallpaper + } + } else { + if wallpaper { + return theme.chat.bubble.outgoing.withWallpaper + } else { + return theme.chat.bubble.outgoing.withoutWallpaper + } + } +} + public final class PresentationThemeChatBubble { - public let incomingFillColor: UIColor - public let incomingFillHighlightedColor: UIColor - public let incomingStrokeColor: UIColor + public let incoming: PresentationThemeBubbleColor + public let outgoing: PresentationThemeBubbleColor - public let outgoingFillColor: UIColor - public let outgoingFillHighlightedColor: UIColor - public let outgoingStrokeColor: UIColor - - public let freeformFillColor: UIColor - public let freeformFillHighlightedColor: UIColor - public let freeformStrokeColor: UIColor - - public let infoFillColor: UIColor - public let infoStrokeColor: UIColor + public let freeform: PresentationThemeBubbleColor public let incomingPrimaryTextColor: UIColor public let incomingSecondaryTextColor: UIColor @@ -451,18 +479,10 @@ public final class PresentationThemeChatBubble { public let mediaHighlightOverlayColor: UIColor - public init(incomingFillColor: UIColor, incomingFillHighlightedColor: UIColor, incomingStrokeColor: UIColor, outgoingFillColor: UIColor, outgoingFillHighlightedColor: UIColor, outgoingStrokeColor: UIColor, freeformFillColor: UIColor, freeformFillHighlightedColor: UIColor, freeformStrokeColor: UIColor, infoFillColor: UIColor, infoStrokeColor: UIColor, incomingPrimaryTextColor: UIColor, incomingSecondaryTextColor: UIColor, incomingLinkTextColor: UIColor, incomingLinkHighlightColor: UIColor, outgoingPrimaryTextColor: UIColor, outgoingSecondaryTextColor: UIColor, outgoingLinkTextColor: UIColor, outgoingLinkHighlightColor: UIColor, infoPrimaryTextColor: UIColor, infoLinkTextColor: UIColor, incomingAccentTextColor: UIColor, outgoingAccentTextColor: UIColor, incomingAccentControlColor: UIColor, outgoingAccentControlColor: UIColor, incomingMediaActiveControlColor: UIColor, outgoingMediaActiveControlColor: UIColor, incomingMediaInactiveControlColor: UIColor, outgoingMediaInactiveControlColor: UIColor, outgoingCheckColor: UIColor, incomingPendingActivityColor: UIColor, outgoingPendingActivityColor: UIColor, mediaDateAndStatusFillColor: UIColor, mediaDateAndStatusTextColor: UIColor, incomingFileTitleColor: UIColor, outgoingFileTitleColor: UIColor, incomingFileDescriptionColor: UIColor, outgoingFileDescriptionColor: UIColor, incomingFileDurationColor: UIColor, outgoingFileDurationColor: UIColor, shareButtonFillColor: UIColor, shareButtonStrokeColor: UIColor, shareButtonForegroundColor: UIColor, mediaOverlayControlBackgroundColor: UIColor, mediaOverlayControlForegroundColor: UIColor, actionButtonsIncomingFillColor: UIColor, actionButtonsIncomingStrokeColor: UIColor, actionButtonsIncomingTextColor: UIColor, actionButtonsOutgoingFillColor: UIColor, actionButtonsOutgoingStrokeColor: UIColor, actionButtonsOutgoingTextColor: UIColor, selectionControlBorderColor: UIColor, selectionControlFillColor: UIColor, selectionControlForegroundColor: UIColor, mediaHighlightOverlayColor: UIColor) { - self.incomingFillColor = incomingFillColor - self.incomingFillHighlightedColor = incomingFillHighlightedColor - self.incomingStrokeColor = incomingStrokeColor - self.outgoingFillColor = outgoingFillColor - self.outgoingFillHighlightedColor = outgoingFillHighlightedColor - self.outgoingStrokeColor = outgoingStrokeColor - self.freeformFillColor = freeformFillColor - self.freeformFillHighlightedColor = freeformFillHighlightedColor - self.freeformStrokeColor = freeformStrokeColor - self.infoFillColor = infoFillColor - self.infoStrokeColor = infoStrokeColor + public init(incoming: PresentationThemeBubbleColor, outgoing: PresentationThemeBubbleColor, freeform: PresentationThemeBubbleColor, incomingPrimaryTextColor: UIColor, incomingSecondaryTextColor: UIColor, incomingLinkTextColor: UIColor, incomingLinkHighlightColor: UIColor, outgoingPrimaryTextColor: UIColor, outgoingSecondaryTextColor: UIColor, outgoingLinkTextColor: UIColor, outgoingLinkHighlightColor: UIColor, infoPrimaryTextColor: UIColor, infoLinkTextColor: UIColor, incomingAccentTextColor: UIColor, outgoingAccentTextColor: UIColor, incomingAccentControlColor: UIColor, outgoingAccentControlColor: UIColor, incomingMediaActiveControlColor: UIColor, outgoingMediaActiveControlColor: UIColor, incomingMediaInactiveControlColor: UIColor, outgoingMediaInactiveControlColor: UIColor, outgoingCheckColor: UIColor, incomingPendingActivityColor: UIColor, outgoingPendingActivityColor: UIColor, mediaDateAndStatusFillColor: UIColor, mediaDateAndStatusTextColor: UIColor, incomingFileTitleColor: UIColor, outgoingFileTitleColor: UIColor, incomingFileDescriptionColor: UIColor, outgoingFileDescriptionColor: UIColor, incomingFileDurationColor: UIColor, outgoingFileDurationColor: UIColor, shareButtonFillColor: UIColor, shareButtonStrokeColor: UIColor, shareButtonForegroundColor: UIColor, mediaOverlayControlBackgroundColor: UIColor, mediaOverlayControlForegroundColor: UIColor, actionButtonsIncomingFillColor: UIColor, actionButtonsIncomingStrokeColor: UIColor, actionButtonsIncomingTextColor: UIColor, actionButtonsOutgoingFillColor: UIColor, actionButtonsOutgoingStrokeColor: UIColor, actionButtonsOutgoingTextColor: UIColor, selectionControlBorderColor: UIColor, selectionControlFillColor: UIColor, selectionControlForegroundColor: UIColor, mediaHighlightOverlayColor: UIColor) { + self.incoming = incoming + self.outgoing = outgoing + self.freeform = freeform self.incomingPrimaryTextColor = incomingPrimaryTextColor self.incomingSecondaryTextColor = incomingSecondaryTextColor diff --git a/TelegramUI/PresentationThemeEssentialGraphics.swift b/TelegramUI/PresentationThemeEssentialGraphics.swift index 7d1ca5c940..a3594e80ef 100644 --- a/TelegramUI/PresentationThemeEssentialGraphics.swift +++ b/TelegramUI/PresentationThemeEssentialGraphics.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import Display +import TelegramCore private func generateCheckImage(partial: Bool, color: UIColor) -> UIImage? { return generateImage(CGSize(width: 11.0, height: 9.0), rotatedContext: { size, context in @@ -44,6 +45,8 @@ public final class PrincipalThemeEssentialGraphics { public let chatMessageBackgroundIncomingHighlightedImage: UIImage public let chatMessageBackgroundIncomingMergedTopImage: UIImage public let chatMessageBackgroundIncomingMergedTopHighlightedImage: UIImage + public let chatMessageBackgroundIncomingMergedTopSideImage: UIImage + public let chatMessageBackgroundIncomingMergedTopSideHighlightedImage: UIImage public let chatMessageBackgroundIncomingMergedBottomImage: UIImage public let chatMessageBackgroundIncomingMergedBottomHighlightedImage: UIImage public let chatMessageBackgroundIncomingMergedBothImage: UIImage @@ -55,6 +58,8 @@ public final class PrincipalThemeEssentialGraphics { public let chatMessageBackgroundOutgoingHighlightedImage: UIImage public let chatMessageBackgroundOutgoingMergedTopImage: UIImage public let chatMessageBackgroundOutgoingMergedTopHighlightedImage: UIImage + public let chatMessageBackgroundOutgoingMergedTopSideImage: UIImage + public let chatMessageBackgroundOutgoingMergedTopSideHighlightedImage: UIImage public let chatMessageBackgroundOutgoingMergedBottomImage: UIImage public let chatMessageBackgroundOutgoingMergedBottomHighlightedImage: UIImage public let chatMessageBackgroundOutgoingMergedBothImage: UIImage @@ -84,30 +89,39 @@ public final class PrincipalThemeEssentialGraphics { public let dateStaticBackground: UIImage public let dateFloatingBackground: UIImage - init(_ theme: PresentationThemeChat) { - let bubble = theme.bubble - self.chatMessageBackgroundIncomingImage = messageBubbleImage(incoming: true, fillColor: bubble.incomingFillColor, strokeColor: bubble.incomingStrokeColor, neighbors: .none) - self.chatMessageBackgroundIncomingHighlightedImage = messageBubbleImage(incoming: true, fillColor: bubble.incomingFillHighlightedColor, strokeColor: bubble.incomingStrokeColor, neighbors: .none) - self.chatMessageBackgroundIncomingMergedTopImage = messageBubbleImage(incoming: true, fillColor: bubble.incomingFillColor, strokeColor: bubble.incomingStrokeColor, neighbors: .top) - self.chatMessageBackgroundIncomingMergedTopHighlightedImage = messageBubbleImage(incoming: true, fillColor: bubble.incomingFillHighlightedColor, strokeColor: bubble.incomingStrokeColor, neighbors: .top) - self.chatMessageBackgroundIncomingMergedBottomImage = messageBubbleImage(incoming: true, fillColor: bubble.incomingFillColor, strokeColor: bubble.incomingStrokeColor, neighbors: .bottom) - self.chatMessageBackgroundIncomingMergedBottomHighlightedImage = messageBubbleImage(incoming: true, fillColor: bubble.incomingFillHighlightedColor, strokeColor: bubble.incomingStrokeColor, neighbors: .bottom) - self.chatMessageBackgroundIncomingMergedBothImage = messageBubbleImage(incoming: true, fillColor: bubble.incomingFillColor, strokeColor: bubble.incomingStrokeColor, neighbors: .both) - self.chatMessageBackgroundIncomingMergedBothHighlightedImage = messageBubbleImage(incoming: true, fillColor: bubble.incomingFillHighlightedColor, strokeColor: bubble.incomingStrokeColor, neighbors: .both) + public let radialIndicatorFileIconIncoming: UIImage + public let radialIndicatorFileIconOutgoing: UIImage + + init(_ theme: PresentationThemeChat, wallpaper: Bool) { + let incoming: PresentationThemeBubbleColorComponents = !wallpaper ? theme.bubble.incoming.withoutWallpaper : theme.bubble.incoming.withWallpaper + let outgoing: PresentationThemeBubbleColorComponents = !wallpaper ? theme.bubble.outgoing.withoutWallpaper : theme.bubble.outgoing.withWallpaper - self.chatMessageBackgroundOutgoingImage = messageBubbleImage(incoming: false, fillColor: bubble.outgoingFillColor, strokeColor: bubble.outgoingStrokeColor, neighbors: .none) - self.chatMessageBackgroundOutgoingHighlightedImage = messageBubbleImage(incoming: false, fillColor: bubble.outgoingFillHighlightedColor, strokeColor: bubble.outgoingStrokeColor, neighbors: .none) - self.chatMessageBackgroundOutgoingMergedTopImage = messageBubbleImage(incoming: false, fillColor: bubble.outgoingFillColor, strokeColor: bubble.outgoingStrokeColor, neighbors: .top) - self.chatMessageBackgroundOutgoingMergedTopHighlightedImage = messageBubbleImage(incoming: false, fillColor: bubble.outgoingFillHighlightedColor, strokeColor: bubble.outgoingStrokeColor, neighbors: .top) - self.chatMessageBackgroundOutgoingMergedBottomImage = messageBubbleImage(incoming: false, fillColor: bubble.outgoingFillColor, strokeColor: bubble.outgoingStrokeColor, neighbors: .bottom) - self.chatMessageBackgroundOutgoingMergedBottomHighlightedImage = messageBubbleImage(incoming: false, fillColor: bubble.outgoingFillHighlightedColor, strokeColor: bubble.outgoingStrokeColor, neighbors: .bottom) - self.chatMessageBackgroundOutgoingMergedBothImage = messageBubbleImage(incoming: false, fillColor: bubble.outgoingFillColor, strokeColor: bubble.outgoingStrokeColor, neighbors: .both) - self.chatMessageBackgroundOutgoingMergedBothHighlightedImage = messageBubbleImage(incoming: false, fillColor: bubble.outgoingFillHighlightedColor, strokeColor: bubble.outgoingStrokeColor, neighbors: .both) + self.chatMessageBackgroundIncomingImage = messageBubbleImage(incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none) + self.chatMessageBackgroundIncomingHighlightedImage = messageBubbleImage(incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .none) + self.chatMessageBackgroundIncomingMergedTopImage = messageBubbleImage(incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .top(side: false)) + self.chatMessageBackgroundIncomingMergedTopHighlightedImage = messageBubbleImage(incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .top(side: false)) + self.chatMessageBackgroundIncomingMergedTopSideImage = messageBubbleImage(incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .top(side: true)) + self.chatMessageBackgroundIncomingMergedTopSideHighlightedImage = messageBubbleImage(incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .top(side: true)) + self.chatMessageBackgroundIncomingMergedBottomImage = messageBubbleImage(incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .bottom) + self.chatMessageBackgroundIncomingMergedBottomHighlightedImage = messageBubbleImage(incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .bottom) + self.chatMessageBackgroundIncomingMergedBothImage = messageBubbleImage(incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .both) + self.chatMessageBackgroundIncomingMergedBothHighlightedImage = messageBubbleImage(incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .both) + + self.chatMessageBackgroundOutgoingImage = messageBubbleImage(incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none) + self.chatMessageBackgroundOutgoingHighlightedImage = messageBubbleImage(incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .none) + self.chatMessageBackgroundOutgoingMergedTopImage = messageBubbleImage(incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .top(side: false)) + self.chatMessageBackgroundOutgoingMergedTopHighlightedImage = messageBubbleImage(incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .top(side: false)) + self.chatMessageBackgroundOutgoingMergedTopSideImage = messageBubbleImage(incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .top(side: true)) + self.chatMessageBackgroundOutgoingMergedTopSideHighlightedImage = messageBubbleImage(incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .top(side: true)) + self.chatMessageBackgroundOutgoingMergedBottomImage = messageBubbleImage(incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .bottom) + self.chatMessageBackgroundOutgoingMergedBottomHighlightedImage = messageBubbleImage(incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .bottom) + self.chatMessageBackgroundOutgoingMergedBothImage = messageBubbleImage(incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .both) + self.chatMessageBackgroundOutgoingMergedBothHighlightedImage = messageBubbleImage(incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .both) - self.chatMessageBackgroundIncomingMergedSideImage = messageBubbleImage(incoming: true, fillColor: bubble.incomingFillColor, strokeColor: bubble.incomingStrokeColor, neighbors: .side) - self.chatMessageBackgroundOutgoingMergedSideImage = messageBubbleImage(incoming: false, fillColor: bubble.outgoingFillColor, strokeColor: bubble.outgoingStrokeColor, neighbors: .side) - self.chatMessageBackgroundIncomingMergedSideHighlightedImage = messageBubbleImage(incoming: true, fillColor: bubble.incomingFillHighlightedColor, strokeColor: bubble.incomingStrokeColor, neighbors: .side) - self.chatMessageBackgroundOutgoingMergedSideHighlightedImage = messageBubbleImage(incoming: false, fillColor: bubble.outgoingFillHighlightedColor, strokeColor: bubble.outgoingStrokeColor, neighbors: .side) + self.chatMessageBackgroundIncomingMergedSideImage = messageBubbleImage(incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .side) + self.chatMessageBackgroundOutgoingMergedSideImage = messageBubbleImage(incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .side) + self.chatMessageBackgroundIncomingMergedSideHighlightedImage = messageBubbleImage(incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .side) + self.chatMessageBackgroundOutgoingMergedSideHighlightedImage = messageBubbleImage(incoming: false, fillColor: outgoing.highlightedFill, strokeColor: outgoing.stroke, neighbors: .side) self.checkBubbleFullImage = generateCheckImage(partial: false, color: theme.bubble.outgoingCheckColor)! self.checkBubblePartialImage = generateCheckImage(partial: true, color: theme.bubble.outgoingCheckColor)! @@ -142,5 +156,8 @@ public final class PrincipalThemeEssentialGraphics { context.setFillColor(theme.serviceMessage.dateFillFloatingColor.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) })!.stretchableImage(withLeftCapWidth: 13, topCapHeight: 13) + + self.radialIndicatorFileIconIncoming = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocumentIncoming"), color: incoming.fill)! + self.radialIndicatorFileIconOutgoing = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocumentIncoming"), color: outgoing.fill)! } } diff --git a/TelegramUI/PresentationThemeSettings.swift b/TelegramUI/PresentationThemeSettings.swift index 1083575e18..69e8c2ec13 100644 --- a/TelegramUI/PresentationThemeSettings.swift +++ b/TelegramUI/PresentationThemeSettings.swift @@ -49,12 +49,102 @@ public enum PresentationFontSize: Int32 { case regular = 2 case large = 3 case extraLarge = 4 + case extraLargeX2 = 5 + case medium = 6 +} + +public enum AutomaticThemeSwitchTimeBasedSetting: PostboxCoding, Equatable { + case manual(fromSeconds: Int32, toSeconds: Int32) + case automatic(latitude: Double, longitude: Double, sunset: Int32, sunrise: Int32, localizedName: String) + + public init(decoder: PostboxDecoder) { + switch decoder.decodeInt32ForKey("_t", orElse: 0) { + case 0: + self = .manual(fromSeconds: decoder.decodeInt32ForKey("fromSeconds", orElse: 0), toSeconds: decoder.decodeInt32ForKey("toSeconds", orElse: 0)) + case 1: + self = .automatic(latitude: decoder.decodeDoubleForKey("latitude", orElse: 0.0), longitude: decoder.decodeDoubleForKey("longitude", orElse: 0.0), sunset: decoder.decodeInt32ForKey("sunset", orElse: 0), sunrise: decoder.decodeInt32ForKey("sunrise", orElse: 0), localizedName: decoder.decodeStringForKey("localizedName", orElse: "")) + default: + assertionFailure() + self = .manual(fromSeconds: 0, toSeconds: 1) + } + } + + public func encode(_ encoder: PostboxEncoder) { + switch self { + case let .manual(fromSeconds, toSeconds): + encoder.encodeInt32(0, forKey: "_t") + encoder.encodeInt32(fromSeconds, forKey: "fromSeconds") + encoder.encodeInt32(toSeconds, forKey: "toSeconds") + case let .automatic(latitude, longitude, sunset, sunrise, localizedName): + encoder.encodeInt32(1, forKey: "_t") + encoder.encodeDouble(latitude, forKey: "latitude") + encoder.encodeDouble(longitude, forKey: "longitude") + encoder.encodeInt32(sunset, forKey: "sunset") + encoder.encodeInt32(sunrise, forKey: "sunrise") + encoder.encodeString(localizedName, forKey: "localizedName") + } + } +} + +public enum AutomaticThemeSwitchTrigger: PostboxCoding, Equatable { + case none + case timeBased(setting: AutomaticThemeSwitchTimeBasedSetting) + case brightness(threshold: Double) + + public init(decoder: PostboxDecoder) { + switch decoder.decodeInt32ForKey("_t", orElse: 0) { + case 0: + self = .none + case 1: + self = .timeBased(setting: decoder.decodeObjectForKey("setting", decoder: { AutomaticThemeSwitchTimeBasedSetting(decoder: $0) }) as! AutomaticThemeSwitchTimeBasedSetting) + case 2: + self = .brightness(threshold: decoder.decodeDoubleForKey("threshold", orElse: 0.2)) + default: + assertionFailure() + self = .none + } + } + + public func encode(_ encoder: PostboxEncoder) { + switch self { + case .none: + encoder.encodeInt32(0, forKey: "_t") + case let .timeBased(setting): + encoder.encodeInt32(1, forKey: "_t") + encoder.encodeObject(setting, forKey: "setting") + case let .brightness(threshold): + encoder.encodeInt32(2, forKey: "_t") + encoder.encodeDouble(threshold, forKey: "threshold") + } + } +} + +public struct AutomaticThemeSwitchSetting: PostboxCoding, Equatable { + public var trigger: AutomaticThemeSwitchTrigger + public var theme: PresentationBuiltinThemeReference + + public init(trigger: AutomaticThemeSwitchTrigger, theme: PresentationBuiltinThemeReference) { + self.trigger = trigger + self.theme = theme + } + + public init(decoder: PostboxDecoder) { + self.trigger = decoder.decodeObjectForKey("trigger", decoder: { AutomaticThemeSwitchTrigger(decoder: $0) }) as! AutomaticThemeSwitchTrigger + self.theme = PresentationBuiltinThemeReference(rawValue: decoder.decodeInt32ForKey("theme", orElse: 0))! + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeObject(self.trigger, forKey: "trigger") + encoder.encodeInt32(self.theme.rawValue, forKey: "theme") + } } public struct PresentationThemeSettings: PreferencesEntry { - public let chatWallpaper: TelegramWallpaper - public let theme: PresentationThemeReference - public let fontSize: PresentationFontSize + public var chatWallpaper: TelegramWallpaper + public var theme: PresentationThemeReference + public var themeAccentColor: Int32? + public var fontSize: PresentationFontSize + public var automaticThemeSwitchSetting: AutomaticThemeSwitchSetting public var relatedResources: [MediaResourceId] { switch self.chatWallpaper { @@ -66,25 +156,35 @@ public struct PresentationThemeSettings: PreferencesEntry { } public static var defaultSettings: PresentationThemeSettings { - return PresentationThemeSettings(chatWallpaper: .color(0x18222D), theme: .builtin(.nightAccent), fontSize: .regular) + return PresentationThemeSettings(chatWallpaper: .builtin, theme: .builtin(.dayClassic), themeAccentColor: nil, fontSize: .regular, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(trigger: .none, theme: .nightAccent)) } - public init(chatWallpaper: TelegramWallpaper, theme: PresentationThemeReference, fontSize: PresentationFontSize) { + public init(chatWallpaper: TelegramWallpaper, theme: PresentationThemeReference, themeAccentColor: Int32?, fontSize: PresentationFontSize, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting) { self.chatWallpaper = chatWallpaper self.theme = theme + self.themeAccentColor = themeAccentColor self.fontSize = fontSize + self.automaticThemeSwitchSetting = automaticThemeSwitchSetting } public init(decoder: PostboxDecoder) { self.chatWallpaper = (decoder.decodeObjectForKey("w", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper) ?? .builtin self.theme = decoder.decodeObjectForKey("t", decoder: { PresentationThemeReference(decoder: $0) }) as! PresentationThemeReference + self.themeAccentColor = decoder.decodeOptionalInt32ForKey("themeAccentColor") self.fontSize = PresentationFontSize(rawValue: decoder.decodeInt32ForKey("f", orElse: PresentationFontSize.regular.rawValue)) ?? .regular + self.automaticThemeSwitchSetting = (decoder.decodeObjectForKey("automaticThemeSwitchSetting", decoder: { AutomaticThemeSwitchSetting(decoder: $0) }) as? AutomaticThemeSwitchSetting) ?? AutomaticThemeSwitchSetting(trigger: .none, theme: .nightAccent) } public func encode(_ encoder: PostboxEncoder) { encoder.encodeObject(self.chatWallpaper, forKey: "w") encoder.encodeObject(self.theme, forKey: "t") + if let themeAccentColor = self.themeAccentColor { + encoder.encodeInt32(themeAccentColor, forKey: "themeAccentColor") + } else { + encoder.encodeNil(forKey: "themeAccentColor") + } encoder.encodeInt32(self.fontSize.rawValue, forKey: "f") + encoder.encodeObject(self.automaticThemeSwitchSetting, forKey: "automaticThemeSwitchSetting") } public func isEqual(to: PreferencesEntry) -> Bool { @@ -96,7 +196,7 @@ public struct PresentationThemeSettings: PreferencesEntry { } public static func ==(lhs: PresentationThemeSettings, rhs: PresentationThemeSettings) -> Bool { - return lhs.chatWallpaper == rhs.chatWallpaper && lhs.theme == rhs.theme && lhs.fontSize == rhs.fontSize + return lhs.chatWallpaper == rhs.chatWallpaper && lhs.theme == rhs.theme && lhs.themeAccentColor == rhs.themeAccentColor && lhs.fontSize == rhs.fontSize && lhs.automaticThemeSwitchSetting == rhs.automaticThemeSwitchSetting } } diff --git a/TelegramUI/ProxyListSettingsController.swift b/TelegramUI/ProxyListSettingsController.swift index 6696d33bab..3f42cedcad 100644 --- a/TelegramUI/ProxyListSettingsController.swift +++ b/TelegramUI/ProxyListSettingsController.swift @@ -51,7 +51,7 @@ private enum ProxySettingsControllerEntry: ItemListNodeEntry { case enabled(PresentationTheme, String, Bool, Bool) case serversHeader(PresentationTheme, String) case addServer(PresentationTheme, String, Bool) - case server(Int, PresentationTheme, PresentationStrings, ProxyServerSettings, Bool, DisplayProxyServerStatus, ProxySettingsServerItemEditing) + case server(Int, PresentationTheme, PresentationStrings, ProxyServerSettings, Bool, DisplayProxyServerStatus, ProxySettingsServerItemEditing, Bool) case useForCalls(PresentationTheme, String, Bool) case useForCallsInfo(PresentationTheme, String) @@ -74,7 +74,7 @@ private enum ProxySettingsControllerEntry: ItemListNodeEntry { return .index(1) case .addServer: return .index(2) - case let .server(_, _, _, settings, _, _, _): + case let .server(_, _, _, settings, _, _, _, _): return .server(settings.host, settings.port, settings.connection) case .useForCalls: return .index(3) @@ -103,8 +103,8 @@ private enum ProxySettingsControllerEntry: ItemListNodeEntry { } else { return false } - case let .server(lhsIndex, lhsTheme, lhsStrings, lhsSettings, lhsActive, lhsStatus, lhsEditing): - if case let .server(rhsIndex, rhsTheme, rhsStrings, rhsSettings, rhsActive, rhsStatus, rhsEditing) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsSettings == rhsSettings, lhsActive == rhsActive, lhsStatus == rhsStatus, lhsEditing == rhsEditing { + case let .server(lhsIndex, lhsTheme, lhsStrings, lhsSettings, lhsActive, lhsStatus, lhsEditing, lhsEnabled): + if case let .server(rhsIndex, rhsTheme, rhsStrings, rhsSettings, rhsActive, rhsStatus, rhsEditing, rhsEnabled) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsSettings == rhsSettings, lhsActive == rhsActive, lhsStatus == rhsStatus, lhsEditing == rhsEditing, lhsEnabled == rhsEnabled { return true } else { return false @@ -147,11 +147,11 @@ private enum ProxySettingsControllerEntry: ItemListNodeEntry { default: return true } - case let .server(lhsIndex, _, _, _, _, _, _): + case let .server(lhsIndex, _, _, _, _, _, _, _): switch rhs { case .enabled, .serversHeader, .addServer: return false - case let .server(rhsIndex, _, _, _, _, _, _): + case let .server(rhsIndex, _, _, _, _, _, _, _): return lhsIndex < rhsIndex default: return true @@ -184,8 +184,8 @@ private enum ProxySettingsControllerEntry: ItemListNodeEntry { return ProxySettingsActionItem(theme: theme, title: text, sectionId: self.section, editing: editing, action: { arguments.addNewServer() }) - case let .server(_, theme, strings, settings, active, status, editing): - return ProxySettingsServerItem(theme: theme, strings: strings, server: settings, activity: status.activity, active: active, label: status.text, labelAccent: status.textActive, editing: editing, sectionId: self.section, action: { + case let .server(_, theme, strings, settings, active, status, editing, enabled): + return ProxySettingsServerItem(theme: theme, strings: strings, server: settings, activity: status.activity, active: active, color: enabled ? .accent : .secondary, label: status.text, labelAccent: status.textActive, editing: editing, sectionId: self.section, action: { arguments.activateServer(settings) }, infoAction: { arguments.editServer(settings) @@ -239,7 +239,7 @@ private func proxySettingsControllerEntries(presentationData: PresentationData, displayStatus = DisplayProxyServerStatus(activity: false, text: presentationData.strings.SocksProxySetup_ProxyStatusPing("\(pingTime)").0, textActive: false) } } - entries.append(.server(index, presentationData.theme, presentationData.strings, server, server == proxySettings.activeServer, displayStatus, ProxySettingsServerItemEditing(editable: true, editing: state.editing, revealed: state.revealedServer == server))) + entries.append(.server(index, presentationData.theme, presentationData.strings, server, server == proxySettings.activeServer, displayStatus, ProxySettingsServerItemEditing(editable: true, editing: state.editing, revealed: state.revealedServer == server), proxySettings.enabled)) index += 1 } @@ -373,7 +373,7 @@ public func proxySettingsController(account: Account) -> ViewController { } controller.reorderEntry = { fromIndex, toIndex, entries in let fromEntry = entries[fromIndex] - guard case let .server(_, _, _, fromServer, _, _, _) = fromEntry else { + guard case let .server(_, _, _, fromServer, _, _, _, _) = fromEntry else { return } var referenceServer: ProxyServerSettings? @@ -381,7 +381,7 @@ public func proxySettingsController(account: Account) -> ViewController { var afterAll = false if toIndex < entries.count { switch entries[toIndex] { - case let .server(_, _, _, toServer, _, _, _): + case let .server(_, _, _, toServer, _, _, _, _): referenceServer = toServer default: if entries[toIndex] < fromEntry { diff --git a/TelegramUI/ProxyServerActionSheetController.swift b/TelegramUI/ProxyServerActionSheetController.swift index 9fe880d4c0..e7da89222f 100644 --- a/TelegramUI/ProxyServerActionSheetController.swift +++ b/TelegramUI/ProxyServerActionSheetController.swift @@ -15,6 +15,8 @@ final class ProxyServerActionSheetController: ActionSheetController { return self._ready } + private var isDismissed: Bool = false + init(account: Account, theme: PresentationTheme, strings: PresentationStrings, server: ProxyServerSettings) { self.theme = theme self.strings = strings @@ -26,8 +28,15 @@ final class ProxyServerActionSheetController: ActionSheetController { var items: [ActionSheetItem] = [] items.append(ProxyServerInfoItem(strings: strings, server: server)) - items.append(ProxyServerActionItem(account: account, strings: strings, server: server, dismiss: { [weak self] in - self?.dismissAnimated() + items.append(ProxyServerActionItem(account: account, strings: strings, server: server, dismiss: { [weak self] success in + guard let strongSelf = self, !strongSelf.isDismissed else { + return + } + strongSelf.isDismissed = true + if success { + strongSelf.present(OverlayStatusController(theme: theme, type: .proxySettingSuccess), in: .window(.root)) + } + strongSelf.dismissAnimated() }, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) })) @@ -81,7 +90,7 @@ private final class ProxyServerInfoItemNode: ActionSheetItemNode { let serverTitleNode = ImmediateTextNode() serverTitleNode.isLayerBacked = true serverTitleNode.displaysAsynchronously = false - serverTitleNode.attributedText = NSAttributedString(string: "Server", font: textFont, textColor: theme.secondaryTextColor) + serverTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Hostname, font: textFont, textColor: theme.secondaryTextColor) let serverTextNode = ImmediateTextNode() serverTextNode.isLayerBacked = true serverTextNode.displaysAsynchronously = false @@ -91,7 +100,7 @@ private final class ProxyServerInfoItemNode: ActionSheetItemNode { let portTitleNode = ImmediateTextNode() portTitleNode.isLayerBacked = true portTitleNode.displaysAsynchronously = false - portTitleNode.attributedText = NSAttributedString(string: "Port", font: textFont, textColor: theme.secondaryTextColor) + portTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Port, font: textFont, textColor: theme.secondaryTextColor) let portTextNode = ImmediateTextNode() portTextNode.isLayerBacked = true portTextNode.displaysAsynchronously = false @@ -104,7 +113,7 @@ private final class ProxyServerInfoItemNode: ActionSheetItemNode { let usernameTitleNode = ImmediateTextNode() usernameTitleNode.isLayerBacked = true usernameTitleNode.displaysAsynchronously = false - usernameTitleNode.attributedText = NSAttributedString(string: "Username", font: textFont, textColor: theme.secondaryTextColor) + usernameTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Username, font: textFont, textColor: theme.secondaryTextColor) let usernameTextNode = ImmediateTextNode() usernameTextNode.isLayerBacked = true usernameTextNode.displaysAsynchronously = false @@ -116,18 +125,18 @@ private final class ProxyServerInfoItemNode: ActionSheetItemNode { let passwordTitleNode = ImmediateTextNode() passwordTitleNode.isLayerBacked = true passwordTitleNode.displaysAsynchronously = false - passwordTitleNode.attributedText = NSAttributedString(string: "Password", font: textFont, textColor: theme.secondaryTextColor) + passwordTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Password, font: textFont, textColor: theme.secondaryTextColor) let passwordTextNode = ImmediateTextNode() passwordTextNode.isLayerBacked = true passwordTextNode.displaysAsynchronously = false passwordTextNode.attributedText = NSAttributedString(string: password, font: textFont, textColor: theme.primaryTextColor) fieldNodes.append((passwordTitleNode, passwordTextNode)) } - case let .mtp(secret): + case .mtp: let passwordTitleNode = ImmediateTextNode() passwordTitleNode.isLayerBacked = true passwordTitleNode.displaysAsynchronously = false - passwordTitleNode.attributedText = NSAttributedString(string: "Secret", font: textFont, textColor: theme.secondaryTextColor) + passwordTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Secret, font: textFont, textColor: theme.secondaryTextColor) let passwordTextNode = ImmediateTextNode() passwordTextNode.isLayerBacked = true passwordTextNode.displaysAsynchronously = false @@ -171,10 +180,10 @@ private final class ProxyServerActionItem: ActionSheetItem { private let account: Account private let strings: PresentationStrings private let server: ProxyServerSettings - private let dismiss: () -> Void + private let dismiss: (Bool) -> Void private let present: (ViewController, Any?) -> Void - init(account: Account, strings: PresentationStrings, server: ProxyServerSettings, dismiss: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void) { + init(account: Account, strings: PresentationStrings, server: ProxyServerSettings, dismiss: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) { self.account = account self.strings = strings self.server = server @@ -195,7 +204,7 @@ private final class ProxyServerActionItemNode: ActionSheetItemNode { private let theme: ActionSheetControllerTheme private let strings: PresentationStrings private let server: ProxyServerSettings - private let dismiss: () -> Void + private let dismiss: (Bool) -> Void private let present: (ViewController, Any?) -> Void private let buttonNode: HighlightableButtonNode @@ -205,7 +214,7 @@ private final class ProxyServerActionItemNode: ActionSheetItemNode { private let disposable = MetaDisposable() private var revertSettings: ProxySettings? - init(account: Account, theme: ActionSheetControllerTheme, strings: PresentationStrings, server: ProxyServerSettings, dismiss: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void) { + init(account: Account, theme: ActionSheetControllerTheme, strings: PresentationStrings, server: ProxyServerSettings, dismiss: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) { self.account = account self.theme = theme self.strings = strings @@ -216,7 +225,7 @@ private final class ProxyServerActionItemNode: ActionSheetItemNode { self.titleNode = ImmediateTextNode() self.titleNode.isLayerBacked = true self.titleNode.displaysAsynchronously = false - self.titleNode.attributedText = NSAttributedString(string: "Connect", font: Font.regular(20.0), textColor: theme.controlAccentColor) + self.titleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_ConnectAndSave, font: Font.regular(20.0), textColor: theme.controlAccentColor) self.activityIndicator = ActivityIndicator(type: .custom(theme.controlAccentColor, 24.0, 1.5)) self.activityIndicator.isHidden = true @@ -293,7 +302,7 @@ private final class ProxyServerActionItemNode: ActionSheetItemNode { if let strongSelf = self { strongSelf.revertSettings = previousSettings strongSelf.buttonNode.isUserInteractionEnabled = false - strongSelf.titleNode.attributedText = NSAttributedString(string: "Connecting...", font: Font.regular(20.0), textColor: strongSelf.theme.primaryTextColor) + strongSelf.titleNode.attributedText = NSAttributedString(string: strongSelf.strings.SocksProxySetup_Connecting, font: Font.regular(20.0), textColor: strongSelf.theme.primaryTextColor) strongSelf.activityIndicator.isHidden = false strongSelf.setNeedsLayout() @@ -320,18 +329,18 @@ private final class ProxyServerActionItemNode: ActionSheetItemNode { strongSelf.activityIndicator.isHidden = true strongSelf.revertSettings = nil if value { - strongSelf.dismiss() + strongSelf.dismiss(true) } else { let _ = updateProxySettingsInteractively(postbox: strongSelf.account.postbox, network: strongSelf.account.network, { _ in return previousSettings }) - strongSelf.titleNode.attributedText = NSAttributedString(string: "Connect", font: Font.regular(20.0), textColor: strongSelf.theme.controlAccentColor) + strongSelf.titleNode.attributedText = NSAttributedString(string: strongSelf.strings.SocksProxySetup_ConnectAndSave, font: Font.regular(20.0), textColor: strongSelf.theme.controlAccentColor) strongSelf.buttonNode.isUserInteractionEnabled = true strongSelf.setNeedsLayout() let presentationData = strongSelf.account.telegramApplicationContext.currentPresentationData.with { $0 } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: "Couldn't connect to the proxy.", actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.SocksProxySetup_FailedToConnect, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) } } })) diff --git a/TelegramUI/ProxySettingsServerItem.swift b/TelegramUI/ProxySettingsServerItem.swift index 707aac41ee..5cf5deb005 100644 --- a/TelegramUI/ProxySettingsServerItem.swift +++ b/TelegramUI/ProxySettingsServerItem.swift @@ -19,6 +19,7 @@ final class ProxySettingsServerItem: ListViewItem, ItemListItem { let server: ProxyServerSettings let activity: Bool let active: Bool + let color: ItemListCheckboxItemColor let label: String let labelAccent: Bool let editing: ProxySettingsServerItemEditing @@ -28,12 +29,13 @@ final class ProxySettingsServerItem: ListViewItem, ItemListItem { let setServerWithRevealedOptions: (ProxyServerSettings?, ProxyServerSettings?) -> Void let removeServer: (ProxyServerSettings) -> Void - init(theme: PresentationTheme, strings: PresentationStrings, server: ProxyServerSettings, activity: Bool, active: Bool, label: String, labelAccent: Bool, editing: ProxySettingsServerItemEditing, sectionId: ItemListSectionId, action: @escaping () -> Void, infoAction: @escaping () -> Void, setServerWithRevealedOptions: @escaping (ProxyServerSettings?, ProxyServerSettings?) -> Void, removeServer: @escaping (ProxyServerSettings) -> Void) { + init(theme: PresentationTheme, strings: PresentationStrings, server: ProxyServerSettings, activity: Bool, active: Bool, color: ItemListCheckboxItemColor, label: String, labelAccent: Bool, editing: ProxySettingsServerItemEditing, sectionId: ItemListSectionId, action: @escaping () -> Void, infoAction: @escaping () -> Void, setServerWithRevealedOptions: @escaping (ProxyServerSettings?, ProxyServerSettings?) -> Void, removeServer: @escaping (ProxyServerSettings) -> Void) { self.theme = theme self.strings = strings self.server = server self.activity = activity self.active = active + self.color = color self.label = label self.labelAccent = labelAccent self.editing = editing @@ -193,10 +195,18 @@ class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode { if currentItem?.theme !== item.theme { updatedTheme = item.theme - updateCheckImage = PresentationResourcesItemList.checkIconImage(item.theme) updateInfoIconImage = PresentationResourcesCallList.infoButton(item.theme) } + if currentItem?.theme !== item.theme || currentItem?.color != item.color { + switch item.color { + case .accent: + updateCheckImage = PresentationResourcesItemList.checkIconImage(item.theme) + case .secondary: + updateCheckImage = PresentationResourcesItemList.secondaryCheckIconImage(item.theme) + } + } + let peerRevealOptions: [ItemListRevealOption] if item.editing.editable { peerRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: nil, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)] @@ -472,7 +482,7 @@ class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode { } } - override func revealOptionSelected(_ option: ItemListRevealOption) { + override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { self.setRevealOptionsOpened(false, animated: true) self.revealOptionsInteractivelyClosed() diff --git a/TelegramUI/RadialProgressContentNode.swift b/TelegramUI/RadialProgressContentNode.swift index 61b366030b..bce06cbd73 100644 --- a/TelegramUI/RadialProgressContentNode.swift +++ b/TelegramUI/RadialProgressContentNode.swift @@ -16,10 +16,12 @@ private final class RadialProgressContentCancelNodeParameters: NSObject { private final class RadialProgressContentSpinnerNodeParameters: NSObject { let color: UIColor let progress: CGFloat + let lineWidth: CGFloat? - init(color: UIColor, progress: CGFloat) { + init(color: UIColor, progress: CGFloat, lineWidth: CGFloat?) { self.color = color self.progress = progress + self.lineWidth = lineWidth } } @@ -87,8 +89,11 @@ private final class RadialProgressContentSpinnerNode: ASDisplayNode { return self.pop_animation(forKey: "progress") != nil } - init(color: UIColor) { + let lineWidth: CGFloat? + + init(color: UIColor, lineWidth: CGFloat?) { self.color = color + self.lineWidth = lineWidth super.init() @@ -98,7 +103,7 @@ private final class RadialProgressContentSpinnerNode: ASDisplayNode { } override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { - return RadialProgressContentSpinnerNodeParameters(color: self.color, progress: self.effectiveProgress) + return RadialProgressContentSpinnerNodeParameters(color: self.color, progress: self.effectiveProgress, lineWidth: self.lineWidth) } @objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { @@ -127,9 +132,14 @@ private final class RadialProgressContentSpinnerNode: ASDisplayNode { } progress = min(1.0, progress) - let lineWidth = max(1.6, 2.25 * factor) + let lineWidth: CGFloat = parameters.lineWidth ?? max(1.6, 2.25 * factor) - let pathDiameter = bounds.size.width - lineWidth - 2.5 * 2.0 + let pathDiameter: CGFloat + if parameters.lineWidth != nil { + pathDiameter = bounds.size.width - lineWidth + } else { + pathDiameter = bounds.size.width - lineWidth - 2.5 * 2.0 + } let path = UIBezierPath(arcCenter: CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0), radius: pathDiameter / 2.0, startAngle: startAngle, endAngle: endAngle, clockwise:true) path.lineWidth = lineWidth @@ -236,11 +246,11 @@ final class RadialProgressContentNode: RadialStatusContentNode { private var enqueuedReadyForTransition: (() -> Void)? - init(color: UIColor, displayCancel: Bool) { + init(color: UIColor, lineWidth: CGFloat?, displayCancel: Bool) { self.color = color self.displayCancel = displayCancel - self.spinnerNode = RadialProgressContentSpinnerNode(color: color) + self.spinnerNode = RadialProgressContentSpinnerNode(color: color, lineWidth: lineWidth) self.cancelNode = RadialProgressContentCancelNode(color: color, displayCancel: displayCancel) super.init() diff --git a/TelegramUI/RadialStatusNode.swift b/TelegramUI/RadialStatusNode.swift index fc389b14b4..0d10c6418d 100644 --- a/TelegramUI/RadialStatusNode.swift +++ b/TelegramUI/RadialStatusNode.swift @@ -6,7 +6,7 @@ enum RadialStatusNodeState: Equatable { case download(UIColor) case play(UIColor) case pause(UIColor) - case progress(color: UIColor, value: CGFloat?, cancelEnabled: Bool) + case progress(color: UIColor, lineWidth: CGFloat?, value: CGFloat?, cancelEnabled: Bool) case check(UIColor) case customIcon(UIImage) case secretTimeout(color: UIColor, icon: UIImage?, beginTime: Double, timeout: Double) @@ -37,8 +37,8 @@ enum RadialStatusNodeState: Equatable { } else { return false } - case let .progress(lhsColor, lhsValue, lhsCancelEnabled): - if case let .progress(rhsColor, rhsValue, rhsCancelEnabled) = rhs, lhsColor.isEqual(rhsColor), lhsValue == rhsValue, lhsCancelEnabled == rhsCancelEnabled { + case let .progress(lhsColor, lhsLineWidth, lhsValue, lhsCancelEnabled): + if case let .progress(rhsColor, rhsLineWidth, rhsValue, rhsCancelEnabled) = rhs, lhsColor.isEqual(rhsColor), lhsValue == rhsValue, lhsLineWidth == rhsLineWidth, lhsCancelEnabled == rhsCancelEnabled { return true } else { return false @@ -87,7 +87,7 @@ enum RadialStatusNodeState: Equatable { return RadialStatusIconContentNode(icon: .custom(image)) case let .check(color): return RadialCheckContentNode(color: color) - case let .progress(color, value, cancelEnabled): + case let .progress(color, lineWidth, value, cancelEnabled): if let current = current as? RadialProgressContentNode, current.displayCancel == cancelEnabled { if !current.color.isEqual(color) { current.color = color @@ -95,7 +95,7 @@ enum RadialStatusNodeState: Equatable { current.progress = value return current } else { - let node = RadialProgressContentNode(color: color, displayCancel: cancelEnabled) + let node = RadialProgressContentNode(color: color, lineWidth: lineWidth, displayCancel: cancelEnabled) node.progress = value return node } diff --git a/TelegramUI/RecentSessionsController.swift b/TelegramUI/RecentSessionsController.swift index 4e575d444f..04d0a4e4d4 100644 --- a/TelegramUI/RecentSessionsController.swift +++ b/TelegramUI/RecentSessionsController.swift @@ -370,7 +370,7 @@ public func recentSessionsController(account: Account) -> ViewController { presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }) - let sessionsSignal: Signal<[RecentAccountSession]?, NoError> = .single(nil) |> then(requestRecentAccountSessions(account: account) |> map { Optional($0) }) + let sessionsSignal: Signal<[RecentAccountSession]?, NoError> = .single(nil) |> then(requestRecentAccountSessions(account: account) |> map(Optional.init)) sessionsPromise.set(sessionsSignal) diff --git a/TelegramUI/ReplyAccessoryPanelNode.swift b/TelegramUI/ReplyAccessoryPanelNode.swift index 3e7d75a40c..836e5bb556 100644 --- a/TelegramUI/ReplyAccessoryPanelNode.swift +++ b/TelegramUI/ReplyAccessoryPanelNode.swift @@ -108,7 +108,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { var mediaUpdated = false if let updatedMediaReference = updatedMediaReference, let previousMediaReference = strongSelf.previousMediaReference { - mediaUpdated = !updatedMediaReference.media.isEqual(previousMediaReference.media) + mediaUpdated = !updatedMediaReference.media.isEqual(to: previousMediaReference.media) } else if (updatedMediaReference != nil) != (strongSelf.previousMediaReference != nil) { mediaUpdated = true } diff --git a/TelegramUI/SearchDisplayController.swift b/TelegramUI/SearchDisplayController.swift index dbb7883640..a39a21bdf1 100644 --- a/TelegramUI/SearchDisplayController.swift +++ b/TelegramUI/SearchDisplayController.swift @@ -9,7 +9,7 @@ final class SearchDisplayController { private var containerLayout: (ContainerViewLayout, CGFloat)? - private(set) var isDeactivating = false + var isDeactivating = false private var isSearchingDisposable: Disposable? diff --git a/TelegramUI/SecretChatKeyControllerNode.swift b/TelegramUI/SecretChatKeyControllerNode.swift index 1c1e2f86e2..1e67fb04af 100644 --- a/TelegramUI/SecretChatKeyControllerNode.swift +++ b/TelegramUI/SecretChatKeyControllerNode.swift @@ -121,7 +121,7 @@ final class SecretChatKeyControllerNode: ViewControllerTracingNode { let linkRange = (infoRaw as NSString).range(of: "telegram.org") if linkRange.location != NSNotFound { - infoText.addAttributes([.foregroundColor: self.presentationData.theme.list.itemAccentColor, NSAttributedStringKey(rawValue: TelegramTextAttributes.Url): "https://telegram.org/faq#secret-chats"], range: linkRange) + infoText.addAttributes([.foregroundColor: self.presentationData.theme.list.itemAccentColor, NSAttributedStringKey(rawValue: TelegramTextAttributes.URL): "https://telegram.org/faq#secret-chats"], range: linkRange) } let (infoLayout, infoApply) = makeInfoLayout(TextNodeLayoutArguments(attributedString: infoText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: layout.size.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets())) @@ -151,7 +151,7 @@ final class SecretChatKeyControllerNode: ViewControllerTracingNode { if case .ended = recognizer.state { let point = recognizer.location(in: recognizer.view) if let attributes = self.infoNode.attributesAtPoint(point)?.1 { - if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] as? String { + if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { openExternalUrl(account: self.account, url: url, presentationData: self.presentationData, applicationContext: self.account.telegramApplicationContext, navigationController: self.getNavigationController(), dismissInput: { [weak self] in self?.view.endEditing(true) }) diff --git a/TelegramUI/SecureIdAuthAcceptNode.swift b/TelegramUI/SecureIdAuthAcceptNode.swift index e5f66eb662..6157b26226 100644 --- a/TelegramUI/SecureIdAuthAcceptNode.swift +++ b/TelegramUI/SecureIdAuthAcceptNode.swift @@ -37,7 +37,7 @@ final class SecureIdAuthAcceptNode: ASDisplayNode { self.labelNode = ImmediateTextNode() self.labelNode.isLayerBacked = true - self.labelNode.attributedText = NSAttributedString(string: "Authorize", font: Font.medium(17.0), textColor: theme.list.itemCheckColors.foregroundColor) + self.labelNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: theme.list.itemCheckColors.foregroundColor) super.init() diff --git a/TelegramUI/SecureIdAuthController.swift b/TelegramUI/SecureIdAuthController.swift index 3806c83391..93649e17c7 100644 --- a/TelegramUI/SecureIdAuthController.swift +++ b/TelegramUI/SecureIdAuthController.swift @@ -26,7 +26,7 @@ final class SecureIdAuthControllerInteraction { } enum SecureIdAuthControllerMode { - case form(peerId: PeerId, scope: String, publicKey: String, opaquePayload: Data) + case form(peerId: PeerId, scope: String, publicKey: String, opaquePayload: Data, opaqueNonce: Data) case list } @@ -58,14 +58,14 @@ final class SecureIdAuthController: ViewController { case .form: self.state = .form(SecureIdAuthControllerFormState(encryptedFormData: nil, formData: nil, verificationState: nil)) case .list: - self.state = .list(SecureIdAuthControllerListState(verificationState: nil, encryptedValues: nil, values: nil)) + self.state = .list(SecureIdAuthControllerListState(verificationState: nil, encryptedValues: nil, primaryLanguageByCountry: [:], values: nil)) } super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style - self.title = self.presentationData.strings.SecureId_Title + self.title = self.presentationData.strings.Passport_Title self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) self.challengeDisposable.set((twoStepAuthData(account.network) @@ -84,14 +84,16 @@ final class SecureIdAuthController: ViewController { })) switch self.mode { - case let .form(peerId, scope, publicKey, _): - self.formDisposable = (requestSecureIdForm(postbox: account.postbox, network: account.network, peerId: peerId, scope: scope, publicKey: publicKey) - |> mapToSignal { form -> Signal in + case let .form(peerId, scope, publicKey, _, _): + self.formDisposable = (combineLatest(requestSecureIdForm(postbox: account.postbox, network: account.network, peerId: peerId, scope: scope, publicKey: publicKey), secureIdConfiguration(postbox: account.postbox, network: account.network) |> introduceError(RequestSecureIdFormError.self)) + |> mapToSignal { form, configuration -> Signal in return account.postbox.transaction { transaction -> Signal in guard let accountPeer = transaction.getPeer(account.peerId), let servicePeer = transaction.getPeer(form.peerId) else { return .fail(.generic) } - return .single(SecureIdEncryptedFormData(form: form, accountPeer: accountPeer, servicePeer: servicePeer)) + + let primaryLanguageByCountry = configuration.nativeLanguageByCountry + return .single(SecureIdEncryptedFormData(form: form, primaryLanguageByCountry: primaryLanguageByCountry, accountPeer: accountPeer, servicePeer: servicePeer)) } |> mapError { _ in return RequestSecureIdFormError.generic } |> switchToLatest @@ -117,15 +119,18 @@ final class SecureIdAuthController: ViewController { } }) case .list: - self.formDisposable = (getAllSecureIdValues(network: self.account.network) - |> deliverOnMainQueue).start(next: { [weak self] values in + self.formDisposable = (combineLatest(getAllSecureIdValues(network: self.account.network), secureIdConfiguration(postbox: account.postbox, network: account.network) |> introduceError(GetAllSecureIdValuesError.self)) + |> deliverOnMainQueue).start(next: { [weak self] values, configuration in if let strongSelf = self { strongSelf.updateState { state in var state = state + let primaryLanguageByCountry = configuration.nativeLanguageByCountry + switch state { case .form: break case var .list(list): + list.primaryLanguageByCountry = primaryLanguageByCountry list.encryptedValues = values return .list(list) } @@ -186,13 +191,13 @@ final class SecureIdAuthController: ViewController { if let strongSelf = self, let verificationState = strongSelf.state.verificationState, case .passwordChallenge(_, .checking) = verificationState { strongSelf.updateState { state in var state = state - state.verificationState = .verified(context) + state.verificationState = .verified(context.context) switch state { case var .form(form): - form.formData = form.encryptedFormData.flatMap({ decryptedSecureIdForm(context: context, form: $0.form) }) + form.formData = form.encryptedFormData.flatMap({ decryptedSecureIdForm(context: context.context, form: $0.form) }) state = .form(form) case var .list(list): - list.values = list.encryptedValues.flatMap({ decryptedAllSecureIdValues(context: context, encryptedValues: $0) }) + list.values = list.encryptedValues.flatMap({ decryptedAllSecureIdValues(context: context.context, encryptedValues: $0) }) state = .list(list) } return state @@ -321,7 +326,7 @@ final class SecureIdAuthController: ViewController { switch self.state { case let .form(form): if case let .form(reqForm) = self.mode, let encryptedFormData = form.encryptedFormData, let formData = form.formData { - let _ = (grantSecureIdAccess(network: self.account.network, peerId: encryptedFormData.servicePeer.id, publicKey: reqForm.publicKey, scope: reqForm.scope, opaquePayload: reqForm.opaquePayload, values: formData.values) + let _ = (grantSecureIdAccess(network: self.account.network, peerId: encryptedFormData.servicePeer.id, publicKey: reqForm.publicKey, scope: reqForm.scope, opaquePayload: reqForm.opaquePayload, opaqueNonce: reqForm.opaqueNonce, values: formData.values, requestedFields: formData.requestedFields) |> deliverOnMainQueue).start(completed: { [weak self] in self?.dismiss() }) diff --git a/TelegramUI/SecureIdAuthControllerNode.swift b/TelegramUI/SecureIdAuthControllerNode.swift index 4787f11140..05be5d56fe 100644 --- a/TelegramUI/SecureIdAuthControllerNode.swift +++ b/TelegramUI/SecureIdAuthControllerNode.swift @@ -31,7 +31,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { self.scrollNode = ASScrollNode() self.headerNode = SecureIdAuthHeaderNode(account: account, theme: presentationData.theme, strings: presentationData.strings) - self.acceptNode = SecureIdAuthAcceptNode(title: "Authorize", theme: presentationData.theme) + self.acceptNode = SecureIdAuthAcceptNode(title: presentationData.strings.Passport_Authorize, theme: presentationData.theme) super.init() @@ -304,151 +304,138 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { } private func presentDocumentSelection(field: SecureIdParsedRequestedFormField) { - guard let state = self.state, case let .form(form) = state, let verificationState = form.verificationState, case let .verified(context) = verificationState, let formData = form.formData else { + guard let state = self.state, case let .form(form) = state, let verificationState = form.verificationState, case let .verified(context) = verificationState, let encryptedFormData = form.encryptedFormData, let formData = form.formData else { return } - - let updatedValue: ([SecureIdValueWithContext]) -> Void = { [weak self] updatedValues in - if let strongSelf = self { - strongSelf.interaction.updateState { state in - switch state { - case let .form(form): - if let formData = form.formData { - var values = formData.values.filter { value in - switch field { - case let .identity(personalDetails, document, _): - if personalDetails { - if case .personalDetails = value.value.key { - return false - } - } - switch value.value.key { - case .passport: - if document.contains(.passport) { - return false - } - case .driversLicense: - if document.contains(.driversLicense) { - return false - } - case .idCard: - if document.contains(.idCard) { - return false - } - default: - break - } - case let .address(addressDetails, document): - if addressDetails { - if case .address = value.value.key { - return false - } - } - switch value.value.key { - case .bankStatement: - if document.contains(.bankStatement) { - return false - } - case .utilityBill: - if document.contains(.utilityBill) { - return false - } - case .rentalAgreement: - if document.contains(.rentalAgreement) { - return false - } - default: - break - } - case .phone: - break - case .email: - break - } - return true - } - values.append(contentsOf: updatedValues) - - return .form(SecureIdAuthControllerFormState(encryptedFormData: form.encryptedFormData, formData: SecureIdForm(peerId: formData.peerId, requestedFields: formData.requestedFields, values: values), verificationState: form.verificationState)) - } - case let .list(list): - break - } + let updatedValues: ([SecureIdValueKey], [SecureIdValueWithContext]) -> Void = { [weak self] touchedKeys, updatedValues in + guard let strongSelf = self else { + return + } + strongSelf.interaction.updateState { state in + guard let formData = form.formData, case let .form(form) = state else { return state } + var values = formData.values.filter { value in + return !touchedKeys.contains(value.value.key) + } + values.append(contentsOf: updatedValues) + return .form(SecureIdAuthControllerFormState(encryptedFormData: form.encryptedFormData, formData: SecureIdForm(peerId: formData.peerId, requestedFields: formData.requestedFields, values: values), verificationState: form.verificationState)) } } switch field { - case let .identity(personalDetails, document, selfie): - var hasPersonalDetails = !personalDetails - if personalDetails { - if findValue(formData.values, key: .personalDetails) != nil { - hasPersonalDetails = true - } - } - var hasValueType: SecureIdRequestedIdentityDocument? - loop: for documentType in document { - switch documentType { - case .passport: - if findValue(formData.values, key: .passport) != nil { - hasValueType = .passport - break loop + case let .identity(personalDetails, document, selfie, translations): + if let document = document { + var hasValueType: SecureIdRequestedIdentityDocument? + switch document { + case let .just(type): + if let value = findValue(formData.values, key: type.valueKey)?.1 { + switch value { + case .passport: + hasValueType = .passport + case .internalPassport: + hasValueType = .internalPassport + case .idCard: + hasValueType = .idCard + case .driversLicense: + hasValueType = .driversLicense + default: + break + } } - case .internalPassport: - if findValue(formData.values, key: .internalPassport) != nil { - hasValueType = .internalPassport - break loop - } - case .driversLicense: - if findValue(formData.values, key: .driversLicense) != nil { - hasValueType = .driversLicense - break loop - } - case .idCard: - if findValue(formData.values, key: .idCard) != nil { - hasValueType = .idCard - break loop + case let .oneOf(types): + for type in types { + if let value = findValue(formData.values, key: type.valueKey)?.1 { + switch value { + case .passport: + hasValueType = .passport + case .internalPassport: + hasValueType = .internalPassport + case .idCard: + hasValueType = .idCard + case .driversLicense: + hasValueType = .driversLicense + default: + break + } + } } } - } - if hasValueType != nil || hasPersonalDetails { - self.interaction.present(SecureIdDocumentFormController(account: self.account, context: context, requestedData: .identity(details: personalDetails, document: hasValueType, selfie: selfie), values: formData.values, updatedValues: updatedValue), nil) + if let hasValueType = hasValueType { + self.interaction.present(SecureIdDocumentFormController(account: self.account, context: context, requestedData: .identity(details: personalDetails, document: hasValueType, selfie: selfie, translations: translations), primaryLanguageByCountry: encryptedFormData.primaryLanguageByCountry, values: formData.values, updatedValues: { values in + var keys: [SecureIdValueKey] = [] + if personalDetails != nil { + keys.append(.personalDetails) + } + keys.append(hasValueType.valueKey) + updatedValues(keys, values) + }), nil) + return + } + } else if personalDetails != nil { + self.interaction.present(SecureIdDocumentFormController(account: self.account, context: context, requestedData: .identity(details: personalDetails, document: nil, selfie: selfie, translations: translations), primaryLanguageByCountry: encryptedFormData.primaryLanguageByCountry, values: formData.values, updatedValues: { values in + updatedValues([.personalDetails], values) + }), nil) return } - case let .address(addressDetails, document): - var hasValueType: SecureIdRequestedAddressDocument? - loop: for documentType in document { - switch documentType { - case .passportRegistration: - if findValue(formData.values, key: .passportRegistration) != nil { - hasValueType = .passportRegistration - break loop - } - case .temporaryRegistration: - if findValue(formData.values, key: .temporaryRegistration) != nil { - hasValueType = .temporaryRegistration - break loop - } - case .bankStatement: - if findValue(formData.values, key: .bankStatement) != nil { - hasValueType = .bankStatement - break loop - } - case .utilityBill: - if findValue(formData.values, key: .utilityBill) != nil { - hasValueType = .utilityBill - break loop - } - case .rentalAgreement: - if findValue(formData.values, key: .rentalAgreement) != nil { - hasValueType = .rentalAgreement - break loop + case let .address(addressDetails, document, translation): + if let document = document { + var hasValueType: SecureIdRequestedAddressDocument? + switch document { + case let .just(type): + if let value = findValue(formData.values, key: type.valueKey)?.1 { + switch value { + case .rentalAgreement: + hasValueType = .rentalAgreement + case .bankStatement: + hasValueType = .bankStatement + case .passportRegistration: + hasValueType = .passportRegistration + case .temporaryRegistration: + hasValueType = .temporaryRegistration + case .utilityBill: + hasValueType = .utilityBill + + default: + break + } } + case let .oneOf(types): + for type in types { + if let value = findValue(formData.values, key: type.valueKey)?.1 { + switch value { + case .rentalAgreement: + hasValueType = .rentalAgreement + case .bankStatement: + hasValueType = .bankStatement + case .passportRegistration: + hasValueType = .passportRegistration + case .temporaryRegistration: + hasValueType = .temporaryRegistration + case .utilityBill: + hasValueType = .utilityBill + + default: + break + } + } + } } - } - if let hasValueType = hasValueType { - self.interaction.present(SecureIdDocumentFormController(account: self.account, context: context, requestedData: .address(details: addressDetails, document: hasValueType), values: formData.values, updatedValues: updatedValue), nil) + if let hasValueType = hasValueType { + self.interaction.present(SecureIdDocumentFormController(account: self.account, context: context, requestedData: .address(details: addressDetails, document: hasValueType, translations: translation), primaryLanguageByCountry: encryptedFormData.primaryLanguageByCountry, values: formData.values, updatedValues: { values in + var keys: [SecureIdValueKey] = [] + if addressDetails { + keys.append(.address) + } + keys.append(hasValueType.valueKey) + updatedValues(keys, values) + }), nil) + return + } + } else if addressDetails { + self.interaction.present(SecureIdDocumentFormController(account: self.account, context: context, requestedData: .address(details: addressDetails, document: nil, translations: false), primaryLanguageByCountry: encryptedFormData.primaryLanguageByCountry, values: formData.values, updatedValues: { values in + updatedValues([.personalDetails], values) + }), nil) return } default: @@ -460,13 +447,32 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { return } - strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: requestedData, values: formData.values, updatedValues: updatedValue), nil) + strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: requestedData, primaryLanguageByCountry: encryptedFormData.primaryLanguageByCountry, values: formData.values, updatedValues: { values in + var keys: [SecureIdValueKey] = [] + switch requestedData { + case let .identity(details, document, _, _): + if details != nil { + keys.append(.personalDetails) + } + if let document = document { + keys.append(document.valueKey) + } + case let .address(details, document, _): + if details { + keys.append(.address) + } + if let document = document { + keys.append(document.valueKey) + } + } + updatedValues(keys, values) + }), nil) }) self.interaction.present(controller, nil) } private func presentPlaintextSelection(type: SecureIdPlaintextFormType) { - guard let state = self.state, case let .form(form) = state, let verificationState = form.verificationState, case let .verified(context) = verificationState, let formData = form.formData else { + guard let state = self.state, case let .form(form) = state, let verificationState = form.verificationState, case let .verified(context) = verificationState else { return } @@ -536,32 +542,33 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { } let openAction: (SecureIdValueKey) -> Void = { [weak self] field in - guard let strongSelf = self else { + guard let strongSelf = self, let state = strongSelf.state, case let .list(list) = state else { return } + let primaryLanguageByCountry = list.primaryLanguageByCountry ?? [:] switch field { case .personalDetails: - strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .identity(details: true, document: nil, selfie: false), values: values, updatedValues: updatedValues(field)), nil) + strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .identity(details: ParsedRequestedPersonalDetails(nativeNames: true), document: nil, selfie: false, translations: false), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil) case .passport: - strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .identity(details: false, document: .passport, selfie: true), values: values, updatedValues: updatedValues(field)), nil) + strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .identity(details: nil, document: .passport, selfie: true, translations: true), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil) case .internalPassport: - strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .identity(details: false, document: .internalPassport, selfie: true), values: values, updatedValues: updatedValues(field)), nil) + strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .identity(details: nil, document: .internalPassport, selfie: true, translations: true), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil) case .driversLicense: - strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .identity(details: false, document: .driversLicense, selfie: true), values: values, updatedValues: updatedValues(field)), nil) + strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .identity(details: nil, document: .driversLicense, selfie: true, translations: true), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil) case .idCard: - strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .identity(details: false, document: .idCard, selfie: true), values: values, updatedValues: updatedValues(field)), nil) + strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .identity(details: nil, document: .idCard, selfie: true, translations: true), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil) case .passportRegistration: - strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: false, document: .passportRegistration), values: values, updatedValues: updatedValues(field)), nil) + strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: false, document: .passportRegistration, translations: true), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil) case .temporaryRegistration: - strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: false, document: .temporaryRegistration), values: values, updatedValues: updatedValues(field)), nil) + strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: false, document: .temporaryRegistration, translations: true), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil) case .address: - strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: true, document: nil), values: values, updatedValues: updatedValues(field)), nil) + strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: true, document: nil, translations: true), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil) case .utilityBill: - strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: false, document: .utilityBill), values: values, updatedValues: updatedValues(field)), nil) + strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: false, document: .utilityBill, translations: true), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil) case .bankStatement: - strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: false, document: .bankStatement), values: values, updatedValues: updatedValues(field)), nil) + strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: false, document: .bankStatement, translations: true), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil) case .rentalAgreement: - strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: false, document: .rentalAgreement), values: values, updatedValues: updatedValues(field)), nil) + strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: false, document: .rentalAgreement, translations: true), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil) case .phone: break case .email: @@ -572,22 +579,22 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { switch field { case .identity, .address: let keys: [(SecureIdValueKey, String, String)] + let strings = self.presentationData.strings if case .identity = field { keys = [ - (.personalDetails, "Add Personal Details", "Edit Personal Details"), - (.passport, "Add Passport", "Edit Passport"), - (.idCard, "Add Identity Card", "Edit Identity Card"), - (.driversLicense, "Add Driver's License", "Edit Driver's License"), - (.internalPassport, "Add Internal Passport", "Edit Internal Passport"), + (.personalDetails, strings.Passport_Identity_AddPersonalDetails, strings.Passport_Identity_EditPersonalDetails), + (.passport, strings.Passport_Identity_AddPassport, strings.Passport_Identity_EditPassport), + (.idCard, strings.Passport_Identity_AddIdentityCard, strings.Passport_Identity_EditIdentityCard), + (.driversLicense, strings.Passport_Identity_AddDriversLicense, strings.Passport_Identity_EditDriversLicense), + (.internalPassport, strings.Passport_Identity_AddInternalPassport, strings.Passport_Identity_EditInternalPassport), ] } else { keys = [ - (.address, "Add Residential Address", "Edit Residential Address"), - (.utilityBill, "Add Utility Bill", "Edit Utility Bill"), - (.bankStatement, "Add Bank Statement", "Edit Bank Statement"), - (.rentalAgreement, "Add Rental Agreement", "Edit Rental Agreement"), - (.passportRegistration, "Add Passport Registration", "Edit Passport Registration"), - (.temporaryRegistration, "Add Temporary Registration", "Edit Temporary Registration") + (.address, strings.Passport_Address_AddResidentialAddress, strings.Passport_Address_EditResidentialAddress), (.utilityBill, strings.Passport_Address_AddUtilityBill, strings.Passport_Address_EditUtilityBill), + (.bankStatement, strings.Passport_Address_AddBankStatement, strings.Passport_Address_EditBankStatement), + (.rentalAgreement, strings.Passport_Address_AddRentalAgreement, strings.Passport_Address_EditRentalAgreement), + (.passportRegistration, strings.Passport_Address_AddPassportRegistration, strings.Passport_Address_EditPassportRegistration), + (.temporaryRegistration, strings.Passport_Address_AddTemporaryRegistration, strings.Passport_Address_EditTemporaryRegistration) ] } @@ -626,7 +633,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { controller?.dismissAnimated() } let items: [ActionSheetItem] = [ - ActionSheetTextItem(title: "Are you sure you want to delete your Telegram Passport? All details will be lost."), + ActionSheetTextItem(title: self.presentationData.strings.Passport_DeletePassportConfirmation), ActionSheetButtonItem(title: self.presentationData.strings.Common_Delete, color: .destructive, enabled: true, action: { [weak self] in dismissAction() self?.interaction.deleteAll() diff --git a/TelegramUI/SecureIdAuthControllerState.swift b/TelegramUI/SecureIdAuthControllerState.swift index 8c6539547c..613b18c350 100644 --- a/TelegramUI/SecureIdAuthControllerState.swift +++ b/TelegramUI/SecureIdAuthControllerState.swift @@ -4,6 +4,7 @@ import TelegramCore struct SecureIdEncryptedFormData { let form: EncryptedSecureIdForm + let primaryLanguageByCountry: [String: String] let accountPeer: Peer let servicePeer: Peer } @@ -76,6 +77,7 @@ struct SecureIdAuthControllerFormState: Equatable { struct SecureIdAuthControllerListState: Equatable { var verificationState: SecureIdAuthControllerVerificationState? var encryptedValues: EncryptedAllSecureIdValues? + var primaryLanguageByCountry: [String: String]? var values: [SecureIdValueWithContext]? static func ==(lhs: SecureIdAuthControllerListState, rhs: SecureIdAuthControllerListState) -> Bool { @@ -85,6 +87,9 @@ struct SecureIdAuthControllerListState: Equatable { if (lhs.encryptedValues != nil) != (rhs.encryptedValues != nil) { return false } + if lhs.primaryLanguageByCountry != rhs.primaryLanguageByCountry { + return false + } if lhs.values != rhs.values { return false } diff --git a/TelegramUI/SecureIdAuthFormContentNode.swift b/TelegramUI/SecureIdAuthFormContentNode.swift index fb0f2cb3b4..1c1e518294 100644 --- a/TelegramUI/SecureIdAuthFormContentNode.swift +++ b/TelegramUI/SecureIdAuthFormContentNode.swift @@ -32,21 +32,21 @@ final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode, self.headerNode = ImmediateTextNode() self.headerNode.displaysAsynchronously = false - self.headerNode.attributedText = NSAttributedString(string: "REQUESTED INFORMATION", font: Font.regular(14.0), textColor: theme.list.sectionHeaderTextColor) + self.headerNode.attributedText = NSAttributedString(string: strings.Passport_RequestedInformation, font: Font.regular(14.0), textColor: theme.list.sectionHeaderTextColor) self.textNode = ImmediateTextNode() self.textNode.displaysAsynchronously = false self.textNode.maximumNumberOfLines = 0 self.textNode.lineSpacing = 0.2 let text = NSMutableAttributedString() - let textData = strings.SecureId_FormPolicy(strings.SecureId_FormPolicyLink(peer.displayTitle).0, "@" + (peer.addressName ?? "")) + let textData = strings.Passport_AcceptHelp(peer.displayTitle, "@" + (peer.addressName ?? "")) text.append(NSAttributedString(string: textData.0, font: Font.regular(14.0), textColor: theme.list.freeTextColor)) for (index, range) in textData.1 { if index == 2 { text.addAttribute(.underlineStyle, value: NSUnderlineStyle.styleSingle.rawValue, range: range) text.addAttribute(.foregroundColor, value: theme.list.itemAccentColor, range: range) if let privacyPolicyUrl = privacyPolicyUrl { - text.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.Url), value: privacyPolicyUrl, range: range) + text.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.URL), value: privacyPolicyUrl, range: range) } } } @@ -56,8 +56,8 @@ final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode, self.textNode.linkHighlightColor = theme.list.itemAccentColor.withAlphaComponent(0.5) self.textNode.highlightAttributeAction = { attributes in - if let _ = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] { - return NSAttributedStringKey(rawValue: TelegramTextAttributes.Url) + if let _ = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedStringKey(rawValue: TelegramTextAttributes.URL) } else if let _ = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerMention)] { return NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerMention) } else { @@ -65,7 +65,7 @@ final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode, } } self.textNode.tapAttributeAction = { attributes in - if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] as? String { + if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { openURL(url) } else if let mention = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention { openMention(mention) diff --git a/TelegramUI/SecureIdAuthFormFieldNode.swift b/TelegramUI/SecureIdAuthFormFieldNode.swift index 88cf52da4e..f68da9d44c 100644 --- a/TelegramUI/SecureIdAuthFormFieldNode.swift +++ b/TelegramUI/SecureIdAuthFormFieldNode.swift @@ -46,71 +46,186 @@ enum SecureIdRequestedAddressDocument: Int32 { } } +struct ParsedRequestedPersonalDetails { + var nativeNames: Bool +} + enum SecureIdParsedRequestedFormField { - case identity(personalDetails: Bool, document: Set, selfie: Bool) - case address(addressDetails: Bool, document: Set) + case identity(personalDetails: ParsedRequestedPersonalDetails?, document: ParsedRequestedIdentityDocument?, selfie: Bool, translation: Bool) + case address(addressDetails: Bool, document: ParsedRequestedAddressDocument?, translation: Bool) case phone case email } -func parseRequestedFormFields(_ types: [SecureIdRequestedFormField]) -> [SecureIdParsedRequestedFormField] { - var identity: (Bool, Set, Bool) = (false, Set(), false) - var address: (Bool, Set) = (false, Set()) +enum ParsedRequestedIdentityDocument { + case just(SecureIdRequestedIdentityDocument) + case oneOf(Set) +} + +enum ParsedRequestedAddressDocument { + case just(SecureIdRequestedAddressDocument) + case oneOf(Set) +} + +private struct RequestedIdentity { + var details: Bool = false + var nativeNames: Bool = false + var documents: [ParsedRequestedIdentityDocument] = [] + var selfie: Bool = false + var translation: Bool = false + + mutating func merge(_ other: RequestedIdentity) { + self.details = self.details || other.details + self.nativeNames = self.nativeNames || other.nativeNames + self.documents.append(contentsOf: other.documents) + self.selfie = self.selfie || other.selfie + self.translation = self.translation || other.translation + } +} + +private struct RequestedAddress { + var details: Bool = false + var documents: [ParsedRequestedAddressDocument] = [] + var translation: Bool = false + + mutating func merge(_ other: RequestedAddress) { + self.details = self.details || other.details + self.documents.append(contentsOf: other.documents) + self.translation = self.translation || other.translation + } +} + +private struct RequestedFieldValues { + var identity = RequestedIdentity() + var address = RequestedAddress() var phone: Bool = false var email: Bool = false + mutating func merge(_ other: RequestedFieldValues) { + self.identity.merge(other.identity) + self.address.merge(other.address) + self.phone = self.phone || other.phone + self.email = self.email || other.email + } +} + +func parseRequestedFormFields(_ types: [SecureIdRequestedFormField]) -> [SecureIdParsedRequestedFormField] { + var values = RequestedFieldValues() + for type in types { switch type { - case .personalDetails: - identity.0 = true - case let .passport(selfie): - identity.1.insert(.passport) - identity.2 = identity.2 || selfie - case let .internalPassport(selfie): - identity.1.insert(.internalPassport) - identity.2 = identity.2 || selfie - case let .driversLicense(selfie): - identity.1.insert(.driversLicense) - identity.2 = identity.2 || selfie - case let .idCard(selfie): - identity.1.insert(.idCard) - identity.2 = identity.2 || selfie - case .address: - address.0 = true - case .passportRegistration: - address.1.insert(.passportRegistration) - case .temporaryRegistration: - address.1.insert(.temporaryRegistration) - case .bankStatement: - address.1.insert(.bankStatement) - case .utilityBill: - address.1.insert(.utilityBill) - case .rentalAgreement: - address.1.insert(.rentalAgreement) - case .phone: - phone = true - case .email: - email = true + case let .just(value): + let subResult = parseRequestedFieldValues(type: value) + values.merge(subResult) + case let .oneOf(subTypes): + var oneOfResult = RequestedFieldValues() + var oneOfIdentity = Set() + var oneOfAddress = Set() + for type in subTypes { + let subResult = parseRequestedFieldValues(type: type) + for document in subResult.identity.documents { + if case let .just(document) = document { + oneOfIdentity.insert(document) + } + } + for document in subResult.address.documents { + if case let .just(document) = document { + oneOfAddress.insert(document) + } + } + oneOfResult.identity.details = oneOfResult.identity.details || subResult.identity.details + oneOfResult.identity.selfie = oneOfResult.identity.selfie || subResult.identity.selfie + oneOfResult.identity.translation = oneOfResult.identity.translation || subResult.identity.translation + oneOfResult.address.details = oneOfResult.address.details || subResult.address.details + oneOfResult.address.translation = oneOfResult.address.translation || subResult.address.translation + } + if !oneOfIdentity.isEmpty { + oneOfResult.identity.documents.append(.oneOf(oneOfIdentity)) + } + if !oneOfAddress.isEmpty { + oneOfResult.address.documents.append(.oneOf(oneOfAddress)) + } + values.merge(oneOfResult) } } var result: [SecureIdParsedRequestedFormField] = [] - if identity.0 || !identity.1.isEmpty { - result.append(.identity(personalDetails: identity.0, document: identity.1, selfie: identity.2)) + if values.identity.details || !values.identity.documents.isEmpty { + if values.identity.documents.isEmpty { + result.append(.identity(personalDetails: ParsedRequestedPersonalDetails(nativeNames: values.identity.nativeNames), document: nil, selfie: false, translation: false)) + } else { + for document in values.identity.documents { + result.append(.identity(personalDetails: values.identity.details ? ParsedRequestedPersonalDetails(nativeNames: values.identity.nativeNames) : nil, document: document, selfie: values.identity.selfie, translation: values.identity.translation)) + } + } } - if address.0 || !address.1.isEmpty { - result.append(.address(addressDetails: address.0, document: address.1)) + if values.address.details || !values.address.documents.isEmpty { + if values.address.documents.isEmpty { + result.append(.address(addressDetails: true, document: nil, translation: false)) + } else { + for document in values.address.documents { + result.append(.address(addressDetails: values.address.details, document: document, translation: values.address.translation)) + } + } } - if phone { + if values.phone { result.append(.phone) } - if email { + if values.email { result.append(.email) } return result } +private func parseRequestedFieldValues(type: SecureIdRequestedFormFieldValue) -> RequestedFieldValues { + var values = RequestedFieldValues() + + switch type { + case let .personalDetails(nativeNames): + values.identity.details = true + values.identity.nativeNames = nativeNames + case let .passport(selfie, translation): + values.identity.documents.append(.just(.passport)) + values.identity.selfie = values.identity.selfie || selfie + values.identity.translation = values.identity.translation || translation + case let .internalPassport(selfie, translation): + values.identity.documents.append(.just(.internalPassport)) + values.identity.selfie = values.identity.selfie || selfie + values.identity.translation = values.identity.translation || translation + case let .driversLicense(selfie, translation): + values.identity.documents.append(.just(.driversLicense)) + values.identity.selfie = values.identity.selfie || selfie + values.identity.translation = values.identity.translation || translation + case let .idCard(selfie, translation): + values.identity.documents.append(.just(.idCard)) + values.identity.selfie = values.identity.selfie || selfie + values.identity.translation = values.identity.translation || translation + case .address: + values.address.details = true + case let .passportRegistration(translation): + values.address.documents.append(.just(.passportRegistration)) + values.address.translation = values.address.translation || translation + case let .temporaryRegistration(translation): + values.address.documents.append(.just(.temporaryRegistration)) + values.address.translation = values.address.translation || translation + case let .bankStatement(translation): + values.address.documents.append(.just(.bankStatement)) + values.address.translation = values.address.translation || translation + case let .utilityBill(translation): + values.address.documents.append(.just(.utilityBill)) + values.address.translation = values.address.translation || translation + case let .rentalAgreement(translation): + values.address.documents.append(.just(.rentalAgreement)) + values.address.translation = values.address.translation || translation + case .phone: + values.phone = true + case .email: + values.email = true + } + return values +} + private let titleFont = Font.regular(17.0) private let textFont = Font.regular(15.0) @@ -137,49 +252,43 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings: var text: String = "" switch field { - case let .identity(personalDetails, documents, selfie): - title = strings.SecureId_FormFieldIdentity - placeholder = strings.SecureId_FormFieldIdentityPlaceholder + case let .identity(personalDetails, document, _, _): + if let document = document { + title = strings.Passport_FieldIdentity + switch document { + case let .just(type): + break + case let .oneOf(types): + break + } + placeholder = strings.Passport_FieldIdentityUploadHelp + } else { + title = strings.Passport_Identity_TypePersonalDetails + placeholder = strings.Passport_FieldIdentityDetailsHelp + } - if personalDetails { + if personalDetails != nil { if let value = findValue(values, key: .personalDetails), case let .personalDetails(personalDetailsValue) = value.1 { if !text.isEmpty { text.append(", ") } - text.append(fieldsText(personalDetailsValue.firstName, personalDetailsValue.lastName, countryName(code: personalDetailsValue.countryCode, strings: strings))) + text.append(fieldsText(personalDetailsValue.latinName.firstName, personalDetailsValue.latinName.lastName, countryName(code: personalDetailsValue.countryCode, strings: strings))) } } - - if !documents.isEmpty { - for documentType in Array(documents).sorted(by: { $0.rawValue < $1.rawValue }) { - let key: SecureIdValueKey - switch documentType { - case .passport: - key = .passport - case .internalPassport: - key = .internalPassport - case .driversLicense: - key = .driversLicense - case .idCard: - key = .idCard - } - if let value = findValue(values, key: key)?.1 { - switch value { - case let .passport(passport): - break - case let .driversLicense(driversLicense): - break - case let .idCard(idCard): - break - default: - break - } - } + case let .address(addressDetails, document, _): + if let document = document { + title = strings.Passport_FieldAddress + switch document { + case let .just(type): + break + case let .oneOf(types): + break } + placeholder = strings.Passport_FieldAddressUploadHelp + } else { + title = strings.Passport_FieldAddress + placeholder = strings.Passport_FieldAddressHelp } - case let .address(addressDetails, documents): - title = strings.SecureId_FormFieldAddress - placeholder = strings.SecureId_FormFieldAddressPlaceholder if addressDetails { if let value = findValue(values, key: .address), case let .address(addressValue) = value.1 { @@ -190,8 +299,8 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings: } } case .phone: - title = strings.SecureId_FormFieldPhone - placeholder = strings.SecureId_FormFieldPhonePlaceholder + title = strings.Passport_FieldPhone + placeholder = strings.Passport_FieldPhoneHelp if let value = findValue(values, key: .phone), case let .phone(phoneValue) = value.1 { if !text.isEmpty { @@ -200,8 +309,8 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings: text = formatPhoneNumber(phoneValue.phone) } case .email: - title = strings.SecureId_FormFieldEmail - placeholder = strings.SecureId_FormFieldEmailPlaceholder + title = strings.Passport_FieldEmail + placeholder = strings.Passport_FieldEmailHelp if let value = findValue(values, key: .email), case let .email(emailValue) = value.1 { if !text.isEmpty { @@ -214,6 +323,40 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings: return (title, text.isEmpty ? placeholder : text) } +private struct ValueAdditionalData { + var selfie: Bool = false + var translation: Bool = false +} + +private func extractValueAdditionalData(_ value: SecureIdValue) -> ValueAdditionalData { + var data = ValueAdditionalData() + switch value { + case let .passport(value): + data.selfie = value.selfieDocument != nil + data.translation = !value.translations.isEmpty + case let .internalPassport(value): + data.selfie = value.selfieDocument != nil + data.translation = !value.translations.isEmpty + case let .idCard(value): + data.selfie = value.selfieDocument != nil + data.translation = !value.translations.isEmpty + case let .driversLicense(value): + data.selfie = value.selfieDocument != nil + data.translation = !value.translations.isEmpty + case let .rentalAgreement(value): + data.translation = !value.translations.isEmpty + case let .bankStatement(value): + data.translation = !value.translations.isEmpty + case let .temporaryRegistration(value): + data.translation = !value.translations.isEmpty + case let .passportRegistration(value): + data.translation = !value.translations.isEmpty + default: + break + } + return data +} + final class SecureIdAuthFormFieldNode: ASDisplayNode { private let selected: () -> Void @@ -322,38 +465,82 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode { var filled = true switch self.field { - case let .identity(personalDetails, document, selfie): - if personalDetails { + case let .identity(personalDetails, document, selfie, translation): + if personalDetails != nil { if findValue(values, key: .personalDetails) == nil { filled = false } } - if !document.isEmpty { - var anyDocument = false - for type in document { - if findValue(values, key: type.valueKey) == nil { - anyDocument = true - } - } - if !anyDocument { - filled = false + if let document = document { + switch document { + case let .just(type): + if let value = findValue(values, key: type.valueKey)?.1 { + let data = extractValueAdditionalData(value) + if selfie && !data.selfie { + filled = false + } + if translation && !data.translation { + filled = false + } + } else { + filled = false + } + case let .oneOf(types): + var anyDocument = false + for type in types { + if let value = findValue(values, key: type.valueKey)?.1 { + let data = extractValueAdditionalData(value) + var dataFilled = true + if selfie && !data.selfie { + dataFilled = false + } + if translation && !data.translation { + dataFilled = false + } + if dataFilled { + anyDocument = true + } + } + } + if !anyDocument { + filled = false + } } } - case let .address(addressDetails, document): + case let .address(addressDetails, document, translation): if addressDetails { if findValue(values, key: .address) == nil { filled = false } } - if !document.isEmpty { - var anyDocument = false - for type in document { - if findValue(values, key: type.valueKey) == nil { - anyDocument = true - } - } - if !anyDocument { - filled = false + if let document = document { + switch document { + case let .just(type): + if let value = findValue(values, key: type.valueKey)?.1 { + let data = extractValueAdditionalData(value) + if translation && !data.translation { + filled = false + } + } else { + filled = false + } + case let .oneOf(types): + var anyDocument = false + for type in types { + if let value = findValue(values, key: type.valueKey)?.1 { + let data = extractValueAdditionalData(value) + var dataFilled = true + if translation && !data.translation { + dataFilled = false + } + if dataFilled { + anyDocument = true + } + } + } + if !anyDocument { + filled = false + } } } case .phone: diff --git a/TelegramUI/SecureIdAuthHeaderNode.swift b/TelegramUI/SecureIdAuthHeaderNode.swift index 4c18a4130f..bce971b4a7 100644 --- a/TelegramUI/SecureIdAuthHeaderNode.swift +++ b/TelegramUI/SecureIdAuthHeaderNode.swift @@ -46,7 +46,7 @@ final class SecureIdAuthHeaderNode: ASDisplayNode { func updateState(formData: SecureIdEncryptedFormData?, verificationState: SecureIdAuthControllerVerificationState) { if let formData = formData { self.serviceAvatarNode.setPeer(account: self.account, peer: formData.servicePeer) - let titleData = self.strings.SecureId_RequestTitle(formData.servicePeer.displayTitle) + let titleData = self.strings.Passport_RequestHeader(formData.servicePeer.displayTitle) let titleString = NSMutableAttributedString() titleString.append(NSAttributedString(string: titleData.0, font: textFont, textColor: self.theme.list.freeTextColor)) diff --git a/TelegramUI/SecureIdAuthListContentNode.swift b/TelegramUI/SecureIdAuthListContentNode.swift index 9002408eff..5f4945d475 100644 --- a/TelegramUI/SecureIdAuthListContentNode.swift +++ b/TelegramUI/SecureIdAuthListContentNode.swift @@ -43,9 +43,9 @@ final class SecureIdAuthListContentNode: ASDisplayNode, SecureIdAuthContentNode, self.headerNode = ImmediateTextNode() self.headerNode.displaysAsynchronously = false - self.headerNode.attributedText = NSAttributedString(string: "PASSPORT INFORMATION", font: Font.regular(14.0), textColor: theme.list.sectionHeaderTextColor) + self.headerNode.attributedText = NSAttributedString(string: strings.Passport_PassportInformation, font: Font.regular(14.0), textColor: theme.list.sectionHeaderTextColor) - self.deleteItem = FormControllerActionItem(type: .destructive, title: "Delete Passport", activated: { + self.deleteItem = FormControllerActionItem(type: .destructive, title: strings.Passport_DeletePassport, activated: { deleteAll() }) self.deleteNode = self.deleteItem.node() as! FormControllerActionItemNode diff --git a/TelegramUI/SecureIdAuthListFieldNode.swift b/TelegramUI/SecureIdAuthListFieldNode.swift index 95db1749de..dc0717cc4f 100644 --- a/TelegramUI/SecureIdAuthListFieldNode.swift +++ b/TelegramUI/SecureIdAuthListFieldNode.swift @@ -39,15 +39,15 @@ private func fieldTitleAndText(field: SecureIdAuthListContentField, strings: Pre switch field { case .identity: - title = strings.SecureId_FormFieldIdentity - placeholder = strings.SecureId_FormFieldIdentityPlaceholder + title = strings.Passport_FieldIdentity + placeholder = strings.Passport_FieldIdentityDetailsHelp let keyList: [(SecureIdValueKey, String)] = [ - (.passport, "Passport"), - (.personalDetails, "Personal Details"), - (.internalPassport, "Internal Passport"), - (.driversLicense, "Driver's License"), - (.idCard, "ID Card") + (.personalDetails, strings.Passport_Identity_TypePersonalDetails), + (.passport, strings.Passport_Identity_TypePassport), + (.internalPassport, strings.Passport_Identity_TypeInternalPassport), + (.driversLicense, strings.Passport_Identity_TypeDriversLicense), + (.idCard, strings.Passport_Identity_TypeIdentityCard) ] var fields: [String] = [] @@ -61,16 +61,16 @@ private func fieldTitleAndText(field: SecureIdAuthListContentField, strings: Pre text = fieldsText(fields) } case .address: - title = strings.SecureId_FormFieldAddress - placeholder = strings.SecureId_FormFieldAddressPlaceholder + title = strings.Passport_FieldAddress + placeholder = strings.Passport_FieldAddressHelp let keyList: [(SecureIdValueKey, String)] = [ - (.address, "Address"), - (.passportRegistration, "Passport Registration"), - (.temporaryRegistration, "Temporary Registration"), - (.utilityBill, "Utility Bill"), - (.bankStatement, "Bank Statement"), - (.rentalAgreement, "Rental Agreement") + (.address, strings.Passport_Address_TypeResidentialAddress), + (.passportRegistration, strings.Passport_Address_TypePassportRegistration), + (.temporaryRegistration, strings.Passport_Address_TypeTemporaryRegistration), + (.utilityBill, strings.Passport_Address_TypeUtilityBill), + (.bankStatement, strings.Passport_Address_TypeBankStatement), + (.rentalAgreement, strings.Passport_Address_TypeRentalAgreement) ] var fields: [String] = [] @@ -84,8 +84,8 @@ private func fieldTitleAndText(field: SecureIdAuthListContentField, strings: Pre text = fieldsText(fields) } case .phone: - title = strings.SecureId_FormFieldPhone - placeholder = strings.SecureId_FormFieldPhonePlaceholder + title = strings.Passport_FieldPhone + placeholder = strings.Passport_FieldPhoneHelp if let value = findValue(values, key: .phone), case let .phone(phoneValue) = value.1 { if !text.isEmpty { @@ -94,8 +94,8 @@ private func fieldTitleAndText(field: SecureIdAuthListContentField, strings: Pre text = formatPhoneNumber(phoneValue.phone) } case .email: - title = strings.SecureId_FormFieldEmail - placeholder = strings.SecureId_FormFieldEmailPlaceholder + title = strings.Passport_FieldEmail + placeholder = strings.Passport_FieldEmailHelp if let value = findValue(values, key: .email), case let .email(emailValue) = value.1 { if !text.isEmpty { diff --git a/TelegramUI/SecureIdAuthPasswordOptionContentNode.swift b/TelegramUI/SecureIdAuthPasswordOptionContentNode.swift index e0db1f5626..d0af8f4e56 100644 --- a/TelegramUI/SecureIdAuthPasswordOptionContentNode.swift +++ b/TelegramUI/SecureIdAuthPasswordOptionContentNode.swift @@ -34,7 +34,7 @@ final class SecureIdAuthPasswordOptionContentNode: ASDisplayNode, SecureIdAuthCo self.inputBackground.displaysAsynchronously = false self.inputBackground.displayWithoutProcessing = true self.titleNode = ImmediateTextNode() - self.titleNode.attributedText = NSAttributedString(string: "Please enter your Telegram Password\nto decrypt your data", font: Font.regular(14.0), textColor: theme.list.freeTextColor) + self.titleNode.attributedText = NSAttributedString(string: strings.Passport_PasswordHelp, font: Font.regular(14.0), textColor: theme.list.freeTextColor) self.titleNode.maximumNumberOfLines = 0 self.titleNode.textAlignment = .center self.inputField = TextFieldNode() diff --git a/TelegramUI/SecureIdDocumentFormController.swift b/TelegramUI/SecureIdDocumentFormController.swift index edcbfa1938..ff475b063b 100644 --- a/TelegramUI/SecureIdDocumentFormController.swift +++ b/TelegramUI/SecureIdDocumentFormController.swift @@ -6,8 +6,8 @@ import Postbox import TelegramCore enum SecureIdDocumentFormRequestedData { - case identity(details: Bool, document: SecureIdRequestedIdentityDocument?, selfie: Bool) - case address(details: Bool, document: SecureIdRequestedAddressDocument?) + case identity(details: ParsedRequestedPersonalDetails?, document: SecureIdRequestedIdentityDocument?, selfie: Bool, translations: Bool) + case address(details: Bool, document: SecureIdRequestedAddressDocument?, translations: Bool) } final class SecureIdDocumentFormController: FormController { @@ -17,52 +17,54 @@ final class SecureIdDocumentFormController: FormController Void) { + init(account: Account, context: SecureIdAccessContext, requestedData: SecureIdDocumentFormRequestedData, primaryLanguageByCountry: [String: String], values: [SecureIdValueWithContext], updatedValues: @escaping ([SecureIdValueWithContext]) -> Void) { self.account = account self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } self.context = context self.requestedData = requestedData + self.primaryLanguageByCountry = primaryLanguageByCountry self.values = values self.updatedValues = updatedValues super.init(initParams: SecureIdDocumentFormControllerNodeInitParams(account: account, context: context), presentationData: self.presentationData) switch requestedData { - case let .identity(_, document, _): + case let .identity(_, document, _, _): if let document = document { switch document { case .passport: - self.title = "Passport" + self.title = self.presentationData.strings.Passport_Identity_TypePassport case .internalPassport: - self.title = "Internal Passport" + self.title = self.presentationData.strings.Passport_Identity_TypeInternalPassport case .driversLicense: - self.title = "Driver's License" + self.title = self.presentationData.strings.Passport_Identity_TypeDriversLicense case .idCard: - self.title = "ID Card" + self.title = self.presentationData.strings.Passport_Identity_TypeIdentityCard } } else { - self.title = "Personal Details" + self.title = self.presentationData.strings.Passport_Identity_TypePersonalDetails } - case let .address(_, document): + case let .address(_, document, _): if let document = document { switch document { case .passportRegistration: - self.title = "Passport Registration" + self.title = self.presentationData.strings.Passport_Address_TypePassportRegistration case .temporaryRegistration: - self.title = "Temporary Registration" + self.title = self.presentationData.strings.Passport_Address_TypeTemporaryRegistration case .utilityBill: - self.title = "Utility Bill" + self.title = self.presentationData.strings.Passport_Address_TypeUtilityBill case .bankStatement: - self.title = "Bank Statement" + self.title = self.presentationData.strings.Passport_Address_TypeBankStatement case .rentalAgreement: - self.title = "Rental Agreement" + self.title = self.presentationData.strings.Passport_Address_TypeRentalAgreement } } else { - self.title = "Address" + self.title = self.presentationData.strings.Passport_Address_TypeResidentialAddress } } self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) @@ -122,6 +124,6 @@ final class SecureIdDocumentFormController: FormController], errorIndex: inout Int) { + if let error = value.errors[key] { + entries.append(.entry(SecureIdDocumentFormEntry.error(errorIndex, error, key))) + errorIndex += 1 + } +} + struct SecureIdDocumentFormState: FormControllerInnerState { fileprivate var previousValues: [SecureIdValueKey: SecureIdValueWithContext] fileprivate var documentState: SecureIdDocumentFormDocumentState @@ -310,6 +345,8 @@ struct SecureIdDocumentFormState: FormControllerInnerState { fileprivate var frontSideDocument: SecureIdVerificationDocument? fileprivate var backSideRequired: Bool fileprivate var backSideDocument: SecureIdVerificationDocument? + fileprivate var translationsRequired: Bool + fileprivate var translations: [SecureIdVerificationDocument] fileprivate var actionState: SecureIdDocumentFormActionState func isEqual(to: SecureIdDocumentFormState) -> Bool { @@ -339,6 +376,17 @@ struct SecureIdDocumentFormState: FormControllerInnerState { if self.backSideDocument != to.backSideDocument { return false } + if self.translationsRequired != to.translationsRequired { + return false + } + if self.translations.count != to.translations.count { + return false + } + for i in 0 ..< self.translations.count { + if self.translations[i] != to.translations[i] { + return false + } + } return true } @@ -348,98 +396,34 @@ struct SecureIdDocumentFormState: FormControllerInnerState { var result: [FormControllerItemEntry] = [] var errorIndex = 0 - if let document = identity.document, false { - result.append(.entry(SecureIdDocumentFormEntry.scansHeader)) - - let filesType: SecureIdValueKey - switch document.type { - case .passport: - filesType = .passport - case .internalPassport: - filesType = .internalPassport - case .driversLicense: - filesType = .driversLicense - case .idCard: - filesType = .idCard - } - - if let value = self.previousValues[filesType] { - var fileHashes: Set? = Set() - loop: for document in self.documents { - switch document { - case .local: - fileHashes = nil - break loop - case let .remote(file): - fileHashes?.insert(file.fileHash) - } - } - - if let fileHashes = fileHashes, !fileHashes.isEmpty, let error = value.errors[.files(hashes: fileHashes)] { - //result.append(.spacer) - result.append(.entry(SecureIdDocumentFormEntry.error(errorIndex, error))) - errorIndex += 1 - } - } - - for i in 0 ..< self.documents.count { - var error: String? - switch self.documents[i] { - case .local: - break - case let .remote(file): - switch self.documentState { - case let .identity(identity): - if let document = identity.document { - switch document.type { - case .passport: - error = self.previousValues[.passport]?.errors[.file(hash: file.fileHash)] - case .internalPassport: - error = self.previousValues[.internalPassport]?.errors[.file(hash: file.fileHash)] - case .driversLicense: - error = self.previousValues[.driversLicense]?.errors[.file(hash: file.fileHash)] - case .idCard: - error = self.previousValues[.idCard]?.errors[.file(hash: file.fileHash)] - } - } - case let .address(address): - if let document = address.document { - switch document { - case .passportRegistration: - error = self.previousValues[.passportRegistration]?.errors[.file(hash: file.fileHash)] - case .temporaryRegistration: - error = self.previousValues[.temporaryRegistration]?.errors[.file(hash: file.fileHash)] - case .bankStatement: - error = self.previousValues[.bankStatement]?.errors[.file(hash: file.fileHash)] - case .utilityBill: - error = self.previousValues[.utilityBill]?.errors[.file(hash: file.fileHash)] - case .rentalAgreement: - error = self.previousValues[.rentalAgreement]?.errors[.file(hash: file.fileHash)] - } - } - } - } - result.append(.entry(SecureIdDocumentFormEntry.scan(i, self.documents[i], error))) - } - result.append(.entry(SecureIdDocumentFormEntry.addScan(!self.documents.isEmpty))) - result.append(.entry(SecureIdDocumentFormEntry.scansInfo(.identity))) - result.append(.spacer) - } - if let details = identity.details { result.append(.entry(SecureIdDocumentFormEntry.infoHeader(.identity))) result.append(.entry(SecureIdDocumentFormEntry.firstName(details.firstName, self.previousValues[.personalDetails]?.errors[.field(.personalDetails(.firstName))]))) + result.append(.entry(SecureIdDocumentFormEntry.middleName(details.middleName, self.previousValues[.personalDetails]?.errors[.field(.personalDetails(.middleName))]))) result.append(.entry(SecureIdDocumentFormEntry.lastName(details.lastName, self.previousValues[.personalDetails]?.errors[.field(.personalDetails(.lastName))]))) result.append(.entry(SecureIdDocumentFormEntry.birthdate(details.birthdate, self.previousValues[.personalDetails]?.errors[.field(.personalDetails(.birthdate))]))) result.append(.entry(SecureIdDocumentFormEntry.gender(details.gender, self.previousValues[.personalDetails]?.errors[.field(.personalDetails(.gender))]))) result.append(.entry(SecureIdDocumentFormEntry.countryCode(details.countryCode, self.previousValues[.personalDetails]?.errors[.field(.personalDetails(.countryCode))]))) result.append(.entry(SecureIdDocumentFormEntry.residenceCountryCode(details.residenceCountryCode, self.previousValues[.personalDetails]?.errors[.field(.personalDetails(.residenceCountryCode))]))) + + if details.nativeNameRequired && !details.countryCode.isEmpty && details.primaryLanguageByCountry[details.countryCode] != "en" { + if let last = result.last, case .spacer = last { + } else { + result.append(.spacer) + } + result.append(.entry(SecureIdDocumentFormEntry.nativeInfoHeader(details.primaryLanguageByCountry[details.countryCode] ?? ""))) + result.append(.entry(SecureIdDocumentFormEntry.nativeFirstName(details.nativeFirstName, self.previousValues[.personalDetails]?.errors[.field(.personalDetails(.firstNameNative))]))) + result.append(.entry(SecureIdDocumentFormEntry.nativeMiddleName(details.nativeMiddleName, self.previousValues[.personalDetails]?.errors[.field(.personalDetails(.middleNameNative))]))) + result.append(.entry(SecureIdDocumentFormEntry.nativeLastName(details.nativeLastName, self.previousValues[.personalDetails]?.errors[.field(.personalDetails(.lastNameNative))]))) + result.append(.entry(SecureIdDocumentFormEntry.nativeInfo(details.primaryLanguageByCountry[details.countryCode] ?? ""))) + result.append(.spacer) + } } if let document = identity.document { - if (identity.details == nil) { + if identity.details == nil { result.append(.entry(SecureIdDocumentFormEntry.infoHeader(.identity))) } @@ -465,7 +449,10 @@ struct SecureIdDocumentFormState: FormControllerInnerState { } if self.selfieRequired || self.frontSideRequired || self.backSideRequired { - result.append(.spacer) + if let last = result.last, case .spacer = last { + } else { + result.append(.spacer) + } result.append(.entry(SecureIdDocumentFormEntry.requestedDocumentsHeader)) if self.frontSideRequired { if let document = self.frontSideDocument { @@ -551,8 +538,91 @@ struct SecureIdDocumentFormState: FormControllerInnerState { } } - if !self.previousValues.isEmpty { + if let document = identity.document, self.translationsRequired { + if let last = result.last, case .spacer = last { + } else { + result.append(.spacer) + } + result.append(.entry(SecureIdDocumentFormEntry.translationsHeader)) + + let filesType: SecureIdValueKey + switch document.type { + case .passport: + filesType = .passport + case .internalPassport: + filesType = .internalPassport + case .driversLicense: + filesType = .driversLicense + case .idCard: + filesType = .idCard + } + + if let value = self.previousValues[filesType] { + var fileHashes: Set? = Set() + loop: for document in self.translations { + switch document { + case .local: + fileHashes = nil + break loop + case let .remote(file): + fileHashes?.insert(file.fileHash) + } + } + + if let fileHashes = fileHashes, !fileHashes.isEmpty { + maybeAddError(key: .translationFiles(hashes: fileHashes), value: value, entries: &result, errorIndex: &errorIndex) + } + } + + for i in 0 ..< self.translations.count { + var error: String? + switch self.translations[i] { + case .local: + break + case let .remote(file): + switch self.documentState { + case let .identity(identity): + if let document = identity.document { + switch document.type { + case .passport: + error = self.previousValues[.passport]?.errors[.translationFile(hash: file.fileHash)] + case .internalPassport: + error = self.previousValues[.internalPassport]?.errors[.translationFile(hash: file.fileHash)] + case .driversLicense: + error = self.previousValues[.driversLicense]?.errors[.translationFile(hash: file.fileHash)] + case .idCard: + error = self.previousValues[.idCard]?.errors[.translationFile(hash: file.fileHash)] + } + } + case let .address(address): + if let document = address.document { + switch document { + case .passportRegistration: + error = self.previousValues[.passportRegistration]?.errors[.translationFile(hash: file.fileHash)] + case .temporaryRegistration: + error = self.previousValues[.temporaryRegistration]?.errors[.translationFile(hash: file.fileHash)] + case .bankStatement: + error = self.previousValues[.bankStatement]?.errors[.translationFile(hash: file.fileHash)] + case .utilityBill: + error = self.previousValues[.utilityBill]?.errors[.translationFile(hash: file.fileHash)] + case .rentalAgreement: + error = self.previousValues[.rentalAgreement]?.errors[.translationFile(hash: file.fileHash)] + } + } + } + } + result.append(.entry(SecureIdDocumentFormEntry.translation(i, self.translations[i], error))) + } + result.append(.entry(SecureIdDocumentFormEntry.addTranslation(!self.translations.isEmpty))) + result.append(.entry(SecureIdDocumentFormEntry.translationsInfo)) result.append(.spacer) + } + + if !self.previousValues.isEmpty { + if let last = result.last, case .spacer = last { + } else { + result.append(.spacer) + } result.append(.entry(SecureIdDocumentFormEntry.deleteDocument)) } @@ -589,9 +659,8 @@ struct SecureIdDocumentFormState: FormControllerInnerState { } } - if let fileHashes = fileHashes, !fileHashes.isEmpty, let error = value.errors[.files(hashes: fileHashes)] { - result.append(.entry(SecureIdDocumentFormEntry.error(errorIndex, error))) - errorIndex += 1 + if let fileHashes = fileHashes, !fileHashes.isEmpty { + maybeAddError(key: .files(hashes: fileHashes), value: value, entries: &result, errorIndex: &errorIndex) } } @@ -650,8 +719,79 @@ struct SecureIdDocumentFormState: FormControllerInnerState { result.append(.entry(SecureIdDocumentFormEntry.postcode(details.postcode, self.previousValues[.address]?.errors[.field(.address(.postCode))]))) } - if !self.previousValues.isEmpty { + if self.translationsRequired, let document = address.document { result.append(.spacer) + result.append(.entry(SecureIdDocumentFormEntry.translationsHeader)) + + let filesType: SecureIdValueKey + switch document { + case .passportRegistration: + filesType = .passportRegistration + case .temporaryRegistration: + filesType = .temporaryRegistration + case .bankStatement: + filesType = .bankStatement + case .rentalAgreement: + filesType = .rentalAgreement + case .utilityBill: + filesType = .utilityBill + } + + if let value = self.previousValues[filesType] { + var fileHashes: Set? = Set() + loop: for document in self.translations { + switch document { + case .local: + fileHashes = nil + break loop + case let .remote(file): + fileHashes?.insert(file.fileHash) + } + } + + if let fileHashes = fileHashes, !fileHashes.isEmpty { + maybeAddError(key: .translationFiles(hashes: fileHashes), value: value, entries: &result, errorIndex: &errorIndex) + } + } + + for i in 0 ..< self.translations.count { + var error: String? + switch self.translations[i] { + case .local: + break + case let .remote(file): + switch self.documentState { + case let .address(address): + if let document = address.document { + switch document { + case .passportRegistration: + error = self.previousValues[.passportRegistration]?.errors[.translationFile(hash: file.fileHash)] + case .temporaryRegistration: + error = self.previousValues[.temporaryRegistration]?.errors[.translationFile(hash: file.fileHash)] + case .bankStatement: + error = self.previousValues[.bankStatement]?.errors[.translationFile(hash: file.fileHash)] + case .utilityBill: + error = self.previousValues[.utilityBill]?.errors[.translationFile(hash: file.fileHash)] + case .rentalAgreement: + error = self.previousValues[.rentalAgreement]?.errors[.translationFile(hash: file.fileHash)] + } + } + default: + break + } + } + result.append(.entry(SecureIdDocumentFormEntry.translation(i, self.translations[i], error))) + } + result.append(.entry(SecureIdDocumentFormEntry.addTranslation(!self.translations.isEmpty))) + result.append(.entry(SecureIdDocumentFormEntry.translationsInfo)) + result.append(.spacer) + } + + if !self.previousValues.isEmpty { + if let last = result.last, case .spacer = last { + } else { + result.append(.spacer) + } result.append(.entry(SecureIdDocumentFormEntry.deleteDocument)) } @@ -710,22 +850,36 @@ struct SecureIdDocumentFormState: FormControllerInnerState { } } + for document in self.translations { + switch document { + case let .local(local): + switch local.state { + case .uploading: + return .saveNotAvailable + case .uploaded: + break + } + case .remote: + break + } + } + return .saveAvailable } } extension SecureIdDocumentFormState { - init(requestedData: SecureIdDocumentFormRequestedData, values: [SecureIdValueKey: SecureIdValueWithContext]) { + init(requestedData: SecureIdDocumentFormRequestedData, values: [SecureIdValueKey: SecureIdValueWithContext], primaryLanguageByCountry: [String: String]) { switch requestedData { - case let .identity(details, document, selfie): + case let .identity(details, document, selfie, translations): var previousValues: [SecureIdValueKey: SecureIdValueWithContext] = [:] var detailsState: SecureIdDocumentFormIdentityDetailsState? - if details { + if let details = details { if let value = values[.personalDetails], case let .personalDetails(personalDetailsValue) = value.value { previousValues[.personalDetails] = value - detailsState = SecureIdDocumentFormIdentityDetailsState(firstName: personalDetailsValue.firstName, lastName: personalDetailsValue.lastName, countryCode: personalDetailsValue.countryCode, residenceCountryCode: personalDetailsValue.residenceCountryCode, birthdate: personalDetailsValue.birthdate, gender: personalDetailsValue.gender) + detailsState = SecureIdDocumentFormIdentityDetailsState(primaryLanguageByCountry: primaryLanguageByCountry, nativeNameRequired: details.nativeNames, firstName: personalDetailsValue.latinName.firstName, middleName: personalDetailsValue.latinName.middleName, lastName: personalDetailsValue.latinName.lastName, nativeFirstName: personalDetailsValue.nativeName?.firstName ?? "", nativeMiddleName: personalDetailsValue.nativeName?.middleName ?? "", nativeLastName: personalDetailsValue.nativeName?.lastName ?? "", countryCode: personalDetailsValue.countryCode, residenceCountryCode: personalDetailsValue.residenceCountryCode, birthdate: personalDetailsValue.birthdate, gender: personalDetailsValue.gender) } else { - detailsState = SecureIdDocumentFormIdentityDetailsState(firstName: "", lastName: "", countryCode: "", residenceCountryCode: "", birthdate: nil, gender: nil) + detailsState = SecureIdDocumentFormIdentityDetailsState(primaryLanguageByCountry: primaryLanguageByCountry, nativeNameRequired: details.nativeNames, firstName: "", middleName: "", lastName: "", nativeFirstName: "", nativeMiddleName: "", nativeLastName: "", countryCode: "", residenceCountryCode: "", birthdate: nil, gender: nil) } } var documentState: SecureIdDocumentFormIdentityDocumentState? @@ -735,6 +889,7 @@ extension SecureIdDocumentFormState { var backSideRequired: Bool = false var frontSideDocument: SecureIdVerificationDocument? var backSideDocument: SecureIdVerificationDocument? + var translationDocuments: [SecureIdVerificationDocument] = [] if let document = document { var identifier: String = "" var expiryDate: SecureIdDate? @@ -745,7 +900,9 @@ extension SecureIdDocumentFormState { identifier = passport.identifier expiryDate = passport.expiryDate verificationDocuments = passport.verificationDocuments.compactMap(SecureIdVerificationDocument.init) + frontSideDocument = passport.frontSideDocument.flatMap(SecureIdVerificationDocument.init) selfieDocument = passport.selfieDocument.flatMap(SecureIdVerificationDocument.init) + translationDocuments = passport.translations.compactMap(SecureIdVerificationDocument.init) } frontSideRequired = true case .internalPassport: @@ -756,6 +913,7 @@ extension SecureIdDocumentFormState { verificationDocuments = internalPassport.verificationDocuments.compactMap(SecureIdVerificationDocument.init) selfieDocument = internalPassport.selfieDocument.flatMap(SecureIdVerificationDocument.init) frontSideDocument = internalPassport.frontSideDocument.flatMap(SecureIdVerificationDocument.init) + translationDocuments = internalPassport.translations.compactMap(SecureIdVerificationDocument.init) } frontSideRequired = true case .driversLicense: @@ -767,6 +925,7 @@ extension SecureIdDocumentFormState { selfieDocument = driversLicense.selfieDocument.flatMap(SecureIdVerificationDocument.init) frontSideDocument = driversLicense.frontSideDocument.flatMap(SecureIdVerificationDocument.init) backSideDocument = driversLicense.backSideDocument.flatMap(SecureIdVerificationDocument.init) + translationDocuments = driversLicense.translations.compactMap(SecureIdVerificationDocument.init) } frontSideRequired = true backSideRequired = true @@ -779,6 +938,7 @@ extension SecureIdDocumentFormState { selfieDocument = idCard.selfieDocument.flatMap(SecureIdVerificationDocument.init) frontSideDocument = idCard.frontSideDocument.flatMap(SecureIdVerificationDocument.init) backSideDocument = idCard.backSideDocument.flatMap(SecureIdVerificationDocument.init) + translationDocuments = idCard.translations.compactMap(SecureIdVerificationDocument.init) } frontSideRequired = true backSideRequired = true @@ -786,12 +946,13 @@ extension SecureIdDocumentFormState { documentState = SecureIdDocumentFormIdentityDocumentState(type: document, identifier: identifier, expiryDate: expiryDate) } let formState = SecureIdDocumentFormIdentityState(details: detailsState, document: documentState) - self.init(previousValues: previousValues, documentState: .identity(formState), documents: verificationDocuments, selfieRequired: selfie, selfieDocument: selfieDocument, frontSideRequired: frontSideRequired, frontSideDocument: frontSideDocument, backSideRequired: backSideRequired, backSideDocument: backSideDocument, actionState: .none) - case let .address(details, document): + self.init(previousValues: previousValues, documentState: .identity(formState), documents: verificationDocuments, selfieRequired: selfie, selfieDocument: selfieDocument, frontSideRequired: frontSideRequired, frontSideDocument: frontSideDocument, backSideRequired: backSideRequired, backSideDocument: backSideDocument, translationsRequired: translations, translations: translationDocuments, actionState: .none) + case let .address(details, document, translations): var previousValues: [SecureIdValueKey: SecureIdValueWithContext] = [:] var detailsState: SecureIdDocumentFormAddressDetailsState? var documentState: SecureIdRequestedAddressDocument? var verificationDocuments: [SecureIdVerificationDocument] = [] + var translationDocuments: [SecureIdVerificationDocument] = [] if details { if let value = values[.address], case let .address(address) = value.value { @@ -807,32 +968,37 @@ extension SecureIdDocumentFormState { if let value = values[.passportRegistration], case let .passportRegistration(passportRegistration) = value.value { previousValues[value.value.key] = value verificationDocuments = passportRegistration.verificationDocuments.compactMap(SecureIdVerificationDocument.init) + translationDocuments = passportRegistration.translations.compactMap(SecureIdVerificationDocument.init) } case .temporaryRegistration: if let value = values[.temporaryRegistration], case let .temporaryRegistration(temporaryRegistration) = value.value { previousValues[value.value.key] = value verificationDocuments = temporaryRegistration.verificationDocuments.compactMap(SecureIdVerificationDocument.init) + translationDocuments = temporaryRegistration.translations.compactMap(SecureIdVerificationDocument.init) } case .bankStatement: if let value = values[.bankStatement], case let .bankStatement(bankStatement) = value.value { previousValues[value.value.key] = value verificationDocuments = bankStatement.verificationDocuments.compactMap(SecureIdVerificationDocument.init) + translationDocuments = bankStatement.translations.compactMap(SecureIdVerificationDocument.init) } case .utilityBill: if let value = values[.utilityBill], case let .utilityBill(utilityBill) = value.value { previousValues[value.value.key] = value verificationDocuments = utilityBill.verificationDocuments.compactMap(SecureIdVerificationDocument.init) + translationDocuments = utilityBill.translations.compactMap(SecureIdVerificationDocument.init) } case .rentalAgreement: if let value = values[.rentalAgreement], case let .rentalAgreement(rentalAgreement) = value.value { previousValues[value.value.key] = value verificationDocuments = rentalAgreement.verificationDocuments.compactMap(SecureIdVerificationDocument.init) + translationDocuments = rentalAgreement.translations.compactMap(SecureIdVerificationDocument.init) } } documentState = document } let formState = SecureIdDocumentFormAddressState(details: detailsState, document: documentState) - self.init(previousValues: previousValues, documentState: .address(formState), documents: verificationDocuments, selfieRequired: false, selfieDocument: nil, frontSideRequired: false, frontSideDocument: nil, backSideRequired: false, backSideDocument: nil, actionState: .none) + self.init(previousValues: previousValues, documentState: .address(formState), documents: verificationDocuments, selfieRequired: false, selfieDocument: nil, frontSideRequired: false, frontSideDocument: nil, backSideRequired: false, backSideDocument: nil, translationsRequired: translations, translations: translationDocuments, actionState: .none) } } @@ -893,6 +1059,20 @@ extension SecureIdDocumentFormState { } } } + var translationDocuments: [SecureIdVerificationDocumentReference] = [] + for document in self.translations { + switch document { + case let .remote(file): + translationDocuments.append(.remote(file)) + case let .local(file): + switch file.state { + case let .uploaded(file): + translationDocuments.append(.uploaded(file)) + case .uploading: + return nil + } + } + } switch self.documentState { case let .identity(identity): @@ -916,7 +1096,7 @@ extension SecureIdDocumentFormState { guard let gender = details.gender else { return nil } - values[.personalDetails] = .personalDetails(SecureIdPersonalDetailsValue(firstName: details.firstName, lastName: details.lastName, birthdate: birthdate, countryCode: details.countryCode, residenceCountryCode: details.residenceCountryCode, gender: gender)) + values[.personalDetails] = .personalDetails(SecureIdPersonalDetailsValue(latinName: SecureIdPersonName(firstName: details.firstName, lastName: details.lastName, middleName: details.middleName), nativeName: SecureIdPersonName(firstName: details.nativeFirstName, lastName: details.nativeLastName, middleName: details.nativeMiddleName), birthdate: birthdate, countryCode: details.countryCode, residenceCountryCode: details.residenceCountryCode, gender: gender)) } if let document = identity.document { guard !document.identifier.isEmpty else { @@ -925,13 +1105,13 @@ extension SecureIdDocumentFormState { switch document.type { case .passport: - values[.passport] = .passport(SecureIdPassportValue(identifier: document.identifier, expiryDate: document.expiryDate, verificationDocuments: verificationDocuments, selfieDocument: selfieDocument, frontSideDocument: frontSideDocument)) + values[.passport] = .passport(SecureIdPassportValue(identifier: document.identifier, expiryDate: document.expiryDate, verificationDocuments: verificationDocuments, translations: translationDocuments, selfieDocument: selfieDocument, frontSideDocument: frontSideDocument)) case .internalPassport: - values[.internalPassport] = .internalPassport(SecureIdInternalPassportValue(identifier: document.identifier, expiryDate: document.expiryDate, verificationDocuments: verificationDocuments, selfieDocument: selfieDocument, frontSideDocument: frontSideDocument)) + values[.internalPassport] = .internalPassport(SecureIdInternalPassportValue(identifier: document.identifier, expiryDate: document.expiryDate, verificationDocuments: verificationDocuments, translations: translationDocuments, selfieDocument: selfieDocument, frontSideDocument: frontSideDocument)) case .driversLicense: - values[.driversLicense] = .driversLicense(SecureIdDriversLicenseValue(identifier: document.identifier, expiryDate: document.expiryDate, verificationDocuments: verificationDocuments, selfieDocument: selfieDocument, frontSideDocument: frontSideDocument, backSideDocument: backSideDocument)) + values[.driversLicense] = .driversLicense(SecureIdDriversLicenseValue(identifier: document.identifier, expiryDate: document.expiryDate, verificationDocuments: verificationDocuments, translations: translationDocuments, selfieDocument: selfieDocument, frontSideDocument: frontSideDocument, backSideDocument: backSideDocument)) case .idCard: - values[.idCard] = .idCard(SecureIdIDCardValue(identifier: document.identifier, expiryDate: document.expiryDate, verificationDocuments: verificationDocuments, selfieDocument: selfieDocument, frontSideDocument: frontSideDocument, backSideDocument: backSideDocument)) + values[.idCard] = .idCard(SecureIdIDCardValue(identifier: document.identifier, expiryDate: document.expiryDate, verificationDocuments: verificationDocuments, translations: translationDocuments, selfieDocument: selfieDocument, frontSideDocument: frontSideDocument, backSideDocument: backSideDocument)) } } return values @@ -955,15 +1135,15 @@ extension SecureIdDocumentFormState { if let document = address.document { switch document { case .passportRegistration: - values[.passportRegistration] = .passportRegistration(SecureIdPassportRegistrationValue(verificationDocuments: verificationDocuments)) + values[.passportRegistration] = .passportRegistration(SecureIdPassportRegistrationValue(verificationDocuments: verificationDocuments, translations: translationDocuments)) case .temporaryRegistration: - values[.temporaryRegistration] = .temporaryRegistration(SecureIdTemporaryRegistrationValue(verificationDocuments: verificationDocuments)) + values[.temporaryRegistration] = .temporaryRegistration(SecureIdTemporaryRegistrationValue(verificationDocuments: verificationDocuments, translations: translationDocuments)) case .bankStatement: - values[.bankStatement] = .bankStatement(SecureIdBankStatementValue(verificationDocuments: verificationDocuments)) + values[.bankStatement] = .bankStatement(SecureIdBankStatementValue(verificationDocuments: verificationDocuments, translations: translationDocuments)) case .utilityBill: - values[.utilityBill] = .utilityBill(SecureIdUtilityBillValue(verificationDocuments: verificationDocuments)) + values[.utilityBill] = .utilityBill(SecureIdUtilityBillValue(verificationDocuments: verificationDocuments, translations: translationDocuments)) case .rentalAgreement: - values[.rentalAgreement] = .rentalAgreement(SecureIdRentalAgreementValue(verificationDocuments: verificationDocuments)) + values[.rentalAgreement] = .rentalAgreement(SecureIdRentalAgreementValue(verificationDocuments: verificationDocuments, translations: translationDocuments)) } } return values @@ -979,7 +1159,13 @@ enum SecureIdDocumentFormEntryId: Hashable { case infoHeader case identifier case firstName + case middleName case lastName + case nativeInfoHeader + case nativeFirstName + case nativeMiddleName + case nativeLastName + case nativeInfo case gender case countryCode case residenceCountryCode @@ -991,6 +1177,10 @@ enum SecureIdDocumentFormEntryId: Hashable { case frontSide case backSide case documentsInfo + case translationsHeader + case translation(SecureIdVerificationDocumentId) + case addTranslation + case translationsInfo case street1 case street2 @@ -998,7 +1188,7 @@ enum SecureIdDocumentFormEntryId: Hashable { case state case postcode - case error + case error(SecureIdValueContentErrorKey) } enum SecureIdDocumentFormEntryCategory { @@ -1014,7 +1204,13 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { case infoHeader(SecureIdDocumentFormEntryCategory) case identifier(String, String?) case firstName(String, String?) + case middleName(String, String?) case lastName(String, String?) + case nativeInfoHeader(String) + case nativeFirstName(String, String?) + case nativeMiddleName(String, String?) + case nativeLastName(String, String?) + case nativeInfo(String) case gender(SecureIdGender?, String?) case countryCode(String, String?) case residenceCountryCode(String, String?) @@ -1026,7 +1222,11 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { case frontSide(Int, SecureIdVerificationDocument?, String?) case backSide(Int, SecureIdVerificationDocument?, String?) case documentsInfo - case error(Int, String) + case translationsHeader + case translation(Int, SecureIdVerificationDocument, String?) + case addTranslation(Bool) + case translationsInfo + case error(Int, String, SecureIdValueContentErrorKey) case street1(String, String?) case street2(String, String?) @@ -1050,8 +1250,20 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { return .identifier case .firstName: return .firstName + case .middleName: + return .middleName case .lastName: return .lastName + case .nativeInfoHeader: + return .nativeInfoHeader + case .nativeFirstName: + return .nativeFirstName + case .nativeMiddleName: + return .nativeMiddleName + case .nativeLastName: + return .nativeLastName + case .nativeInfo: + return .nativeInfo case .countryCode: return .countryCode case .residenceCountryCode: @@ -1084,8 +1296,16 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { return .backSide case .documentsInfo: return .documentsInfo - case .error: - return .error + case .translationsHeader: + return .translationsHeader + case let .translation(_, document, _): + return .translation(document.id) + case .addTranslation: + return .addTranslation + case .translationsInfo: + return .translationsInfo + case let .error(_, _, key): + return .error(key) } } @@ -1133,12 +1353,48 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { } else { return false } + case let .middleName(value, error): + if case .middleName(value, error) = to { + return true + } else { + return false + } case let .lastName(value, error): if case .lastName(value, error) = to { return true } else { return false } + case let .nativeInfoHeader(language): + if case .nativeInfoHeader(language) = to { + return true + } else { + return false + } + case let .nativeFirstName(value, error): + if case .nativeFirstName(value, error) = to { + return true + } else { + return false + } + case let .nativeMiddleName(value, error): + if case .nativeMiddleName(value, error) = to { + return true + } else { + return false + } + case let .nativeLastName(value, error): + if case .nativeLastName(value, error) = to { + return true + } else { + return false + } + case let .nativeInfo(language): + if case .nativeInfo(language) = to { + return true + } else { + return false + } case let .gender(value, error): if case .gender(value, error) = to { return true @@ -1235,8 +1491,32 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { } else { return false } - case let .error(index, text): - if case .error(index, text) = to { + case .translationsHeader: + if case .translationsHeader = to { + return true + } else { + return false + } + case let .translation(index, document, error): + if case .translation(index, document, error) = to { + return true + } else { + return false + } + case let .addTranslation(hasAny): + if case .addTranslation(hasAny) = to { + return true + } else{ + return false + } + case .translationsInfo: + if case .translationsInfo = to { + return true + } else { + return false + } + case let .error(index, text, key): + if case .error(index, text, key) = to { return true } else { return false @@ -1247,100 +1527,132 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { func item(params: SecureIdDocumentFormParams, strings: PresentationStrings) -> FormControllerItem { switch self { case .scansHeader: - return FormControllerHeaderItem(text: "SCANS") + return FormControllerHeaderItem(text: strings.Passport_Scans) case let .scan(index, document, error): - return SecureIdValueFormFileItem(account: params.account, context: params.context, document: document, placeholder: nil, title: "Scan \(index + 1)", label: error.flatMap(SecureIdValueFormFileItemLabel.error) ?? .timestamp, activated: { + return SecureIdValueFormFileItem(account: params.account, context: params.context, document: document, placeholder: nil, title: strings.Passport_Scans_ScanIndex("\(index + 1)").0, label: error.flatMap(SecureIdValueFormFileItemLabel.error) ?? .timestamp, activated: { params.openDocument(document) }) case let .addScan(hasAny): - return FormControllerActionItem(type: .accent, title: hasAny ? "Upload Additional Scan" : "Upload Scan", fullTopInset: true, activated: { + return FormControllerActionItem(type: .accent, title: hasAny ? strings.Passport_Scans_UploadNew : strings.Passport_Scans_Upload, fullTopInset: true, activated: { params.addFile(.scan) }) case let .scansInfo(type): let text: String switch type { case .identity: - text = "The document must contain your photograph, name, surname, date of birth, citizenship, document issue date and document number." + text = strings.Passport_Identity_ScansHelp case .address: - text = "The document must contain your first and last name, your residential address, a stamp / barcode / QR code / logo, and issue date, no more that 3 months ago." + text = strings.Passport_Address_ScansHelp } return FormControllerTextItem(text: text) case let .infoHeader(type): let text: String switch type { case .identity: - text = "DOCUMENT DETAILS" + text = strings.Passport_Identity_DocumentDetails case .address: - text = "ADDRESS" + text = strings.Passport_Address_Address } return FormControllerHeaderItem(text: text) case let .identifier(value, error): - return FormControllerTextInputItem(title: "Document #", text: value, placeholder: "Document Number", error: error, textUpdated: { text in + return FormControllerTextInputItem(title: strings.Passport_Identity_DocumentNumber, text: value, placeholder: strings.Passport_Identity_DocumentNumberPlaceholder, error: error, textUpdated: { text in params.updateText(.identifier, text) }) case let .firstName(value, error): - return FormControllerTextInputItem(title: "First Name", text: value, placeholder: "First Name", error: error, textUpdated: { text in + return FormControllerTextInputItem(title: strings.Passport_Identity_Name, text: value, placeholder: strings.Passport_Identity_NamePlaceholder, error: error, textUpdated: { text in params.updateText(.firstName, text) }) + case let .middleName(value, error): + return FormControllerTextInputItem(title: strings.Passport_Identity_MiddleName, text: value, placeholder: strings.Passport_Identity_MiddleNamePlaceholder, error: error, textUpdated: { text in + params.updateText(.middleName, text) + }) case let .lastName(value, error): - return FormControllerTextInputItem(title: "Last Name", text: value, placeholder: "Last Name", error: error, textUpdated: { text in + return FormControllerTextInputItem(title: strings.Passport_Identity_Surname, text: value, placeholder: strings.Passport_Identity_SurnamePlaceholder, error: error, textUpdated: { text in params.updateText(.lastName, text) }) + case let .nativeInfoHeader(language): + let title: String + if !language.isEmpty, let value = strings.dict["Passport.Language.\(language)"] { + title = strings.Passport_Identity_NativeNameTitle(value).0.uppercased() + } else { + title = strings.Passport_Identity_NativeNameGenericTitle + } + return FormControllerHeaderItem(text: title) + case let .nativeFirstName(value, error): + return FormControllerTextInputItem(title: strings.Passport_Identity_Name, text: value, placeholder: strings.Passport_Identity_NamePlaceholder, error: error, textUpdated: { text in + params.updateText(.nativeFirstName, text) + }) + case let .nativeMiddleName(value, error): + return FormControllerTextInputItem(title: strings.Passport_Identity_MiddleName, text: value, placeholder: strings.Passport_Identity_MiddleNamePlaceholder, error: error, textUpdated: { text in + params.updateText(.nativeMiddleName, text) + }) + case let .nativeLastName(value, error): + return FormControllerTextInputItem(title: strings.Passport_Identity_Surname, text: value, placeholder: strings.Passport_Identity_SurnamePlaceholder, error: error, textUpdated: { text in + params.updateText(.nativeLastName, text) + }) + case let .nativeInfo(language): + let text: String + if !language.isEmpty, let value = strings.dict["Passport.Language.\(language)"] { + text = strings.Passport_Identity_NativeNameGenericHelp(value).0.uppercased() + } else { + text = strings.Passport_Identity_NativeNameHelp + } + return FormControllerTextItem(text: text) case let .gender(value, error): var text = "" if let value = value { switch value { case .male: - text = "Male" + text = strings.Passport_Identity_GenderMale case .female: - text = "Female" + text = strings.Passport_Identity_GenderFemale } } - return FormControllerDetailActionItem(title: "Gender", text: text, placeholder: "Gender", error: error, activated: { + return FormControllerDetailActionItem(title: strings.Passport_Identity_Gender, text: text, placeholder: strings.Passport_Identity_GenderPlaceholder, error: error, activated: { params.activateSelection(.gender) }) case let .countryCode(value, error): - return FormControllerDetailActionItem(title: "Country", text: AuthorizationSequenceCountrySelectionController.lookupCountryNameById(value.uppercased(), strings: strings) ?? "", placeholder: "Country", error: error, activated: { + return FormControllerDetailActionItem(title: strings.Passport_Identity_Country, text: AuthorizationSequenceCountrySelectionController.lookupCountryNameById(value.uppercased(), strings: strings) ?? "", placeholder: strings.Passport_Identity_CountryPlaceholder, error: error, activated: { params.activateSelection(.country) }) case let .residenceCountryCode(value, error): - return FormControllerDetailActionItem(title: "Residence", text: AuthorizationSequenceCountrySelectionController.lookupCountryNameById(value.uppercased(), strings: strings) ?? "", placeholder: "Residence Country", error: error, activated: { + return FormControllerDetailActionItem(title: strings.Passport_Identity_ResidenceCountry, text: AuthorizationSequenceCountrySelectionController.lookupCountryNameById(value.uppercased(), strings: strings) ?? "", placeholder: strings.Passport_Identity_ResidenceCountryPlaceholder, error: error, activated: { params.activateSelection(.residenceCountry) }) case let .birthdate(value, error): - return FormControllerDetailActionItem(title: "Date of Birth", text: value.flatMap({ stringForDate(timestamp: $0.timestamp, strings: strings) }) ?? "", placeholder: "Date of Birth", error: error, activated: { + return FormControllerDetailActionItem(title: strings.Passport_Identity_DateOfBirth, text: value.flatMap({ stringForDate(timestamp: $0.timestamp, strings: strings) }) ?? "", placeholder: strings.Passport_Identity_DateOfBirthPlaceholder, error: error, activated: { params.activateSelection(.date(value?.timestamp, .birthdate)) }) case let .expiryDate(value, error): - return FormControllerDetailActionItem(title: "Expiry Date", text: value.flatMap({ stringForDate(timestamp: $0.timestamp, strings: strings) }) ?? "", placeholder: "Expiry Date", error: error, activated: { + return FormControllerDetailActionItem(title: strings.Passport_Identity_ExpiryDate, text: value.flatMap({ stringForDate(timestamp: $0.timestamp, strings: strings) }) ?? "", placeholder: strings.Passport_Identity_ExpiryDatePlaceholder, error: error, activated: { params.activateSelection(.date(value?.timestamp, .expiry)) }) case .deleteDocument: - return FormControllerActionItem(type: .destructive, title: "Delete Document", activated: { + return FormControllerActionItem(type: .destructive, title: strings.Passport_DeleteDocument, activated: { params.deleteValue() }) case let .street1(value, error): - return FormControllerTextInputItem(title: "Street", text: value, placeholder: "Street and number, P.O. box", error: error, textUpdated: { text in + return FormControllerTextInputItem(title: strings.Passport_Address_Street, text: value, placeholder: strings.Passport_Address_Street1Placeholder, error: error, textUpdated: { text in params.updateText(.street1, text) }) case let .street2(value, error): - return FormControllerTextInputItem(title: "", text: value, placeholder: "Apt., suite, unit, builting, block", error: error, textUpdated: { text in + return FormControllerTextInputItem(title: "", text: value, placeholder: strings.Passport_Address_Street2Placeholder, error: error, textUpdated: { text in params.updateText(.street2, text) }) case let .city(value, error): - return FormControllerTextInputItem(title: "City", text: value, placeholder: "City", error: error, textUpdated: { text in + return FormControllerTextInputItem(title: strings.Passport_Address_City, text: value, placeholder: strings.Passport_Address_CityPlaceholder, error: error, textUpdated: { text in params.updateText(.city, text) }) case let .state(value, error): - return FormControllerTextInputItem(title: "Region", text: value, placeholder: "State / Province / Region", error: error, textUpdated: { text in + return FormControllerTextInputItem(title: strings.Passport_Address_Region, text: value, placeholder: strings.Passport_Address_RegionPlaceholder, error: error, textUpdated: { text in params.updateText(.state, text) }) case let .postcode(value, error): - return FormControllerTextInputItem(title: "Postcode", text: value, placeholder: "Postcode", error: error, textUpdated: { text in + return FormControllerTextInputItem(title: strings.Passport_Address_Postcode, text: value, placeholder: strings.Passport_Address_PostcodePlaceholder, error: error, textUpdated: { text in params.updateText(.postcode, text) }) case .requestedDocumentsHeader: - return FormControllerHeaderItem(text: "REQUESTED FILES") + return FormControllerHeaderItem(text: strings.Passport_Identity_FilesTitle) case let .selfie(_, document, error): let label: SecureIdValueFormFileItemLabel if let error = error { @@ -1348,9 +1660,9 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { } else if document != nil { label = .timestamp } else { - label = .text("Upload a selfie of yourself holding document") + label = .text(strings.Passport_Identity_SelfieHelp) } - return SecureIdValueFormFileItem(account: params.account, context: params.context, document: document, placeholder: UIImage(bundleImageName: "Secure ID/DocumentInputSelfie"), title: "Selfie", label: label, activated: { + return SecureIdValueFormFileItem(account: params.account, context: params.context, document: document, placeholder: UIImage(bundleImageName: "Secure ID/DocumentInputSelfie"), title: strings.Passport_Identity_Selfie, label: label, activated: { if let document = document { params.openDocument(document) } else { @@ -1364,9 +1676,9 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { } else if document != nil { label = .timestamp } else { - label = .text("Upload a front side photo of a document") + label = .text(strings.Passport_Identity_FrontSideHelp) } - return SecureIdValueFormFileItem(account: params.account, context: params.context, document: document, placeholder: UIImage(bundleImageName: "Secure ID/PassportInputFrontSide"), title: "Front Side", label: label, activated: { + return SecureIdValueFormFileItem(account: params.account, context: params.context, document: document, placeholder: UIImage(bundleImageName: "Secure ID/PassportInputFrontSide"), title: strings.Passport_Identity_FrontSide, label: label, activated: { if let document = document { params.openDocument(document) } else { @@ -1380,9 +1692,9 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { } else if document != nil { label = .timestamp } else { - label = .text("Upload a reverse side photo of a document") + label = .text(strings.Passport_Identity_ReverseSideHelp) } - return SecureIdValueFormFileItem(account: params.account, context: params.context, document: document, placeholder: UIImage(bundleImageName: "Secure ID/DocumentInputBackSide"), title: "Reverse Side", label: label, activated: { + return SecureIdValueFormFileItem(account: params.account, context: params.context, document: document, placeholder: UIImage(bundleImageName: "Secure ID/DocumentInputBackSide"), title: strings.Passport_Identity_ReverseSide, label: label, activated: { if let document = document { params.openDocument(document) } else { @@ -1391,7 +1703,19 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { }) case .documentsInfo: return FormControllerTextItem(text: "") - case let .error(_, text): + case .translationsHeader: + return FormControllerHeaderItem(text: strings.Passport_Identity_Translations) + case let .translation(index, document, error): + return SecureIdValueFormFileItem(account: params.account, context: params.context, document: document, placeholder: nil, title: strings.Passport_Scans_ScanIndex("\(index + 1)").0, label: error.flatMap(SecureIdValueFormFileItemLabel.error) ?? .timestamp, activated: { + params.openDocument(document) + }) + case let .addTranslation(hasAny): + return FormControllerActionItem(type: .accent, title: hasAny ? strings.Passport_Scans_UploadNew : strings.Passport_Scans_Upload, fullTopInset: true, activated: { + params.addFile(.translation) + }) + case .translationsInfo: + return FormControllerTextItem(text: strings.Passport_Identity_TranslationHelp) + case let .error(_, text, _): return FormControllerTextItem(text: text, color: .error) } } @@ -1439,6 +1763,7 @@ final class SecureIdDocumentFormControllerNode: FormControllerNode deliverOnMainQueue).start(next: { [weak self] entry in + self.hiddenItemDisposable.set((galleryController.hiddenMedia + |> deliverOnMainQueue).start(next: { [weak self] entry in guard let strongSelf = self else { return } @@ -1970,6 +2342,7 @@ final class SecureIdDocumentFormControllerNode: FormControllerNode timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak self] _ in self?.didSetReady = true - print("ready") } self._ready.set(ready |> map { true }) } diff --git a/TelegramUI/SecureIdDocumentImageGalleryItem.swift b/TelegramUI/SecureIdDocumentImageGalleryItem.swift index dbc0971b52..fcca2f2491 100644 --- a/TelegramUI/SecureIdDocumentImageGalleryItem.swift +++ b/TelegramUI/SecureIdDocumentImageGalleryItem.swift @@ -31,7 +31,7 @@ class SecureIdDocumentGalleryItem: GalleryItem { node.setResource(context: self.context, resource: self.resource) - node._title.set(.single("\(self.location.position + 1) of \(self.location.totalCount)")) + node._title.set(.single("\(self.location.position + 1) \(self.strings.Common_of) \(self.location.totalCount)")) node.setCaption(self.caption) node.delete = self.delete @@ -41,7 +41,7 @@ class SecureIdDocumentGalleryItem: GalleryItem { func updateNode(node: GalleryItemNode) { if let node = node as? SecureIdDocumentGalleryItemNode { - node._title.set(.single("\(self.location.position + 1) of \(self.location.totalCount)")) + node._title.set(.single("\(self.location.position + 1) \(self.strings.Common_of) \(self.location.totalCount)")) node.setCaption(self.caption) node.delete = self.delete @@ -80,9 +80,6 @@ final class SecureIdDocumentGalleryItemNode: ZoomableContentGalleryItemNode { super.init() self.imageNode.imageUpdated = { [weak self] in - if self?.index == 1 { - print("image updated") - } self?._ready.set(.single(Void())) } @@ -115,9 +112,6 @@ final class SecureIdDocumentGalleryItemNode: ZoomableContentGalleryItemNode { if let strongSelf = self { if let size = value.0(), strongSelf.zoomableContent?.0 != size { strongSelf.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets()))() - if strongSelf.index == 1 { - print("1") - } strongSelf.zoomableContent = (size, strongSelf.imageNode) } } diff --git a/TelegramUI/SecureIdDocumentTypeSelectionController.swift b/TelegramUI/SecureIdDocumentTypeSelectionController.swift index 38b30fdd43..a2bf6500a3 100644 --- a/TelegramUI/SecureIdDocumentTypeSelectionController.swift +++ b/TelegramUI/SecureIdDocumentTypeSelectionController.swift @@ -4,30 +4,64 @@ import AsyncDisplayKit import SwiftSignalKit import TelegramCore +private func stringForDocumentType(_ type: SecureIdRequestedIdentityDocument, strings: PresentationStrings) -> String { + switch type { + case .passport: + return strings.Passport_Identity_TypePassport + case .internalPassport: + return strings.Passport_Identity_TypeInternalPassport + case .idCard: + return strings.Passport_Identity_TypeIdentityCard + case .driversLicense: + return strings.Passport_Identity_TypeDriversLicense + } +} + +private func stringForDocumentType(_ type: SecureIdRequestedAddressDocument, strings: PresentationStrings) -> String { + switch type { + case .rentalAgreement: + return strings.Passport_Address_TypeRentalAgreement + case .bankStatement: + return strings.Passport_Address_TypeBankStatement + case .passportRegistration: + return strings.Passport_Address_TypePassportRegistration + case .temporaryRegistration: + return strings.Passport_Address_TypeTemporaryRegistration + case .utilityBill: + return strings.Passport_Address_TypeUtilityBill + } +} + private func itemsForField(field: SecureIdParsedRequestedFormField, strings: PresentationStrings) -> [(String, SecureIdDocumentFormRequestedData)] { switch field { - case let .identity(personalDetails, document, selfie): + case let .identity(personalDetails, document, selfie, translation): var result: [(String, SecureIdDocumentFormRequestedData)] = [] - if document.contains(.passport) { - result.append(("Passport", .identity(details: personalDetails, document: .passport, selfie: selfie))) - } - if document.contains(.driversLicense) { - result.append(("Driver's License", .identity(details: personalDetails, document: .driversLicense, selfie: selfie))) - } - if document.contains(.idCard) { - result.append(("ID Card", .identity(details: personalDetails, document: .idCard, selfie: selfie))) + if let document = document { + switch document { + case let .just(type): + result.append((stringForDocumentType(type, strings: strings), .identity(details: personalDetails, document: type, selfie: selfie, translations: translation))) + case let .oneOf(types): + for type in types.sorted(by: { $0.rawValue < $1.rawValue }) { + result.append((stringForDocumentType(type, strings: strings), .identity(details: personalDetails, document: type, selfie: selfie, translations: translation))) + } + } + } else if let personalDetails = personalDetails { + result.append((strings.Passport_Identity_TypePersonalDetails, .identity(details: personalDetails, document: nil, selfie: false, translations: false))) } return result - case let .address(addressDetails, document): + case let .address(addressDetails, document, translations): var result: [(String, SecureIdDocumentFormRequestedData)] = [] - if document.contains(.utilityBill) { - result.append(("Utility Bill", .address(details: addressDetails, document: .utilityBill))) - } - if document.contains(.bankStatement) { - result.append(("Bank Statement", .address(details: addressDetails, document: .bankStatement))) - } - if document.contains(.rentalAgreement) { - result.append(("Rental Agreement", .address(details: addressDetails, document: .rentalAgreement))) + if let document = document { + switch document { + case let .just(type): + result.append((stringForDocumentType(type, strings: strings), .address(details: addressDetails, document: type, translations: translations))) + case let .oneOf(types): + for type in types.sorted(by: { $0.rawValue < $1.rawValue }) { + result.append((stringForDocumentType(type, strings: strings), .address(details: addressDetails, document: type, translations: translations))) + } + } + } else if addressDetails { + result.append((strings.Passport_Address_TypeResidentialAddress, .address(details: true, document: nil, translations: false))) } return result default: diff --git a/TelegramUI/SecureIdLocalResource.swift b/TelegramUI/SecureIdLocalResource.swift index e6168685ee..e5fb2d4c37 100644 --- a/TelegramUI/SecureIdLocalResource.swift +++ b/TelegramUI/SecureIdLocalResource.swift @@ -60,7 +60,7 @@ private final class Buffer { var data = Data() } -func fetchSecureIdLocalImageResource(postbox: Postbox, resource: SecureIdLocalImageResource) -> Signal { +func fetchSecureIdLocalImageResource(postbox: Postbox, resource: SecureIdLocalImageResource) -> Signal { return Signal { subscriber in guard let fetchResource = postbox.mediaBox.fetchResource else { return EmptyDisposable diff --git a/TelegramUI/SecureIdPlaintextFormController.swift b/TelegramUI/SecureIdPlaintextFormController.swift index b37917dcde..273a56b590 100644 --- a/TelegramUI/SecureIdPlaintextFormController.swift +++ b/TelegramUI/SecureIdPlaintextFormController.swift @@ -34,9 +34,9 @@ final class SecureIdPlaintextFormController: FormController UIImage? { +private func countryButtonBackground(color: UIColor, separatorColor: UIColor) -> UIImage? { return generateImage(CGSize(width: 45.0, height: 44.0 + 6.0), rotatedContext: { size, context in let arrowSize: CGFloat = 6.0 let lineWidth = UIScreenPixel context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(UIColor.white.cgColor) + context.setFillColor(color.cgColor) context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height - arrowSize))) context.move(to: CGPoint(x: size.width, y: size.height - arrowSize)) context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - arrowSize)) @@ -113,9 +113,16 @@ final class SecureIdValueFormPhoneItemNode: FormBlockItemNode (FormControllerItemPreLayout, (FormControllerItemLayoutParams) -> CGFloat) { if self.theme !== theme { - self.countryButton.setBackgroundImage(countryButtonBackground(separatorColor: theme.list.itemBlocksSeparatorColor), for: []) + self.countryButton.setBackgroundImage(countryButtonBackground(color: theme.list.itemBlocksBackgroundColor, separatorColor: theme.list.itemBlocksSeparatorColor), for: []) self.countryButton.setBackgroundImage(countryButtonHighlightedBackground(fillColor: theme.list.itemHighlightedBackgroundColor), for: .highlighted) self.theme = theme + + self.phoneInputNode.countryCodeField.textField.textColor = theme.list.itemPrimaryTextColor + self.phoneInputNode.numberField.textField.textColor = theme.list.itemPrimaryTextColor + + self.phoneInputNode.countryCodeField.textField.keyboardAppearance = theme.chatList.searchBarKeyboardColor.keyboardAppearance + + self.phoneInputNode.numberField.textField.keyboardAppearance = theme.chatList.searchBarKeyboardColor.keyboardAppearance } self.item = item diff --git a/TelegramUI/SettingsController.swift b/TelegramUI/SettingsController.swift index ee6fcadeb8..0bae4bc1aa 100644 --- a/TelegramUI/SettingsController.swift +++ b/TelegramUI/SettingsController.swift @@ -476,7 +476,7 @@ public func settingsController(account: Account, accountManager: AccountManager) }, openRecentCalls: { pushControllerImpl?(CallListController(account: account, mode: .navigation)) }, openPrivacyAndSecurity: { - pushControllerImpl?(privacyAndSecurityController(account: account, initialSettings: .single(nil) |> then(requestAccountPrivacySettings(account: account) |> map { Optional($0) }))) + pushControllerImpl?(privacyAndSecurityController(account: account, initialSettings: .single(nil) |> then(requestAccountPrivacySettings(account: account) |> map(Optional.init)))) }, openDataAndStorage: { pushControllerImpl?(dataAndStorageController(account: account)) }, openThemes: { diff --git a/TelegramUI/ShareController.swift b/TelegramUI/ShareController.swift index b26c592fba..5cdfc955a3 100644 --- a/TelegramUI/ShareController.swift +++ b/TelegramUI/ShareController.swift @@ -12,6 +12,7 @@ public struct ShareControllerAction { public enum ShareControllerExternalStatus { case preparing + case progress(Float) case done } @@ -349,12 +350,19 @@ public final class ShareController: ViewController { } let _ = enqueueMessages(account: strongSelf.account, peerId: peerId, messages: messagesToEnqueue).start() } - return .complete() + return .single(.done) case let .fromExternal(f): return f(peerIds, text) - |> mapToSignal { _ -> Signal in - return .complete() + |> map { state -> ShareState in + switch state { + case .preparing: + return .preparing + case let .progress(value): + return .progress(value) + case .done: + return .done } + } } } return .complete() diff --git a/TelegramUI/ShareControllerNode.swift b/TelegramUI/ShareControllerNode.swift index 35281e123d..fda7f710a7 100644 --- a/TelegramUI/ShareControllerNode.swift +++ b/TelegramUI/ShareControllerNode.swift @@ -5,6 +5,12 @@ import SwiftSignalKit import Postbox import TelegramCore +enum ShareState { + case preparing + case progress(Float) + case done +} + enum ShareExternalState { case preparing case done @@ -40,7 +46,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate var dismiss: (() -> Void)? var cancel: (() -> Void)? - var share: ((String, [PeerId]) -> Signal)? + var share: ((String, [PeerId]) -> Signal)? var shareExternal: (() -> Signal)? let ready = Promise() @@ -55,6 +61,8 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate private let shareDisposable = MetaDisposable() + private var hapticFeedback: HapticFeedback? + init(account: Account, defaultAction: ShareControllerAction?, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, externalShare: Bool, immediateExternalShare: Bool) { self.account = account self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } @@ -403,14 +411,42 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate if let signal = self.share?(self.inputFieldNode.text, self.controllerInteraction!.selectedPeers.map { $0.id }) { self.transitionToContentNode(ShareLoadingContainerNode(theme: self.presentationData.theme), fastOut: true) let timestamp = CACurrentMediaTime() - self.shareDisposable.set(signal.start(completed: { [weak self] in - let minDelay = 0.6 - let delay = max(0.0, (timestamp + minDelay) - CACurrentMediaTime()) + var wasDone = false + let doneImpl: (Bool) -> Void = { [weak self] shouldDelay in + let minDelay: Double = shouldDelay ? 0.9 : 0.6 + let delay = max(minDelay, (timestamp + minDelay) - CACurrentMediaTime()) Queue.mainQueue().after(delay, { if let strongSelf = self { strongSelf.cancel?() } }) + } + self.shareDisposable.set((signal + |> deliverOnMainQueue).start(next: { [weak self] status in + guard let strongSelf = self, let contentNode = strongSelf.contentNode as? ShareLoadingContainerNode else { + return + } + switch status { + case .preparing: + contentNode.state = .preparing + case let .progress(value): + contentNode.state = .progress(value) + case .done: + contentNode.state = .done + if !wasDone { + if strongSelf.hapticFeedback == nil { + strongSelf.hapticFeedback = HapticFeedback() + } + strongSelf.hapticFeedback?.success() + + wasDone = true + doneImpl(true) + } + } + }, completed: { + if !wasDone { + doneImpl(false) + } })) } } diff --git a/TelegramUI/ShareLoadingContainerNode.swift b/TelegramUI/ShareLoadingContainerNode.swift index 827cedc567..a99fdbe921 100644 --- a/TelegramUI/ShareLoadingContainerNode.swift +++ b/TelegramUI/ShareLoadingContainerNode.swift @@ -3,17 +3,51 @@ import AsyncDisplayKit import Display import Postbox +enum ShareLoadingState { + case preparing + case progress(Float) + case done +} + final class ShareLoadingContainerNode: ASDisplayNode, ShareContentContainerNode { private var contentOffsetUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)? + private let theme: PresentationTheme private let activityIndicator: ActivityIndicator + private let statusNode: RadialStatusNode + private let doneStatusNode: RadialStatusNode + + var state: ShareLoadingState = .preparing { + didSet { + switch self.state { + case .preparing: + self.activityIndicator.isHidden = false + self.statusNode.isHidden = true + case let .progress(value): + self.activityIndicator.isHidden = true + self.statusNode.isHidden = false + self.statusNode.transitionToState(.progress(color: self.theme.actionSheet.controlAccentColor, lineWidth: 2.0, value: max(0.12, CGFloat(value)), cancelEnabled: false), completion: {}) + case .done: + self.activityIndicator.isHidden = true + self.statusNode.isHidden = false + self.statusNode.transitionToState(.progress(color: self.theme.actionSheet.controlAccentColor, lineWidth: 2.0, value: 1.0, cancelEnabled: false), completion: {}) + self.doneStatusNode.transitionToState(.check(self.theme.actionSheet.controlAccentColor), completion: {}) + } + } + } init(theme: PresentationTheme) { + self.theme = theme self.activityIndicator = ActivityIndicator(type: ActivityIndicatorType.custom(theme.actionSheet.controlAccentColor, 50.0, 2.0)) + self.statusNode = RadialStatusNode(backgroundNodeColor: .clear) + self.doneStatusNode = RadialStatusNode(backgroundNodeColor: .clear) super.init() self.addSubnode(self.activityIndicator) + self.addSubnode(self.statusNode) + self.addSubnode(self.doneStatusNode) + self.doneStatusNode.transitionToState(.progress(color: self.theme.actionSheet.controlAccentColor, lineWidth: 2.0, value: 0.0, cancelEnabled: false), completion: {}) } func activate() { @@ -33,7 +67,11 @@ final class ShareLoadingContainerNode: ASDisplayNode, ShareContentContainerNode let nodeHeight: CGFloat = 125.0 let indicatorSize = self.activityIndicator.calculateSizeThatFits(size) - transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: floor((size.width - indicatorSize.width) / 2.0), y: size.height - nodeHeight + floor((nodeHeight - indicatorSize.height) / 2.0)), size: indicatorSize)) + let indicatorFrame = CGRect(origin: CGPoint(x: floor((size.width - indicatorSize.width) / 2.0), y: size.height - nodeHeight + floor((nodeHeight - indicatorSize.height) / 2.0)), size: indicatorSize) + transition.updateFrame(node: self.activityIndicator, frame: indicatorFrame) + let statusFrame = indicatorFrame + transition.updateFrame(node: self.statusNode, frame: statusFrame) + transition.updateFrame(node: self.doneStatusNode, frame: statusFrame) self.contentOffsetUpdated?(-size.height + 64.0, transition) } diff --git a/TelegramUI/SharedMediaPlayer.swift b/TelegramUI/SharedMediaPlayer.swift index ad7b64dd9c..4b4cea280c 100644 --- a/TelegramUI/SharedMediaPlayer.swift +++ b/TelegramUI/SharedMediaPlayer.swift @@ -18,6 +18,7 @@ enum SharedMediaPlayerControlAction { case seek(Double) case setOrder(MusicPlaybackSettingsOrder) case setLooping(MusicPlaybackSettingsLooping) + case setBaseRate(AudioPlaybackRate) } enum SharedMediaPlaylistControlAction { @@ -38,7 +39,7 @@ enum SharedMediaPlaybackDataSource: Equatable { switch lhs { case let .telegramFile(lhsFileReference): if case let .telegramFile(rhsFileReference) = rhs { - if !lhsFileReference.media.isEqual(rhsFileReference.media) { + if !lhsFileReference.media.isEqual(to: rhsFileReference.media) { return false } return true @@ -268,7 +269,7 @@ private enum SharedMediaPlaybackItem: Equatable { if let status = status { return status } else { - return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, seekId: 0, status: .paused) + return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused) } } } @@ -363,6 +364,8 @@ final class SharedMediaPlayer { private let playerIndex: Int32 private let playlist: SharedMediaPlaylist + private var playbackRate: AudioPlaybackRate + private var proximityManagerIndex: Int? private let controlPlaybackWithProximity: Bool private var forceAudioToSpeaker = false @@ -393,7 +396,7 @@ final class SharedMediaPlayer { private var inForegroundDisposable: Disposable? - init(mediaManager: MediaManager, inForeground: Signal, postbox: Postbox, audioSession: ManagedAudioSession, overlayMediaManager: OverlayMediaManager, playlist: SharedMediaPlaylist, initialOrder: MusicPlaybackSettingsOrder, initialLooping: MusicPlaybackSettingsLooping, playerIndex: Int32, controlPlaybackWithProximity: Bool) { + init(mediaManager: MediaManager, inForeground: Signal, postbox: Postbox, audioSession: ManagedAudioSession, overlayMediaManager: OverlayMediaManager, playlist: SharedMediaPlaylist, initialOrder: MusicPlaybackSettingsOrder, initialLooping: MusicPlaybackSettingsLooping, initialPlaybackRate: AudioPlaybackRate, playerIndex: Int32, controlPlaybackWithProximity: Bool) { self.mediaManager = mediaManager self.postbox = postbox self.audioSession = audioSession @@ -402,6 +405,7 @@ final class SharedMediaPlayer { playlist.setLooping(initialLooping) self.playlist = playlist self.playerIndex = playerIndex + self.playbackRate = initialPlaybackRate self.controlPlaybackWithProximity = controlPlaybackWithProximity if controlPlaybackWithProximity { @@ -423,21 +427,34 @@ final class SharedMediaPlayer { } strongSelf.playbackItem = nil if let item = state.item, let playbackData = item.playbackData { + let rateValue: Double + if case .music = playbackData.type { + rateValue = 1.0 + } else { + switch strongSelf.playbackRate { + case .x1: + rateValue = 1.0 + case .x2: + rateValue = 1.8 + } + } + switch playbackData.type { case .voice, .music: switch playbackData.source { case let .telegramFile(fileReference): - strongSelf.playbackItem = .audio(MediaPlayer(audioSessionManager: strongSelf.audioSession, postbox: strongSelf.postbox, resourceReference: fileReference.resourceReference(fileReference.media.resource), streamable: playbackData.type == .music, video: false, preferSoftwareDecoding: false, enableSound: true, fetchAutomatically: true, playAndRecord: controlPlaybackWithProximity)) + strongSelf.playbackItem = .audio(MediaPlayer(audioSessionManager: strongSelf.audioSession, postbox: strongSelf.postbox, resourceReference: fileReference.resourceReference(fileReference.media.resource), streamable: playbackData.type == .music, video: false, preferSoftwareDecoding: false, enableSound: true, baseRate: rateValue, fetchAutomatically: true, playAndRecord: controlPlaybackWithProximity)) } case .instantVideo: if let mediaManager = strongSelf.mediaManager, let item = item as? MessageMediaPlaylistItem { switch playbackData.source { case let .telegramFile(fileReference): - let videoNode = OverlayInstantVideoNode(postbox: strongSelf.postbox, audioSession: strongSelf.audioSession, manager: mediaManager.universalVideoManager, content: NativeVideoContent(id: .message(item.message.id, item.message.stableId, fileReference.media.fileId), fileReference: fileReference, streamVideo: false, enableSound: false), close: { [weak mediaManager] in + let videoNode = OverlayInstantVideoNode(postbox: strongSelf.postbox, audioSession: strongSelf.audioSession, manager: mediaManager.universalVideoManager, content: NativeVideoContent(id: .message(item.message.id, item.message.stableId, fileReference.media.fileId), fileReference: fileReference, streamVideo: false, enableSound: false, baseRate: rateValue), close: { [weak mediaManager] in mediaManager?.setPlaylist(nil, type: .voice) }) strongSelf.playbackItem = .instantVideo(videoNode) videoNode.setSoundEnabled(true) + videoNode.setBaseRate(rateValue) } } } @@ -606,6 +623,24 @@ final class SharedMediaPlayer { self.playlist.setOrder(order) case let .setLooping(looping): self.playlist.setLooping(looping) + case let .setBaseRate(baseRate): + self.playbackRate = baseRate + if let playbackItem = self.playbackItem { + let rateValue: Double + switch baseRate { + case .x1: + rateValue = 1.0 + case .x2: + rateValue = 1.8 + } + switch playbackItem { + case let .audio(player): + player.setBaseRate(rateValue) + + case let .instantVideo(node): + node.setBaseRate(rateValue) + } + } } } diff --git a/TelegramUI/SoftwareVideoThumbnailLayer.swift b/TelegramUI/SoftwareVideoThumbnailLayer.swift index e3c038ffc1..b345a0868d 100644 --- a/TelegramUI/SoftwareVideoThumbnailLayer.swift +++ b/TelegramUI/SoftwareVideoThumbnailLayer.swift @@ -18,7 +18,7 @@ final class SoftwareVideoThumbnailLayer: CALayer { init(account: Account, fileReference: FileMediaReference) { super.init() - self.backgroundColor = UIColor.white.cgColor + self.backgroundColor = UIColor.black.cgColor self.contentsGravity = "resizeAspectFill" self.masksToBounds = true diff --git a/TelegramUI/StickerPanePeerSpecificSetupGridItem.swift b/TelegramUI/StickerPanePeerSpecificSetupGridItem.swift new file mode 100644 index 0000000000..6dbccbf7e7 --- /dev/null +++ b/TelegramUI/StickerPanePeerSpecificSetupGridItem.swift @@ -0,0 +1,189 @@ +import Foundation +import Display +import AsyncDisplayKit +import TelegramCore +import SwiftSignalKit +import Postbox + +final class StickerPanePeerSpecificSetupGridItem: GridItem { + let theme: PresentationTheme + let strings: PresentationStrings + let setup: () -> Void + let dismiss: (() -> Void)? + + let section: GridSection? = nil + let fillsRowWithDynamicHeight: ((CGFloat) -> CGFloat)? + + init(theme: PresentationTheme, strings: PresentationStrings, setup: @escaping () -> Void, dismiss: (() -> Void)?) { + self.theme = theme + self.strings = strings + self.setup = setup + self.dismiss = dismiss + self.fillsRowWithDynamicHeight = { width in + let makeDescriptionLayout = TextNode.asyncLayout(nil) + let params = ListViewItemLayoutParams(width: width, leftInset: 0.0, rightInset: 0.0) + let leftInset: CGFloat = 12.0 + let rightInset: CGFloat = 16.0 + let (descriptionLayout, _) = makeDescriptionLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: strings.Stickers_GroupStickersHelp, font: statusFont, textColor: .black), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + return 67.0 + descriptionLayout.size.height + } + } + + func node(layout: GridNodeLayout) -> GridItemNode { + let node = StickerPanePeerSpecificSetupGridItemNode() + node.setup(item: self) + return node + } + + func update(node: GridItemNode) { + guard let node = node as? StickerPanePeerSpecificSetupGridItemNode else { + assertionFailure() + return + } + node.setup(item: self) + } +} + +private let titleFont = Font.medium(12.0) +private let statusFont = Font.regular(15.0) +private let buttonFont = Font.medium(13.0) + +class StickerPanePeerSpecificSetupGridItemNode: GridItemNode { + private let titleNode: TextNode + private let descriptionNode: TextNode + private let installTextNode: TextNode + private let installBackgroundNode: ASImageNode + private let installButtonNode: HighlightTrackingButtonNode + private let dismissButtonNode: HighlightTrackingButtonNode + + private var item: StickerPanePeerSpecificSetupGridItem? + private var appliedItem: StickerPanePeerSpecificSetupGridItem? + + override init() { + self.titleNode = TextNode() + self.titleNode.isLayerBacked = true + self.titleNode.contentMode = .left + self.titleNode.contentsScale = UIScreen.main.scale + + self.descriptionNode = TextNode() + self.descriptionNode.isLayerBacked = true + self.descriptionNode.contentMode = .left + self.descriptionNode.contentsScale = UIScreen.main.scale + + self.installTextNode = TextNode() + self.installTextNode.isLayerBacked = true + self.installTextNode.contentMode = .left + self.installTextNode.contentsScale = UIScreen.main.scale + + self.installBackgroundNode = ASImageNode() + self.installBackgroundNode.isLayerBacked = true + self.installBackgroundNode.displayWithoutProcessing = true + self.installBackgroundNode.displaysAsynchronously = false + + self.installButtonNode = HighlightTrackingButtonNode() + self.dismissButtonNode = HighlightTrackingButtonNode() + + super.init() + + self.addSubnode(self.titleNode) + self.addSubnode(self.descriptionNode) + self.addSubnode(self.installBackgroundNode) + self.addSubnode(self.installTextNode) + self.addSubnode(self.installButtonNode) + self.addSubnode(self.dismissButtonNode) + + self.installButtonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.installBackgroundNode.layer.removeAnimation(forKey: "opacity") + strongSelf.installBackgroundNode.alpha = 0.4 + strongSelf.installTextNode.layer.removeAnimation(forKey: "opacity") + strongSelf.installTextNode.alpha = 0.4 + } else { + strongSelf.installBackgroundNode.alpha = 1.0 + strongSelf.installBackgroundNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.installTextNode.alpha = 1.0 + strongSelf.installTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + + self.installButtonNode.addTarget(self, action: #selector(self.installPressed), forControlEvents: .touchUpInside) + + self.dismissButtonNode.addTarget(self, action: #selector(self.dismissPressed), forControlEvents: .touchUpInside) + } + + func setup(item: StickerPanePeerSpecificSetupGridItem) { + self.item = item + self.setNeedsLayout() + } + + override func layout() { + super.layout() + guard let item = self.item else { + return + } + + let params = ListViewItemLayoutParams(width: self.bounds.size.width, leftInset: 0.0, rightInset: 0.0) + + let makeInstallLayout = TextNode.asyncLayout(self.installTextNode) + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode) + + let currentItem = self.appliedItem + self.appliedItem = item + + var updateButtonBackgroundImage: UIImage? + if currentItem?.theme !== item.theme { + updateButtonBackgroundImage = PresentationResourcesChat.chatInputMediaPanelAddPackButtonImage(item.theme) + self.dismissButtonNode.setImage(PresentationResourcesChat.chatInputMediaPanelGridDismissImage(item.theme), for: []) + } + + let leftInset: CGFloat = 12.0 + let rightInset: CGFloat = 16.0 + let topOffset: CGFloat = 9.0 + let textSpacing: CGFloat = 2.0 + let buttonSpacing: CGFloat = 3.0 + + let (installLayout, installApply) = makeInstallLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_GroupChooseStickerPack, font: buttonFont, textColor: item.theme.list.itemAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_GroupStickers.uppercased(), font: titleFont, textColor: item.theme.chat.inputMediaPanel.stickersSectionTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0 - installLayout.size.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let (descriptionLayout, descriptionApply) = makeDescriptionLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_GroupStickersHelp, font: statusFont, textColor: item.theme.chat.inputMediaPanel.stickersSectionTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + self.item = item + + let _ = installApply() + let _ = titleApply() + let _ = descriptionApply() + + if let updateButtonBackgroundImage = updateButtonBackgroundImage { + self.installBackgroundNode.image = updateButtonBackgroundImage + } + + let installWidth: CGFloat = installLayout.size.width + 20.0 + let buttonFrame = CGRect(origin: CGPoint(x: params.leftInset + leftInset, y: topOffset + titleLayout.size.height + textSpacing + descriptionLayout.size.height + buttonSpacing), size: CGSize(width: installWidth, height: 26.0)) + self.installBackgroundNode.frame = buttonFrame + self.installTextNode.frame = CGRect(origin: CGPoint(x: buttonFrame.minX + floor((buttonFrame.width - installLayout.size.width) / 2.0), y: buttonFrame.minY + floor((buttonFrame.height - installLayout.size.height) / 2.0)), size: installLayout.size) + self.installButtonNode.frame = buttonFrame + + let titleFrame = CGRect(origin: CGPoint(x: params.leftInset + leftInset, y: topOffset), size: titleLayout.size) + let dismissButtonSize = CGSize(width: 12.0, height: 12.0) + self.dismissButtonNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - rightInset - dismissButtonSize.width, y: topOffset - 1.0), size: dismissButtonSize) + self.dismissButtonNode.isHidden = item.dismiss == nil + self.titleNode.frame = titleFrame + self.descriptionNode.frame = CGRect(origin: CGPoint(x: params.leftInset + leftInset, y: topOffset + titleLayout.size.height + 1.0), size: descriptionLayout.size) + } + + @objc private func installPressed() { + if let item = self.item { + item.setup() + } + } + + @objc private func dismissPressed() { + if let item = self.item { + item.dismiss?() + } + } +} diff --git a/TelegramUI/StickerPaneSearchContainerNode.swift b/TelegramUI/StickerPaneSearchContainerNode.swift index 494ed28ce6..da93e0ac37 100644 --- a/TelegramUI/StickerPaneSearchContainerNode.swift +++ b/TelegramUI/StickerPaneSearchContainerNode.swift @@ -386,7 +386,7 @@ final class StickerPaneSearchContainerNode: ASDisplayNode { self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: contentFrame.size, insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0 + bottomInset, right: 0.0), preloadSize: 300.0, type: .fixed(itemSize: CGSize(width: 75.0, height: 75.0), lineSpacing: 0.0)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) transition.updateFrame(node: self.trendingPane, frame: contentFrame) - self.trendingPane.updateLayout(size: contentFrame.size, topInset: 0.0, bottomInset: bottomInset, transition: transition) + self.trendingPane.updateLayout(size: contentFrame.size, topInset: 0.0, bottomInset: bottomInset, isExpanded: false, transition: transition) transition.updateFrame(node: self.gridNode, frame: contentFrame) if firstLayout { @@ -434,6 +434,8 @@ final class StickerPaneSearchContainerNode: ASDisplayNode { self.searchBar.transitionOut(to: placeholder, transition: transition, completion: { completion() }) + transition.updateAlpha(node: self.searchBar, alpha: 0.0, completion: { _ in + }) transition.updateAlpha(node: self.backgroundNode, alpha: 0.0, completion: { _ in }) transition.updateAlpha(node: self.gridNode, alpha: 0.0, completion: { _ in diff --git a/TelegramUI/StickerSettings.swift b/TelegramUI/StickerSettings.swift new file mode 100644 index 0000000000..f05007a00b --- /dev/null +++ b/TelegramUI/StickerSettings.swift @@ -0,0 +1,59 @@ +import Foundation +import Postbox +import SwiftSignalKit + +public enum EmojiStickerSuggestionMode: Int32 { + case none + case all + case installed +} + +public struct StickerSettings: PreferencesEntry, Equatable { + public var emojiStickerSuggestionMode: EmojiStickerSuggestionMode + + public static var defaultSettings: StickerSettings { + return StickerSettings(emojiStickerSuggestionMode: .all) + } + + init(emojiStickerSuggestionMode: EmojiStickerSuggestionMode) { + self.emojiStickerSuggestionMode = emojiStickerSuggestionMode + } + + public init(decoder: PostboxDecoder) { + self.emojiStickerSuggestionMode = EmojiStickerSuggestionMode(rawValue: decoder.decodeInt32ForKey("emojiStickerSuggestionMode", orElse: 0))! + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.emojiStickerSuggestionMode.rawValue, forKey: "emojiStickerSuggestionMode") + } + + public func isEqual(to: PreferencesEntry) -> Bool { + if let to = to as? StickerSettings { + return self == to + } else { + return false + } + } + + public static func ==(lhs: StickerSettings, rhs: StickerSettings) -> Bool { + return lhs.emojiStickerSuggestionMode == rhs.emojiStickerSuggestionMode + } + + func withUpdatedEmojiStickerSuggestionMode(_ emojiStickerSuggestionMode: EmojiStickerSuggestionMode) -> StickerSettings { + return StickerSettings(emojiStickerSuggestionMode: emojiStickerSuggestionMode) + } +} + +func updateStickerSettingsInteractively(postbox: Postbox, _ f: @escaping (StickerSettings) -> StickerSettings) -> Signal { + return postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.stickerSettings, { entry in + let currentSettings: StickerSettings + if let entry = entry as? StickerSettings { + currentSettings = entry + } else { + currentSettings = StickerSettings.defaultSettings + } + return f(currentSettings) + }) + } +} diff --git a/TelegramUI/StorageUsageController.swift b/TelegramUI/StorageUsageController.swift index 28b7a0a2e2..ccd0549802 100644 --- a/TelegramUI/StorageUsageController.swift +++ b/TelegramUI/StorageUsageController.swift @@ -233,7 +233,7 @@ func storageUsageController(account: Account) -> ViewController { var presentControllerImpl: ((ViewController) -> Void)? let statsPromise = Promise() - statsPromise.set(.single(nil) |> then(collectCacheUsageStats(account: account) |> map { Optional($0) })) + statsPromise.set(.single(nil) |> then(collectCacheUsageStats(account: account) |> map(Optional.init))) let actionDisposables = DisposableSet() diff --git a/TelegramUI/StringWithAppliedEntities.swift b/TelegramUI/StringWithAppliedEntities.swift index a777289b2e..3b6f6ef4aa 100644 --- a/TelegramUI/StringWithAppliedEntities.swift +++ b/TelegramUI/StringWithAppliedEntities.swift @@ -69,7 +69,7 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba if nsString == nil { nsString = text as NSString } - string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.Url), value: nsString!.substring(with: range), range: range) + string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.URL), value: nsString!.substring(with: range), range: range) case .Email: string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range) if nsString == nil { @@ -78,7 +78,7 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba if underlineAllLinks { string.addAttribute(NSAttributedStringKey.underlineStyle, value: NSUnderlineStyle.styleSingle.rawValue as NSNumber, range: range) } - string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.Url), value: "mailto:\(nsString!.substring(with: range))", range: range) + string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.URL), value: "mailto:\(nsString!.substring(with: range))", range: range) case .PhoneNumber: string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range) if nsString == nil { @@ -87,7 +87,7 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba if underlineAllLinks { string.addAttribute(NSAttributedStringKey.underlineStyle, value: NSUnderlineStyle.styleSingle.rawValue as NSNumber, range: range) } - string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.Url), value: "tel:\(nsString!.substring(with: range))", range: range) + string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.URL), value: "tel:\(nsString!.substring(with: range))", range: range) case let .TextUrl(url): string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range) if nsString == nil { @@ -96,7 +96,7 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba if underlineAllLinks { string.addAttribute(NSAttributedStringKey.underlineStyle, value: NSUnderlineStyle.styleSingle.rawValue as NSNumber, range: range) } - string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.Url), value: url, range: range) + string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.URL), value: url, range: range) case .Bold: string.addAttribute(NSAttributedStringKey.font, value: boldFont, range: range) case .Italic: diff --git a/TelegramUI/SystemVideoContent.swift b/TelegramUI/SystemVideoContent.swift index 39cdbd5324..24da679807 100644 --- a/TelegramUI/SystemVideoContent.swift +++ b/TelegramUI/SystemVideoContent.swift @@ -39,7 +39,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent private let playbackCompletedListeners = Bag<() -> Void>() private var initializedStatus = false - private var statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, seekId: 0, status: .paused) + private var statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused) private var isBuffering = false private let _status = ValuePromise() var status: Signal { @@ -145,7 +145,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent } else { status = isPlaying ? .playing : .paused } - self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, seekId: 0, status: status) + self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: status) self._status.set(self.statusValue) } else if keyPath == "playbackBufferEmpty" { let isPlaying = !self.player.rate.isZero @@ -156,7 +156,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent } else { status = isPlaying ? .playing : .paused } - self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, seekId: 0, status: status) + self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: status) self._status.set(self.statusValue) } else if keyPath == "playbackLikelyToKeepUp" || keyPath == "playbackBufferFull" { let isPlaying = !self.player.rate.isZero @@ -167,7 +167,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent } else { status = isPlaying ? .playing : .paused } - self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, seekId: 0, status: status) + self.statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: status) self._status.set(self.statusValue) } } @@ -186,7 +186,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent func play() { assert(Queue.mainQueue().isCurrent()) if !self.initializedStatus { - self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, seekId: 0, status: .buffering(initial: true, whilePlaying: true))) + self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .buffering(initial: true, whilePlaying: true))) } if !self.hasAudioSession { self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play, activate: { [weak self] _ in @@ -205,7 +205,7 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent func pause() { assert(Queue.mainQueue().isCurrent()) if !self.initializedStatus { - self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, seekId: 0, status: .paused)) + self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused)) } self.player.pause() } @@ -237,6 +237,9 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent func continuePlayingWithoutSound() { } + func setBaseRate(_ baseRate: Double) { + } + func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int { return self.playbackCompletedListeners.add(f) } diff --git a/TelegramUI/TGChannelIntroController.h b/TelegramUI/TGChannelIntroController.h new file mode 100644 index 0000000000..f93aee2615 --- /dev/null +++ b/TelegramUI/TGChannelIntroController.h @@ -0,0 +1,20 @@ +#import + +@interface TGChannelIntroControllerTheme : NSObject + +@property (nonatomic, strong, readonly) UIColor *backgroundColor; +@property (nonatomic, strong, readonly) UIColor *primaryColor; +@property (nonatomic, strong, readonly) UIColor *secondaryColor; +@property (nonatomic, strong, readonly) UIColor *accentColor; +@property (nonatomic, strong, readonly) UIImage *backArrowImage; +@property (nonatomic, strong, readonly) UIImage *introImage; + +- (instancetype)initWithBackgroundColor:(UIColor *)backgroundColor primaryColor:(UIColor *)primaryColor secondaryColor:(UIColor *)secondaryColor accentColor:(UIColor *)accentColor backArrowImage:(UIImage *)backArrowImage introImage:(UIImage *)introImage; + +@end + +@interface TGChannelIntroController : TGViewController + +- (instancetype)initWithContext:(id)context getLocalizedString:(NSString *(^)(NSString *))getLocalizedString theme:(TGChannelIntroControllerTheme *)theme completion:(void (^)(void))completion; + +@end diff --git a/TelegramUI/TGChannelIntroController.m b/TelegramUI/TGChannelIntroController.m new file mode 100644 index 0000000000..eb9c049c5d --- /dev/null +++ b/TelegramUI/TGChannelIntroController.m @@ -0,0 +1,260 @@ +#import "TGChannelIntroController.h" + +#import +#import + +@implementation TGChannelIntroControllerTheme + +- (instancetype)initWithBackgroundColor:(UIColor *)backgroundColor primaryColor:(UIColor *)primaryColor secondaryColor:(UIColor *)secondaryColor accentColor:(UIColor *)accentColor backArrowImage:(UIImage *)backArrowImage introImage:(UIImage *)introImage { + self = [super init]; + if (self != nil) { + _backgroundColor = backgroundColor; + _primaryColor = primaryColor; + _secondaryColor = secondaryColor; + _accentColor = accentColor; + _backArrowImage = backArrowImage; + _introImage = introImage; + } + return self; +} + +@end + +@interface TGChannelIntroController () +{ + TGModernButton *_backButton; + UIImageView *_phoneImageView; + UILabel *_titleLabel; + UILabel *_descriptionLabel; + TGModernButton *_createButton; + TGChannelIntroControllerTheme *_theme; + NSString *(^_getLocalizedString)(NSString *); + void (^_completion)(void); +} +@end + +@implementation TGChannelIntroController + +- (instancetype)initWithContext:(id)context getLocalizedString:(NSString *(^)(NSString *))getLocalizedString theme:(TGChannelIntroControllerTheme *)theme completion:(void (^)(void))completion { + self = [super initWithContext:context]; + if (self != nil) { + _getLocalizedString = [getLocalizedString copy]; + _theme = theme; + _completion = [completion copy]; + } + return self; +} + +- (void)loadView +{ + [super loadView]; + + self.view.backgroundColor = _theme.backgroundColor; + + UIImage *image = _theme.backArrowImage; + UIGraphicsBeginImageContextWithOptions(image.size, false, 0.0f); + CGContextRef context = UIGraphicsGetCurrentContext(); + [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)]; + CGContextSetBlendMode (context, kCGBlendModeSourceAtop); + CGContextSetFillColorWithColor(context, _theme.accentColor.CGColor); + CGContextFillRect(context, CGRectMake(0, 0, image.size.width, image.size.height)); + + UIImage *arrowImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + _backButton = [[TGModernButton alloc] initWithFrame:CGRectZero]; + _backButton.exclusiveTouch = true; + _backButton.titleLabel.font = TGSystemFontOfSize(17); + [_backButton setTitle:_getLocalizedString(@"Common.Back") forState:UIControlStateNormal]; + [_backButton setTitleColor:_theme.accentColor]; + [_backButton addTarget:self action:@selector(backButtonPressed) forControlEvents:UIControlEventTouchUpInside]; + [self.view addSubview:_backButton]; + + UIImageView *arrowView = [[UIImageView alloc] initWithFrame:CGRectMake(-19, 5.5f, 13, 22)]; + arrowView.image = arrowImage; + [_backButton addSubview:arrowView]; + + _phoneImageView = [[UIImageView alloc] initWithImage:_theme.introImage]; + _phoneImageView.frame = CGRectMake(0, 0, 154, 220); + [self.view addSubview:_phoneImageView]; + + _titleLabel = [[UILabel alloc] init]; + _titleLabel.backgroundColor = [UIColor clearColor]; + _titleLabel.font = TGSystemFontOfSize(21); + _titleLabel.textColor = _theme.primaryColor; + _titleLabel.textAlignment = NSTextAlignmentCenter; + _titleLabel.text = _getLocalizedString(@"ChannelIntro.Title"); + [self.view addSubview:_titleLabel]; + + _descriptionLabel = [[UILabel alloc] init]; + _descriptionLabel.backgroundColor = [UIColor clearColor]; + _descriptionLabel.numberOfLines = 0; + _descriptionLabel.textAlignment = NSTextAlignmentCenter; + [self.view addSubview:_descriptionLabel]; + + NSString *description = _getLocalizedString(@"ChannelIntro.Text"); + NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:description]; + NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; + style.lineSpacing = 2; + style.alignment = NSTextAlignmentCenter; + [attrString addAttribute:NSParagraphStyleAttributeName value:style range:NSMakeRange(0, description.length)]; + [attrString addAttribute:NSForegroundColorAttributeName value:_theme.secondaryColor range:NSMakeRange(0, description.length)]; + [attrString addAttribute:NSFontAttributeName value:TGSystemFontOfSize(16) range:NSMakeRange(0, description.length)]; + _descriptionLabel.attributedText = attrString; + + _createButton = [[TGModernButton alloc] init]; + _createButton.exclusiveTouch = true; + _createButton.backgroundColor = [UIColor clearColor]; + _createButton.titleLabel.font = TGSystemFontOfSize(21); + [_createButton setTitleColor:_theme.accentColor]; + [_createButton setTitle:_getLocalizedString(@"ChannelIntro.CreateChannel") forState:UIControlStateNormal]; + [_createButton addTarget:self action:@selector(buttonPressed) forControlEvents:UIControlEventTouchUpInside]; + [self.view addSubview:_createButton]; +} + +- (bool)navigationBarShouldBeHidden +{ + return true; +} + +- (void)backButtonPressed +{ + [self.navigationController popViewControllerAnimated:true]; +} + +- (void)buttonPressed +{ + _completion(); +} + +- (void)viewWillLayoutSubviews +{ + CGRect bounds = self.context.fullscreenBounds; + UIInterfaceOrientation orientation = UIInterfaceOrientationPortrait; + if (bounds.size.width > bounds.size.height) { + orientation = UIInterfaceOrientationLandscapeLeft; + } + + UIEdgeInsets safeAreaInset = [self calculatedSafeAreaInset]; + int iosVersion = [[[UIDevice currentDevice] systemVersion] intValue]; + if (UIEdgeInsetsEqualToEdgeInsets(safeAreaInset, UIEdgeInsetsZero) && (iosVersion < 11 || TGIsPad() || UIInterfaceOrientationIsPortrait(orientation))) + safeAreaInset.top = 20.0f; + + [_backButton sizeToFit]; + _backButton.frame = CGRectMake(27 + safeAreaInset.left, 5.0f + TGScreenPixel + safeAreaInset.top, ceil(_backButton.frame.size.width), ceil(_backButton.frame.size.height)); + + [_titleLabel sizeToFit]; + [_descriptionLabel sizeToFit]; + [_createButton sizeToFit]; + + int screenSize = (int)TGScreenSize().height; + CGFloat titleY = 0; + CGFloat imageY = 0; + CGFloat descY = 0; + CGFloat buttonY = 0; + + if (UIInterfaceOrientationIsPortrait(orientation)) + { + switch (screenSize) + { + case 812: + titleY = 445 + 44; + imageY = 141 + 44; + descY = 490 + 44; + buttonY = 610 + 44; + break; + + case 736: + titleY = 445; + imageY = 141; + descY = 490; + buttonY = 610; + break; + + case 667: + titleY = 407; + imageY = 120; + descY = 448; + buttonY = 558; + break; + + case 568: + titleY = 354; + imageY = 87; + descY = 397; + buttonY = 496; + break; + + default: + titleY = 307; + imageY = 60; + descY = 344; + buttonY = 424; + break; + } + + _phoneImageView.frame = CGRectMake((self.view.frame.size.width - _phoneImageView.frame.size.width) / 2, imageY, _phoneImageView.frame.size.width, _phoneImageView.frame.size.height); + _titleLabel.frame = CGRectMake((self.view.frame.size.width - _titleLabel.frame.size.width) / 2, titleY, ceil(_titleLabel.frame.size.width), ceil(_titleLabel.frame.size.height)); + _descriptionLabel.frame = CGRectMake((self.view.frame.size.width - _descriptionLabel.frame.size.width) / 2, descY, ceil(_descriptionLabel.frame.size.width), ceil(_descriptionLabel.frame.size.height)); + + _createButton.frame = CGRectMake((self.view.frame.size.width - _createButton.frame.size.width) / 2, buttonY, ceil(_createButton.frame.size.width), ceil(_createButton.frame.size.height)); + } + else + { + CGFloat leftX = 0; + CGFloat rightX = 0; + + switch (screenSize) + { + case 812: + leftX = 190 + 44; + rightX = 448 + 44; + titleY = 103; + descY = 148; + buttonY = 237; + break; + + case 736: + leftX = 209; + rightX = 504; + titleY = 115; + descY = 156; + buttonY = 278; + break; + + case 667: + leftX = 190; + rightX = 448; + titleY = 103; + descY = 148; + buttonY = 237; + break; + + case 568: + leftX = 164; + rightX = 388; + titleY = 78; + descY = 121; + buttonY = 217; + break; + + default: + leftX = 125; + rightX = 328; + titleY = 78; + descY = 121; + buttonY = 219; + break; + } + + _phoneImageView.frame = CGRectMake(leftX - _phoneImageView.frame.size.width / 2, (self.view.frame.size.height - _phoneImageView.frame.size.height) / 2, _phoneImageView.frame.size.width, _phoneImageView.frame.size.height); + + _titleLabel.frame = CGRectMake(rightX - _titleLabel.frame.size.width / 2, titleY, ceil(_titleLabel.frame.size.width), ceil(_titleLabel.frame.size.height)); + + _descriptionLabel.frame = CGRectMake(rightX - _descriptionLabel.frame.size.width / 2, descY, ceil(_descriptionLabel.frame.size.width), ceil(_descriptionLabel.frame.size.height)); + + _createButton.frame = CGRectMake(rightX - _createButton.frame.size.width / 2, buttonY, ceil(_createButton.frame.size.width), ceil(_createButton.frame.size.height)); + } +} + +@end diff --git a/TelegramUI/TelegramApplicationContext.swift b/TelegramUI/TelegramApplicationContext.swift index d47c6c44b7..c5fd864ade 100644 --- a/TelegramUI/TelegramApplicationContext.swift +++ b/TelegramUI/TelegramApplicationContext.swift @@ -80,6 +80,7 @@ public final class TelegramApplicationContext { public var presentGlobalController: (ViewController, Any?) -> Void = { _, _ in } + public var presentCrossfadeController: () -> Void = {} public var navigateToCurrentCall: (() -> Void)? public var hasOngoingCall: Signal? @@ -116,7 +117,8 @@ public final class TelegramApplicationContext { self.currentMediaInputSettings = Atomic(value: initialPresentationDataAndSettings.mediaInputSettings) if let account = account { - self._presentationData.set(.single(initialPresentationDataAndSettings.presentationData) |> then(updatedPresentationData(postbox: account.postbox))) + self._presentationData.set(.single(initialPresentationDataAndSettings.presentationData) + |> then(updatedPresentationData(postbox: account.postbox))) self._automaticMediaDownloadSettings.set(.single(initialPresentationDataAndSettings.automaticMediaDownloadSettings) |> then(updatedAutomaticMediaDownloadSettings(postbox: account.postbox))) } else { self._presentationData.set(.single(initialPresentationDataAndSettings.presentationData)) @@ -148,10 +150,12 @@ public final class TelegramApplicationContext { } }) - self.presentationDataDisposable.set(self._presentationData.get().start(next: { [weak self] next in + self.presentationDataDisposable.set((self._presentationData.get() + |> deliverOnMainQueue).start(next: { [weak self] next in if let strongSelf = self { var stringsUpdated = false var themeUpdated = false + var themeNameUpdated = false let _ = strongSelf.currentPresentationData.modify { current in if next.strings !== current.strings { stringsUpdated = true @@ -159,6 +163,9 @@ public final class TelegramApplicationContext { if next.theme !== current.theme { themeUpdated = true } + if next.theme.name != current.theme.name { + themeNameUpdated = true + } return next } if stringsUpdated { @@ -167,6 +174,9 @@ public final class TelegramApplicationContext { if themeUpdated { updateLegacyTheme() } + if themeNameUpdated { + strongSelf.presentCrossfadeController() + } } })) diff --git a/TelegramUI/TelegramController.swift b/TelegramUI/TelegramController.swift index 42844f8a06..5485921f14 100644 --- a/TelegramUI/TelegramController.swift +++ b/TelegramUI/TelegramController.swift @@ -347,7 +347,7 @@ public class TelegramController: ViewController { mediaAccessoryPanel.updateLayout(size: panelFrame.size, transition: transition) mediaAccessoryPanel.containerNode.headerNode.playbackItem = item mediaAccessoryPanel.containerNode.headerNode.playbackStatus = self.account.telegramApplicationContext.mediaManager.globalMediaPlayerState |> map { state in - return state?.0.status ?? MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, seekId: 0, status: .paused) + return state?.0.status ?? MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused) } } else { if let (mediaAccessoryPanel, _) = self.mediaAccessoryPanel { @@ -368,6 +368,32 @@ public class TelegramController: ViewController { strongSelf.account.telegramApplicationContext.mediaManager.setPlaylist(nil, type: type) } } + mediaAccessoryPanel.toggleRate = { + [weak self] in + guard let strongSelf = self else { + return + } + let _ = (strongSelf.account.postbox.transaction { transaction -> AudioPlaybackRate in + let settings = transaction.getPreferencesEntry(key: ApplicationSpecificPreferencesKeys.musicPlaybackSettings) as? MusicPlaybackSettings ?? MusicPlaybackSettings.defaultSettings + + let nextRate: AudioPlaybackRate + switch settings.voicePlaybackRate { + case .x1: + nextRate = .x2 + case .x2: + nextRate = .x1 + } + transaction.setPreferencesEntry(key: ApplicationSpecificPreferencesKeys.musicPlaybackSettings, value: settings.withUpdatedVoicePlaybackRate(nextRate)) + return nextRate + } + |> deliverOnMainQueue).start(next: { baseRate in + guard let strongSelf = self, let (_, _, type) = strongSelf.playlistStateAndType else { + return + } + + strongSelf.account.telegramApplicationContext.mediaManager.playlistControl(.setBaseRate(baseRate), type: type) + }) + } mediaAccessoryPanel.togglePlayPause = { [weak self] in if let strongSelf = self, let (_, _, type) = strongSelf.playlistStateAndType { strongSelf.account.telegramApplicationContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: type) @@ -398,7 +424,7 @@ public class TelegramController: ViewController { mediaAccessoryPanel.updateLayout(size: panelFrame.size, transition: .immediate) mediaAccessoryPanel.containerNode.headerNode.playbackItem = item mediaAccessoryPanel.containerNode.headerNode.playbackStatus = self.account.telegramApplicationContext.mediaManager.globalMediaPlayerState |> map { state in - return state?.0.status ?? MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, seekId: 0, status: .paused) + return state?.0.status ?? MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused) } mediaAccessoryPanel.animateIn(transition: transition) } diff --git a/TelegramUI/TelegramInitializeLegacyComponents.swift b/TelegramUI/TelegramInitializeLegacyComponents.swift index fa3607f3df..5261a93692 100644 --- a/TelegramUI/TelegramInitializeLegacyComponents.swift +++ b/TelegramUI/TelegramInitializeLegacyComponents.swift @@ -219,7 +219,7 @@ private final class LegacyComponentsGlobalsProviderImpl: NSObject, LegacyCompone let convertedType: ManagedAudioSessionType switch type { case TGAudioSessionTypePlayAndRecord, TGAudioSessionTypePlayAndRecordHeadphones: - convertedType = .playAndRecord + convertedType = .record default: convertedType = .play } diff --git a/TelegramUI/TelegramUIPrivate/module.modulemap b/TelegramUI/TelegramUIPrivate/module.modulemap index f3bc42a031..3d0eee950b 100644 --- a/TelegramUI/TelegramUIPrivate/module.modulemap +++ b/TelegramUI/TelegramUIPrivate/module.modulemap @@ -26,4 +26,6 @@ module TelegramUIPrivateModule { header "../RaiseToListenActivator.h" header "../TGMimeTypeMap.h" header "../TGEmojiSuggestions.h" + header "../TGChannelIntroController.h" + header "../EDSunriseSet.h" } diff --git a/TelegramUI/TermsOfServiceController.swift b/TelegramUI/TermsOfServiceController.swift index 45966ade95..9e6ba80589 100644 --- a/TelegramUI/TermsOfServiceController.swift +++ b/TelegramUI/TermsOfServiceController.swift @@ -5,12 +5,44 @@ import SwiftSignalKit import Display import AsyncDisplayKit +public class TermsOfServiceControllerTheme { + public let statusBarStyle: StatusBarStyle + public let navigationBackground: UIColor + public let navigationSeparator: UIColor + public let listBackground: UIColor + public let itemBackground: UIColor + public let itemSeparator: UIColor + public let primary: UIColor + public let accent: UIColor + + public init(statusBarStyle: StatusBarStyle, navigationBackground: UIColor, navigationSeparator: UIColor, listBackground: UIColor, itemBackground: UIColor, itemSeparator: UIColor, primary: UIColor, accent: UIColor) { + self.statusBarStyle = statusBarStyle + self.navigationBackground = navigationBackground + self.navigationSeparator = navigationSeparator + self.listBackground = listBackground + self.itemBackground = itemBackground + self.itemSeparator = itemSeparator + self.primary = primary + self.accent = accent + } +} + +public extension TermsOfServiceControllerTheme { + convenience init(presentationTheme: PresentationTheme) { + self.init(statusBarStyle: presentationTheme.rootController.statusBar.style.style, navigationBackground: presentationTheme.rootController.navigationBar.backgroundColor, navigationSeparator: presentationTheme.rootController.navigationBar.separatorColor, listBackground: presentationTheme.list.blocksBackgroundColor, itemBackground: presentationTheme.list.itemBlocksBackgroundColor, itemSeparator: presentationTheme.list.itemBlocksSeparatorColor, primary: presentationTheme.list.itemPrimaryTextColor, accent: presentationTheme.list.itemAccentColor) + } + + convenience init(authTheme: AuthorizationTheme) { + self.init(statusBarStyle: authTheme.statusBarStyle, navigationBackground: authTheme.navigationBarBackgroundColor, navigationSeparator: authTheme.navigationBarSeparatorColor, listBackground: authTheme.listBackgroundColor, itemBackground: authTheme.backgroundColor, itemSeparator: authTheme.separatorColor, primary: authTheme.primaryColor, accent: authTheme.accentColor) + } +} + public class TermsOfServiceController: ViewController { private var controllerNode: TermsOfServiceControllerNode { return self.displayNode as! TermsOfServiceControllerNode } - private let theme: PresentationTheme + private let theme: TermsOfServiceControllerTheme private let strings: PresentationStrings private let text: String private let entities: [MessageTextEntity] @@ -25,7 +57,7 @@ public class TermsOfServiceController: ViewController { public var inProgress: Bool = false { didSet { if self.inProgress { - let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.theme.rootController.navigationBar.accentTextColor)) + let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.theme.accent)) self.navigationItem.rightBarButtonItem = item } else { self.navigationItem.rightBarButtonItem = nil @@ -34,7 +66,7 @@ public class TermsOfServiceController: ViewController { } } - public init(theme: PresentationTheme, strings: PresentationStrings, text: String, entities: [MessageTextEntity], ageConfirmation: Int32?, signingUp: Bool, accept: @escaping () -> Void, decline: @escaping () -> Void, openUrl: @escaping (String) -> Void) { + public init(theme: TermsOfServiceControllerTheme, strings: PresentationStrings, text: String, entities: [MessageTextEntity], ageConfirmation: Int32?, signingUp: Bool, accept: @escaping () -> Void, decline: @escaping () -> Void, openUrl: @escaping (String) -> Void) { self.theme = theme self.strings = strings self.text = text @@ -45,11 +77,11 @@ public class TermsOfServiceController: ViewController { self.decline = decline self.openUrl = openUrl - super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme), strings: NavigationBarStrings(back: strings.Common_Back, close: strings.Common_Close))) + super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(buttonColor: self.theme.accent, primaryTextColor: self.theme.primary, backgroundColor: self.theme.navigationBackground, separatorColor: self.theme.navigationSeparator, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(back: strings.Common_Back, close: strings.Common_Close))) - self.statusBar.statusBarStyle = self.theme.rootController.statusBar.style.style + self.statusBar.statusBarStyle = self.theme.statusBarStyle - self.title = self.strings.TermsOfService_Title + self.title = self.strings.Login_TermsOfServiceHeader self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.strings.Common_Back, style: .plain, target: nil, action: nil) @@ -74,13 +106,19 @@ public class TermsOfServiceController: ViewController { let text: String let declineTitle: String if strongSelf.signingUp { - text = strongSelf.strings.TermsOfService_DeclineUnauthorized - declineTitle = strongSelf.strings.TermsOfService_Decline + text = strongSelf.strings.Login_TermsOfServiceSignupDecline + declineTitle = strongSelf.strings.Login_TermsOfServiceDecline } else { - text = strongSelf.strings.TermsOfService_DeclineAuthorized - declineTitle = strongSelf.strings.TermsOfService_DeclineAndDelete + text = strongSelf.strings.PrivacyPolicy_DeclineMessage + declineTitle = strongSelf.strings.PrivacyPolicy_DeclineDeclineAndDelete } - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.theme), title: strongSelf.strings.TermsOfService_Decline, text: text, actions: [TextAlertAction(type: .destructiveAction, title: declineTitle, action: { + let theme: PresentationTheme + if strongSelf.theme.itemBackground.argb == 0xffffffff { + theme = defaultPresentationTheme + } else { + theme = defaultDarkPresentationTheme + } + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: theme), title: strongSelf.strings.PrivacyPolicy_Decline, text: text, actions: [TextAlertAction(type: .destructiveAction, title: declineTitle, action: { self?.decline() }), TextAlertAction(type: .defaultAction, title: strongSelf.strings.Common_Cancel, action: { })], actionLayout: .vertical), in: .window(.root)) @@ -90,7 +128,13 @@ public class TermsOfServiceController: ViewController { } if let ageConfirmation = strongSelf.ageConfirmation { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.theme), title: strongSelf.strings.TermsOfService_AgeVerificationTitle, text: strongSelf.strings.TermsOfService_AgeVerificationText(Int(ageConfirmation)).0, actions: [TextAlertAction(type: .genericAction, title: strongSelf.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.strings.TermsOfService_Confirm, action: { + let theme: PresentationTheme + if strongSelf.theme.itemBackground.argb == 0xffffffff { + theme = defaultPresentationTheme + } else { + theme = defaultDarkPresentationTheme + } + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: theme), title: strongSelf.strings.PrivacyPolicy_AgeVerificationTitle, text: strongSelf.strings.PrivacyPolicy_AgeVerificationMessage("\(ageConfirmation)").0, actions: [TextAlertAction(type: .genericAction, title: strongSelf.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.strings.PrivacyPolicy_AgeVerificationAgree, action: { self?.accept() })]), in: .window(.root)) } else { diff --git a/TelegramUI/TermsOfServiceControllerNode.swift b/TelegramUI/TermsOfServiceControllerNode.swift index 0579b14261..53c164d712 100644 --- a/TelegramUI/TermsOfServiceControllerNode.swift +++ b/TelegramUI/TermsOfServiceControllerNode.swift @@ -6,7 +6,7 @@ import Display import AsyncDisplayKit final class TermsOfServiceControllerNode: ViewControllerTracingNode { - private let theme: PresentationTheme + private let theme: TermsOfServiceControllerTheme private let strings: PresentationStrings private let text: String private let entities: [MessageTextEntity] @@ -39,7 +39,7 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { } } - init(theme: PresentationTheme, strings: PresentationStrings, text: String, entities: [MessageTextEntity], ageConfirmation: Int32?, leftAction: @escaping () -> Void, rightAction: @escaping () -> Void, openUrl: @escaping (String) -> Void, present: @escaping (ViewController, Any?) -> Void) { + init(theme: TermsOfServiceControllerTheme, strings: PresentationStrings, text: String, entities: [MessageTextEntity], ageConfirmation: Int32?, leftAction: @escaping () -> Void, rightAction: @escaping () -> Void, openUrl: @escaping (String) -> Void, present: @escaping (ViewController, Any?) -> Void) { self.theme = theme self.strings = strings self.text = text @@ -55,7 +55,7 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { self.contentTextNode = ImmediateTextNode() self.contentTextNode.displaysAsynchronously = false self.contentTextNode.maximumNumberOfLines = 0 - self.contentTextNode.attributedText = stringWithAppliedEntities(text, entities: entities, baseColor: theme.list.itemPrimaryTextColor, linkColor: theme.list.itemAccentColor, baseFont: Font.regular(15.0), linkFont: Font.regular(15.0), boldFont: Font.semibold(15.0), italicFont: Font.italic(15.0), fixedFont: Font.monospace(15.0)) + self.contentTextNode.attributedText = stringWithAppliedEntities(text, entities: entities, baseColor: theme.primary, linkColor: theme.accent, baseFont: Font.regular(15.0), linkFont: Font.regular(15.0), boldFont: Font.semibold(15.0), italicFont: Font.italic(15.0), fixedFont: Font.monospace(15.0)) self.toolbarNode = ASDisplayNode() self.toolbarSeparatorNode = ASDisplayNode() @@ -63,20 +63,20 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { self.leftActionTextNode = ImmediateTextNode() self.leftActionTextNode.displaysAsynchronously = false self.leftActionTextNode.isLayerBacked = true - self.leftActionTextNode.attributedText = NSAttributedString(string: self.strings.TermsOfService_Disagree, font: Font.regular(17.0), textColor: self.theme.list.itemAccentColor) + self.leftActionTextNode.attributedText = NSAttributedString(string: self.strings.PrivacyPolicy_Decline, font: Font.regular(17.0), textColor: self.theme.accent) self.rightActionNode = HighlightableButtonNode() self.rightActionTextNode = ImmediateTextNode() self.rightActionTextNode.displaysAsynchronously = false self.rightActionTextNode.isLayerBacked = true - self.rightActionTextNode.attributedText = NSAttributedString(string: self.strings.TermsOfService_Agree, font: Font.semibold(17.0), textColor: self.theme.list.itemAccentColor) + self.rightActionTextNode.attributedText = NSAttributedString(string: self.strings.PrivacyPolicy_Accept, font: Font.semibold(17.0), textColor: self.theme.accent) super.init() - self.backgroundColor = self.theme.list.blocksBackgroundColor - self.toolbarNode.backgroundColor = self.theme.rootController.navigationBar.backgroundColor - self.toolbarSeparatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor + self.backgroundColor = self.theme.listBackground + self.toolbarNode.backgroundColor = self.theme.navigationBackground + self.toolbarSeparatorNode.backgroundColor = self.theme.navigationSeparator - self.contentBackgroundNode.backgroundColor = self.theme.list.itemBlocksBackgroundColor + self.contentBackgroundNode.backgroundColor = self.theme.itemBackground self.addSubnode(self.scrollNode) self.scrollNode.addSubnode(self.contentBackgroundNode) @@ -115,14 +115,14 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { self.leftActionNode.addTarget(self, action: #selector(self.leftActionPressed), forControlEvents: .touchUpInside) self.rightActionNode.addTarget(self, action: #selector(self.rightActionPressed), forControlEvents: .touchUpInside) - self.contentTextNode.linkHighlightColor = self.theme.list.itemAccentColor.withAlphaComponent(0.5) + self.contentTextNode.linkHighlightColor = self.theme.accent.withAlphaComponent(0.5) self.contentTextNode.highlightAttributeAction = { attributes in - if let _ = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] { - return NSAttributedStringKey(rawValue: TelegramTextAttributes.Url) + if let _ = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedStringKey(rawValue: TelegramTextAttributes.URL) } else if let _ = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerMention)] { - return NSAttributedStringKey(rawValue: TelegramTextAttributes.Url) + return NSAttributedStringKey(rawValue: TelegramTextAttributes.URL) } else if let _ = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerTextMention)] { - return NSAttributedStringKey(rawValue: TelegramTextAttributes.Url) + return NSAttributedStringKey(rawValue: TelegramTextAttributes.URL) } else { return nil } @@ -131,10 +131,16 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { guard let strongSelf = self else { return } - if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] as? String { + if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { strongSelf.openUrl(url) } else if let mention = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention { - let actionSheet = ActionSheetController(presentationTheme: strongSelf.theme) + let theme: PresentationTheme + if strongSelf.theme.itemBackground.argb == 0xffffffff { + theme = defaultPresentationTheme + } else { + theme = defaultDarkPresentationTheme + } + let actionSheet = ActionSheetController(presentationTheme: theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetTextItem(title: mention.mention), ActionSheetButtonItem(title: strongSelf.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in @@ -148,7 +154,13 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { ])]) strongSelf.present(actionSheet, nil) } else if let mention = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { - let actionSheet = ActionSheetController(presentationTheme: strongSelf.theme) + let theme: PresentationTheme + if strongSelf.theme.itemBackground.argb == 0xffffffff { + theme = defaultPresentationTheme + } else { + theme = defaultDarkPresentationTheme + } + let actionSheet = ActionSheetController(presentationTheme: theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetTextItem(title: mention), ActionSheetButtonItem(title: strongSelf.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in @@ -167,8 +179,14 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { guard let strongSelf = self else { return } - if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.Url)] as? String { - let actionSheet = ActionSheetController(presentationTheme: strongSelf.theme) + if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String { + let theme: PresentationTheme + if strongSelf.theme.itemBackground.argb == 0xffffffff { + theme = defaultPresentationTheme + } else { + theme = defaultDarkPresentationTheme + } + let actionSheet = ActionSheetController(presentationTheme: theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetTextItem(title: url), ActionSheetButtonItem(title: strongSelf.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in @@ -186,7 +204,13 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { ])]) strongSelf.present(actionSheet, nil) } else if let mention = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention { - let actionSheet = ActionSheetController(presentationTheme: strongSelf.theme) + let theme: PresentationTheme + if strongSelf.theme.itemBackground.argb == 0xffffffff { + theme = defaultPresentationTheme + } else { + theme = defaultDarkPresentationTheme + } + let actionSheet = ActionSheetController(presentationTheme: theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetTextItem(title: mention.mention), ActionSheetButtonItem(title: strongSelf.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in @@ -200,7 +224,13 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { ])]) strongSelf.present(actionSheet, nil) } else if let mention = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { - let actionSheet = ActionSheetController(presentationTheme: strongSelf.theme) + let theme: PresentationTheme + if strongSelf.theme.itemBackground.argb == 0xffffffff { + theme = defaultPresentationTheme + } else { + theme = defaultDarkPresentationTheme + } + let actionSheet = ActionSheetController(presentationTheme: theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetTextItem(title: mention), ActionSheetButtonItem(title: strongSelf.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in diff --git a/TelegramUI/TextNode.swift b/TelegramUI/TextNode.swift index 5d66f04a5f..7b131309bc 100644 --- a/TelegramUI/TextNode.swift +++ b/TelegramUI/TextNode.swift @@ -24,7 +24,7 @@ final class TelegramPeerMention { } struct TelegramTextAttributes { - static let Url = "UrlAttributeT" + static let URL = "UrlAttributeT" static let PeerMention = "TelegramPeerMention" static let PeerTextMention = "TelegramPeerTextMention" static let BotCommand = "TelegramBotCommand" diff --git a/TelegramUI/ThemeAccentColorActionSheet.swift b/TelegramUI/ThemeAccentColorActionSheet.swift new file mode 100644 index 0000000000..1182255c8e --- /dev/null +++ b/TelegramUI/ThemeAccentColorActionSheet.swift @@ -0,0 +1,174 @@ +import Foundation +import Display +import AsyncDisplayKit +import UIKit +import SwiftSignalKit +import Photos + +final class ThemeAccentColorActionSheet: ActionSheetController { + private let theme: PresentationTheme + private let strings: PresentationStrings + + private let _ready = Promise() + override var ready: Promise { + return self._ready + } + + init(theme: PresentationTheme, strings: PresentationStrings, currentValue: Int32, applyValue: @escaping (Int32) -> Void) { + self.theme = theme + self.strings = strings + + super.init(theme: ActionSheetControllerTheme(presentationTheme: theme)) + + self._ready.set(.single(true)) + + var items: [ActionSheetItem] = [] + items.append(ThemeAccentColorActionSheetItem(strings: strings, currentValue: currentValue, valueChanged: { [weak self] value in + self?.dismissAnimated() + applyValue(value) + })) + + self.setItemGroups([ + ActionSheetItemGroup(items: items), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in + self?.dismissAnimated() + }), + ]) + ]) + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private final class ThemeAccentColorActionSheetItem: ActionSheetItem { + let strings: PresentationStrings + + let currentValue: Int32 + let valueChanged: (Int32) -> Void + + init(strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) { + self.strings = strings + self.currentValue = currentValue + self.valueChanged = valueChanged + } + + func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { + return ThemeAccentColorActionSheetItemNode(theme: theme, strings: self.strings, currentValue: self.currentValue, valueChanged: self.valueChanged) + } + + func updateNode(_ node: ActionSheetItemNode) { + } +} + +private final class ThemeAccentColorActionSheetItemNode: ActionSheetItemNode { + private let theme: ActionSheetControllerTheme + private let strings: PresentationStrings + + private let titleNode: ImmediateTextNode + + private let valueChanged: (Int32) -> Void + private let items: [Int32] + private let itemNodes: [ASImageNode] + + init(theme: ActionSheetControllerTheme, strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) { + self.theme = theme + self.strings = strings + self.valueChanged = valueChanged + + self.titleNode = ImmediateTextNode() + self.titleNode.displaysAsynchronously = false + self.titleNode.isLayerBacked = true + self.titleNode.attributedText = NSAttributedString(string: strings.Appearance_PickAccentColor, font: Font.medium(18.0), textColor: theme.primaryTextColor) + + self.items = [ + 0xf83b4c, // red + 0xff7519, // orange + 0xeba239, // yellow + 0x29b327, // green + 0x00c2ed, // light blue + 0x007ee5, // blue + 0x7748ff, // purple + 0xff5da2 + ] + self.itemNodes = self.items.map { color in + let imageNode = ASImageNode() + imageNode.displaysAsynchronously = false + imageNode.displayWithoutProcessing = true + + imageNode.image = generateImage(CGSize(width: 60.0, height: 60.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.draw(generateFilledCircleImage(diameter: size.width, color: UIColor(rgb: UInt32(bitPattern: color)))!.cgImage!, in: CGRect(origin: CGPoint(), size: size)) + if color == currentValue { + context.scaleBy(x: 0.333, y: 0.333) + context.translateBy(x: 62.0, y: 72.0) + context.setLineWidth(10.0) + context.setLineCap(.round) + context.setStrokeColor(UIColor.white.cgColor) + + let _ = try? drawSvgPath(context, path: "M0,23.173913 L16.699191,39.765981 C17.390617,40.452972 18.503246,40.464805 19.209127,39.792676 L61,0 S ") + } + }) + return imageNode + } + + super.init(theme: theme) + + self.addSubnode(self.titleNode) + + for itemNode in self.itemNodes { + itemNode.isUserInteractionEnabled = true + self.addSubnode(itemNode) + itemNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + } + } + + @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + for i in 0 ..< self.itemNodes.count { + if self.itemNodes[i].view == recognizer.view { + self.valueChanged(self.items[i]) + break + } + } + } + } + + override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + let topInset: CGFloat = 12.0 + let maximumItemSpacing: CGFloat = 18.0 + let sideInset: CGFloat = 10.0 + let columnCount: CGFloat = 4.0 + let rowCount: CGFloat = 2.0 + let itemSide: CGFloat = 60.0 + + let itemsWidth = itemSide * columnCount + let remainingWidth = constrainedSize.width - itemsWidth - sideInset * 2.0 + let proposedSpacing = floor(remainingWidth / (columnCount - 1.0)) + let itemSpacing = min(proposedSpacing, maximumItemSpacing) + + let titleSpacing: CGFloat = itemSpacing + let bottomInset: CGFloat = itemSpacing + + let titleSize = self.titleNode.updateLayout(constrainedSize) + self.titleNode.frame = CGRect(origin: CGPoint(x: floor((constrainedSize.width - titleSize.width) / 2.0), y: topInset), size: titleSize) + + let itemsWidthWithSpacing = itemSpacing * (columnCount - 1.0) + itemSide * columnCount + let itemsLeftOrigin = floor((constrainedSize.width - itemsWidthWithSpacing) / 2.0) + + for i in 0 ..< self.itemNodes.count { + let row = CGFloat(i / 4) + let column = CGFloat(i % 4) + self.itemNodes[i].frame = CGRect(origin: CGPoint(x: itemsLeftOrigin + column * (itemSide + itemSpacing), y: topInset + titleSize.height + titleSpacing + row * (itemSide + itemSpacing)), size: CGSize(width: itemSide, height: itemSide)) + } + + return CGSize(width: constrainedSize.width, height: topInset + titleSize.height + titleSpacing + rowCount * itemSide + (rowCount - 1.0) * itemSpacing + bottomInset) + } + + override func layout() { + super.layout() + } +} + diff --git a/TelegramUI/ThemeAutoNightSettingsController.swift b/TelegramUI/ThemeAutoNightSettingsController.swift new file mode 100644 index 0000000000..d5319d4a58 --- /dev/null +++ b/TelegramUI/ThemeAutoNightSettingsController.swift @@ -0,0 +1,555 @@ +import Foundation +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import CoreLocation + +import TelegramUIPrivateModule + +private enum TriggerMode { + case none + case timeBased + case brightness +} + +private enum TimeBasedManualField { + case from + case to +} + +private func reverseGeocodeLocation(latitude: Double, longitude: Double) -> Signal { + return Signal { subscriber in + let geocoder = CLGeocoder() + geocoder.reverseGeocodeLocation(CLLocation(latitude: latitude, longitude: longitude), completionHandler: { placemarks, _ in + if let placemarks = placemarks, let locality = placemarks.first?.locality { + subscriber.putNext(locality) + subscriber.putCompletion() + } + }) + + return ActionDisposable { + + } + } +} + +private final class ThemeAutoNightSettingsControllerArguments { + let updateMode: (TriggerMode) -> Void + let updateTimeBasedAutomatic: (Bool) -> Void + let openTimeBasedManual: (TimeBasedManualField) -> Void + let updateTimeBasedAutomaticLocation: () -> Void + let updateAutomaticBrightness: (Double) -> Void + let updateTheme: (PresentationBuiltinThemeReference) -> Void + + init(updateMode: @escaping (TriggerMode) -> Void, updateTimeBasedAutomatic: @escaping (Bool) -> Void, openTimeBasedManual: @escaping (TimeBasedManualField) -> Void, updateTimeBasedAutomaticLocation: @escaping () -> Void, updateAutomaticBrightness: @escaping (Double) -> Void, updateTheme: @escaping (PresentationBuiltinThemeReference) -> Void) { + self.updateMode = updateMode + self.updateTimeBasedAutomatic = updateTimeBasedAutomatic + self.openTimeBasedManual = openTimeBasedManual + self.updateTimeBasedAutomaticLocation = updateTimeBasedAutomaticLocation + self.updateAutomaticBrightness = updateAutomaticBrightness + self.updateTheme = updateTheme + } +} + +private enum ThemeAutoNightSettingsControllerSection: Int32 { + case mode + case settings + case theme +} + +private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry { + case modeDisabled(PresentationTheme, String, Bool) + case modeTimeBased(PresentationTheme, String, Bool) + case modeBrightness(PresentationTheme, String, Bool) + + case settingsHeader(PresentationTheme, String) + case timeBasedAutomaticLocation(PresentationTheme, String, Bool) + case timeBasedAutomaticLocationValue(PresentationTheme, String, String) + case timeBasedManualFrom(PresentationTheme, String, String) + case timeBasedManualTo(PresentationTheme, String, String) + case brightnessValue(PresentationTheme, Double) + case settingInfo(PresentationTheme, String) + + case themeHeader(PresentationTheme, String) + case themeNightBlue(PresentationTheme, String, Bool) + case themeNight(PresentationTheme, String, Bool) + + var section: ItemListSectionId { + switch self { + case .modeDisabled, .modeTimeBased, .modeBrightness: + return ThemeAutoNightSettingsControllerSection.mode.rawValue + case .settingsHeader, .timeBasedAutomaticLocation, .timeBasedAutomaticLocationValue, .timeBasedManualFrom, .timeBasedManualTo, .brightnessValue, .settingInfo: + return ThemeAutoNightSettingsControllerSection.settings.rawValue + case .themeHeader, .themeNightBlue, .themeNight: + return ThemeAutoNightSettingsControllerSection.theme.rawValue + } + } + + var stableId: Int32 { + switch self { + case .modeDisabled: + return 0 + case .modeTimeBased: + return 1 + case .modeBrightness: + return 2 + case .settingsHeader: + return 3 + case .timeBasedAutomaticLocation: + return 4 + case .timeBasedAutomaticLocationValue: + return 5 + case .timeBasedManualFrom: + return 6 + case .timeBasedManualTo: + return 7 + case .brightnessValue: + return 8 + case .settingInfo: + return 9 + case .themeHeader: + return 10 + case .themeNightBlue: + return 11 + case .themeNight: + return 12 + } + } + + static func ==(lhs: ThemeAutoNightSettingsControllerEntry, rhs: ThemeAutoNightSettingsControllerEntry) -> Bool { + switch lhs { + case let .modeDisabled(lhsTheme, lhsTitle, lhsValue): + if case let .modeDisabled(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue { + return true + } else { + return false + } + case let .modeTimeBased(lhsTheme, lhsTitle, lhsValue): + if case let .modeTimeBased(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue { + return true + } else { + return false + } + case let .modeBrightness(lhsTheme, lhsTitle, lhsValue): + if case let .modeBrightness(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue { + return true + } else { + return false + } + case let .settingsHeader(lhsTheme, lhsTitle): + if case let .settingsHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle { + return true + } else { + return false + } + case let .timeBasedAutomaticLocation(lhsTheme, lhsTitle, lhsValue): + if case let .timeBasedAutomaticLocation(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue { + return true + } else { + return false + } + case let .timeBasedAutomaticLocationValue(lhsTheme, lhsTitle, lhsValue): + if case let .timeBasedAutomaticLocationValue(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue { + return true + } else { + return false + } + case let .timeBasedManualFrom(lhsTheme, lhsTitle, lhsValue): + if case let .timeBasedManualFrom(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue { + return true + } else { + return false + } + case let .timeBasedManualTo(lhsTheme, lhsTitle, lhsValue): + if case let .timeBasedManualTo(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue { + return true + } else { + return false + } + case let .brightnessValue(lhsTheme, lhsValue): + if case let .brightnessValue(rhsTheme, rhsValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue { + return true + } else { + return false + } + case let .settingInfo(lhsTheme, lhsValue): + if case let .settingInfo(rhsTheme, rhsValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue { + return true + } else { + return false + } + case let .themeHeader(lhsTheme, lhsValue): + if case let .themeHeader(rhsTheme, rhsValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue { + return true + } else { + return false + } + case let .themeNightBlue(lhsTheme, lhsTitle, lhsValue): + if case let .themeNightBlue(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue { + return true + } else { + return false + } + case let .themeNight(lhsTheme, lhsTitle, lhsValue): + if case let .themeNight(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue { + return true + } else { + return false + } + } + } + + static func <(lhs: ThemeAutoNightSettingsControllerEntry, rhs: ThemeAutoNightSettingsControllerEntry) -> Bool { + return lhs.stableId < rhs.stableId + } + + func item(_ arguments: ThemeAutoNightSettingsControllerArguments) -> ListViewItem { + switch self { + case let .modeDisabled(theme, title, value): + return ItemListCheckboxItem(theme: theme, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + arguments.updateMode(.none) + }) + case let .modeTimeBased(theme, title, value): + return ItemListCheckboxItem(theme: theme, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + arguments.updateMode(.timeBased) + }) + case let .modeBrightness(theme, title, value): + return ItemListCheckboxItem(theme: theme, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + arguments.updateMode(.brightness) + }) + case let .settingsHeader(theme, title): + return ItemListSectionHeaderItem(theme: theme, text: title, sectionId: self.section) + case let .timeBasedAutomaticLocation(theme, title, value): + return ItemListSwitchItem(theme: theme, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in + arguments.updateTimeBasedAutomatic(value) + }) + case let .timeBasedAutomaticLocationValue(theme, title, value): + return ItemListDisclosureItem(theme: theme, icon: nil, title: title, titleColor: .accent, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: { + arguments.updateTimeBasedAutomaticLocation() + }) + case let .timeBasedManualFrom(theme, title, value): + return ItemListDisclosureItem(theme: theme, icon: nil, title: title, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + arguments.openTimeBasedManual(.from) + }) + case let .timeBasedManualTo(theme, title, value): + return ItemListDisclosureItem(theme: theme, icon: nil, title: title, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + arguments.openTimeBasedManual(.to) + }) + case let .brightnessValue(theme, value): + return ThemeSettingsBrightnessItem(theme: theme, value: Int32(value * 100.0), sectionId: self.section, updated: { value in + arguments.updateAutomaticBrightness(Double(value) / 100.0) + }) + case let .settingInfo(theme, text): + return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) + case let .themeHeader(theme, title): + return ItemListSectionHeaderItem(theme: theme, text: title, sectionId: self.section) + case let .themeNightBlue(theme, title, value): + return ItemListCheckboxItem(theme: theme, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + arguments.updateTheme(.nightAccent) + }) + case let .themeNight(theme, title, value): + return ItemListCheckboxItem(theme: theme, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + arguments.updateTheme(.nightGrayscale) + }) + } + } +} + +private func themeAutoNightSettingsControllerEntries(theme: PresentationTheme, strings: PresentationStrings, switchSetting: AutomaticThemeSwitchSetting, timeFormat: PresentationTimeFormat) -> [ThemeAutoNightSettingsControllerEntry] { + var entries: [ThemeAutoNightSettingsControllerEntry] = [] + + let activeTriggerMode: TriggerMode + switch switchSetting.trigger { + case .none: + activeTriggerMode = .none + case .timeBased: + activeTriggerMode = .timeBased + case .brightness: + activeTriggerMode = .brightness + } + + entries.append(.modeDisabled(theme, strings.AutoNightTheme_Disabled, activeTriggerMode == .none)) + entries.append(.modeTimeBased(theme, strings.AutoNightTheme_Scheduled, activeTriggerMode == .timeBased)) + entries.append(.modeBrightness(theme, strings.AutoNightTheme_Automatic, activeTriggerMode == .brightness)) + + switch switchSetting.trigger { + case .none: + break + case let .timeBased(setting): + entries.append(.settingsHeader(theme, strings.AutoNightTheme_ScheduleSection)) + var automaticLocation = false + if case .automatic = setting { + automaticLocation = true + } + entries.append(.timeBasedAutomaticLocation(theme, strings.AutoNightTheme_UseSunsetSunrise, automaticLocation)) + switch setting { + case let .automatic(_, _, sunset, sunrise, localizedName): + entries.append(.timeBasedAutomaticLocationValue(theme, strings.AutoNightTheme_UpdateLocation, localizedName)) + if sunset != 0 || sunrise != 0 { + entries.append(.settingInfo(theme, strings.AutoNightTheme_LocationHelp(stringForMessageTimestamp(timestamp: sunrise, timeFormat: timeFormat, local: false), stringForMessageTimestamp(timestamp: sunset, timeFormat: timeFormat, local: false)).0)) + } + case let .manual(fromSeconds, toSeconds): + entries.append(.timeBasedManualFrom(theme, strings.AutoNightTheme_ScheduledFrom, stringForMessageTimestamp(timestamp: fromSeconds, timeFormat: timeFormat, local: false))) + entries.append(.timeBasedManualTo(theme, strings.AutoNightTheme_ScheduledTo, stringForMessageTimestamp(timestamp: toSeconds, timeFormat: timeFormat, local: false))) + } + case let .brightness(threshold): + entries.append(.settingsHeader(theme, strings.AutoNightTheme_AutomaticSection)) + entries.append(.brightnessValue(theme, threshold)) + entries.append(.settingInfo(theme, strings.AutoNightTheme_AutomaticHelp("\(Int(threshold * 100.0))").0.replacingOccurrences(of: "%%", with: "%"))) + } + + switch switchSetting.trigger { + case .none: + break + case .timeBased, .brightness: + entries.append(.themeHeader(theme, strings.AutoNightTheme_PreferredTheme)) + entries.append(.themeNightBlue(theme, strings.Appearance_ThemeNightBlue, switchSetting.theme == .nightAccent)) + entries.append(.themeNight(theme, strings.Appearance_ThemeNight, switchSetting.theme == .nightGrayscale)) + } + + return entries +} + +private func roundTimeToDay(_ timestamp: Int32) -> Int32 { + let calendar = Calendar.current + let offset = TimeZone.current.secondsFromGMT(for: Date()) + let components = calendar.dateComponents([.hour, .minute, .second], from: Date(timeIntervalSince1970: Double(timestamp + Int32(offset)))) + return Int32(components.hour! * 60 * 60 + components.minute! * 60 + components.second!) +} + +private func areSettingsValid(_ settings: AutomaticThemeSwitchSetting) -> Bool { + switch settings.trigger { + case .none: + return true + case let .timeBased(setting): + switch setting { + case let .automatic(latitude, longitude, sunset, sunrise, _): + if !latitude.isZero || !longitude.isZero { + return true + } else { + return false + } + case .manual: + return true + } + case .brightness: + return true + } +} + +public func themeAutoNightSettingsController(account: Account) -> ViewController { + var pushControllerImpl: ((ViewController) -> Void)? + var presentControllerImpl: ((ViewController) -> Void)? + + let actionsDisposable = DisposableSet() + let updateAutomaticBrightnessDisposable = MetaDisposable() + + let stagingSettingsPromise = ValuePromise(nil) + let themeSettingsKey = ApplicationSpecificPreferencesKeys.presentationThemeSettings + let localizationSettingsKey = PreferencesKeys.localizationSettings + let preferences = account.postbox.preferencesView(keys: [themeSettingsKey, localizationSettingsKey]) + + let updateLocationDisposable = MetaDisposable() + actionsDisposable.add(updateLocationDisposable) + + let updateSettings: (@escaping (AutomaticThemeSwitchSetting) -> AutomaticThemeSwitchSetting) -> Void = { f in + let _ = (combineLatest(stagingSettingsPromise.get(), preferences) + |> take(1) + |> deliverOnMainQueue).start(next: { stagingSettings, preferences in + let settings = (preferences.values[themeSettingsKey] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings + let updated = f(stagingSettings ?? settings.automaticThemeSwitchSetting) + stagingSettingsPromise.set(updated) + if areSettingsValid(updated) { + let _ = updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in + var current = current + current.automaticThemeSwitchSetting = updated + return current + }).start() + } + }) + } + + let forceUpdateLocation: () -> Void = { + let locationCoordinates = Signal<(Double, Double), NoError> { subscriber in + return account.telegramApplicationContext.locationManager!.push(mode: DeviceLocationMode.precise, updated: { coordinate in + subscriber.putNext((coordinate.latitude, coordinate.longitude)) + subscriber.putCompletion() + }) + } + let geocodedLocation = locationCoordinates + |> mapToSignal { coordinates -> Signal<(Double, Double, String), NoError> in + return reverseGeocodeLocation(latitude: coordinates.0, longitude: coordinates.1) + |> map { locality in + return (coordinates.0, coordinates.1, locality) + } + } + + let disposable = (geocodedLocation + |> take(1) + |> deliverOnMainQueue).start(next: { location in + updateSettings { settings in + var settings = settings + if case let .timeBased(setting) = settings.trigger, case .automatic = setting { + let calculator = EDSunriseSet(date: Date(), timezone: TimeZone(secondsFromGMT: 0), latitude: location.0, longitude: location.1)! + let sunset = roundTimeToDay(Int32(calculator.sunset.timeIntervalSince1970)) + let sunrise = roundTimeToDay(Int32(calculator.sunrise.timeIntervalSince1970)) + + settings.trigger = .timeBased(setting: .automatic(latitude: location.0, longitude: location.1, sunset: sunset, sunrise: sunrise, localizedName: location.2)) + } + return settings + } + }) + updateLocationDisposable.set(disposable) + } + + let arguments = ThemeAutoNightSettingsControllerArguments(updateMode: { mode in + var updateLocation = false + updateSettings { settings in + var settings = settings + switch mode { + case .none: + settings.trigger = .none + case .timeBased: + if case .timeBased = settings.trigger { + } else { + settings.trigger = .timeBased(setting: .automatic(latitude: 0.0, longitude: 0.0, sunset: 0, sunrise: 0, localizedName: "")) + updateLocation = true + } + case .brightness: + if case .brightness = settings.trigger { + } else { + settings.trigger = .brightness(threshold: 0.2) + } + } + if updateLocation { + forceUpdateLocation() + } + return settings + } + }, updateTimeBasedAutomatic: { value in + var updateLocation = false + updateSettings { settings in + var settings = settings + if case let .timeBased(setting) = settings.trigger { + switch setting { + case .automatic: + if !value { + settings.trigger = .timeBased(setting: .manual(fromSeconds: 22 * 60 * 60, toSeconds: 9 * 60 * 60)) + } + case .manual: + if value { + settings.trigger = .timeBased(setting: .automatic(latitude: 0.0, longitude: 0.0, sunset: 0, sunrise: 0, localizedName: "")) + updateLocation = true + } + } + } + if updateLocation { + forceUpdateLocation() + } + return settings + } + }, openTimeBasedManual: { field in + var currentValue: Int32 + switch field { + case .from: + currentValue = 22 * 60 * 60 + case .to: + currentValue = 9 * 60 * 60 + } + updateSettings { settings in + let settings = settings + switch settings.trigger { + case let .timeBased(setting): + switch setting { + case let .manual(fromSeconds, toSeconds): + switch field { + case .from: + currentValue = fromSeconds + case .to: + currentValue = toSeconds + } + default: + break + } + default: + break + } + + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + presentControllerImpl?(ThemeAutoNightTimeSelectionActionSheet(theme: presentationData.theme, strings: presentationData.strings, currentValue: currentValue, applyValue: { value in + guard let value = value else { + return + } + updateSettings { settings in + var settings = settings + switch settings.trigger { + case let .timeBased(setting): + switch setting { + case var .manual(fromSeconds, toSeconds): + switch field { + case .from: + fromSeconds = value + case .to: + toSeconds = value + } + settings.trigger = .timeBased(setting: .manual(fromSeconds: fromSeconds, toSeconds: toSeconds)) + default: + break + } + default: + break + } + return settings + } + })) + + return settings + } + }, updateTimeBasedAutomaticLocation: { + forceUpdateLocation() + }, updateAutomaticBrightness: { value in + updateAutomaticBrightnessDisposable.set((Signal.complete() + |> delay(0.1, queue: Queue.mainQueue())).start(completed: { + updateSettings { settings in + var settings = settings + switch settings.trigger { + case .brightness: + settings.trigger = .brightness(threshold: max(0.0, min(1.0, value))) + default: + break + } + return settings + } + })) + }, updateTheme: { theme in + updateSettings { settings in + var settings = settings + settings.theme = theme + return settings + } + }) + + let signal = combineLatest(account.telegramApplicationContext.presentationData, preferences, stagingSettingsPromise.get()) + |> deliverOnMainQueue + |> map { presentationData, preferences, stagingSettings -> (ItemListControllerState, (ItemListNodeState, ThemeAutoNightSettingsControllerEntry.ItemGenerationArguments)) in + let settings = (preferences.values[themeSettingsKey] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings + + let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.AutoNightTheme_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) + let listState = ItemListNodeState(entries: themeAutoNightSettingsControllerEntries(theme: presentationData.theme, strings: presentationData.strings, switchSetting: stagingSettings ?? settings.automaticThemeSwitchSetting, timeFormat: presentationData.timeFormat), style: .blocks, animateChanges: false) + + return (controllerState, (listState, arguments)) + } + |> afterDisposed { + actionsDisposable.dispose() + } + + let controller = ItemListController(account: account, state: signal) + pushControllerImpl = { [weak controller] c in + (controller?.navigationController as? NavigationController)?.pushViewController(c) + } + presentControllerImpl = { [weak controller] c in + controller?.present(c, in: .window(.root)) + } + return controller +} diff --git a/TelegramUI/ThemeAutoNightTimeSelectionActionSheet.swift b/TelegramUI/ThemeAutoNightTimeSelectionActionSheet.swift new file mode 100644 index 0000000000..0136da4f6b --- /dev/null +++ b/TelegramUI/ThemeAutoNightTimeSelectionActionSheet.swift @@ -0,0 +1,115 @@ +import Foundation +import Display +import AsyncDisplayKit +import UIKit +import SwiftSignalKit +import Photos + +final class ThemeAutoNightTimeSelectionActionSheet: ActionSheetController { + private let theme: PresentationTheme + private let strings: PresentationStrings + + private let _ready = Promise() + override var ready: Promise { + return self._ready + } + + init(theme: PresentationTheme, strings: PresentationStrings, currentValue: Int32, emptyTitle: String? = nil, applyValue: @escaping (Int32?) -> Void) { + self.theme = theme + self.strings = strings + + super.init(theme: ActionSheetControllerTheme(presentationTheme: theme)) + + self._ready.set(.single(true)) + + var updatedValue = currentValue + var items: [ActionSheetItem] = [] + items.append(ThemeAutoNightTimeSelectionActionSheetItem(strings: strings, currentValue: currentValue, valueChanged: { value in + updatedValue = value + })) + if let emptyTitle = emptyTitle { + items.append(ActionSheetButtonItem(title: emptyTitle, action: { [weak self] in + self?.dismissAnimated() + applyValue(nil) + })) + } + items.append(ActionSheetButtonItem(title: strings.Wallpaper_Set, action: { [weak self] in + self?.dismissAnimated() + applyValue(updatedValue) + })) + self.setItemGroups([ + ActionSheetItemGroup(items: items), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in + self?.dismissAnimated() + }), + ]) + ]) + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private final class ThemeAutoNightTimeSelectionActionSheetItem: ActionSheetItem { + let strings: PresentationStrings + + let currentValue: Int32 + let valueChanged: (Int32) -> Void + + init(strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) { + self.strings = strings + self.currentValue = currentValue + self.valueChanged = valueChanged + } + + func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { + return ThemeAutoNightTimeSelectionActionSheetItemNode(theme: theme, strings: self.strings, currentValue: self.currentValue, valueChanged: self.valueChanged) + } + + func updateNode(_ node: ActionSheetItemNode) { + } +} + +private final class ThemeAutoNightTimeSelectionActionSheetItemNode: ActionSheetItemNode { + private let theme: ActionSheetControllerTheme + private let strings: PresentationStrings + + private let valueChanged: (Int32) -> Void + private let pickerView: UIDatePicker + + init(theme: ActionSheetControllerTheme, strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) { + self.theme = theme + self.strings = strings + self.valueChanged = valueChanged + + self.pickerView = UIDatePicker() + self.pickerView.datePickerMode = .time + self.pickerView.timeZone = TimeZone(secondsFromGMT: 0) + self.pickerView.date = Date(timeIntervalSince1970: Double(currentValue)) + self.pickerView.locale = localeWithStrings(strings) + + self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor") + + super.init(theme: theme) + + self.view.addSubview(self.pickerView) + self.pickerView.addTarget(self, action: #selector(self.datePickerUpdated), for: .valueChanged) + } + + override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + return CGSize(width: constrainedSize.width, height: 216.0) + } + + override func layout() { + super.layout() + + self.pickerView.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.bounds.size.width, height: 216.0)) + } + + @objc private func datePickerUpdated() { + self.valueChanged(Int32(self.pickerView.date.timeIntervalSince1970)) + } +} + diff --git a/TelegramUI/ThemeGalleryController.swift b/TelegramUI/ThemeGalleryController.swift index bf83344b25..5084a8ae1b 100644 --- a/TelegramUI/ThemeGalleryController.swift +++ b/TelegramUI/ThemeGalleryController.swift @@ -249,10 +249,10 @@ class ThemeGalleryController: ViewController { } let _ = (updatePresentationThemeSettingsInteractively(postbox: strongSelf.account.postbox, { current in if case .color(0x000000) = wallpaper { - return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, fontSize: .regular) + return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: .regular, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting) } - return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, fontSize: current.fontSize) + return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting) }) |> deliverOnMainQueue).start(completed: { self?.dismiss(forceAway: true) }) diff --git a/TelegramUI/ThemeGridController.swift b/TelegramUI/ThemeGridController.swift index 8a429ef4c3..a728be99bd 100644 --- a/TelegramUI/ThemeGridController.swift +++ b/TelegramUI/ThemeGridController.swift @@ -99,10 +99,10 @@ final class ThemeGridController: ViewController { let wallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: image.size, resource: resource)]) let _ = (updatePresentationThemeSettingsInteractively(postbox: self.account.postbox, { current in if case .color(0x000000) = wallpaper { - return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, fontSize: .regular) + return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting) } - return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, fontSize: current.fontSize) + return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting) }) |> deliverOnMainQueue).start(completed: { [weak self] in let _ = (self?.navigationController as? NavigationController)?.popViewController(animated: true) }) diff --git a/TelegramUI/ThemeSettingsBrightnessItem.swift b/TelegramUI/ThemeSettingsBrightnessItem.swift new file mode 100644 index 0000000000..61376610e1 --- /dev/null +++ b/TelegramUI/ThemeSettingsBrightnessItem.swift @@ -0,0 +1,230 @@ +import Foundation +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramCore + +import LegacyComponents + +class ThemeSettingsBrightnessItem: ListViewItem, ItemListItem { + let theme: PresentationTheme + let value: Int32 + let sectionId: ItemListSectionId + let updated: (Int32) -> Void + + init(theme: PresentationTheme, value: Int32, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) { + self.theme = theme + self.value = value + self.sectionId = sectionId + self.updated = updated + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, () -> Void)) -> Void) { + async { + let node = ThemeSettingsBrightnessItemNode() + let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + + node.contentSize = layout.contentSize + node.insets = layout.insets + + completion(node, { + return (nil, { apply() }) + }) + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) { + if let node = node as? ThemeSettingsBrightnessItemNode { + Queue.mainQueue().async { + let makeLayout = node.asyncLayout() + + async { + let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + Queue.mainQueue().async { + completion(layout, { + apply() + }) + } + } + } + } + } +} + +private func generateKnobImage() -> UIImage? { + return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setShadow(offset: CGSize(width: 0.0, height: -1.0), blur: 3.5, color: UIColor(white: 0.0, alpha: 0.25).cgColor) + context.setFillColor(UIColor.white.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 28.0, height: 28.0))) + }) +} + +class ThemeSettingsBrightnessItemNode: ListViewItemNode { + private let backgroundNode: ASDisplayNode + private let topStripeNode: ASDisplayNode + private let bottomStripeNode: ASDisplayNode + + private var sliderView: TGPhotoEditorSliderView? + private let leftIconNode: ASImageNode + private let rightIconNode: ASImageNode + + private var item: ThemeSettingsBrightnessItem? + private var layoutParams: ListViewItemLayoutParams? + + init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + + self.topStripeNode = ASDisplayNode() + self.topStripeNode.isLayerBacked = true + + self.bottomStripeNode = ASDisplayNode() + self.bottomStripeNode.isLayerBacked = true + + self.leftIconNode = ASImageNode() + self.leftIconNode.displaysAsynchronously = false + self.leftIconNode.displayWithoutProcessing = true + + self.rightIconNode = ASImageNode() + self.rightIconNode.displaysAsynchronously = false + self.rightIconNode.displayWithoutProcessing = true + + super.init(layerBacked: false, dynamicBounce: false) + + self.addSubnode(self.leftIconNode) + self.addSubnode(self.rightIconNode) + } + + override func didLoad() { + super.didLoad() + + let sliderView = TGPhotoEditorSliderView() + sliderView.enablePanHandling = true + sliderView.trackCornerRadius = 1.0 + sliderView.lineSize = 2.0 + sliderView.minimumValue = 0.0 + sliderView.startValue = 0.0 + sliderView.maximumValue = 100.0 + sliderView.disablesInteractiveTransitionGestureRecognizer = true + if let item = self.item, let params = self.layoutParams { + sliderView.value = CGFloat(item.value) + sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor + sliderView.backColor = item.theme.list.itemSecondaryTextColor + sliderView.trackColor = item.theme.list.itemAccentColor + sliderView.knobImage = generateKnobImage() + + sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 38.0, y: 8.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 38.0 * 2.0, height: 44.0)) + } + self.view.addSubview(sliderView) + sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged) + self.sliderView = sliderView + } + + func asyncLayout() -> (_ item: ThemeSettingsBrightnessItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { + let currentItem = self.item + + return { item, params, neighbors in + var updatedLeftIcon: UIImage? + var updatedRightIcon: UIImage? + + var themeUpdated = false + if currentItem?.theme !== item.theme { + themeUpdated = true + + updatedLeftIcon = generateTintedImage(image: UIImage(bundleImageName: "Instant View/SettingsBrightnessMinIcon"), color: item.theme.list.itemPrimaryTextColor) + updatedRightIcon = generateTintedImage(image: UIImage(bundleImageName: "Instant View/SettingsBrightnessMaxIcon"), color: item.theme.list.itemPrimaryTextColor) + } + + let contentSize: CGSize + let insets: UIEdgeInsets + let separatorHeight = UIScreenPixel + + contentSize = CGSize(width: params.width, height: 60.0) + insets = itemListNeighborsGroupedInsets(neighbors) + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) + let layoutSize = layout.size + + return (layout, { [weak self] in + if let strongSelf = self { + strongSelf.item = item + strongSelf.layoutParams = params + + strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor + strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor + strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor + + if strongSelf.backgroundNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) + } + if strongSelf.topStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1) + } + if strongSelf.bottomStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) + } + switch neighbors.top { + case .sameSection(false): + strongSelf.topStripeNode.isHidden = true + default: + strongSelf.topStripeNode.isHidden = false + } + let bottomStripeInset: CGFloat + let bottomStripeOffset: CGFloat + switch neighbors.bottom { + case .sameSection(false): + bottomStripeInset = params.leftInset + 16.0 + bottomStripeOffset = -separatorHeight + default: + bottomStripeInset = 0.0 + bottomStripeOffset = 0.0 + } + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) + strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) + + if let updatedLeftIcon = updatedLeftIcon { + strongSelf.leftIconNode.image = updatedLeftIcon + } + if let image = strongSelf.leftIconNode.image { + strongSelf.leftIconNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 18.0, y: 25.0), size: CGSize(width: image.size.width, height: image.size.height)) + } + if let updatedRightIcon = updatedRightIcon { + strongSelf.rightIconNode.image = updatedRightIcon + } + if let image = strongSelf.rightIconNode.image { + strongSelf.rightIconNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 14.0 - image.size.width, y: 21.0), size: CGSize(width: image.size.width, height: image.size.height)) + } + + if let sliderView = strongSelf.sliderView { + if themeUpdated { + sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor + sliderView.backColor = item.theme.list.itemSecondaryTextColor + sliderView.trackColor = item.theme.list.itemAccentColor + sliderView.knobImage = generateKnobImage() + } + + sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 38.0, y: 8.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 38.0 * 2.0, height: 44.0)) + } + } + }) + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } + + @objc func sliderValueChanged() { + guard let sliderView = self.sliderView else { + return + } + self.item?.updated(Int32(sliderView.value)) + } +} + diff --git a/TelegramUI/ThemeSettingsChatPreviewItem.swift b/TelegramUI/ThemeSettingsChatPreviewItem.swift index 14127f49bf..8373d032eb 100644 --- a/TelegramUI/ThemeSettingsChatPreviewItem.swift +++ b/TelegramUI/ThemeSettingsChatPreviewItem.swift @@ -8,15 +8,17 @@ import Postbox class ThemeSettingsChatPreviewItem: ListViewItem, ItemListItem { let account: Account let theme: PresentationTheme + let componentTheme: PresentationTheme let strings: PresentationStrings let sectionId: ItemListSectionId let fontSize: PresentationFontSize let wallpaper: TelegramWallpaper let timeFormat: PresentationTimeFormat - init(account: Account, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, wallpaper: TelegramWallpaper, timeFormat: PresentationTimeFormat) { + init(account: Account, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, wallpaper: TelegramWallpaper, timeFormat: PresentationTimeFormat) { self.account = account self.theme = theme + self.componentTheme = componentTheme self.strings = strings self.sectionId = sectionId self.fontSize = fontSize @@ -87,7 +89,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { self.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) self.controllerInteraction = ChatControllerInteraction(openMessage: { _ in - return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in + return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in }, presentController: { _, _ in }, navigationController: { return nil }, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in @@ -140,14 +142,14 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { var peers = SimpleDictionary() var messages = SimpleDictionary() - peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: "Lucio", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: item.strings.Appearance_PreviewReplyAuthor, 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: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "Reinhart, we need to find you some...", 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: item.strings.Appearance_PreviewReplyText, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - let chatPresentationData = ChatPresentationData(theme: item.theme, fontSize: item.fontSize, strings: item.strings, wallpaper: item.wallpaper, timeFormat: item.timeFormat) + let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: item.componentTheme, wallpaper: item.wallpaper), fontSize: item.fontSize, strings: item.strings, timeFormat: item.timeFormat) - let item2: ChatMessageItem = ChatMessageItem(presentationData: chatPresentationData, account: item.account, chatLocation: .peer(peerId), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact), controllerInteraction: controllerInteraction, content: .message(message: 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: nil, text: "Ahh you kids today with techno music! Enjoy the classics, like Hasselhoff!", attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), read: true, selection: .none, isAdmin: false), disableDate: true) - let item1: ChatMessageItem = ChatMessageItem(presentationData: chatPresentationData, account: item.account, chatLocation: .peer(peerId), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact), controllerInteraction: controllerInteraction, content: .message(message: 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: TelegramUser(id: item.account.peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []), text: "I can't take you seriously right now. Sorry..", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), read: true, selection: .none, isAdmin: false), disableDate: true) + let item2: ChatMessageItem = ChatMessageItem(presentationData: chatPresentationData, account: item.account, chatLocation: .peer(peerId), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadNetworkType: .cellular, isRecentActions: false), controllerInteraction: controllerInteraction, content: .message(message: 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: nil, text: item.strings.Appearance_PreviewIncomingText, attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), read: true, selection: .none, isAdmin: false), disableDate: true) + let item1: ChatMessageItem = ChatMessageItem(presentationData: chatPresentationData, account: item.account, chatLocation: .peer(peerId), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadNetworkType: .cellular, isRecentActions: false), controllerInteraction: controllerInteraction, content: .message(message: 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: TelegramUser(id: item.account.peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []), text: item.strings.Appearance_PreviewOutgoingText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), read: true, selection: .none, isAdmin: false), disableDate: true) var node1: ListViewItemNode? if let current = currentNode1 { diff --git a/TelegramUI/ThemeSettingsController.swift b/TelegramUI/ThemeSettingsController.swift index 35b0394cff..1462b24681 100644 --- a/TelegramUI/ThemeSettingsController.swift +++ b/TelegramUI/ThemeSettingsController.swift @@ -9,12 +9,16 @@ private final class ThemeSettingsControllerArguments { let selectTheme: (Int32) -> Void let selectFontSize: (PresentationFontSize) -> Void let openWallpaperSettings: () -> Void + let openAccentColor: (Int32) -> Void + let openAutoNightTheme: () -> Void - init(account: Account, selectTheme: @escaping (Int32) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void) { + init(account: Account, selectTheme: @escaping (Int32) -> Void, selectFontSize: @escaping (PresentationFontSize) -> Void, openWallpaperSettings: @escaping () -> Void, openAccentColor: @escaping (Int32) -> Void, openAutoNightTheme: @escaping () -> Void) { self.account = account self.selectTheme = selectTheme self.selectFontSize = selectFontSize self.openWallpaperSettings = openWallpaperSettings + self.openAccentColor = openAccentColor + self.openAutoNightTheme = openAutoNightTheme } } @@ -28,14 +32,16 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { case fontSizeHeader(PresentationTheme, String) case fontSize(PresentationTheme, PresentationFontSize) case chatPreviewHeader(PresentationTheme, String) - case chatPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationTimeFormat) + case chatPreview(PresentationTheme, PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationStrings, PresentationTimeFormat) case wallpaper(PresentationTheme, String) + case accentColor(PresentationTheme, String, Int32) + case autoNightTheme(PresentationTheme, String, String) case themeListHeader(PresentationTheme, String) case themeItem(PresentationTheme, String, Bool, Int32) var section: ItemListSectionId { switch self { - case .chatPreviewHeader, .chatPreview, .wallpaper: + case .chatPreviewHeader, .chatPreview, .wallpaper, .accentColor, .autoNightTheme: return ThemeSettingsControllerSection.chatPreview.rawValue case .themeListHeader, .themeItem: return ThemeSettingsControllerSection.themeList.rawValue @@ -56,10 +62,14 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { return 3 case .wallpaper: return 4 - case .themeListHeader: + case .accentColor: return 5 + case .autoNightTheme: + return 6 + case .themeListHeader: + return 7 case let .themeItem(_, _, _, index): - return 6 + index + return 8 + index } } @@ -71,8 +81,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { } else { return false } - case let .chatPreview(lhsTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat): - if case let .chatPreview(rhsTheme, rhsWallpaper, rhsFontSize, rhsStrings, rhsTimeFormat) = rhs, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat { + case let .chatPreview(lhsTheme, lhsComponentTheme, lhsWallpaper, lhsFontSize, lhsStrings, lhsTimeFormat): + if case let .chatPreview(rhsTheme, rhsComponentTheme, rhsWallpaper, rhsFontSize, rhsStrings, rhsTimeFormat) = rhs, lhsComponentTheme === rhsComponentTheme, lhsTheme === rhsTheme, lhsWallpaper == rhsWallpaper, lhsFontSize == rhsFontSize, lhsStrings === rhsStrings, lhsTimeFormat == rhsTimeFormat { return true } else { return false @@ -83,6 +93,18 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { } else { return false } + case let .accentColor(lhsTheme, lhsText, lhsColor): + if case let .accentColor(rhsTheme, rhsText, rhsColor) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsColor == rhsColor { + return true + } else { + return false + } + case let .autoNightTheme(lhsTheme, lhsText, lhsValue): + if case let .autoNightTheme(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + return true + } else { + return false + } case let .themeListHeader(lhsTheme, lhsText): if case let .themeListHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true @@ -124,12 +146,20 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { }) case let .chatPreviewHeader(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) - case let .chatPreview(theme, wallpaper, fontSize, strings, timeFormat): - return ThemeSettingsChatPreviewItem(account: arguments.account, theme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, wallpaper: wallpaper, timeFormat: timeFormat) + case let .chatPreview(theme, componentTheme, wallpaper, fontSize, strings, timeFormat): + return ThemeSettingsChatPreviewItem(account: arguments.account, theme: theme, componentTheme: componentTheme, strings: strings, sectionId: self.section, fontSize: fontSize, wallpaper: wallpaper, timeFormat: timeFormat) case let .wallpaper(theme, text): return ItemListDisclosureItem(theme: theme, title: text, label: "", sectionId: self.section, style: .blocks, action: { arguments.openWallpaperSettings() }) + case let .accentColor(theme, text, color): + return ItemListDisclosureItem(theme: theme, icon: nil, title: text, label: "", labelStyle: .color(UIColor(rgb: UInt32(bitPattern: color))), sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + arguments.openAccentColor(color) + }) + case let .autoNightTheme(theme, text, value): + return ItemListDisclosureItem(theme: theme, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + arguments.openAutoNightTheme() + }) case let .themeListHeader(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) case let .themeItem(theme, title, value, index): @@ -140,19 +170,34 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { } } -private func themeSettingsControllerEntries(theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, timeFormat: PresentationTimeFormat) -> [ThemeSettingsControllerEntry] { +private func themeSettingsControllerEntries(presentationData: PresentationData, theme: PresentationTheme, themeAccentColor: Int32?, autoNightSettings: AutomaticThemeSwitchSetting, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, timeFormat: PresentationTimeFormat) -> [ThemeSettingsControllerEntry] { var entries: [ThemeSettingsControllerEntry] = [] - entries.append(.fontSizeHeader(theme, "TEXT SIZE")) - entries.append(.fontSize(theme, fontSize)) - entries.append(.chatPreviewHeader(theme, "CHAT PREVIEW")) - entries.append(.chatPreview(theme, wallpaper, fontSize, strings, timeFormat)) - entries.append(.wallpaper(theme, "Chat Background")) - entries.append(.themeListHeader(theme, "COLOR THEME")) - entries.append(.themeItem(theme, "Day Classic", theme.name == .builtin(.dayClassic), 0)) - entries.append(.themeItem(theme, "Day", theme.name == .builtin(.day), 1)) - entries.append(.themeItem(theme, "Night", theme.name == .builtin(.nightGrayscale), 2)) - entries.append(.themeItem(theme, "Night Blue", theme.name == .builtin(.nightAccent), 3)) + entries.append(.fontSizeHeader(presentationData.theme, strings.Appearance_TextSize)) + entries.append(.fontSize(presentationData.theme, fontSize)) + entries.append(.chatPreviewHeader(presentationData.theme, strings.Appearance_Preview)) + entries.append(.chatPreview(presentationData.theme, theme, wallpaper, fontSize, presentationData.strings, timeFormat)) + entries.append(.wallpaper(presentationData.theme, strings.Settings_ChatBackground)) + if theme.name == .builtin(.day) { + entries.append(.accentColor(presentationData.theme, strings.Appearance_AccentColor, themeAccentColor ?? defaultDayAccentColor)) + } + if theme.name == .builtin(.day) || theme.name == .builtin(.dayClassic) { + let title: String + switch autoNightSettings.trigger { + case .none: + title = strings.AutoNightTheme_Disabled + case .timeBased: + title = strings.AutoNightTheme_Scheduled + case .brightness: + title = strings.AutoNightTheme_Automatic + } + entries.append(.autoNightTheme(presentationData.theme, strings.Appearance_AutoNightTheme, title)) + } + entries.append(.themeListHeader(presentationData.theme, strings.Appearance_ColorTheme)) + entries.append(.themeItem(presentationData.theme, strings.Appearance_ThemeDayClassic, theme.name == .builtin(.dayClassic), 0)) + entries.append(.themeItem(presentationData.theme, strings.Appearance_ThemeDay, theme.name == .builtin(.day), 1)) + entries.append(.themeItem(presentationData.theme, strings.Appearance_ThemeNight, theme.name == .builtin(.nightGrayscale), 2)) + entries.append(.themeItem(presentationData.theme, strings.Appearance_ThemeNightBlue, theme.name == .builtin(.nightAccent), 3)) return entries } @@ -178,14 +223,23 @@ public func themeSettingsController(account: Account) -> ViewController { wallpaper = .color(0x18222D) theme = .builtin(.nightAccent) } - return PresentationThemeSettings(chatWallpaper: wallpaper, theme: theme, fontSize: current.fontSize) + return PresentationThemeSettings(chatWallpaper: wallpaper, theme: theme, themeAccentColor: current.themeAccentColor, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting) }).start() }, selectFontSize: { size in let _ = updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in - return PresentationThemeSettings(chatWallpaper: current.chatWallpaper, theme: current.theme, fontSize: size) + return PresentationThemeSettings(chatWallpaper: current.chatWallpaper, theme: current.theme, themeAccentColor: current.themeAccentColor, fontSize: size, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting) }).start() }, openWallpaperSettings: { pushControllerImpl?(ThemeGridController(account: account)) + }, openAccentColor: { color in + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + presentControllerImpl?(ThemeAccentColorActionSheet(theme: presentationData.theme, strings: presentationData.strings, currentValue: color, applyValue: { color in + let _ = updatePresentationThemeSettingsInteractively(postbox: account.postbox, { current in + return PresentationThemeSettings(chatWallpaper: current.chatWallpaper, theme: current.theme, themeAccentColor: color, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting) + }).start() + })) + }, openAutoNightTheme: { + pushControllerImpl?(themeAutoNightSettingsController(account: account)) }) let themeSettingsKey = ApplicationSpecificPreferencesKeys.presentationThemeSettings @@ -194,9 +248,9 @@ public func themeSettingsController(account: Account) -> ViewController { let previousTheme = Atomic(value: nil) - let signal = preferences + let signal = combineLatest(account.telegramApplicationContext.presentationData, preferences) |> deliverOnMainQueue - |> map { preferences -> (ItemListControllerState, (ItemListNodeState, ThemeSettingsControllerEntry.ItemGenerationArguments)) in + |> map { presentationData, preferences -> (ItemListControllerState, (ItemListNodeState, ThemeSettingsControllerEntry.ItemGenerationArguments)) in let theme: PresentationTheme let fontSize: PresentationFontSize let wallpaper: TelegramWallpaper @@ -214,7 +268,7 @@ public func themeSettingsController(account: Account) -> ViewController { case .nightAccent: theme = defaultDarkAccentPresentationTheme case .day: - theme = defaultDayPresentationTheme + theme = makeDefaultDayPresentationTheme(accentColor: settings.themeAccentColor ?? defaultDayAccentColor) } } wallpaper = settings.chatWallpaper @@ -228,10 +282,10 @@ public func themeSettingsController(account: Account) -> ViewController { timeFormat = .regular - let controllerState = ItemListControllerState(theme: theme, title: .text("Appearance"), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: strings.Common_Back)) - let listState = ItemListNodeState(entries: themeSettingsControllerEntries(theme: theme, strings: strings, wallpaper: wallpaper, fontSize: fontSize, timeFormat: timeFormat), style: .blocks, animateChanges: false) + let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Appearance_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: strings.Common_Back)) + let listState = ItemListNodeState(entries: themeSettingsControllerEntries(presentationData: presentationData, theme: theme, themeAccentColor: settings.themeAccentColor, autoNightSettings: settings.automaticThemeSwitchSetting, strings: presentationData.strings, wallpaper: wallpaper, fontSize: fontSize, timeFormat: timeFormat), style: .blocks, animateChanges: false) - if previousTheme.swap(theme) !== theme { + if previousTheme.swap(theme)?.name != theme.name { presentControllerImpl?(ThemeSettingsCrossfadeController()) } @@ -248,10 +302,10 @@ public func themeSettingsController(account: Account) -> ViewController { return controller } -private final class ThemeSettingsCrossfadeController: ViewController { +public final class ThemeSettingsCrossfadeController: ViewController { private let snapshotView: UIView? - init() { + public init() { self.snapshotView = UIScreen.main.snapshotView(afterScreenUpdates: false) super.init(navigationBarPresentationData: nil) @@ -259,11 +313,11 @@ private final class ThemeSettingsCrossfadeController: ViewController { self.statusBar.statusBarStyle = .Hide } - required init(coder aDecoder: NSCoder) { + required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func loadDisplayNode() { + override public func loadDisplayNode() { self.displayNode = ViewControllerTracingNode() self.displayNode.backgroundColor = nil @@ -273,7 +327,7 @@ private final class ThemeSettingsCrossfadeController: ViewController { } } - override func viewDidAppear(_ animated: Bool) { + override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.displayNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in diff --git a/TelegramUI/ThemeSettingsFontSizeItem.swift b/TelegramUI/ThemeSettingsFontSizeItem.swift index 3df7dafd3c..d55284a69f 100644 --- a/TelegramUI/ThemeSettingsFontSizeItem.swift +++ b/TelegramUI/ThemeSettingsFontSizeItem.swift @@ -106,9 +106,9 @@ class ThemeSettingsFontSizeItemNode: ListViewItemNode { sliderView.lineSize = 2.0 sliderView.dotSize = 5.0 sliderView.minimumValue = 0.0 - sliderView.maximumValue = 4.0 + sliderView.maximumValue = 6.0 sliderView.startValue = 0.0 - sliderView.positionsCount = 5 + sliderView.positionsCount = 7 sliderView.disablesInteractiveTransitionGestureRecognizer = true if let item = self.item, let params = self.layoutParams { let value: CGFloat @@ -117,12 +117,16 @@ class ThemeSettingsFontSizeItemNode: ListViewItemNode { value = 0.0 case .small: value = 1.0 - case .regular: + case .medium: value = 2.0 - case .large: + case .regular: value = 3.0 - case .extraLarge: + case .large: value = 4.0 + case .extraLarge: + value = 5.0 + case .extraLargeX2: + value = 6.0 } sliderView.value = value sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor @@ -247,11 +251,15 @@ class ThemeSettingsFontSizeItemNode: ListViewItemNode { case 1: fontSize = .small case 2: - fontSize = .regular + fontSize = .medium case 3: - fontSize = .large + fontSize = .regular case 4: + fontSize = .large + case 5: fontSize = .extraLarge + case 6: + fontSize = .extraLargeX2 default: fontSize = .regular } diff --git a/TelegramUI/TonePlayer.swift b/TelegramUI/TonePlayer.swift new file mode 100644 index 0000000000..5f66154579 --- /dev/null +++ b/TelegramUI/TonePlayer.swift @@ -0,0 +1,88 @@ +import Foundation +import AVFoundation +import SwiftSignalKit + +final class TonePlayerData { + fileprivate let file: AVAudioFile + + fileprivate init(file: AVAudioFile) { + self.file = file + } +} + +func loadTonePlayerData(path: String) -> TonePlayerData? { + guard let file = try? AVAudioFile(forReading: URL(fileURLWithPath: path)) else { + return nil + } + return TonePlayerData(file: file) +} + +private final class TonePlayerContext { + private let queue: Queue + private let audioEngine: AVAudioEngine + private let playerNode: AVAudioPlayerNode + + private var scheduledData: (TonePlayerData, () -> Void)? + + init(queue: Queue) { + self.queue = queue + self.audioEngine = AVAudioEngine() + self.playerNode = AVAudioPlayerNode() + self.audioEngine.attach(self.playerNode) + self.audioEngine.connect(self.playerNode, to: audioEngine.outputNode, format: nil) + self.audioEngine.prepare() + } + + func play(data: TonePlayerData, completed: @escaping () -> Void) { + self.scheduledData = (data, completed) + } + + func start() { + do { + try self.audioEngine.start() + + if let (data, completion) = self.scheduledData { + self.playerNode.scheduleFile(data.file, at: nil, completionHandler: {}) + self.playerNode.play() + completion() + } + } catch let e { + print("Couldn't start tone engine: \(e)") + } + } + + func stop() { + self.audioEngine.stop() + } +} + +final class TonePlayer { + private let queue: Queue + private let impl: QueueLocalObject + + init() { + let queue = Queue() + self.queue = queue + self.impl = .init(queue: queue, generate: { + return TonePlayerContext(queue: queue) + }) + } + + func play(data: TonePlayerData, completed: @escaping () -> Void) { + self.impl.with { impl in + impl.play(data: data, completed: completed) + } + } + + func start() { + self.impl.with({ impl in + impl.start() + }) + } + + func stop() { + self.impl.with({ impl in + impl.stop() + }) + } +} diff --git a/TelegramUI/TransformImageNode.swift b/TelegramUI/TransformImageNode.swift index e63f00ea74..238fd7f167 100644 --- a/TelegramUI/TransformImageNode.swift +++ b/TelegramUI/TransformImageNode.swift @@ -40,7 +40,9 @@ public class TransformImageNode: ASDisplayNode { func setSignal(_ signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>, dispatchOnDisplayLink: Bool = true) { let argumentsPromise = self.argumentsPromise - let result = combineLatest(signal, argumentsPromise.get()) |> deliverOn(Queue.concurrentDefaultQueue()) |> mapToThrottled { transform, arguments -> Signal in + let result = combineLatest(signal, argumentsPromise.get()) + |> deliverOn(Queue.concurrentDefaultQueue()) + |> mapToThrottled { transform, arguments -> Signal in return deferred { if let context = transform(arguments) { return Signal.single(context.generateImage()) diff --git a/TelegramUI/TransformOutgoingMessageMedia.swift b/TelegramUI/TransformOutgoingMessageMedia.swift index a8e33d48ee..fa624be5e7 100644 --- a/TelegramUI/TransformOutgoingMessageMedia.swift +++ b/TelegramUI/TransformOutgoingMessageMedia.swift @@ -8,7 +8,7 @@ public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, me switch media.media { case let file as TelegramMediaFile: let signal = Signal { subscriber in - let fetch = postbox.mediaBox.fetchedResource(file.resource, parameters: nil).start() + let fetch = fetchedMediaResource(postbox: postbox, reference: media.resourceReference(file.resource)).start() let data = postbox.mediaBox.resourceData(file.resource, option: .complete(waitUntilFetchStatus: true)).start(next: { next in subscriber.putNext(next) if next.complete { @@ -83,6 +83,25 @@ public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, me subscriber.putCompletion() } + return EmptyDisposable + } |> runOn(opportunistic ? Queue.mainQueue() : Queue.concurrentDefaultQueue()) + } else if file.mimeType.hasPrefix("video/") { + return Signal { subscriber in + if let scaledImage = generateVideoFirstFrame(data.path, maxDimensions: CGSize(width: 90.0, height: 90.0)), let thumbnailData = UIImageJPEGRepresentation(scaledImage, 0.6) { + let thumbnailResource = LocalFileMediaResource(fileId: arc4random64()) + postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData) + + let scaledImageSize = CGSize(width: scaledImage.size.width * scaledImage.scale, height: scaledImage.size.height * scaledImage.scale) + + let updatedFile = file.withUpdatedSize(data.size).withUpdatedPreviewRepresentations([TelegramMediaImageRepresentation(dimensions: scaledImageSize, resource: thumbnailResource)]) + subscriber.putNext(.standalone(media: updatedFile)) + subscriber.putCompletion() + } else { + let updatedFile = file.withUpdatedSize(data.size) + subscriber.putNext(.standalone(media: updatedFile)) + subscriber.putCompletion() + } + return EmptyDisposable } |> runOn(opportunistic ? Queue.mainQueue() : Queue.concurrentDefaultQueue()) } else { diff --git a/TelegramUI/TwoStepVerificationUnlockController.swift b/TelegramUI/TwoStepVerificationUnlockController.swift index 07b481f92a..d701bf2aee 100644 --- a/TelegramUI/TwoStepVerificationUnlockController.swift +++ b/TelegramUI/TwoStepVerificationUnlockController.swift @@ -333,10 +333,10 @@ func twoStepVerificationUnlockSettingsController(account: Account, mode: TwoStep updateState { $0.withUpdatedChecking(false) } - presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: "An error occured. Please try again later.", actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) })) } else { - presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: "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.", actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: "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.", actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } case .notSet: break @@ -454,6 +454,8 @@ func twoStepVerificationUnlockSettingsController(account: Account, mode: TwoStep })) }) + var initialFocusImpl: (() -> Void)? + let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get(), dataPromise.get() |> deliverOnMainQueue) |> deliverOnMainQueue |> map { presentationData, state, data -> (ItemListControllerState, (ItemListNodeState, TwoStepVerificationUnlockSettingsEntry.ItemGenerationArguments)) in @@ -497,11 +499,11 @@ func twoStepVerificationUnlockSettingsController(account: Account, mode: TwoStep let text: String switch error { case .limitExceeded: - text = "You have entered invalid password too many times. Please try again later." + text = presentationData.strings.LoginPassword_FloodError case .invalidPassword: - text = "Invalid password. Please try again." + text = presentationData.strings.LoginPassword_InvalidPasswordError case .generic: - text = "An error occured. Please try again later." + text = presentationData.strings.Login_UnknownError } presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})]), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) @@ -537,6 +539,25 @@ func twoStepVerificationUnlockSettingsController(account: Account, mode: TwoStep controller.present(c, in: .window(.root), with: p) } } + initialFocusImpl = { [weak controller] in + guard let controller = controller, controller.didAppearOnce else { + return + } + var resultItemNode: ItemListSingleLineInputItemNode? + let _ = controller.frameForItemNode({ itemNode in + if let itemNode = itemNode as? ItemListSingleLineInputItemNode, let tag = itemNode.tag, tag.isEqual(to: TwoStepVerificationUnlockSettingsEntryTag.password) { + resultItemNode = itemNode + return true + } + return false + }) + if let resultItemNode = resultItemNode { + resultItemNode.focus() + } + } + controller.didAppear = { + initialFocusImpl?() + } return controller } diff --git a/TelegramUI/UniversalVideoCalleryItem.swift b/TelegramUI/UniversalVideoCalleryItem.swift index 1dedf40688..eefdd1c835 100644 --- a/TelegramUI/UniversalVideoCalleryItem.swift +++ b/TelegramUI/UniversalVideoCalleryItem.swift @@ -233,7 +233,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if let value = value, !value.duration.isZero { return value } else { - return MediaPlayerStatus(generationTimestamp: 0.0, duration: max(Double(item.content.duration), 0.01), dimensions: CGSize(), timestamp: 0.0, seekId: 0, status: .paused) + return MediaPlayerStatus(generationTimestamp: 0.0, duration: max(Double(item.content.duration), 0.01), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused) } }) @@ -273,7 +273,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } if initialBuffering { - strongSelf.statusNode.transitionToState(.progress(color: .white, value: nil, cancelEnabled: false), animated: false, completion: {}) + strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: nil, cancelEnabled: false), animated: false, completion: {}) } else { strongSelf.statusNode.transitionToState(.play(.white), animated: false, completion: {}) diff --git a/TelegramUI/UniversalVideoNode.swift b/TelegramUI/UniversalVideoNode.swift index b227858634..b67d45d08f 100644 --- a/TelegramUI/UniversalVideoNode.swift +++ b/TelegramUI/UniversalVideoNode.swift @@ -20,6 +20,7 @@ protocol UniversalVideoContentNode: class { func playOnceWithSound(playAndRecord: Bool) func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool) func continuePlayingWithoutSound() + func setBaseRate(_ baseRate: Double) func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int func removePlaybackCompleted(_ index: Int) func fetchControl(_ control: UniversalVideoNodeFetchControl) @@ -284,6 +285,14 @@ final class UniversalVideoNode: ASDisplayNode { }) } + func setBaseRate(_ baseRate: Double) { + self.manager.withUniversalVideoContent(id: self.content.id, { contentNode in + if let contentNode = contentNode { + contentNode.setBaseRate(baseRate) + } + }) + } + func continuePlayingWithoutSound() { self.manager.withUniversalVideoContent(id: self.content.id, { contentNode in if let contentNode = contentNode { diff --git a/TelegramUI/UrlEscaping.swift b/TelegramUI/UrlEscaping.swift index 50d39d3408..ea3402bb11 100644 --- a/TelegramUI/UrlEscaping.swift +++ b/TelegramUI/UrlEscaping.swift @@ -1,5 +1,12 @@ import Foundation +func doesUrlMatchText(url: String, text: String) -> Bool { + if url == text { + return true + } + return false +} + extension CharacterSet { static let urlQueryValueAllowed: CharacterSet = { let generalDelimitersToEncode = ":#[]@" diff --git a/TelegramUI/UserInfoController.swift b/TelegramUI/UserInfoController.swift index 9f2d0d87aa..80bec046f1 100644 --- a/TelegramUI/UserInfoController.swift +++ b/TelegramUI/UserInfoController.swift @@ -752,7 +752,7 @@ public func userInfoController(account: Account, peerId: PeerId) -> ViewControll if let value = value { title = muteForIntervalString(strings: presentationData.strings, value: value) } else { - title = "Default" + title = presentationData.strings.UserInfo_NotificationsDefault } items.append(ActionSheetButtonItem(title: title, action: { dismissAction() @@ -1171,10 +1171,56 @@ public func userInfoController(account: Account, peerId: PeerId) -> ViewControll return nil } })) - } } } + controller.didAppear = { [weak controller] in + guard let controller = controller else { + return + } + + var resultItemNode: ItemListAvatarAndNameInfoItemNode? + let _ = controller.frameForItemNode({ itemNode in + if let itemNode = itemNode as? ItemListAvatarAndNameInfoItemNode { + resultItemNode = itemNode + return true + } + return false + }) + if let resultItemNode = resultItemNode, let callButtonFrame = resultItemNode.callButtonFrame { + let _ = (ApplicationSpecificNotice.getProfileCallTips(postbox: account.postbox) + |> deliverOnMainQueue).start(next: { [weak controller] counter in + guard let controller = controller else { + return + } + + var displayTip = false + if counter == 0 { + displayTip = true + } else if counter < 3 && arc4random_uniform(4) == 1 { + displayTip = true + } + if !displayTip { + return + } + let _ = ApplicationSpecificNotice.incrementProfileCallTips(postbox: account.postbox).start() + + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + let text: String = presentationData.strings.UserInfo_TapToCall + + let tooltipController = TooltipController(text: text, dismissByTapOutside: true) + tooltipController.dismissed = { + } + controller.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak resultItemNode] in + if let resultItemNode = resultItemNode { + return (resultItemNode, callButtonFrame) + } + return nil + })) + }) + } + } + return controller } diff --git a/TelegramUI/UserInfoEditingPhoneItem.swift b/TelegramUI/UserInfoEditingPhoneItem.swift index e47ff5086a..ae0fb1e415 100644 --- a/TelegramUI/UserInfoEditingPhoneItem.swift +++ b/TelegramUI/UserInfoEditingPhoneItem.swift @@ -276,7 +276,7 @@ class UserInfoEditingPhoneItemNode: ItemListRevealOptionsItemNode, ItemListItemN transition.updateFrame(node: self.phoneNode, frame: phoneFrame) } - override func revealOptionSelected(_ option: ItemListRevealOption) { + override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { self.item?.delete() } diff --git a/TelegramUI/VoiceCallDataSavingController.swift b/TelegramUI/VoiceCallDataSavingController.swift index 49476e5812..3b3814aa4b 100644 --- a/TelegramUI/VoiceCallDataSavingController.swift +++ b/TelegramUI/VoiceCallDataSavingController.swift @@ -130,7 +130,9 @@ func voiceCallDataSavingController(account: Account) -> ViewController { let arguments = VoiceCallDataSavingControllerArguments(updateSelection: { option in let _ = updateVoiceCallSettingsSettingsInteractively(postbox: account.postbox, { current in - return current.withUpdatedDataSaving(option) + var current = current + current.dataSaving = option + return current }).start() }) diff --git a/TelegramUI/VoiceCallSettings.swift b/TelegramUI/VoiceCallSettings.swift index 93b599c494..08cf60b40c 100644 --- a/TelegramUI/VoiceCallSettings.swift +++ b/TelegramUI/VoiceCallSettings.swift @@ -8,23 +8,37 @@ public enum VoiceCallDataSaving: Int32 { case always } +public enum VoiceCallP2PMode: Int32 { + case never = 0 + case contacts = 1 + case always = 2 +} + public struct VoiceCallSettings: PreferencesEntry, Equatable { - public let dataSaving: VoiceCallDataSaving + public var dataSaving: VoiceCallDataSaving + public var p2pMode: VoiceCallP2PMode + public var enableSystemIntegration: Bool public static var defaultSettings: VoiceCallSettings { - return VoiceCallSettings(dataSaving: .never) + return VoiceCallSettings(dataSaving: .never, p2pMode: .always, enableSystemIntegration: true) } - init(dataSaving: VoiceCallDataSaving) { + init(dataSaving: VoiceCallDataSaving, p2pMode: VoiceCallP2PMode, enableSystemIntegration: Bool) { self.dataSaving = dataSaving + self.p2pMode = p2pMode + self.enableSystemIntegration = enableSystemIntegration } public init(decoder: PostboxDecoder) { self.dataSaving = VoiceCallDataSaving(rawValue: decoder.decodeInt32ForKey("ds", orElse: 0))! + self.p2pMode = VoiceCallP2PMode(rawValue: decoder.decodeInt32ForKey("p2pMode", orElse: 2))! + self.enableSystemIntegration = decoder.decodeInt32ForKey("enableSystemIntegration", orElse: 1) != 0 } public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.dataSaving.rawValue, forKey: "ds") + encoder.encodeInt32(self.p2pMode.rawValue, forKey: "p2pMode") + encoder.encodeInt32(self.enableSystemIntegration ? 1 : 0, forKey: "enableSystemIntegration") } public func isEqual(to: PreferencesEntry) -> Bool { @@ -36,11 +50,16 @@ public struct VoiceCallSettings: PreferencesEntry, Equatable { } public static func ==(lhs: VoiceCallSettings, rhs: VoiceCallSettings) -> Bool { - return lhs.dataSaving == rhs.dataSaving - } - - func withUpdatedDataSaving(_ dataSaving: VoiceCallDataSaving) -> VoiceCallSettings { - return VoiceCallSettings(dataSaving: dataSaving) + if lhs.dataSaving != rhs.dataSaving { + return false + } + if lhs.p2pMode != rhs.p2pMode { + return false + } + if lhs.enableSystemIntegration != rhs.enableSystemIntegration { + return false + } + return true } } diff --git a/TelegramUI/WebEmbedVideoContent.swift b/TelegramUI/WebEmbedVideoContent.swift index 879fe0d835..53d223c465 100644 --- a/TelegramUI/WebEmbedVideoContent.swift +++ b/TelegramUI/WebEmbedVideoContent.swift @@ -178,7 +178,7 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte self._ready.set(.single(Void())) } - self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, seekId: self.seekId, status: .buffering(initial: true, whilePlaying: true))) + self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: self.seekId, status: .buffering(initial: true, whilePlaying: true))) let stateSignal = self.playerView.stateSignal()! self.statusDisposable = (Signal { subscriber in @@ -192,7 +192,7 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte } else { status = .paused } - subscriber.putNext(MediaPlayerStatus(generationTimestamp: 0.0, duration: next.duration, dimensions: CGSize(), timestamp: max(0.0, next.position), seekId: 0, status: status)) + subscriber.putNext(MediaPlayerStatus(generationTimestamp: 0.0, duration: next.duration, dimensions: CGSize(), timestamp: max(0.0, next.position), baseRate: 1.0, seekId: 0, status: status)) } }) return ActionDisposable { @@ -206,7 +206,7 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte } } strongSelf.initializedStatus = true - strongSelf._status.set(MediaPlayerStatus(generationTimestamp: value.generationTimestamp, duration: value.duration, dimensions: CGSize(), timestamp: value.timestamp, seekId: strongSelf.seekId, status: value.status)) + strongSelf._status.set(MediaPlayerStatus(generationTimestamp: value.generationTimestamp, duration: value.duration, dimensions: CGSize(), timestamp: value.timestamp, baseRate: 1.0, seekId: strongSelf.seekId, status: value.status)) } }) @@ -229,7 +229,7 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte func play() { assert(Queue.mainQueue().isCurrent()) if !self.initializedStatus { - self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, seekId: self.seekId, status: .buffering(initial: true, whilePlaying: true))) + self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: self.seekId, status: .buffering(initial: true, whilePlaying: true))) } else { self.playerView.playVideo() } @@ -238,7 +238,7 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte func pause() { assert(Queue.mainQueue().isCurrent()) if !self.initializedStatus { - self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, seekId: self.seekId, status: .paused)) + self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: self.seekId, status: .paused)) } self.playerView.pauseVideo() } @@ -276,6 +276,9 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte func continuePlayingWithoutSound() { } + func setBaseRate(_ baseRate: Double) { + } + func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int { return self.playbackCompletedListeners.add(f) } diff --git a/TelegramUI/WebpagePreviewAccessoryPanelNode.swift b/TelegramUI/WebpagePreviewAccessoryPanelNode.swift index 88dea782b3..1b0d276809 100644 --- a/TelegramUI/WebpagePreviewAccessoryPanelNode.swift +++ b/TelegramUI/WebpagePreviewAccessoryPanelNode.swift @@ -86,7 +86,7 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode { } func replaceWebpage(url: String, webpage: TelegramMediaWebpage) { - if self.url != url || !self.webpage.isEqual(webpage) { + if self.url != url || !self.webpage.isEqual(to: webpage) { self.url = url self.webpage = webpage self.updateWebpage() diff --git a/third-party/RMIntro/core/animations.c b/third-party/RMIntro/core/animations.c index a4a7755e4d..6755a80488 100644 --- a/third-party/RMIntro/core/animations.c +++ b/third-party/RMIntro/core/animations.c @@ -1497,7 +1497,7 @@ void draw_safe(int type, float alpha, float screw_alpha) } -static float backgroundColor[3] = {0.0, 0.0, 0.0}; +static float backgroundColor[3] = {1.0, 1.0, 1.0}; void set_intro_background_color(float r, float g, float b) { backgroundColor[0] = r;