diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index fd700af66c..89ae77074f 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -35,7 +35,6 @@ D01C99781F4F382C00DCFAF6 /* InstantPageSettingsItemTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C99771F4F382C00DCFAF6 /* InstantPageSettingsItemTheme.swift */; }; D02660941F34CE5C000E2DC5 /* LegacyLocationVenueIconDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02660931F34CE5C000E2DC5 /* LegacyLocationVenueIconDataSource.swift */; }; D033C60B1F0D306E0044EABA /* TelegramVideoNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033C60A1F0D306E0044EABA /* TelegramVideoNode.swift */; }; - D03E838F1EC10FE5001A6ED9 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = D0FC40831D5B8E7400261D9D /* Info.plist */; }; D0471B491EFD59170074D609 /* BotCheckoutControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0471B481EFD59170074D609 /* BotCheckoutControllerNode.swift */; }; D0471B4B1EFD64AC0074D609 /* BotCheckoutHeaderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0471B4A1EFD64AC0074D609 /* BotCheckoutHeaderItem.swift */; }; D0471B4F1EFD84600074D609 /* BotCheckoutPriceItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0471B4E1EFD84600074D609 /* BotCheckoutPriceItem.swift */; }; @@ -49,6 +48,10 @@ D0471B601EFEB5A70074D609 /* BotPaymentTextItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0471B5F1EFEB5A70074D609 /* BotPaymentTextItemNode.swift */; }; D0471B621EFEB5B70074D609 /* BotPaymentSwitchItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0471B611EFEB5B70074D609 /* BotPaymentSwitchItemNode.swift */; }; D0471B641EFEB5CB0074D609 /* BotPaymentItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0471B631EFEB5CB0074D609 /* BotPaymentItemNode.swift */; }; + D0477D1B1F617E5800412B44 /* UniversalVideoNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0477D1A1F617E5800412B44 /* UniversalVideoNode.swift */; }; + D0477D1D1F617E8900412B44 /* NativeVideoContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0477D1C1F617E8900412B44 /* NativeVideoContent.swift */; }; + D0477D1F1F619E0700412B44 /* GalleryVideoDecoration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0477D1E1F619E0700412B44 /* GalleryVideoDecoration.swift */; }; + D0477D211F61A47600412B44 /* UniversalVideoContentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0477D201F61A47600412B44 /* UniversalVideoContentManager.swift */; }; D048EA851F4F295300188713 /* InstantPageSettingsBacklightItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D048EA841F4F295300188713 /* InstantPageSettingsBacklightItemNode.swift */; }; D048EA871F4F296400188713 /* InstantPageSettingsFontSizeItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D048EA861F4F296400188713 /* InstantPageSettingsFontSizeItemNode.swift */; }; D048EA891F4F297500188713 /* InstantPageSettingsFontFamilyItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D048EA881F4F297500188713 /* InstantPageSettingsFontFamilyItemNode.swift */; }; @@ -64,6 +67,8 @@ D05677531F4CA0D0001B723E /* InstantPagePeerReferenceNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05677521F4CA0D0001B723E /* InstantPagePeerReferenceNode.swift */; }; D0642EFC1F3E1E7B00792790 /* ChatHistoryNavigationButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0642EFB1F3E1E7B00792790 /* ChatHistoryNavigationButtons.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 */; }; D0754D1E1EEDDF6200884F6E /* ChatMessageAttachedContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0754D1D1EEDDF6200884F6E /* ChatMessageAttachedContentNode.swift */; }; D0754D201EEDEBA000884F6E /* ChatMessageGameBubbleContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0754D1F1EEDEBA000884F6E /* ChatMessageGameBubbleContentNode.swift */; }; D0754D221EEDF89900884F6E /* ChatMessageInvoiceBubbleContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0754D211EEDF89900884F6E /* ChatMessageInvoiceBubbleContentNode.swift */; }; @@ -76,6 +81,7 @@ D079FCE91F06A76C0038FADE /* Notices.swift in Sources */ = {isa = PBXBuildFile; fileRef = D079FCE81F06A76C0038FADE /* Notices.swift */; }; D07BCBFE1F2B792300ED97AA /* LegacyComponents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D07BCBFD1F2B792300ED97AA /* LegacyComponents.framework */; }; D080B27F1F4C7C6000AA3847 /* InstantPageManagedMediaId.swift in Sources */ = {isa = PBXBuildFile; fileRef = D080B27E1F4C7C6000AA3847 /* InstantPageManagedMediaId.swift */; }; + D08803C51F6064CF00DD7951 /* TelegramUI.h in Headers */ = {isa = PBXBuildFile; fileRef = D0FC40821D5B8E7400261D9D /* TelegramUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; D089F78A1F4E0C14000E934D /* InstantPagePresentationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D089F7891F4E0C14000E934D /* InstantPagePresentationSettings.swift */; }; D099D74D1EEFEE1500A3128C /* GameController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D74C1EEFEE1500A3128C /* GameController.swift */; }; D099D74F1EEFEE6A00A3128C /* GameControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D74E1EEFEE6A00A3128C /* GameControllerNode.swift */; }; @@ -87,6 +93,8 @@ D09E63AA1F0FC681003444CD /* PictureInPictureVideoControlsNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09E63A91F0FC681003444CD /* PictureInPictureVideoControlsNode.swift */; }; D09E63B01F1010FE003444CD /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D09E63AF1F1010FE003444CD /* Contacts.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; D09E63B21F11289A003444CD /* PassKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D09E63B11F11289A003444CD /* PassKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + D0A8BB9F1F61EC9D000F03FD /* ChatTextInputPanelNodeOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A8BB9E1F61EC9D000F03FD /* ChatTextInputPanelNodeOperators.swift */; }; + D0A8BBA11F61EE83000F03FD /* UniversalVideoCalleryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A8BBA01F61EE83000F03FD /* UniversalVideoCalleryItem.swift */; }; D0ACCB1A1EC5E0C20079D8BF /* CallControllerKeyPreviewNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ACCB191EC5E0C20079D8BF /* CallControllerKeyPreviewNode.swift */; }; D0ACCB1C1EC5FF4B0079D8BF /* ChatMessageCallBubbleContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ACCB1B1EC5FF4B0079D8BF /* ChatMessageCallBubbleContentNode.swift */; }; D0AF7C461ED84BC500CD8E0F /* LanguageSelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AF7C451ED84BC500CD8E0F /* LanguageSelectionController.swift */; }; @@ -304,7 +312,6 @@ D0EC6C9D1EB9F42E00EBF1C3 /* ooura_fft_neon.cc in Sources */ = {isa = PBXBuildFile; fileRef = D0EC6C241EB9F42E00EBF1C3 /* ooura_fft_neon.cc */; }; D0EC6C9E1EB9F42E00EBF1C3 /* ooura_fft_sse2.cc in Sources */ = {isa = PBXBuildFile; fileRef = D0EC6C251EB9F42E00EBF1C3 /* ooura_fft_sse2.cc */; }; D0EC6C9F1EB9F42E00EBF1C3 /* cpu_features.cc in Sources */ = {isa = PBXBuildFile; fileRef = D0EC6C2F1EB9F42E00EBF1C3 /* cpu_features.cc */; }; - D0EC6CA91EB9F4CC00EBF1C3 /* TelegramUI.h in Headers */ = {isa = PBXBuildFile; fileRef = D0EC6CA71EB9F4CC00EBF1C3 /* TelegramUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; D0EC6CAE1EB9F58800EBF1C3 /* animations.c in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2CC1E48797500650E93 /* animations.c */; }; D0EC6CAF1EB9F58800EBF1C3 /* buffer.c in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2CE1E48797500650E93 /* buffer.c */; }; D0EC6CB01EB9F58800EBF1C3 /* objects.c in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2D41E48797500650E93 /* objects.c */; }; @@ -892,8 +899,6 @@ D0F0AAE21EC20EF8005EE2A5 /* CallControllerStatusNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0AAE11EC20EF8005EE2A5 /* CallControllerStatusNode.swift */; }; D0F0AAE41EC21AAA005EE2A5 /* CallControllerButtonsNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0AAE31EC21AAA005EE2A5 /* CallControllerButtonsNode.swift */; }; D0F0AAE61EC21B68005EE2A5 /* CallControllerButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F0AAE51EC21B68005EE2A5 /* CallControllerButton.swift */; }; - D0F0AAE91EC22658005EE2A5 /* SecretChatKeyVisualization.h in Headers */ = {isa = PBXBuildFile; fileRef = D00C7CF51E37BF680080C3D5 /* SecretChatKeyVisualization.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D0F0AAEA1EC254A8005EE2A5 /* NumberPluralizationForm.h in Headers */ = {isa = PBXBuildFile; fileRef = D0EAE0A11EB212DE005296C1 /* NumberPluralizationForm.h */; settings = {ATTRIBUTES = (Public, ); }; }; D0F67FF01EE6B8A8000E5906 /* ChannelMembersSearchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F67FEF1EE6B8A8000E5906 /* ChannelMembersSearchController.swift */; }; D0F67FF21EE6B915000E5906 /* ChannelMembersSearchControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F67FF11EE6B915000E5906 /* ChannelMembersSearchControllerNode.swift */; }; D0F67FF41EE6C10F000E5906 /* ChannelMembersSearchContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F67FF31EE6C10F000E5906 /* ChannelMembersSearchContainerNode.swift */; }; @@ -1071,6 +1076,10 @@ D0471B5F1EFEB5A70074D609 /* BotPaymentTextItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BotPaymentTextItemNode.swift; sourceTree = ""; }; D0471B611EFEB5B70074D609 /* BotPaymentSwitchItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BotPaymentSwitchItemNode.swift; sourceTree = ""; }; D0471B631EFEB5CB0074D609 /* BotPaymentItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BotPaymentItemNode.swift; sourceTree = ""; }; + D0477D1A1F617E5800412B44 /* UniversalVideoNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniversalVideoNode.swift; sourceTree = ""; }; + D0477D1C1F617E8900412B44 /* NativeVideoContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeVideoContent.swift; sourceTree = ""; }; + D0477D1E1F619E0700412B44 /* GalleryVideoDecoration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryVideoDecoration.swift; sourceTree = ""; }; + D0477D201F61A47600412B44 /* UniversalVideoContentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniversalVideoContentManager.swift; sourceTree = ""; }; D04791661E79A22000F18979 /* ItemListStickerPackItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListStickerPackItem.swift; sourceTree = ""; }; D0486F091E523C8500091F0C /* GroupInfoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupInfoController.swift; sourceTree = ""; }; D048EA841F4F295300188713 /* InstantPageSettingsBacklightItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageSettingsBacklightItemNode.swift; sourceTree = ""; }; @@ -1209,6 +1218,8 @@ 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 = ""; }; 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 = ""; }; D06E4AC31E84806300627D1D /* FetchPhotoLibraryImageResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchPhotoLibraryImageResource.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 = ""; }; @@ -1296,6 +1307,8 @@ D0A11BFB1E7840750081CE03 /* ChangePhoneNumberController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangePhoneNumberController.swift; sourceTree = ""; }; D0A11BFD1E7840A50081CE03 /* ChangePhoneNumberControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangePhoneNumberControllerNode.swift; sourceTree = ""; }; D0A749961E3AA25200AD786E /* NotificationSoundSelection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationSoundSelection.swift; sourceTree = ""; }; + D0A8BB9E1F61EC9D000F03FD /* ChatTextInputPanelNodeOperators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTextInputPanelNodeOperators.swift; sourceTree = ""; }; + D0A8BBA01F61EE83000F03FD /* UniversalVideoCalleryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniversalVideoCalleryItem.swift; sourceTree = ""; }; D0AB0BB01D6718DA002C78E7 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; }; D0AB0BB21D6718EB002C78E7 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; D0AB0BB41D6718F1002C78E7 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; @@ -1736,8 +1749,6 @@ D0EC6C2F1EB9F42E00EBF1C3 /* cpu_features.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = cpu_features.cc; sourceTree = ""; }; D0EC6C301EB9F42E00EBF1C3 /* typedefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = typedefs.h; sourceTree = ""; }; D0EC6CA51EB9F4CC00EBF1C3 /* TelegramUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TelegramUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D0EC6CA71EB9F4CC00EBF1C3 /* TelegramUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TelegramUI.h; sourceTree = ""; }; - D0EC6CA81EB9F4CC00EBF1C3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D0EC6E941EB9F5B300EBF1C3 /* MtProtoKitDynamic.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MtProtoKitDynamic.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-diblohvjozhgaifjcniwdlixlilx/Build/Products/Debug-iphonesimulator/MtProtoKitDynamic.framework"; sourceTree = ""; }; D0EC6E951EB9F5B300EBF1C3 /* TelegramLegacyComponents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TelegramLegacyComponents.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-diblohvjozhgaifjcniwdlixlilx/Build/Products/Debug-iphonesimulator/TelegramLegacyComponents.framework"; sourceTree = ""; }; D0EC6EBC1EBA100F00EBF1C3 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; }; @@ -2256,6 +2267,19 @@ name = Resources; sourceTree = ""; }; + D0477D191F617E4B00412B44 /* Video */ = { + isa = PBXGroup; + children = ( + D0477D1A1F617E5800412B44 /* UniversalVideoNode.swift */, + D06BEC761F62F68B0035A545 /* OverlayUniversalVideoNode.swift */, + D0477D1C1F617E8900412B44 /* NativeVideoContent.swift */, + D0477D1E1F619E0700412B44 /* GalleryVideoDecoration.swift */, + D06BEC891F6597A80035A545 /* OverlayVideoDecoration.swift */, + D0477D201F61A47600412B44 /* UniversalVideoContentManager.swift */, + ); + name = Video; + sourceTree = ""; + }; D049EAE01E447AB700A2CD3A /* Stickers */ = { isa = PBXGroup; children = ( @@ -3598,15 +3622,6 @@ path = source; sourceTree = ""; }; - D0EC6CA61EB9F4CC00EBF1C3 /* TelegramUI */ = { - isa = PBXGroup; - children = ( - D0EC6CA71EB9F4CC00EBF1C3 /* TelegramUI.h */, - D0EC6CA81EB9F4CC00EBF1C3 /* Info.plist */, - ); - path = TelegramUI; - sourceTree = ""; - }; D0EC6EBE1EBA10BB00EBF1C3 /* webrtc_dsp */ = { isa = PBXGroup; children = ( @@ -4038,6 +4053,7 @@ children = ( D01776B61F1D6CCF0044446D /* Radial Status */, D0F69DCA1D6B89F20046BCD6 /* Search */, + D0477D191F617E4B00412B44 /* Video */, D0F69DC81D6B89EB0046BCD6 /* ImageNode.swift */, D0F69DC61D6B89E70046BCD6 /* TransformImageNode.swift */, D0F69DC41D6B89E10046BCD6 /* RadialProgressNode.swift */, @@ -4221,6 +4237,7 @@ isa = PBXGroup; children = ( D0F69E401D6B8B7E0046BCD6 /* ChatTextInputPanelNode.swift */, + D0A8BB9E1F61EC9D000F03FD /* ChatTextInputPanelNodeOperators.swift */, D01F66121DE8903300345CBE /* ChatTextInputMediaRecordingButton.swift */, D039EB021DEAEFEE00886EBC /* ChatTextInputAudioRecordingOverlayButton.swift */, D039EB071DEC725600886EBC /* ChatTextInputAudioRecordingTimeNode.swift */, @@ -4294,6 +4311,7 @@ D0575AFB1EA104A6006F2541 /* PeerAvatarImageGalleryItem.swift */, D0104F291F471DA6004E4881 /* InstantImageGalleryItem.swift */, D0104F2B1F471EEB004E4881 /* InstantPageGalleryFooterContentNode.swift */, + D0A8BBA01F61EE83000F03FD /* UniversalVideoCalleryItem.swift */, ); name = Items; sourceTree = ""; @@ -4453,7 +4471,6 @@ D073CE611DCBBE09007511FD /* Sounds */, D0FC40811D5B8E7400261D9D /* TelegramUI */, D0FC408C1D5B8E7500261D9D /* TelegramUITests */, - D0EC6CA61EB9F4CC00EBF1C3 /* TelegramUI */, D0FC40801D5B8E7400261D9D /* Products */, D08D45281D5E340200A7428A /* Frameworks */, ); @@ -4521,6 +4538,7 @@ D0EC6FE81EBA138700EBF1C3 /* ooura_fft.h in Headers */, D0E9BADC1F0574D800F079A4 /* PKPayment+Stripe.h in Headers */, D0E9BA491F0559B600F079A4 /* STPPaymentMethod.h in Headers */, + D08803C51F6064CF00DD7951 /* TelegramUI.h in Headers */, D0E9BA171F05574500F079A4 /* STPPaymentCardTextFieldViewModel.h in Headers */, D0EB42001F30ED4F00838FE6 /* LegacyImageProcessors.h in Headers */, D0E9BA291F0557A600F079A4 /* STPFormEncodable.h in Headers */, @@ -4532,14 +4550,11 @@ D0E9BA601F055A4300F079A4 /* STPDelegateProxy.h in Headers */, D0E9BADF1F0574D800F079A4 /* STPDispatchFunctions.h in Headers */, D0E9BACB1F05738600F079A4 /* STPAPIPostRequest.h in Headers */, - D0EC6CA91EB9F4CC00EBF1C3 /* TelegramUI.h in Headers */, D0E9BA561F055A0B00F079A4 /* STPFormTextField.h in Headers */, - D0F0AAEA1EC254A8005EE2A5 /* NumberPluralizationForm.h in Headers */, D0E9BABE1F05735F00F079A4 /* STPPaymentConfiguration+Private.h in Headers */, D0E9BACA1F05738600F079A4 /* STPAPIClient+Private.h in Headers */, D0E9BA251F05578900F079A4 /* STPCardBrand.h in Headers */, D0E9BAC81F05738600F079A4 /* STPAPIClient+ApplePay.h in Headers */, - D0F0AAE91EC22658005EE2A5 /* SecretChatKeyVisualization.h in Headers */, D0E9BA451F0559A500F079A4 /* STPAPIResponseDecodable.h in Headers */, D0E9BA201F05577700F079A4 /* STPCardParams.h in Headers */, D0E9BA151F05574500F079A4 /* STPCardValidator.h in Headers */, @@ -4663,7 +4678,6 @@ D0E9BAAF1F056F4C00F079A4 /* stp_card_mastercard@2x.png in Resources */, D0C12A1D1F33A85600B3F66D /* ChatWallpaperBuiltin0.jpg in Resources */, D0E9BAAC1F056F4C00F079A4 /* stp_card_jcb@3x.png in Resources */, - D03E838F1EC10FE5001A6ED9 /* Info.plist in Resources */, D0E9BA911F056F4C00F079A4 /* stp_card_amex@2x.png in Resources */, D0E9BA931F056F4C00F079A4 /* stp_card_amex_template@2x.png in Resources */, D0E9BAA91F056F4C00F079A4 /* stp_card_form_front@2x.png in Resources */, @@ -4739,6 +4753,7 @@ D089F78A1F4E0C14000E934D /* InstantPagePresentationSettings.swift in Sources */, D01776B51F1D6CCC0044446D /* RadialStatusContentNode.swift in Sources */, D0EC6FC81EBA135100EBF1C3 /* cross_correlation.c in Sources */, + D0477D1F1F619E0700412B44 /* GalleryVideoDecoration.swift in Sources */, D01C99781F4F382C00DCFAF6 /* InstantPageSettingsItemTheme.swift in Sources */, D0EC6FB11EBA112600EBF1C3 /* ooura_fft_neon.cc in Sources */, D0EC6CC21EB9F58800EBF1C3 /* LegacyEmptyController.swift in Sources */, @@ -4787,6 +4802,7 @@ D01BAA1A1ECC8E0D00295217 /* CallListControllerNode.swift in Sources */, D0EC6FDF1EBA135100EBF1C3 /* resample_by_2_internal.c in Sources */, D0EC6CDD1EB9F58800EBF1C3 /* PresentationCallManager.swift in Sources */, + D0A8BBA11F61EE83000F03FD /* UniversalVideoCalleryItem.swift in Sources */, D0EC6CDE1EB9F58800EBF1C3 /* CompomentsThemes.swift in Sources */, D0642EFC1F3E1E7B00792790 /* ChatHistoryNavigationButtons.swift in Sources */, D0EC6CDF1EB9F58800EBF1C3 /* PresentationResourceKey.swift in Sources */, @@ -4815,6 +4831,7 @@ D0EC6FC61EBA135100EBF1C3 /* complex_fft.c in Sources */, D0EC6CEE1EB9F58800EBF1C3 /* InAppNotificationSettings.swift in Sources */, D0EC6CEF1EB9F58800EBF1C3 /* PresentationPasscodeSettings.swift in Sources */, + D06BEC8A1F6597A80035A545 /* OverlayVideoDecoration.swift in Sources */, D0EC6CF01EB9F58800EBF1C3 /* AutomaticMediaDownloadSettings.swift in Sources */, D0EC6CF11EB9F58800EBF1C3 /* GeneratedMediaStoreSettings.swift in Sources */, D0EC6CF21EB9F58800EBF1C3 /* VoiceCallSettings.swift in Sources */, @@ -5123,6 +5140,7 @@ D0EC6DBE1EB9F58900EBF1C3 /* ChatMediaInputGridEntries.swift in Sources */, D0EC6DBF1EB9F58900EBF1C3 /* ChatMediaInputMetaSectionItemNode.swift in Sources */, D0EC6DC01EB9F58900EBF1C3 /* ChatMediaInputRecentGifsItem.swift in Sources */, + D0477D211F61A47600412B44 /* UniversalVideoContentManager.swift in Sources */, D0EC6DC11EB9F58900EBF1C3 /* ChatMediaInputTrendingItem.swift in Sources */, D0EC6FBC1EBA132B00EBF1C3 /* noise_suppression.c in Sources */, D0EC6DC21EB9F58900EBF1C3 /* ChatMediaInputStickerPackItem.swift in Sources */, @@ -5159,6 +5177,7 @@ D0EC6DDA1EB9F58900EBF1C3 /* HorizontalListContextResultsChatInputPanelItem.swift in Sources */, D0EC6DDB1EB9F58900EBF1C3 /* ChatInputPanelNode.swift in Sources */, D01BAA221ECE076100295217 /* CallListCallItem.swift in Sources */, + D0477D1B1F617E5800412B44 /* UniversalVideoNode.swift in Sources */, D0EC6EAA1EBA0FBB00EBF1C3 /* BufferPool.cpp in Sources */, D0E9BA081F0446A300F079A4 /* BotCheckoutPaymentShippingOptionSheetController.swift in Sources */, D0EC6DDC1EB9F58900EBF1C3 /* ChatTextInputPanelNode.swift in Sources */, @@ -5175,6 +5194,7 @@ D0EC6DE31EB9F58900EBF1C3 /* ChatBotStartInputPanelNode.swift in Sources */, D0EC6DE41EB9F58900EBF1C3 /* ChatUnblockInputPanelNode.swift in Sources */, D0EC6DE51EB9F58900EBF1C3 /* SecretChatHandshakeStatusInputPanelNode.swift in Sources */, + D06BEC771F62F68B0035A545 /* OverlayUniversalVideoNode.swift in Sources */, D0EC6DE61EB9F58900EBF1C3 /* DeleteChatInputPanelNode.swift in Sources */, D01BAA241ECE173200295217 /* PresentationResourcesCallList.swift in Sources */, D0EC6DE71EB9F58900EBF1C3 /* ChatTitleAccessoryPanelNode.swift in Sources */, @@ -5193,11 +5213,13 @@ D0EC6FC21EBA135100EBF1C3 /* auto_corr_to_refl_coef.c in Sources */, D0EC6DF21EB9F58900EBF1C3 /* ShareControllerNode.swift in Sources */, D0EC6DF31EB9F58900EBF1C3 /* ShareControllerPeerGridItem.swift in Sources */, + D0A8BB9F1F61EC9D000F03FD /* ChatTextInputPanelNodeOperators.swift in Sources */, D0EC6DF41EB9F58900EBF1C3 /* ShareActionButtonNode.swift in Sources */, D0EC6DF51EB9F58900EBF1C3 /* PeerMediaCollectionController.swift in Sources */, D0EC6DF61EB9F58900EBF1C3 /* PeerMediaCollectionControllerNode.swift in Sources */, D0EC6FCA1EBA135100EBF1C3 /* division_operations.c in Sources */, D0EC6DF71EB9F58900EBF1C3 /* PeerMediaCollectionTitleView.swift in Sources */, + D0477D1D1F617E8900412B44 /* NativeVideoContent.swift in Sources */, D0EC6DF81EB9F58900EBF1C3 /* PeerMediaCollectionInterfaceState.swift in Sources */, D033C60B1F0D306E0044EABA /* TelegramVideoNode.swift in Sources */, D0EC6DF91EB9F58900EBF1C3 /* PeerMediaCollectionInterfaceStateButtons.swift in Sources */, @@ -5565,10 +5587,10 @@ 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"; + OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies -driver-show-incremental"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.TelegramUITests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.1; + SWIFT_VERSION = 4.0; }; name = "Release AppStore"; }; @@ -5644,10 +5666,10 @@ 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"; + OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies -driver-show-incremental"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.TelegramUITests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.1; + SWIFT_VERSION = 4.0; }; name = "Debug AppStore"; }; @@ -5661,6 +5683,7 @@ 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; @@ -5683,7 +5706,8 @@ PRODUCT_NAME = TelegramUI; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = submodules/libtgvoip/webrtc_dsp; }; name = "Debug AppStore"; @@ -5698,6 +5722,7 @@ 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; @@ -5720,7 +5745,8 @@ PRODUCT_NAME = TelegramUI; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = submodules/libtgvoip/webrtc_dsp; }; name = "Debug Hockeyapp"; @@ -5734,6 +5760,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = X834Q8SBVP; DYLIB_INSTALL_NAME_BASE = "@rpath"; + HEADERMAP_USES_VFS = YES; HEADER_SEARCH_PATHS = "third-party/ogg"; INFOPLIST_FILE = "$(SRCROOT)/TelegramUI/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -5756,7 +5783,8 @@ PRODUCT_NAME = TelegramUI; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = submodules/libtgvoip/webrtc_dsp; }; name = "Release Hockeyapp"; @@ -5768,6 +5796,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEVELOPMENT_TEAM = X834Q8SBVP; DYLIB_INSTALL_NAME_BASE = "@rpath"; + HEADERMAP_USES_VFS = YES; HEADER_SEARCH_PATHS = "third-party/ogg"; INFOPLIST_FILE = "$(SRCROOT)/TelegramUI/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -5790,7 +5819,8 @@ PRODUCT_NAME = TelegramUI; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = submodules/libtgvoip/webrtc_dsp; }; name = "Release AppStore"; @@ -5923,10 +5953,10 @@ 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"; + OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies -driver-show-incremental"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.TelegramUITests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.1; + SWIFT_VERSION = 4.0; }; name = "Debug Hockeyapp"; }; @@ -5940,10 +5970,10 @@ 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"; + OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies -driver-show-incremental"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Telegram.TelegramUITests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.1; + SWIFT_VERSION = 4.0; }; name = "Release Hockeyapp"; }; diff --git a/TelegramUI.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/TelegramUI.xcscheme b/TelegramUI.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/TelegramUI.xcscheme index 040ab93dbf..019b78eefc 100644 --- a/TelegramUI.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/TelegramUI.xcscheme +++ b/TelegramUI.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/TelegramUI.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" shouldUseLaunchSchemeArgsEnv = "YES"> @@ -33,9 +34,10 @@ NSAttributedString { let result = NSMutableAttributedString() - var bodyAttributes: [String: Any] = [NSFontAttributeName: body.font, NSForegroundColorAttributeName: body.textColor, NSParagraphStyleAttributeName: paragraphStyleWithAlignment(textAlignment)] + var bodyAttributes: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: body.font, NSAttributedStringKey.foregroundColor: body.textColor, NSAttributedStringKey.paragraphStyle: paragraphStyleWithAlignment(textAlignment)] if !body.additionalAttributes.isEmpty { for (key, value) in body.additionalAttributes { - bodyAttributes[key] = value + bodyAttributes[NSAttributedStringKey(rawValue: key)] = value } } @@ -14,10 +15,10 @@ func addAttributesToStringWithRanges(_ stringWithRanges: (String, [(Int, NSRange for (index, range) in stringWithRanges.1 { if let attributes = argumentAttributes[index] { - var argumentAttributes: [String: Any] = [NSFontAttributeName: attributes.font, NSForegroundColorAttributeName: attributes.textColor, NSParagraphStyleAttributeName: paragraphStyleWithAlignment(textAlignment)] + var argumentAttributes: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: attributes.font, NSAttributedStringKey.foregroundColor: attributes.textColor, NSAttributedStringKey.paragraphStyle: paragraphStyleWithAlignment(textAlignment)] if !attributes.additionalAttributes.isEmpty { for (key, value) in attributes.additionalAttributes { - argumentAttributes[key] = value + argumentAttributes[NSAttributedStringKey(rawValue: key)] = value } } result.addAttributes(argumentAttributes, range: range) diff --git a/TelegramUI/ArhivedStickerPacksController.swift b/TelegramUI/ArhivedStickerPacksController.swift index 3a134c2cec..286432f299 100644 --- a/TelegramUI/ArhivedStickerPacksController.swift +++ b/TelegramUI/ArhivedStickerPacksController.swift @@ -136,7 +136,7 @@ private enum ArchivedStickerPacksEntry: ItemListNodeEntry { case let .info(text): return ItemListTextItem(text: .plain(text), sectionId: self.section) case let .pack(_, theme, info, topItem, count, enabled, editing): - return ItemListStickerPackItem(theme: theme, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: .none, editing: editing, enabled: enabled, sectionId: self.section, action: { _ in + return ItemListStickerPackItem(theme: theme, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: .none, editing: editing, enabled: enabled, sectionId: self.section, action: { arguments.openStickerPack(info) }, setPackIdWithRevealedOptions: { current, previous in arguments.setPackIdWithRevealedOptions(current, previous) diff --git a/TelegramUI/AuthorizationSequenceCodeEntryControllerNode.swift b/TelegramUI/AuthorizationSequenceCodeEntryControllerNode.swift index a1eb44d1ea..24052918c6 100644 --- a/TelegramUI/AuthorizationSequenceCodeEntryControllerNode.swift +++ b/TelegramUI/AuthorizationSequenceCodeEntryControllerNode.swift @@ -15,7 +15,7 @@ func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType) -> NSAttr string.append(NSAttributedString(string: " app on your other device.", font: Font.regular(16.0), textColor: UIColor.black)) let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center - string.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSMakeRange(0, string.length)) + string.addAttribute(NSAttributedStringKey.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, string.length)) return string case .call, .flashCall: return NSAttributedString(string: "Telegram dialed your number", font: Font.regular(16.0), textColor: UIColor.black, paragraphAlignment: .center) diff --git a/TelegramUI/AuthorizationSequenceCountrySelectionController.swift b/TelegramUI/AuthorizationSequenceCountrySelectionController.swift index 4a92ac56c4..7192ca9b22 100644 --- a/TelegramUI/AuthorizationSequenceCountrySelectionController.swift +++ b/TelegramUI/AuthorizationSequenceCountrySelectionController.swift @@ -302,7 +302,8 @@ final class AuthorizationSequenceCountrySelectionController: ViewController { self.displayNode.view.addSubview(self.innerNavigationController.view) self.innerNavigationController.didMove(toParentViewController: self) - self.innerController.itemSelected = { [weak self] _, countryId, code in + self.innerController.itemSelected = { [weak self] args in + let (_, countryId, code) = args self?.completeWithCountryCode?(code, countryId) self?.controllerNode.animateOut() } diff --git a/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift b/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift index 4386cf0cf2..5fe15aa93a 100644 --- a/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift +++ b/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift @@ -338,7 +338,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { termsString.append(NSAttributedString(string: ".", font: Font.regular(16.0), textColor: UIColor.black)) let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center - termsString.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSMakeRange(0, termsString.length)) + termsString.addAttribute(NSAttributedStringKey.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, termsString.length)) self.termsOfServiceNode.attributedText = termsString self.countryButton = ASButtonNode() diff --git a/TelegramUI/AuthorizationSequenceSignUpController.swift b/TelegramUI/AuthorizationSequenceSignUpController.swift index 2cf3a48c69..586094f5f2 100644 --- a/TelegramUI/AuthorizationSequenceSignUpController.swift +++ b/TelegramUI/AuthorizationSequenceSignUpController.swift @@ -38,7 +38,7 @@ final class AuthorizationSequenceSignUpController: ViewController { self.displayNode = AuthorizationSequenceSignUpControllerNode() self.displayNodeDidLoad() - self.controllerNode.signUpWithName = { [weak self] _ in + self.controllerNode.signUpWithName = { [weak self] _, _ in self?.nextPressed() } } diff --git a/TelegramUI/AvatarNode.swift b/TelegramUI/AvatarNode.swift index 79ac0eab6d..59b9e3d647 100644 --- a/TelegramUI/AvatarNode.swift +++ b/TelegramUI/AvatarNode.swift @@ -188,7 +188,7 @@ public final class AvatarNode: ASDisplayNode { if let parameters = parameters as? AvatarNodeParameters { let letters = parameters.letters let string = letters.count == 0 ? "" : (letters[0] + (letters.count == 1 ? "" : letters[1])) - let attributedString = NSAttributedString(string: string, attributes: [NSFontAttributeName: parameters.font, NSForegroundColorAttributeName: UIColor.white]) + let attributedString = NSAttributedString(string: string, attributes: [NSAttributedStringKey.font: parameters.font, NSAttributedStringKey.foregroundColor: UIColor.white]) let line = CTLineCreateWithAttributedString(attributedString) let lineBounds = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds) diff --git a/TelegramUI/BotCheckoutActionButton.swift b/TelegramUI/BotCheckoutActionButton.swift index 747a768f38..92e6f676b0 100644 --- a/TelegramUI/BotCheckoutActionButton.swift +++ b/TelegramUI/BotCheckoutActionButton.swift @@ -207,8 +207,8 @@ final class BotCheckoutActionButton: HighlightTrackingButtonNode { self.inactiveBackgroundNode.alpha = 0.0 self.activeBackgroundNode.alpha = 0.0 if self.applePayButton == nil { - if #available(iOSApplicationExtension 8.3, *) { - let applePayButton = PKPaymentButton(type: .buy, style: .black) + if #available(iOSApplicationExtension 9.0, *) { + let applePayButton = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black) self.view.addSubview(applePayButton) self.applePayButton = applePayButton } diff --git a/TelegramUI/BotCheckoutControllerNode.swift b/TelegramUI/BotCheckoutControllerNode.swift index 0f26802eaa..27a6443ef3 100644 --- a/TelegramUI/BotCheckoutControllerNode.swift +++ b/TelegramUI/BotCheckoutControllerNode.swift @@ -647,11 +647,12 @@ final class BotCheckoutControllerNode: ItemListControllerNode, request.paymentSummaryItems = items - let controller = PKPaymentAuthorizationViewController(paymentRequest: request) - controller.delegate = strongSelf - if let window = strongSelf.view.window { - strongSelf.applePayController = controller - window.rootViewController?.present(controller, animated: true) + if let controller = PKPaymentAuthorizationViewController(paymentRequest: request) { + controller.delegate = strongSelf + if let window = strongSelf.view.window { + strongSelf.applePayController = controller + window.rootViewController?.present(controller, animated: true) + } } } }) diff --git a/TelegramUI/BotCheckoutPasswordEntryController.swift b/TelegramUI/BotCheckoutPasswordEntryController.swift index 349c15c196..c8b48f92c9 100644 --- a/TelegramUI/BotCheckoutPasswordEntryController.swift +++ b/TelegramUI/BotCheckoutPasswordEntryController.swift @@ -148,7 +148,7 @@ private final class BotCheckoutPasswordAlertContentNode: AlertContentNode { self.textFieldNode = TextFieldNode() self.textFieldNode.textField.textColor = .black self.textFieldNode.textField.font = Font.regular(12.0) - self.textFieldNode.textField.typingAttributes = [NSFontAttributeName: Font.regular(12.0)] + self.textFieldNode.textField.typingAttributes = [NSAttributedStringKey.font.rawValue: Font.regular(12.0)] self.textFieldNode.textField.isSecureTextEntry = true super.init() diff --git a/TelegramUI/CallControllerButtonsNode.swift b/TelegramUI/CallControllerButtonsNode.swift index 13316ff544..0ce05bea09 100644 --- a/TelegramUI/CallControllerButtonsNode.swift +++ b/TelegramUI/CallControllerButtonsNode.swift @@ -118,8 +118,8 @@ final class CallControllerButtonsNode: ASDisplayNode { let twoButtonSpacing: CGFloat = 105.0 let buttonSize = CGSize(width: 75.0, height: 75.0) - let threeButtonsWidth = 3.0 * buttonSize.width + max(0.0, 3.0 - 1.0) * threeButtonSpacing - let twoButtonsWidth = 2.0 * buttonSize.width + max(0.0, 2.0 - 1.0) * twoButtonSpacing + let threeButtonsWidth = 3.0 * buttonSize.width + 2.0 * threeButtonSpacing + let twoButtonsWidth = 2.0 * buttonSize.width + 1.0 * twoButtonSpacing var origin = CGPoint(x: floor((width - threeButtonsWidth) / 2.0), y: 0.0) for button in [self.muteButton, self.endButton, self.speakerButton] { diff --git a/TelegramUI/CallControllerKeyPreviewNode.swift b/TelegramUI/CallControllerKeyPreviewNode.swift index 25774fd058..3b419d128a 100644 --- a/TelegramUI/CallControllerKeyPreviewNode.swift +++ b/TelegramUI/CallControllerKeyPreviewNode.swift @@ -31,7 +31,7 @@ final class CallControllerKeyPreviewNode: ASDisplayNode { super.init() - self.keyTextNode.attributedText = NSAttributedString(string: keyText, attributes: [NSFontAttributeName: Font.regular(58.0), NSKernAttributeName: 9.0 as NSNumber]) + self.keyTextNode.attributedText = NSAttributedString(string: keyText, attributes: [NSAttributedStringKey.font: Font.regular(58.0), NSAttributedStringKey.kern: 9.0 as NSNumber]) self.infoTextNode.attributedText = NSAttributedString(string: infoText, font: Font.regular(14.0), textColor: UIColor.white, paragraphAlignment: .center) diff --git a/TelegramUI/CallControllerNode.swift b/TelegramUI/CallControllerNode.swift index 49a5bf403a..76b3036ea5 100644 --- a/TelegramUI/CallControllerNode.swift +++ b/TelegramUI/CallControllerNode.swift @@ -5,6 +5,8 @@ import Postbox import TelegramCore import SwiftSignalKit +import TelegramUIPrivateModule + private func generateBackArrowImage(color: UIColor) -> UIImage? { return generateImage(CGSize(width: 13.0, height: 22.0), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) @@ -178,7 +180,7 @@ final class CallControllerNode: ASDisplayNode { let text = stringForEmojiHashOfData(keyVisualHash, 4)! self.keyTextData = (keyVisualHash, text) - self.keyButtonNode.setAttributedTitle(NSAttributedString(string: text, attributes: [NSFontAttributeName: Font.regular(22.0), NSKernAttributeName: 2.5 as NSNumber]), for: []) + self.keyButtonNode.setAttributedTitle(NSAttributedString(string: text, attributes: [NSAttributedStringKey.font: Font.regular(22.0), NSAttributedStringKey.kern: 2.5 as NSNumber]), for: []) let keyTextSize = self.keyButtonNode.measure(CGSize(width: 200.0, height: 200.0)) self.keyButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) diff --git a/TelegramUI/ChatBotInfoItem.swift b/TelegramUI/ChatBotInfoItem.swift index 94ca991820..47d74a99c2 100644 --- a/TelegramUI/ChatBotInfoItem.swift +++ b/TelegramUI/ChatBotInfoItem.swift @@ -174,15 +174,15 @@ 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[TextNode.UrlAttribute] as? String { + if let url = attributes[NSAttributedStringKey(rawValue: TextNode.UrlAttribute)] as? String { return .url(url) - } else if let peerMention = attributes[TextNode.TelegramPeerMentionAttribute] as? TelegramPeerMention { + } else if let peerMention = attributes[NSAttributedStringKey(rawValue: TextNode.TelegramPeerMentionAttribute)] as? TelegramPeerMention { return .peerMention(peerMention.peerId, peerMention.mention) - } else if let peerName = attributes[TextNode.TelegramPeerTextMentionAttribute] as? String { + } else if let peerName = attributes[NSAttributedStringKey(rawValue: TextNode.TelegramPeerTextMentionAttribute)] as? String { return .textMention(peerName) - } else if let botCommand = attributes[TextNode.TelegramBotCommandAttribute] as? String { + } else if let botCommand = attributes[NSAttributedStringKey(rawValue: TextNode.TelegramBotCommandAttribute)] as? String { return .botCommand(botCommand) - } else if let hashtag = attributes[TextNode.TelegramHashtagAttribute] as? TelegramHashtag { + } else if let hashtag = attributes[NSAttributedStringKey(rawValue: TextNode.TelegramHashtagAttribute)] as? TelegramHashtag { return .hashtag(hashtag.peerName, hashtag.hashtag) } else { return .none diff --git a/TelegramUI/ChatControllerNode.swift b/TelegramUI/ChatControllerNode.swift index b466e7552b..473058fa35 100644 --- a/TelegramUI/ChatControllerNode.swift +++ b/TelegramUI/ChatControllerNode.swift @@ -46,7 +46,7 @@ class ChatControllerNode: ASDisplayNode { var chatPresentationInterfaceState: ChatPresentationInterfaceState var automaticMediaDownloadSettings: AutomaticMediaDownloadSettings - var requestUpdateChatInterfaceState: (Bool, (ChatInterfaceState) -> ChatInterfaceState) -> Void = { _ in } + var requestUpdateChatInterfaceState: (Bool, (ChatInterfaceState) -> ChatInterfaceState) -> Void = { _, _ in } var displayAttachmentMenu: () -> Void = { } var updateTypingActivity: () -> Void = { } var dismissUrlPreview: () -> Void = { } diff --git a/TelegramUI/ChatInterfaceStateNavigationButtons.swift b/TelegramUI/ChatInterfaceStateNavigationButtons.swift index a21bae5627..73d01eed52 100644 --- a/TelegramUI/ChatInterfaceStateNavigationButtons.swift +++ b/TelegramUI/ChatInterfaceStateNavigationButtons.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit enum ChatNavigationButtonAction { case openChatInfo diff --git a/TelegramUI/ChatItemGalleryFooterContentNode.swift b/TelegramUI/ChatItemGalleryFooterContentNode.swift index 80dae8bdd0..f06e8218a1 100644 --- a/TelegramUI/ChatItemGalleryFooterContentNode.swift +++ b/TelegramUI/ChatItemGalleryFooterContentNode.swift @@ -9,10 +9,34 @@ import Photos private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Acessory Panels/MessageSelectionThrash"), color: .white) private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Acessory Panels/MessageSelectionAction"), color: .white) +private let pauseImage = generateImage(CGSize(width: 16.0, height: 16.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + let color = UIColor.white + let diameter: CGFloat = 16.0 + + context.setFillColor(color.cgColor) + + context.translateBy(x: (diameter - size.width) / 2.0, y: (diameter - size.height) / 2.0) + let _ = try? drawSvgPath(context, path: "M0,1.00087166 C0,0.448105505 0.443716645,0 0.999807492,0 L4.00019251,0 C4.55237094,0 5,0.444630861 5,1.00087166 L5,14.9991283 C5,15.5518945 4.55628335,16 4.00019251,16 L0.999807492,16 C0.447629061,16 0,15.5553691 0,14.9991283 L0,1.00087166 Z M10,1.00087166 C10,0.448105505 10.4437166,0 10.9998075,0 L14.0001925,0 C14.5523709,0 15,0.444630861 15,1.00087166 L15,14.9991283 C15,15.5518945 14.5562834,16 14.0001925,16 L10.9998075,16 C10.4476291,16 10,15.5553691 10,14.9991283 L10,1.00087166 ") + context.fillPath() + if (diameter < 40.0) { + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) + context.scaleBy(x: 1.0 / 0.8, y: 1.0 / 0.8) + context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + } + context.translateBy(x: -(diameter - size.width) / 2.0, y: -(diameter - size.height) / 2.0) +}) + private let textFont = Font.regular(16.0) private let titleFont = Font.medium(15.0) private let dateFont = Font.regular(14.0) +enum ChatItemGalleryFooterContent { + case info + case playbackPause +} + final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { private let account: Account private var theme: PresentationTheme @@ -23,6 +47,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { private let textNode: ASTextNode private let authorNameNode: ASTextNode private let dateNode: ASTextNode + private let playbackControlButton: HighlightableButtonNode private var currentMessageText: String? private var currentAuthorNameText: String? @@ -32,6 +57,25 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { private let messageContextDisposable = MetaDisposable() + var playbackControl: (() -> Void)? + + var content: ChatItemGalleryFooterContent = .info { + didSet { + if self.content != oldValue { + switch self.content { + case .info: + self.authorNameNode.isHidden = false + self.dateNode.isHidden = false + self.playbackControlButton.isHidden = true + case .playbackPause: + self.authorNameNode.isHidden = true + self.dateNode.isHidden = true + self.playbackControlButton.isHidden = false + } + } + } + } + init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { self.account = account self.theme = theme @@ -49,6 +93,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { self.dateNode = ASTextNode() self.dateNode.maximumNumberOfLines = 1 + self.playbackControlButton = HighlightableButtonNode() + self.playbackControlButton.setImage(pauseImage, for: []) + self.playbackControlButton.isHidden = true + super.init() self.view.addSubview(self.deleteButton) @@ -57,14 +105,50 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { self.addSubnode(self.authorNameNode) self.addSubnode(self.dateNode) + self.addSubnode(self.playbackControlButton) + self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), for: [.touchUpInside]) self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), for: [.touchUpInside]) + + self.playbackControlButton.addTarget(self, action: #selector(self.playbackControlPressed), forControlEvents: .touchUpInside) } deinit { self.messageContextDisposable.dispose() } + func setup(origin: GalleryItemOriginData?, caption: String) { + let titleText = origin?.title + let dateText = origin?.timestamp.flatMap { humanReadableStringForTimestamp(strings: self.strings, timestamp: $0) } + + if self.currentMessageText != caption || self.currentAuthorNameText != titleText || self.currentDateText != dateText { + self.currentMessageText = caption + + if caption.isEmpty { + self.textNode.isHidden = true + self.textNode.attributedText = nil + } else { + self.textNode.isHidden = false + self.textNode.attributedText = NSAttributedString(string: caption, font: textFont, textColor: .white) + } + + if let titleText = titleText { + self.authorNameNode.attributedText = NSAttributedString(string: titleText, font: titleFont, textColor: .white) + } else { + self.authorNameNode.attributedText = nil + } + if let dateText = dateText { + self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: .white) + } else { + self.dateNode.attributedText = nil + } + + //self.deleteButton.isHidden = !canDelete + + self.requestLayout?(.immediate) + } + } + func setMessage(_ message: Message) { self.currentMessage = message @@ -135,6 +219,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { self.actionButton.frame = CGRect(origin: CGPoint(x: 0.0, y: panelHeight - 44.0), size: CGSize(width: 44.0, height: 44.0)) self.deleteButton.frame = CGRect(origin: CGPoint(x: width - 44.0, y: panelHeight - 44.0), size: CGSize(width: 44.0, height: 44.0)) + self.playbackControlButton.frame = CGRect(origin: CGPoint(x: floor((width - 44.0) / 2.0), y: panelHeight - 44.0), size: CGSize(width: 44.0, height: 44.0)) + let authorNameSize = self.authorNameNode.measure(CGSize(width: width - 44.0 * 2.0 - 8.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)) let dateSize = self.dateNode.measure(CGSize(width: width - 44.0 * 2.0 - 8.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)) @@ -166,11 +252,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { if options.contains(.globally) { let globalTitle: String if isChannel { - globalTitle = "Delete" + globalTitle = strongSelf.strings.Common_Delete } else if let personalPeerName = personalPeerName { - globalTitle = "Delete for me and \(personalPeerName)" + globalTitle = strongSelf.strings.Conversation_DeleteMessagesFor(personalPeerName).0 } else { - globalTitle = "Delete for everyone" + globalTitle = strongSelf.strings.Conversation_DeleteMessagesForEveryone } items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in actionSheet?.dismissAnimated() @@ -181,7 +267,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { })) } if options.contains(.locally) { - items.append(ActionSheetButtonItem(title: "Delete for me", color: .destructive, action: { [weak actionSheet] in + items.append(ActionSheetButtonItem(title: strongSelf.strings.Conversation_DeleteMessagesForMe, color: .destructive, action: { [weak actionSheet] in actionSheet?.dismissAnimated() if let strongSelf = self { let _ = deleteMessagesInteractively(postbox: strongSelf.account.postbox, messageIds: [currentMessage.id], type: .forLocalPeer).start() @@ -190,7 +276,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { })) } actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: "Cancel", color: .accent, action: { [weak actionSheet] in + ActionSheetButtonItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() }) ])]) @@ -378,4 +464,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { controllerInteraction.presentController(actionSheet, nil) } } + + @objc func playbackControlPressed() { + self.playbackControl?() + } } diff --git a/TelegramUI/ChatListSearchContainerNode.swift b/TelegramUI/ChatListSearchContainerNode.swift index 2c3a4802c6..042499baa8 100644 --- a/TelegramUI/ChatListSearchContainerNode.swift +++ b/TelegramUI/ChatListSearchContainerNode.swift @@ -394,9 +394,9 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { openMessage(peer, message.id) } self?.listNode.clearHighlightAnimated(true) - }, setPeerIdWithRevealedOptions: { _ in - }, setPeerPinned: { _ in - }, setPeerMuted: { _ in + }, setPeerIdWithRevealedOptions: { _, _ in + }, setPeerPinned: { _, _ in + }, setPeerMuted: { _, _ in }, deletePeer: { _ in }) diff --git a/TelegramUI/ChatMessageActionItemNode.swift b/TelegramUI/ChatMessageActionItemNode.swift index 11f9b3cb2e..f6e0fa7189 100644 --- a/TelegramUI/ChatMessageActionItemNode.swift +++ b/TelegramUI/ChatMessageActionItemNode.swift @@ -598,7 +598,7 @@ class ChatMessageActionItemNode: ChatMessageItemView { TextNode.TelegramHashtagAttribute ] for name in possibleNames { - if let _ = attributes[name] { + if let _ = attributes[NSAttributedStringKey(rawValue: name)] { rects = self.labelNode.attributeRects(name: name, at: index) break } @@ -635,15 +635,15 @@ class ChatMessageActionItemNode: ChatMessageItemView { private 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[TextNode.UrlAttribute] as? String { + if let url = attributes[NSAttributedStringKey(rawValue: TextNode.UrlAttribute)] as? String { return .url(url) - } else if let peerMention = attributes[TextNode.TelegramPeerMentionAttribute] as? TelegramPeerMention { + } else if let peerMention = attributes[NSAttributedStringKey(rawValue: TextNode.TelegramPeerMentionAttribute)] as? TelegramPeerMention { return .peerMention(peerMention.peerId, peerMention.mention) - } else if let peerName = attributes[TextNode.TelegramPeerTextMentionAttribute] as? String { + } else if let peerName = attributes[NSAttributedStringKey(rawValue: TextNode.TelegramPeerTextMentionAttribute)] as? String { return .textMention(peerName) - } else if let botCommand = attributes[TextNode.TelegramBotCommandAttribute] as? String { + } else if let botCommand = attributes[NSAttributedStringKey(rawValue: TextNode.TelegramBotCommandAttribute)] as? String { return .botCommand(botCommand) - } else if let hashtag = attributes[TextNode.TelegramHashtagAttribute] as? TelegramHashtag { + } else if let hashtag = attributes[NSAttributedStringKey(rawValue: TextNode.TelegramHashtagAttribute)] as? TelegramHashtag { return .hashtag(hashtag.peerName, hashtag.hashtag) } else { return .none diff --git a/TelegramUI/ChatMessageBubbleContentCalclulateImageCorners.swift b/TelegramUI/ChatMessageBubbleContentCalclulateImageCorners.swift index b2c8106798..e18cc4c776 100644 --- a/TelegramUI/ChatMessageBubbleContentCalclulateImageCorners.swift +++ b/TelegramUI/ChatMessageBubbleContentCalclulateImageCorners.swift @@ -1,3 +1,4 @@ +import UIKit func chatMessageBubbleImageContentCorners(relativeContentPosition position: ChatMessageBubbleContentPosition, normalRadius: CGFloat, mergedRadius: CGFloat, mergedWithAnotherContentRadius: CGFloat) -> ImageCorners { let topLeftCorner: ImageCorner diff --git a/TelegramUI/ChatMessageBubbleItemNode.swift b/TelegramUI/ChatMessageBubbleItemNode.swift index 52cec7c8d1..eacfcffc47 100644 --- a/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/TelegramUI/ChatMessageBubbleItemNode.swift @@ -49,7 +49,7 @@ private func contentNodeClassesForItem(_ item: ChatMessageItem) -> [AnyClass] { private let nameFont: UIFont = { if #available(iOS 8.2, *) { - return UIFont.systemFont(ofSize: 14.0, weight: UIFontWeightMedium) + return UIFont.systemFont(ofSize: 14.0, weight: UIFont.Weight.medium) } else { return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, 14.0, nil) } @@ -383,9 +383,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { let attributedString: NSAttributedString if let authorNameString = authorNameString, let authorNameColor = authorNameColor, let inlineBotNameString = inlineBotNameString { let botPrefixString: NSString = " via " - let mutableString = NSMutableAttributedString(string: "\(authorNameString)\(botPrefixString)@\(inlineBotNameString)", attributes: [NSFontAttributeName: inlineBotNameFont, NSForegroundColorAttributeName: inlineBotNameColor]) - mutableString.addAttributes([NSFontAttributeName: nameFont, NSForegroundColorAttributeName: authorNameColor], range: NSMakeRange(0, (authorNameString as NSString).length)) - mutableString.addAttributes([NSFontAttributeName: inlineBotPrefixFont, NSForegroundColorAttributeName: inlineBotNameColor], range: NSMakeRange((authorNameString as NSString).length, botPrefixString.length)) + let mutableString = NSMutableAttributedString(string: "\(authorNameString)\(botPrefixString)@\(inlineBotNameString)", attributes: [NSAttributedStringKey.font: inlineBotNameFont, NSAttributedStringKey.foregroundColor: inlineBotNameColor]) + mutableString.addAttributes([NSAttributedStringKey.font: nameFont, NSAttributedStringKey.foregroundColor: authorNameColor], range: NSMakeRange(0, (authorNameString as NSString).length)) + mutableString.addAttributes([NSAttributedStringKey.font: inlineBotPrefixFont, NSAttributedStringKey.foregroundColor: inlineBotNameColor], range: NSMakeRange((authorNameString as NSString).length, botPrefixString.length)) attributedString = mutableString } else if let authorNameString = authorNameString, let authorNameColor = authorNameColor { attributedString = NSAttributedString(string: authorNameString, font: nameFont, textColor: authorNameColor) diff --git a/TelegramUI/ChatMessageForwardInfoNode.swift b/TelegramUI/ChatMessageForwardInfoNode.swift index 806f467715..1683a04507 100644 --- a/TelegramUI/ChatMessageForwardInfoNode.swift +++ b/TelegramUI/ChatMessageForwardInfoNode.swift @@ -26,8 +26,8 @@ class ChatMessageForwardInfoNode: ASDisplayNode { } let completeString: NSString = "\(prefix)\(peerString)" as NSString let color = incoming ? theme.chat.bubble.incomingAccentColor : theme.chat.bubble.outgoingAccentColor - let string = NSMutableAttributedString(string: completeString as String, attributes: [NSForegroundColorAttributeName: color, NSFontAttributeName: prefixFont]) - string.addAttributes([NSFontAttributeName: peerFont], range: NSMakeRange(prefix.length, completeString.length - prefix.length)) + let string = NSMutableAttributedString(string: completeString as String, attributes: [NSAttributedStringKey.foregroundColor: color, NSAttributedStringKey.font: prefixFont]) + string.addAttributes([NSAttributedStringKey.font: peerFont], range: NSMakeRange(prefix.length, completeString.length - prefix.length)) let (textLayout, textApply) = textNodeLayout(string, nil, 2, .end, constrainedSize, .natural, nil, UIEdgeInsets()) return (textLayout.size, { diff --git a/TelegramUI/ChatMessageReplyInfoNode.swift b/TelegramUI/ChatMessageReplyInfoNode.swift index 7c7e17739e..28b070d076 100644 --- a/TelegramUI/ChatMessageReplyInfoNode.swift +++ b/TelegramUI/ChatMessageReplyInfoNode.swift @@ -7,9 +7,9 @@ import SwiftSignalKit private let titleFont: UIFont = { if #available(iOS 8.2, *) { - return UIFont.systemFont(ofSize: 14.0, weight: UIFontWeightMedium) + return UIFont.systemFont(ofSize: 14.0, weight: UIFont.Weight.medium) } else { - return CTFontCreateWithName("HelveticaNeue-Medium" as CFString?, 14.0, nil) + return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, 14.0, nil) } }() private let textFont = Font.regular(14.0) diff --git a/TelegramUI/ChatMessageTextBubbleContentNode.swift b/TelegramUI/ChatMessageTextBubbleContentNode.swift index 29cf79e3f4..c223c8f8d4 100644 --- a/TelegramUI/ChatMessageTextBubbleContentNode.swift +++ b/TelegramUI/ChatMessageTextBubbleContentNode.swift @@ -243,15 +243,15 @@ 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[TextNode.UrlAttribute] as? String { + if let url = attributes[NSAttributedStringKey(rawValue: TextNode.UrlAttribute)] as? String { return .url(url) - } else if let peerMention = attributes[TextNode.TelegramPeerMentionAttribute] as? TelegramPeerMention { + } else if let peerMention = attributes[NSAttributedStringKey(rawValue: TextNode.TelegramPeerMentionAttribute)] as? TelegramPeerMention { return .peerMention(peerMention.peerId, peerMention.mention) - } else if let peerName = attributes[TextNode.TelegramPeerTextMentionAttribute] as? String { + } else if let peerName = attributes[NSAttributedStringKey(rawValue: TextNode.TelegramPeerTextMentionAttribute)] as? String { return .textMention(peerName) - } else if let botCommand = attributes[TextNode.TelegramBotCommandAttribute] as? String { + } else if let botCommand = attributes[NSAttributedStringKey(rawValue: TextNode.TelegramBotCommandAttribute)] as? String { return .botCommand(botCommand) - } else if let hashtag = attributes[TextNode.TelegramHashtagAttribute] as? TelegramHashtag { + } else if let hashtag = attributes[NSAttributedStringKey(rawValue: TextNode.TelegramHashtagAttribute)] as? TelegramHashtag { return .hashtag(hashtag.peerName, hashtag.hashtag) } else { return .none @@ -275,7 +275,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { TextNode.TelegramHashtagAttribute ] for name in possibleNames { - if let _ = attributes[name] { + if let _ = attributes[NSAttributedStringKey(rawValue: name)] { rects = self.textNode.attributeRects(name: name, at: index) break } diff --git a/TelegramUI/ChatTextInputAudioRecordingCancelIndicator.swift b/TelegramUI/ChatTextInputAudioRecordingCancelIndicator.swift index 51bcce420c..2b3edf1b09 100644 --- a/TelegramUI/ChatTextInputAudioRecordingCancelIndicator.swift +++ b/TelegramUI/ChatTextInputAudioRecordingCancelIndicator.swift @@ -11,7 +11,7 @@ final class ChatTextInputAudioRecordingCancelIndicator: ASDisplayNode { private let labelNode: TextNode private let cancelButton: HighlightableButtonNode - private var isDisplayingCancel = false + private(set) var isDisplayingCancel = false init(theme: PresentationTheme, strings: PresentationStrings, cancel: @escaping () -> Void) { self.cancel = cancel @@ -64,6 +64,15 @@ final class ChatTextInputAudioRecordingCancelIndicator: ASDisplayNode { self.cancelButton.alpha = 1.0 if animated { + //CGAffineTransform transform = CGAffineTransformMakeTranslation(0.0f, -22.0f); + //transform = CGAffineTransformScale(transform, 0.25f, 0.25f); + + self.labelNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -22.0), duration: 0.2, removeOnCompletion: false, additive: true) + self.labelNode.layer.animateScale(from: 1.0, to: 0.25, duration: 0.25, removeOnCompletion: false) + + self.cancelButton.layer.animatePosition(from: CGPoint(x: 0.0, y: 22.0), to: CGPoint(), duration: 0.2, additive: true) + self.cancelButton.layer.animateScale(from: 0.25, to: 1.0, duration: 0.25) + self.arrowNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) self.labelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) self.cancelButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) @@ -92,4 +101,12 @@ final class ChatTextInputAudioRecordingCancelIndicator: ASDisplayNode { @objc func cancelPressed() { self.cancel() } + + func animateIn() { + + } + + func animateOut() { + + } } diff --git a/TelegramUI/ChatTextInputMediaRecordingButton.swift b/TelegramUI/ChatTextInputMediaRecordingButton.swift index 840f5f34fd..834a4a2b9a 100644 --- a/TelegramUI/ChatTextInputMediaRecordingButton.swift +++ b/TelegramUI/ChatTextInputMediaRecordingButton.swift @@ -26,13 +26,53 @@ private final class ChatTextInputMediaRecordingButtonPresenterContainer: UIView } private final class ChatTextInputMediaRecordingButtonPresenterController: ViewController { + private var controllerNode: ChatTextInputMediaRecordingButtonPresenterControllerNode { + return self.displayNode as! ChatTextInputMediaRecordingButtonPresenterControllerNode + } + + var containerView: UIView? { + didSet { + if self.isNodeLoaded { + self.controllerNode.containerView = self.containerView + } + } + } + override func loadDisplayNode() { self.displayNode = ChatTextInputMediaRecordingButtonPresenterControllerNode() + if let containerView = self.containerView { + self.controllerNode.containerView = containerView + } } } private final class ChatTextInputMediaRecordingButtonPresenterControllerNode: ViewControllerTracingNode { + var containerView: UIView? { + didSet { + if self.containerView !== oldValue { + if self.isNodeLoaded, let containerView = oldValue, containerView.superview === self.view { + containerView.removeFromSuperview() + } + if self.isNodeLoaded, let containerView = self.containerView { + self.view.addSubview(containerView) + } + } + } + } + + override func didLoad() { + super.didLoad() + if let containerView = self.containerView { + self.view.addSubview(containerView) + } + } + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let containerView = self.containerView { + if let result = containerView.hitTest(point, with: event), result !== containerView { + return result + } + } return nil } } @@ -41,7 +81,7 @@ private final class ChatTextInputMediaRecordingButtonPresenter : NSObject, TGMod private let account: Account? private let presentController: (ViewController) -> Void private let container: ChatTextInputMediaRecordingButtonPresenterContainer - private var presentationController: ViewController? + private var presentationController: ChatTextInputMediaRecordingButtonPresenterController? init(account: Account, presentController: @escaping (ViewController) -> Void) { self.account = account @@ -77,7 +117,7 @@ private final class ChatTextInputMediaRecordingButtonPresenter : NSObject, TGMod presentNow = true } - self.presentationController?.displayNode.view.addSubview(self.container) + self.presentationController?.containerView = self.container if let presentationController = self.presentationController, presentNow { self.presentController(presentationController) } @@ -101,7 +141,7 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto let presentController: (ViewController) -> Void var beginRecording: () -> Void = { } var endRecording: (Bool) -> Void = { _ in } - var stopRecording: () -> Void = { _ in } + var stopRecording: () -> Void = { } var offsetRecordingControls: () -> Void = { } var switchMode: () -> Void = { } var updateLocked: (Bool) -> Void = { _ in } diff --git a/TelegramUI/ChatTextInputPanelNode.swift b/TelegramUI/ChatTextInputPanelNode.swift index 5ba85cf1cb..8989a76965 100644 --- a/TelegramUI/ChatTextInputPanelNode.swift +++ b/TelegramUI/ChatTextInputPanelNode.swift @@ -22,57 +22,11 @@ enum ChatTextInputAccessoryItem: Equatable { case stickers case inputButtons case messageAutoremoveTimeout(Int32?) - - static func ==(lhs: ChatTextInputAccessoryItem, rhs: ChatTextInputAccessoryItem) -> Bool { - switch lhs { - case .keyboard: - if case .keyboard = rhs { - return true - } else { - return false - } - case .stickers: - if case .stickers = rhs { - return true - } else { - return false - } - case .inputButtons: - if case .inputButtons = rhs { - return true - } else { - return false - } - case let .messageAutoremoveTimeout(lhsTimeout): - if case let .messageAutoremoveTimeout(rhsTimeout) = rhs, lhsTimeout == rhsTimeout { - return true - } else { - return false - } - } - } } enum ChatVideoRecordingStatus: Equatable { case recording(InstantVideoControllerRecordingStatus) case editing - - static func ==(lhs: ChatVideoRecordingStatus, rhs: ChatVideoRecordingStatus) -> Bool { - switch lhs { - case let .recording(lhsStatus): - if case let .recording(rhsStatus) = rhs, lhsStatus === rhsStatus { - return true - } else { - return false - } - case .editing: - if case .editing = rhs { - return true - } else { - return false - } - } - } } enum ChatTextInputPanelMediaRecordingState: Equatable { @@ -96,23 +50,6 @@ enum ChatTextInputPanelMediaRecordingState: Equatable { return .video(status: status, isLocked: isLocked) } } - - static func ==(lhs: ChatTextInputPanelMediaRecordingState, rhs: ChatTextInputPanelMediaRecordingState) -> Bool { - switch lhs { - case let .audio(lhsRecorder, lhsIsLocked): - if case let .audio(rhsRecorder, rhsIsLocked) = rhs, lhsRecorder === rhsRecorder, lhsIsLocked == rhsIsLocked { - return true - } else { - return false - } - case let .video(status, isLocked): - if case .video(status, isLocked) = rhs { - return true - } else { - return false - } - } - } } struct ChatTextInputPanelState: Equatable { @@ -132,21 +69,6 @@ struct ChatTextInputPanelState: Equatable { self.mediaRecordingState = nil } - static func ==(lhs: ChatTextInputPanelState, rhs: ChatTextInputPanelState) -> Bool { - if lhs.accessoryItems != rhs.accessoryItems { - return false - } - if let lhsContextPlaceholder = lhs.contextPlaceholder, let rhsContextPlaceholder = rhs.contextPlaceholder { - return lhsContextPlaceholder.isEqual(to: rhsContextPlaceholder) - } else if (lhs.contextPlaceholder != nil) != (rhs.contextPlaceholder != nil) { - return false - } - if lhs.mediaRecordingState != rhs.mediaRecordingState { - return false - } - return true - } - func withUpdatedMediaRecordingState(_ mediaRecordingState: ChatTextInputPanelMediaRecordingState?) -> ChatTextInputPanelState { return ChatTextInputPanelState(accessoryItems: self.accessoryItems, contextPlaceholder: self.contextPlaceholder, mediaRecordingState: mediaRecordingState) } @@ -413,7 +335,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { keyboardAppearance = .dark } } - textInputNode.typingAttributes = [NSFontAttributeName: Font.regular(17.0), NSForegroundColorAttributeName: textColor] + textInputNode.typingAttributes = [NSAttributedStringKey.font.rawValue: Font.regular(17.0), NSAttributedStringKey.foregroundColor.rawValue: textColor] textInputNode.clipsToBounds = true textInputNode.delegate = self textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0) @@ -480,7 +402,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { textInputNode.attributedText = NSAttributedString(string: text, font: Font.regular(17.0), textColor: textColor) textInputNode.selectedRange = range } - textInputNode.typingAttributes = [NSFontAttributeName: Font.regular(17.0), NSForegroundColorAttributeName: textColor] + textInputNode.typingAttributes = [NSAttributedStringKey.font.rawValue: Font.regular(17.0), NSAttributedStringKey.foregroundColor.rawValue: textColor] } } @@ -735,10 +657,17 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { if let audioRecordingCancelIndicator = self.audioRecordingCancelIndicator { self.audioRecordingCancelIndicator = nil if transition.isAnimated { - let position = audioRecordingCancelIndicator.layer.position - audioRecordingCancelIndicator.layer.animatePosition(from: position, to: CGPoint(x: 0.0 - audioRecordingCancelIndicator.bounds.size.width, y: position.y), duration: 0.3, removeOnCompletion: false, completion: { [weak audioRecordingCancelIndicator] _ in - audioRecordingCancelIndicator?.removeFromSupernode() - }) + if audioRecordingCancelIndicator.isDisplayingCancel { + audioRecordingCancelIndicator.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) + audioRecordingCancelIndicator.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -22.0), duration: 0.25, removeOnCompletion: false, additive: true, completion: { [weak audioRecordingCancelIndicator] _ in + audioRecordingCancelIndicator?.removeFromSupernode() + }) + } else { + let position = audioRecordingCancelIndicator.layer.position + audioRecordingCancelIndicator.layer.animatePosition(from: position, to: CGPoint(x: 0.0 - audioRecordingCancelIndicator.bounds.size.width, y: position.y), duration: 0.3, removeOnCompletion: false, completion: { [weak audioRecordingCancelIndicator] _ in + audioRecordingCancelIndicator?.removeFromSupernode() + }) + } } else { audioRecordingCancelIndicator.removeFromSupernode() } @@ -1047,4 +976,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } return super.hitTest(point, with: event) } + + func f2() { + + } } diff --git a/TelegramUI/ChatTextInputPanelNodeOperators.swift b/TelegramUI/ChatTextInputPanelNodeOperators.swift new file mode 100644 index 0000000000..d2a4640f6d --- /dev/null +++ b/TelegramUI/ChatTextInputPanelNodeOperators.swift @@ -0,0 +1,88 @@ +import Foundation + +extension ChatTextInputAccessoryItem { + static func ==(lhs: ChatTextInputAccessoryItem, rhs: ChatTextInputAccessoryItem) -> Bool { + switch lhs { + case .keyboard: + if case .keyboard = rhs { + return true + } else { + return false + } + case .stickers: + if case .stickers = rhs { + return true + } else { + return false + } + case .inputButtons: + if case .inputButtons = rhs { + return true + } else { + return false + } + case let .messageAutoremoveTimeout(lhsTimeout): + if case let .messageAutoremoveTimeout(rhsTimeout) = rhs, lhsTimeout == rhsTimeout { + return true + } else { + return false + } + } + } +} + +extension ChatVideoRecordingStatus { + static func ==(lhs: ChatVideoRecordingStatus, rhs: ChatVideoRecordingStatus) -> Bool { + switch lhs { + case let .recording(lhsStatus): + if case let .recording(rhsStatus) = rhs, lhsStatus === rhsStatus { + return true + } else { + return false + } + case .editing: + if case .editing = rhs { + return true + } else { + return false + } + } + } +} + +extension ChatTextInputPanelMediaRecordingState { + static func ==(lhs: ChatTextInputPanelMediaRecordingState, rhs: ChatTextInputPanelMediaRecordingState) -> Bool { + switch lhs { + case let .audio(lhsRecorder, lhsIsLocked): + if case let .audio(rhsRecorder, rhsIsLocked) = rhs, lhsRecorder === rhsRecorder, lhsIsLocked == rhsIsLocked { + return true + } else { + return false + } + case let .video(status, isLocked): + if case .video(status, isLocked) = rhs { + return true + } else { + return false + } + } + } +} + +extension ChatTextInputPanelState { + static func ==(lhs: ChatTextInputPanelState, rhs: ChatTextInputPanelState) -> Bool { + if lhs.accessoryItems != rhs.accessoryItems { + return false + } + if let lhsContextPlaceholder = lhs.contextPlaceholder, let rhsContextPlaceholder = rhs.contextPlaceholder { + return lhsContextPlaceholder.isEqual(to: rhsContextPlaceholder) + } else if (lhs.contextPlaceholder != nil) != (rhs.contextPlaceholder != nil) { + return false + } + if lhs.mediaRecordingState != rhs.mediaRecordingState { + return false + } + return true + } +} + diff --git a/TelegramUI/CommandChatInputContextPanelNode.swift b/TelegramUI/CommandChatInputContextPanelNode.swift index 9aefcd8276..a7dbdcd373 100644 --- a/TelegramUI/CommandChatInputContextPanelNode.swift +++ b/TelegramUI/CommandChatInputContextPanelNode.swift @@ -16,7 +16,7 @@ private struct CommandChatInputContextPanelEntryStableId: Hashable { } } -private struct CommandChatInputContextPanelEntry: Equatable, Comparable, Identifiable { +private struct CommandChatInputContextPanelEntry: Comparable, Identifiable { let index: Int let command: PeerCommand @@ -102,7 +102,10 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { let replacementText = command.command.text + " " inputText.replaceSubrange(range, with: replacementText) - let utfLowerIndex = inputText.utf16.distance(from: inputText.utf16.startIndex, to: range.lowerBound.samePosition(in: inputText.utf16)) + guard let lowerBound = range.lowerBound.samePosition(in: inputText.utf16) else { + return textInputState + } + let utfLowerIndex = inputText.utf16.distance(from: inputText.utf16.startIndex, to: lowerBound) let replacementLength = replacementText.utf16.distance(from: replacementText.utf16.startIndex, to: replacementText.utf16.endIndex) diff --git a/TelegramUI/CreateGroupController.swift b/TelegramUI/CreateGroupController.swift index 2585bd13d4..10266480ad 100644 --- a/TelegramUI/CreateGroupController.swift +++ b/TelegramUI/CreateGroupController.swift @@ -116,7 +116,7 @@ private enum CreateGroupEntry: ItemListNodeEntry { }) case let .member(_, theme, strings, peer, presence): - return ItemListPeerItem(theme: theme, strings: strings, account: arguments.account, peer: peer, presence: presence, text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _ in }, removePeer: { _ in }) + return ItemListPeerItem(theme: theme, strings: strings, account: arguments.account, peer: peer, presence: presence, text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) } } } diff --git a/TelegramUI/DefaultDarkPresentationTheme.swift b/TelegramUI/DefaultDarkPresentationTheme.swift index 736ac0cc58..a3c53644cc 100644 --- a/TelegramUI/DefaultDarkPresentationTheme.swift +++ b/TelegramUI/DefaultDarkPresentationTheme.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit private let accentColor: UIColor = UIColor(rgb: 0xb2b2b2) private let destructiveColor: UIColor = .red diff --git a/TelegramUI/DefaultPresentationTheme.swift b/TelegramUI/DefaultPresentationTheme.swift index 68a01a7105..a0ca9d0413 100644 --- a/TelegramUI/DefaultPresentationTheme.swift +++ b/TelegramUI/DefaultPresentationTheme.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit private let accentColor: UIColor = UIColor(rgb: 0x007ee5) private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30) diff --git a/TelegramUI/EmbedVideoNode.swift b/TelegramUI/EmbedVideoNode.swift index 49a3f01e8f..3b6c86ef33 100644 --- a/TelegramUI/EmbedVideoNode.swift +++ b/TelegramUI/EmbedVideoNode.swift @@ -106,11 +106,11 @@ private final class SharedEmbedVideoContext: SharedVideoContext { self.thumbnailDisposable = (rawMessagePhoto(account: account, photo: image) |> deliverOnMainQueue).start(next: { [weak self] image in if let strongSelf = self { strongSelf.thumbnail.set(.single(image)) - strongSelf._ready.set(.single()) + strongSelf._ready.set(.single(Void())) } }) } else { - self._ready.set(.single()) + self._ready.set(.single(Void())) } /*self.playerView.requestAudioSession = { [weak self] in diff --git a/TelegramUI/EmojiUtils.swift b/TelegramUI/EmojiUtils.swift index 5fb871cb27..34fda25aa2 100644 --- a/TelegramUI/EmojiUtils.swift +++ b/TelegramUI/EmojiUtils.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit extension UnicodeScalar { var isEmoji: Bool { diff --git a/TelegramUI/FeaturedStickerPacksController.swift b/TelegramUI/FeaturedStickerPacksController.swift index 1ecc02ec5e..38c191c51a 100644 --- a/TelegramUI/FeaturedStickerPacksController.swift +++ b/TelegramUI/FeaturedStickerPacksController.swift @@ -105,12 +105,12 @@ private enum FeaturedStickerPacksEntry: ItemListNodeEntry { func item(_ arguments: FeaturedStickerPacksControllerArguments) -> ListViewItem { switch self { case let .pack(_, theme, info, unread, topItem, count, installed): - return ItemListStickerPackItem(theme: theme, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: unread, control: .installation(installed: installed), editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false), enabled: true, sectionId: self.section, action: { _ in + return ItemListStickerPackItem(theme: theme, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: unread, control: .installation(installed: installed), editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false), enabled: true, sectionId: self.section, action: { arguments.openStickerPack(info) - }, setPackIdWithRevealedOptions: { _ in + }, setPackIdWithRevealedOptions: { _, _ in }, addPack: { arguments.addPack(info) - }, removePack: { _ in + }, removePack: { }) } } diff --git a/TelegramUI/FrameworkBundle.swift b/TelegramUI/FrameworkBundle.swift index adc1f674ee..3f7a202833 100644 --- a/TelegramUI/FrameworkBundle.swift +++ b/TelegramUI/FrameworkBundle.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit private class FrameworkBundleClass: NSObject { } diff --git a/TelegramUI/GalleryController.swift b/TelegramUI/GalleryController.swift index 3636b3103e..2937dcbde7 100644 --- a/TelegramUI/GalleryController.swift +++ b/TelegramUI/GalleryController.swift @@ -79,7 +79,8 @@ 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 ChatVideoGalleryItem(account: account, theme: theme, strings: strings, message: message, location: location) + return UniversalVideoGalleryItem(account: account, theme: theme, strings: strings, content: NativeVideoContent(file: file), originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, caption: message.text) + //return ChatVideoGalleryItem(account: account, theme: theme, strings: strings, message: message, location: location) } else { if file.mimeType.hasPrefix("image/") { return ChatImageGalleryItem(account: account, theme: theme, strings: strings, message: message, location: location) @@ -421,6 +422,7 @@ class GalleryController: ViewController { if let media = mediaForMessage(message: message), let transitionArguments = presentationArguments.transitionArguments(message.id, media) { nodeAnimatesItself = true + centralItemNode.activateAsInitial() centralItemNode.animateIn(from: transitionArguments.transitionNode) self._hiddenMedia.set(.single((message.id, media))) diff --git a/TelegramUI/GalleryControllerNode.swift b/TelegramUI/GalleryControllerNode.swift index 4c66f92d12..87781af6b1 100644 --- a/TelegramUI/GalleryControllerNode.swift +++ b/TelegramUI/GalleryControllerNode.swift @@ -35,6 +35,7 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate { self.backgroundNode = ASDisplayNode() self.backgroundNode.backgroundColor = UIColor.black self.scrollView = UIScrollView() + self.scrollView.delaysContentTouches = false if #available(iOSApplicationExtension 11.0, *) { self.scrollView.contentInsetAdjustmentBehavior = .never diff --git a/TelegramUI/GalleryItem.swift b/TelegramUI/GalleryItem.swift index 7a6a3f4d64..62fade205f 100644 --- a/TelegramUI/GalleryItem.swift +++ b/TelegramUI/GalleryItem.swift @@ -1,6 +1,22 @@ import Foundation +struct GalleryItemOriginData: Equatable { + let title: String? + let timestamp: Int32? + + static func ==(lhs: GalleryItemOriginData, rhs: GalleryItemOriginData) -> Bool { + return lhs.title == rhs.title && lhs.timestamp == rhs.timestamp + } +} +struct GalleryItemIndexData: Equatable { + let position: Int32 + let totalCount: Int32 + + static func ==(lhs: GalleryItemIndexData, rhs: GalleryItemIndexData) -> Bool { + return lhs.position == rhs.position && lhs.totalCount == rhs.totalCount + } +} protocol GalleryItem { func node() -> GalleryItemNode diff --git a/TelegramUI/GalleryItemNode.swift b/TelegramUI/GalleryItemNode.swift index b32f4d08f4..f220a488f8 100644 --- a/TelegramUI/GalleryItemNode.swift +++ b/TelegramUI/GalleryItemNode.swift @@ -62,6 +62,9 @@ open class GalleryItemNode: ASDisplayNode { open func centralityUpdated(isCentral: Bool) { } + open func activateAsInitial() { + } + open func visibilityUpdated(isVisible: Bool) { } diff --git a/TelegramUI/GalleryPagerNode.swift b/TelegramUI/GalleryPagerNode.swift index f42ef22299..cf90539925 100644 --- a/TelegramUI/GalleryPagerNode.swift +++ b/TelegramUI/GalleryPagerNode.swift @@ -45,6 +45,7 @@ final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate { self.scrollView.delegate = self self.scrollView.clipsToBounds = false self.scrollView.scrollsToTop = false + self.scrollView.delaysContentTouches = false self.view.addSubview(self.scrollView) } diff --git a/TelegramUI/GalleryVideoDecoration.swift b/TelegramUI/GalleryVideoDecoration.swift new file mode 100644 index 0000000000..ecddc1086f --- /dev/null +++ b/TelegramUI/GalleryVideoDecoration.swift @@ -0,0 +1,63 @@ +import Foundation +import AsyncDisplayKit +import Display +import SwiftSignalKit + +final class GalleryVideoDecoration: UniversalVideoDecoration { + let backgroundNode: ASDisplayNode? = nil + let contentContainerNode: ASDisplayNode + let foregroundNode: ASDisplayNode? = nil + + private var contentNode: (ASDisplayNode & UniversalVideoContentNode)? + + private var validLayoutSize: CGSize? + + init() { + self.contentContainerNode = ASDisplayNode() + } + + func updateContentNode(_ contentNode: (UniversalVideoContentNode & ASDisplayNode)?) { + if self.contentNode !== contentNode { + let previous = self.contentNode + self.contentNode = contentNode + + if let previous = previous { + if previous.supernode === self.contentContainerNode { + previous.removeFromSupernode() + } + } + + if let contentNode = contentNode { + if contentNode.supernode !== self.contentContainerNode { + self.contentContainerNode.addSubnode(contentNode) + if let validLayoutSize = self.validLayoutSize { + contentNode.frame = CGRect(origin: CGPoint(), size: validLayoutSize) + contentNode.updateLayout(size: validLayoutSize, transition: .immediate) + } + } + } + } + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + self.validLayoutSize = size + + if let backgroundNode = self.backgroundNode { + transition.updateFrame(node: backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) + } + if let foregroundNode = self.foregroundNode { + transition.updateFrame(node: foregroundNode, frame: CGRect(origin: CGPoint(), size: size)) + } + transition.updateFrame(node: self.contentContainerNode, frame: CGRect(origin: CGPoint(), size: size)) + if let contentNode = self.contentNode { + transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(), size: size)) + contentNode.updateLayout(size: size, transition: transition) + } + } + + func setStatus(_ status: Signal) { + } + + func tap() { + } +} diff --git a/TelegramUI/GroupAdminsController.swift b/TelegramUI/GroupAdminsController.swift index bc7a0e3df6..bc7b817f15 100644 --- a/TelegramUI/GroupAdminsController.swift +++ b/TelegramUI/GroupAdminsController.swift @@ -150,7 +150,7 @@ private enum GroupAdminsEntry: ItemListNodeEntry { case let .allAdminsInfo(text): return ItemListTextItem(text: .plain(text), sectionId: self.section) case let .peerItem(_, peer, presence, toggled, enabled): - return ItemListPeerItem(account: arguments.account, peer: peer, presence: presence, text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: toggled, enabled: enabled, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _ in }, removePeer: { _ in }, toggleUpdated: { value in + return ItemListPeerItem(account: arguments.account, peer: peer, presence: presence, text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: toggled, enabled: enabled, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: { value in arguments.updatePeerIsAdmin(peer.id, value) }) } diff --git a/TelegramUI/GroupsInCommonController.swift b/TelegramUI/GroupsInCommonController.swift index fc729cd77e..2fecbb449f 100644 --- a/TelegramUI/GroupsInCommonController.swift +++ b/TelegramUI/GroupsInCommonController.swift @@ -96,7 +96,7 @@ private enum GroupsInCommonEntry: ItemListNodeEntry { case let .peerItem(_, theme, strings, peer): return ItemListPeerItem(theme: theme, strings: strings, account: arguments.account, peer: peer, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, sectionId: self.section, action: { arguments.openPeer(peer.id) - }, setPeerIdWithRevealedOptions: { _ in + }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) } diff --git a/TelegramUI/HapticFeedback.swift b/TelegramUI/HapticFeedback.swift index 82254348de..d7e3786586 100644 --- a/TelegramUI/HapticFeedback.swift +++ b/TelegramUI/HapticFeedback.swift @@ -23,7 +23,7 @@ private final class HapticFeedbackImpl { self.notificationGenerator.notificationOccurred(.error) } - dynamic func f() { + @objc dynamic func f() { } } diff --git a/TelegramUI/HashtagChatInputContextPanelNode.swift b/TelegramUI/HashtagChatInputContextPanelNode.swift index c3d6586f1f..2c4b7a7bfe 100644 --- a/TelegramUI/HashtagChatInputContextPanelNode.swift +++ b/TelegramUI/HashtagChatInputContextPanelNode.swift @@ -4,7 +4,7 @@ import Postbox import TelegramCore import Display -private struct HashtagChatInputContextPanelEntry: Equatable, Comparable, Identifiable { +private struct HashtagChatInputContextPanelEntry: Comparable, Identifiable { let index: Int let text: String @@ -87,7 +87,10 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { let replacementText = text + " " inputText.replaceSubrange(range, with: replacementText) - let utfLowerIndex = inputText.utf16.distance(from: inputText.utf16.startIndex, to: range.lowerBound.samePosition(in: inputText.utf16)) + guard let lowerBound = range.lowerBound.samePosition(in: inputText.utf16) else { + return textInputState + } + let utfLowerIndex = inputText.utf16.distance(from: inputText.utf16.startIndex, to: lowerBound) let replacementLength = replacementText.utf16.distance(from: replacementText.utf16.startIndex, to: replacementText.utf16.endIndex) diff --git a/TelegramUI/HashtagSearchController.swift b/TelegramUI/HashtagSearchController.swift index a609a1f18b..3469993b53 100644 --- a/TelegramUI/HashtagSearchController.swift +++ b/TelegramUI/HashtagSearchController.swift @@ -59,9 +59,9 @@ final class HashtagSearchController: TelegramController { } strongSelf.controllerNode.listNode.clearHighlightAnimated(true) } - }, setPeerIdWithRevealedOptions: { _ in - }, setPeerPinned: { _ in - }, setPeerMuted: { _ in + }, setPeerIdWithRevealedOptions: { _, _ in + }, setPeerPinned: { _, _ in + }, setPeerMuted: { _, _ in }, deletePeer: { _ in }) diff --git a/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift b/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift index 028e3d7dcc..54d6d49b9b 100644 --- a/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift +++ b/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift @@ -16,7 +16,7 @@ private struct ChatContextResultStableId: Hashable { } } -private struct HorizontalListContextResultsChatInputContextPanelEntry: Equatable, Comparable, Identifiable { +private struct HorizontalListContextResultsChatInputContextPanelEntry: Comparable, Identifiable { let index: Int let result: ChatContextResult diff --git a/TelegramUI/InstalledStickerPacksController.swift b/TelegramUI/InstalledStickerPacksController.swift index e46be5f960..3727b6b294 100644 --- a/TelegramUI/InstalledStickerPacksController.swift +++ b/TelegramUI/InstalledStickerPacksController.swift @@ -225,7 +225,7 @@ private enum InstalledStickerPacksEntry: ItemListNodeEntry { case let .packsTitle(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) case let .pack(_, theme, info, topItem, count, enabled, editing): - return ItemListStickerPackItem(theme: theme, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: .none, editing: editing, enabled: enabled, sectionId: self.section, action: { _ in + return ItemListStickerPackItem(theme: theme, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: .none, editing: editing, enabled: enabled, sectionId: self.section, action: { arguments.openStickerPack(info) }, setPackIdWithRevealedOptions: { current, previous in arguments.setPackIdWithRevealedOptions(current, previous) diff --git a/TelegramUI/InstantPageTextItem.swift b/TelegramUI/InstantPageTextItem.swift index 21f89e7fcc..e953de5200 100644 --- a/TelegramUI/InstantPageTextItem.swift +++ b/TelegramUI/InstantPageTextItem.swift @@ -107,7 +107,7 @@ final class InstantPageTextItem: InstantPageItem { context.restoreGState() } - private func attributesAtPoint(_ point: CGPoint) -> (Int, [String: Any])? { + private func attributesAtPoint(_ point: CGPoint) -> (Int, [NSAttributedStringKey: Any])? { let transformedPoint = CGPoint(x: point.x, y: point.y) for line in self.lines { let lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y), size: line.frame.size) @@ -150,7 +150,7 @@ final class InstantPageTextItem: InstantPageItem { return nil } - private func attributeRects(name: String, at index: Int) -> [CGRect]? { + private func attributeRects(name: NSAttributedStringKey, at index: Int) -> [CGRect]? { var range = NSRange() let _ = self.attributedString.attribute(name, at: index, effectiveRange: &range) if range.length != 0 { @@ -190,8 +190,8 @@ final class InstantPageTextItem: InstantPageItem { func linkSelectionRects(at point: CGPoint) -> [CGRect] { if let (index, dict) = self.attributesAtPoint(point) { - if let _ = dict[TextNode.UrlAttribute] { - if let rects = self.attributeRects(name: TextNode.UrlAttribute, at: index) { + if let _ = dict[NSAttributedStringKey(rawValue: TextNode.UrlAttribute)] { + if let rects = self.attributeRects(name: NSAttributedStringKey(rawValue: TextNode.UrlAttribute), at: index) { return rects } } @@ -202,7 +202,7 @@ final class InstantPageTextItem: InstantPageItem { func urlAttribute(at point: CGPoint) -> InstantPageUrlItem? { if let (_, dict) = self.attributesAtPoint(point) { - if let url = dict[TextNode.UrlAttribute] as? InstantPageUrlItem { + if let url = dict[NSAttributedStringKey(rawValue: TextNode.UrlAttribute)] as? InstantPageUrlItem { return url } } @@ -264,7 +264,7 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt case let .plain(string): var attributes = styleStack.textAttributes() if let url = url { - attributes[TextNode.UrlAttribute] = url + attributes[NSAttributedStringKey(rawValue: TextNode.UrlAttribute)] = url } return NSAttributedString(string: string, attributes: attributes) case let .bold(text): @@ -320,12 +320,12 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo } var lines: [InstantPageTextLine] = [] - guard let font = string.attribute(NSFontAttributeName, at: 0, effectiveRange: nil) as? UIFont else { + guard let font = string.attribute(NSAttributedStringKey.font, at: 0, effectiveRange: nil) as? UIFont else { return InstantPageTextItem(frame: CGRect(), attributedString: string, lines: []) } var lineSpacingFactor: CGFloat = 1.12 - if let lineSpacingFactorAttribute = string.attribute(InstantPageLineSpacingFactorAttribute, at: 0, effectiveRange: nil) { + if let lineSpacingFactorAttribute = string.attribute(NSAttributedStringKey(rawValue: InstantPageLineSpacingFactorAttribute), at: 0, effectiveRange: nil) { lineSpacingFactor = CGFloat((lineSpacingFactorAttribute as! NSNumber).floatValue) } @@ -355,7 +355,7 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo let lineRange = NSMakeRange(lastIndex, lineCharacterCount) - string.enumerateAttribute(NSStrikethroughStyleAttributeName, in: lineRange, options: [], using: { item, range, _ in + string.enumerateAttribute(NSAttributedStringKey.strikethroughStyle, in: lineRange, options: [], using: { item, range, _ in if let item = item { let lowerX = floor(CTLineGetOffsetForStringIndex(line, range.location, nil)) let upperX = ceil(CTLineGetOffsetForStringIndex(line, range.location + range.length, nil)) diff --git a/TelegramUI/InstantPageTextStyleStack.swift b/TelegramUI/InstantPageTextStyleStack.swift index 67a534a034..3d10a6bb51 100644 --- a/TelegramUI/InstantPageTextStyleStack.swift +++ b/TelegramUI/InstantPageTextStyleStack.swift @@ -29,7 +29,7 @@ final class InstantPageTextStyleStack { } } - func textAttributes() -> [String: Any] { + func textAttributes() -> [NSAttributedStringKey: Any] { var fontSize: CGFloat? var fontSerif: Bool? var fontFixed: Bool? @@ -81,7 +81,7 @@ final class InstantPageTextStyleStack { } } - var attributes: [String: Any] = [:] + var attributes: [NSAttributedStringKey: Any] = [:] var parsedFontSize: CGFloat if let fontSize = fontSize { @@ -92,54 +92,54 @@ final class InstantPageTextStyleStack { if (bold != nil && bold!) && (italic != nil && italic!) { if fontSerif != nil && fontSerif! { - attributes[NSFontAttributeName] = UIFont(name: "Georgia-BoldItalic", size: parsedFontSize) + attributes[NSAttributedStringKey.font] = UIFont(name: "Georgia-BoldItalic", size: parsedFontSize) } else if fontFixed != nil && fontFixed! { - attributes[NSFontAttributeName] = UIFont(name: "Menlo-BoldItalic", size: parsedFontSize) + attributes[NSAttributedStringKey.font] = UIFont(name: "Menlo-BoldItalic", size: parsedFontSize) } else { - attributes[NSFontAttributeName] = Font.bold(parsedFontSize) + attributes[NSAttributedStringKey.font] = Font.bold(parsedFontSize) } } else if bold != nil && bold! { if fontSerif != nil && fontSerif! { - attributes[NSFontAttributeName] = UIFont(name: "Georgia-Bold", size: parsedFontSize) + attributes[NSAttributedStringKey.font] = UIFont(name: "Georgia-Bold", size: parsedFontSize) } else if fontFixed != nil && fontFixed! { - attributes[NSFontAttributeName] = UIFont(name: "Menlo-Bold", size: parsedFontSize) + attributes[NSAttributedStringKey.font] = UIFont(name: "Menlo-Bold", size: parsedFontSize) } else { - attributes[NSFontAttributeName] = Font.bold(parsedFontSize) + attributes[NSAttributedStringKey.font] = Font.bold(parsedFontSize) } } else if italic != nil && italic! { if fontSerif != nil && fontSerif! { - attributes[NSFontAttributeName] = UIFont(name: "Georgia-Italic", size: parsedFontSize) + attributes[NSAttributedStringKey.font] = UIFont(name: "Georgia-Italic", size: parsedFontSize) } else if fontFixed != nil && fontFixed! { - attributes[NSFontAttributeName] = UIFont(name: "Menlo-Italic", size: parsedFontSize) + attributes[NSAttributedStringKey.font] = UIFont(name: "Menlo-Italic", size: parsedFontSize) } else { - attributes[NSFontAttributeName] = Font.italic(parsedFontSize) + attributes[NSAttributedStringKey.font] = Font.italic(parsedFontSize) } } else { if fontSerif != nil && fontSerif! { - attributes[NSFontAttributeName] = UIFont(name: "Georgia", size: parsedFontSize) + attributes[NSAttributedStringKey.font] = UIFont(name: "Georgia", size: parsedFontSize) } else if fontFixed != nil && fontFixed! { - attributes[NSFontAttributeName] = UIFont(name: "Menlo", size: parsedFontSize) + attributes[NSAttributedStringKey.font] = UIFont(name: "Menlo", size: parsedFontSize) } else { - attributes[NSFontAttributeName] = Font.regular(parsedFontSize) + attributes[NSAttributedStringKey.font] = Font.regular(parsedFontSize) } } if strikethrough != nil && strikethrough! { - attributes[NSStrikethroughStyleAttributeName] = (NSUnderlineStyle.styleSingle.rawValue | NSUnderlineStyle.patternSolid.rawValue) as NSNumber + attributes[NSAttributedStringKey.strikethroughStyle] = (NSUnderlineStyle.styleSingle.rawValue | NSUnderlineStyle.patternSolid.rawValue) as NSNumber } if underline != nil && underline! { - attributes[NSUnderlineStyleAttributeName] = NSUnderlineStyle.styleSingle.rawValue as NSNumber + attributes[NSAttributedStringKey.underlineStyle] = NSUnderlineStyle.styleSingle.rawValue as NSNumber } if let color = color { - attributes[NSForegroundColorAttributeName] = color + attributes[NSAttributedStringKey.foregroundColor] = color } else { - attributes[NSForegroundColorAttributeName] = UIColor.black + attributes[NSAttributedStringKey.foregroundColor] = UIColor.black } if let lineSpacingFactor = lineSpacingFactor { - attributes[InstantPageLineSpacingFactorAttribute] = lineSpacingFactor as NSNumber + attributes[NSAttributedStringKey(rawValue: InstantPageLineSpacingFactorAttribute)] = lineSpacingFactor as NSNumber } return attributes diff --git a/TelegramUI/InstantPageTile.swift b/TelegramUI/InstantPageTile.swift index f27a6d5aa3..1333b66f46 100644 --- a/TelegramUI/InstantPageTile.swift +++ b/TelegramUI/InstantPageTile.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit final class InstantPageTile { let frame: CGRect diff --git a/TelegramUI/ItemListActivityTextItem.swift b/TelegramUI/ItemListActivityTextItem.swift index 05b1d3023e..17971c4faf 100644 --- a/TelegramUI/ItemListActivityTextItem.swift +++ b/TelegramUI/ItemListActivityTextItem.swift @@ -101,8 +101,8 @@ class ItemListActivityTextItemNode: ListViewItemNode { } let titleString = NSMutableAttributedString(attributedString: item.text) - titleString.removeAttribute(NSFontAttributeName, range: NSMakeRange(0, titleString.length)) - titleString.addAttributes([NSFontAttributeName: titleFont], range: NSMakeRange(0, titleString.length)) + titleString.removeAttribute(NSAttributedStringKey.font, range: NSMakeRange(0, titleString.length)) + titleString.addAttributes([NSAttributedStringKey.font: titleFont], range: NSMakeRange(0, titleString.length)) let (titleLayout, titleApply) = makeTitleLayout(titleString, nil, 0, .end, CGSize(width: width - 20, height: CGFloat.greatestFiniteMagnitude), .natural, TextNodeCutout(position: .TopLeft, size: CGSize(width: activityWidth, height: 4.0)), UIEdgeInsets()) diff --git a/TelegramUI/ItemListControllerNode.swift b/TelegramUI/ItemListControllerNode.swift index 6e1fb31853..12585c3b08 100644 --- a/TelegramUI/ItemListControllerNode.swift +++ b/TelegramUI/ItemListControllerNode.swift @@ -5,7 +5,7 @@ import TelegramCore typealias ItemListSectionId = Int32 -protocol ItemListNodeEntry: Equatable, Comparable, Identifiable { +protocol ItemListNodeEntry: Comparable, Identifiable { associatedtype ItemGenerationArguments var section: ItemListSectionId { get } diff --git a/TelegramUI/ItemListMultilineInputItem.swift b/TelegramUI/ItemListMultilineInputItem.swift index 9e14f4f8c7..7a8737c29c 100644 --- a/TelegramUI/ItemListMultilineInputItem.swift +++ b/TelegramUI/ItemListMultilineInputItem.swift @@ -99,7 +99,7 @@ class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNodeDelega if let item = self.item { textColor = item.theme.list.itemPrimaryTextColor } - self.textNode.typingAttributes = [NSFontAttributeName: Font.regular(17.0), NSForegroundColorAttributeName: textColor] + self.textNode.typingAttributes = [NSAttributedStringKey.font.rawValue: Font.regular(17.0), NSAttributedStringKey.foregroundColor.rawValue: textColor] self.textNode.clipsToBounds = true self.textNode.delegate = self self.textNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0) @@ -155,7 +155,7 @@ class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNodeDelega strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBackgroundColor if strongSelf.isNodeLoaded { - strongSelf.textNode.typingAttributes = [NSFontAttributeName: Font.regular(17.0), NSForegroundColorAttributeName: item.theme.list.itemPrimaryTextColor] + strongSelf.textNode.typingAttributes = [NSAttributedStringKey.font.rawValue: Font.regular(17.0), NSAttributedStringKey.foregroundColor.rawValue: item.theme.list.itemPrimaryTextColor] } } diff --git a/TelegramUI/ItemListSingleLineInputItem.swift b/TelegramUI/ItemListSingleLineInputItem.swift index ebe2d2cd49..2d2622546b 100644 --- a/TelegramUI/ItemListSingleLineInputItem.swift +++ b/TelegramUI/ItemListSingleLineInputItem.swift @@ -105,7 +105,7 @@ class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDelegate, It override func didLoad() { super.didLoad() - self.textNode.textField.typingAttributes = [NSFontAttributeName: Font.regular(17.0)] + self.textNode.textField.typingAttributes = [NSAttributedStringKey.font.rawValue: Font.regular(17.0)] self.textNode.textField.font = Font.regular(17.0) if let item = self.item { self.textNode.textField.textColor = item.theme.list.itemPrimaryTextColor @@ -132,8 +132,8 @@ class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDelegate, It let leftInset: CGFloat = 16.0 let titleString = NSMutableAttributedString(attributedString: item.title) - titleString.removeAttribute(NSFontAttributeName, range: NSMakeRange(0, titleString.length)) - titleString.addAttributes([NSFontAttributeName: Font.regular(17.0)], range: NSMakeRange(0, titleString.length)) + titleString.removeAttribute(NSAttributedStringKey.font, range: NSMakeRange(0, titleString.length)) + titleString.addAttributes([NSAttributedStringKey.font: Font.regular(17.0)], range: NSMakeRange(0, titleString.length)) let (titleLayout, titleApply) = makeTitleLayout(titleString, nil, 0, .end, CGSize(width: width - 32 - leftInset, height: CGFloat.greatestFiniteMagnitude), .natural, nil, UIEdgeInsets()) diff --git a/TelegramUI/ItemListTextItem.swift b/TelegramUI/ItemListTextItem.swift index 3d9a6078d3..e8f5c2e3da 100644 --- a/TelegramUI/ItemListTextItem.swift +++ b/TelegramUI/ItemListTextItem.swift @@ -149,7 +149,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[TextNode.UrlAttribute] as? String { + if let url = attributes[NSAttributedStringKey(rawValue: TextNode.UrlAttribute)] as? String { item.linkAction?(.tap(url)) } } diff --git a/TelegramUI/LanguageSelectionController.swift b/TelegramUI/LanguageSelectionController.swift index 517152acda..9dd969e98b 100644 --- a/TelegramUI/LanguageSelectionController.swift +++ b/TelegramUI/LanguageSelectionController.swift @@ -372,7 +372,7 @@ final class LanguageSelectionController: ViewController { context.fill(CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: UIScreenPixel))) }) self.innerNavigationController.navigationBar.isTranslucent = false - self.innerNavigationController.navigationBar.titleTextAttributes = [NSFontAttributeName: Font.semibold(17.0), NSForegroundColorAttributeName: self.presentationData.theme.rootController.navigationBar.primaryTextColor] + self.innerNavigationController.navigationBar.titleTextAttributes = [NSAttributedStringKey.font: Font.semibold(17.0), NSAttributedStringKey.foregroundColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor] self.innerController.dismiss = { [weak self] in self?.cancelPressed() diff --git a/TelegramUI/LegacyMediaLocations.swift b/TelegramUI/LegacyMediaLocations.swift index e97f5ccc3d..fdf9443912 100644 --- a/TelegramUI/LegacyMediaLocations.swift +++ b/TelegramUI/LegacyMediaLocations.swift @@ -18,10 +18,10 @@ func resourceFromLegacyImageUri(_ uri: String) -> MediaResource? { let matches = legacyImageUriExpr.matches(in: uri, options: [], range: NSRange(location: 0, length: uri.characters.count)) if let match = matches.first { let nsString = uri as NSString - let datacenterId = nsString.substring(with: match.rangeAt(1)) - let volumeId = nsString.substring(with: match.rangeAt(2)) - let localId = nsString.substring(with: match.rangeAt(3)) - let secret = nsString.substring(with: match.rangeAt(4)) + let datacenterId = nsString.substring(with: match.range(at: 1)) + let volumeId = nsString.substring(with: match.range(at: 2)) + let localId = nsString.substring(with: match.range(at: 3)) + let secret = nsString.substring(with: match.range(at: 4)) guard let nDatacenterId = Int(datacenterId) else { return nil diff --git a/TelegramUI/ListMessageSnippetItemNode.swift b/TelegramUI/ListMessageSnippetItemNode.swift index 79f20a1e43..1f4cc03d81 100644 --- a/TelegramUI/ListMessageSnippetItemNode.swift +++ b/TelegramUI/ListMessageSnippetItemNode.swift @@ -144,7 +144,7 @@ final class ListMessageSnippetItemNode: ListMessageNode { let style = NSMutableParagraphStyle() style.lineSpacing = 4.0 - mutableDescriptionText.addAttribute(NSParagraphStyleAttributeName, value: style, range: NSMakeRange(0, mutableDescriptionText.length)) + mutableDescriptionText.addAttribute(NSAttributedStringKey.paragraphStyle, value: style, range: NSMakeRange(0, mutableDescriptionText.length)) descriptionText = mutableDescriptionText } diff --git a/TelegramUI/Markdown.swift b/TelegramUI/Markdown.swift index 26123b78ba..6ee70b744b 100644 --- a/TelegramUI/Markdown.swift +++ b/TelegramUI/Markdown.swift @@ -59,10 +59,10 @@ func parseMarkdownIntoAttributedString(_ string: String, attributes: MarkdownAtt let result = NSMutableAttributedString() var remainingRange = NSMakeRange(0, nsString.length) - var bodyAttributes: [String: Any] = [NSFontAttributeName: attributes.body.font, NSForegroundColorAttributeName: attributes.body.textColor, NSParagraphStyleAttributeName: paragraphStyleWithAlignment(textAlignment)] + var bodyAttributes: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: attributes.body.font, NSAttributedStringKey.foregroundColor: attributes.body.textColor, NSAttributedStringKey.paragraphStyle: paragraphStyleWithAlignment(textAlignment)] if !attributes.body.additionalAttributes.isEmpty { for (key, value) in attributes.body.additionalAttributes { - bodyAttributes[key] = value + bodyAttributes[NSAttributedStringKey(rawValue: key)] = value } } @@ -78,14 +78,14 @@ func parseMarkdownIntoAttributedString(_ string: String, attributes: MarkdownAtt if character == UInt16(("[" as UnicodeScalar).value) { remainingRange = NSMakeRange(range.location + range.length, remainingRange.location + remainingRange.length - (range.location + range.length)) if let (parsedLinkText, parsedLinkContents) = parseLink(string: nsString, remainingRange: &remainingRange) { - var linkAttributes: [String: Any] = [NSFontAttributeName: attributes.link.font, NSForegroundColorAttributeName: attributes.link.textColor, NSParagraphStyleAttributeName: paragraphStyleWithAlignment(textAlignment)] + var linkAttributes: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: attributes.link.font, NSAttributedStringKey.foregroundColor: attributes.link.textColor, NSAttributedStringKey.paragraphStyle: paragraphStyleWithAlignment(textAlignment)] if !attributes.body.additionalAttributes.isEmpty { for (key, value) in attributes.link.additionalAttributes { - linkAttributes[key] = value + linkAttributes[NSAttributedStringKey(rawValue: key)] = value } } if let (attributeName, attributeValue) = attributes.linkAttribute(parsedLinkContents) { - linkAttributes[attributeName] = attributeValue + linkAttributes[NSAttributedStringKey(rawValue: attributeName)] = attributeValue } result.append(NSAttributedString(string: parsedLinkText, attributes: linkAttributes)) } @@ -96,10 +96,10 @@ func parseMarkdownIntoAttributedString(_ string: String, attributes: MarkdownAtt remainingRange = NSMakeRange(range.location + range.length + 1, remainingRange.location + remainingRange.length - (range.location + range.length + 1)) if let bold = parseBold(string: nsString, remainingRange: &remainingRange) { - var boldAttributes: [String: Any] = [NSFontAttributeName: attributes.bold.font, NSForegroundColorAttributeName: attributes.bold.textColor, NSParagraphStyleAttributeName: paragraphStyleWithAlignment(textAlignment)] + var boldAttributes: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: attributes.bold.font, NSAttributedStringKey.foregroundColor: attributes.bold.textColor, NSAttributedStringKey.paragraphStyle: paragraphStyleWithAlignment(textAlignment)] if !attributes.body.additionalAttributes.isEmpty { for (key, value) in attributes.bold.additionalAttributes { - boldAttributes[key] = value + boldAttributes[NSAttributedStringKey(rawValue: key)] = value } } result.append(NSAttributedString(string: bold, attributes: boldAttributes)) diff --git a/TelegramUI/MediaManager.swift b/TelegramUI/MediaManager.swift index a3fb762918..dbd68477b8 100644 --- a/TelegramUI/MediaManager.swift +++ b/TelegramUI/MediaManager.swift @@ -182,6 +182,8 @@ public final class MediaManager: NSObject { private var managedVideoContexts: [WrappedManagedMediaId: ActiveManagedVideoContext] = [:] + let universalVideoManager = UniversalVideoContentManager() + override init() { self.audioSession = ManagedAudioSession() diff --git a/TelegramUI/MediaNavigationAccessoryItemListNode.swift b/TelegramUI/MediaNavigationAccessoryItemListNode.swift index 33fa4c5e81..662f076e9b 100644 --- a/TelegramUI/MediaNavigationAccessoryItemListNode.swift +++ b/TelegramUI/MediaNavigationAccessoryItemListNode.swift @@ -64,8 +64,8 @@ final class MediaNavigationAccessoryItemListNode: ASDisplayNode { } } } - }, openSecretMessagePreview: { _ in }, closeSecretMessagePreview: { }, openPeer: { _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _ in }, navigateToMessage: { _ in }, clickThroughMessage: { }, toggleMessageSelection: { _ in }, sendMessage: { _ in }, sendSticker: { _ in }, sendGif: { _ in }, requestMessageActionCallback: { _ in }, openUrl: { _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openHashtag: { _ in }, updateInputState: { _ in }, openMessageShareMenu: { _ in - }, presentController: { _ in }, callPeer: { _ in }, longTap: { _ in }, openCheckoutOrReceipt: { _ in }, automaticMediaDownloadSettings: .none) + }, openSecretMessagePreview: { _ in }, closeSecretMessagePreview: { }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessageSelection: { _ in }, sendMessage: { _ in }, sendSticker: { _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, openUrl: { _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, openMessageShareMenu: { _ in + }, presentController: { _, _ in }, callPeer: { _ in }, longTap: { _ in }, openCheckoutOrReceipt: { _ in }, automaticMediaDownloadSettings: .none) let listNode = ChatHistoryListNode(account: account, peerId: updatedPlaylistPeerId, tagMask: .music, messageId: nil, controllerInteraction: controllerInteraction, mode: .list) listNode.preloadPages = true diff --git a/TelegramUI/MediaPlayer.swift b/TelegramUI/MediaPlayer.swift index d8e6c53774..2c2e8338e5 100644 --- a/TelegramUI/MediaPlayer.swift +++ b/TelegramUI/MediaPlayer.swift @@ -59,7 +59,9 @@ private final class MediaPlayerContext { fileprivate var actionAtEnd: MediaPlayerActionAtEnd = .stop - init(queue: Queue, audioSessionManager: ManagedAudioSession, playerStatus: ValuePromise, postbox: Postbox, resource: MediaResource, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, enableSound: Bool) { + private var stoppedAtEnd = false + + init(queue: Queue, audioSessionManager: ManagedAudioSession, playerStatus: ValuePromise, postbox: Postbox, resource: MediaResource, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool, enableSound: Bool) { assert(queue.isCurrent()) self.queue = queue @@ -80,7 +82,7 @@ private final class MediaPlayerContext { if let strongSelf = self, !strongSelf.enableSound { switch strongSelf.state { case .empty: - if value { + if value && playAutomatically { strongSelf.play() } case .paused: @@ -322,9 +324,13 @@ private final class MediaPlayerContext { self.state = .seeking(frameSource: frameSource, timestamp: timestamp, disposable: disposable, action: .play) self.lastStatusUpdateTimestamp = nil case let .paused(loadedState): - self.state = .playing(loadedState) self.lastStatusUpdateTimestamp = nil - self.tick() + if self.stoppedAtEnd { + self.seek(timestamp: 0.0, action: .play) + } else { + self.state = .playing(loadedState) + self.tick() + } case .playing: break } @@ -387,7 +393,7 @@ private final class MediaPlayerContext { switch self.state { case .empty: - break + self.play() case let .seeking(_, _, _, action): switch action { case .play: @@ -572,17 +578,23 @@ private final class MediaPlayerContext { if performActionAtEndNow { switch self.actionAtEnd { case .loop: + self.stoppedAtEnd = false self.seek(timestamp: 0.0, action: .play) case .stop: + self.stoppedAtEnd = true self.pause() case let .action(f): + self.stoppedAtEnd = true self.pause() f() case let .loopDisablingSound(f): + self.stoppedAtEnd = false self.enableSound = false self.seek(timestamp: 0.0, action: .play) f() } + } else { + self.stoppedAtEnd = false } } } @@ -660,9 +672,9 @@ final class MediaPlayer { } } - init(audioSessionManager: ManagedAudioSession, postbox: Postbox, resource: MediaResource, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, enableSound: Bool) { + init(audioSessionManager: ManagedAudioSession, postbox: Postbox, resource: MediaResource, streamable: Bool, video: Bool, preferSoftwareDecoding: Bool, playAutomatically: Bool = false, enableSound: Bool) { self.queue.async { - let context = MediaPlayerContext(queue: self.queue, audioSessionManager: audioSessionManager, playerStatus: self.statusValue, postbox: postbox, resource: resource, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, enableSound: enableSound) + let context = MediaPlayerContext(queue: self.queue, audioSessionManager: audioSessionManager, playerStatus: self.statusValue, postbox: postbox, resource: resource, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, playAutomatically: playAutomatically, enableSound: enableSound) self.contextRef = Unmanaged.passRetained(context) } } diff --git a/TelegramUI/MentionChatInputContextPanelNode.swift b/TelegramUI/MentionChatInputContextPanelNode.swift index 0a32ed06bd..a92988962a 100644 --- a/TelegramUI/MentionChatInputContextPanelNode.swift +++ b/TelegramUI/MentionChatInputContextPanelNode.swift @@ -4,7 +4,7 @@ import Postbox import TelegramCore import Display -private struct MentionChatInputContextPanelEntry: Equatable, Comparable, Identifiable { +private struct MentionChatInputContextPanelEntry: Comparable, Identifiable { let index: Int let peer: Peer @@ -88,7 +88,10 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { let replacementText = addressName + " " inputText.replaceSubrange(range, with: replacementText) - let utfLowerIndex = inputText.utf16.distance(from: inputText.utf16.startIndex, to: range.lowerBound.samePosition(in: inputText.utf16)) + guard let lowerBound = range.lowerBound.samePosition(in: inputText.utf16) else { + return textInputState + } + let utfLowerIndex = inputText.utf16.distance(from: inputText.utf16.startIndex, to: lowerBound) let replacementLength = replacementText.utf16.distance(from: replacementText.utf16.startIndex, to: replacementText.utf16.endIndex) diff --git a/TelegramUI/MultiplexedSoftwareVideoNode.swift b/TelegramUI/MultiplexedSoftwareVideoNode.swift index 45f8c53d5b..8b13789179 100644 --- a/TelegramUI/MultiplexedSoftwareVideoNode.swift +++ b/TelegramUI/MultiplexedSoftwareVideoNode.swift @@ -1,690 +1 @@ -import Foundation -import UIKit -import GLKit -import OpenGLES -import Display -import SwiftSignalKit -import AsyncDisplayKit -import Postbox -import TelegramCore -private final class MultiplexedSoftwareVideoTrackingNode: ASDisplayNode { - var inHierarchyUpdated: ((Bool) -> Void)? - - override func willEnterHierarchy() { - super.willEnterHierarchy() - - self.inHierarchyUpdated?(true) - } - - override func didExitHierarchy() { - super.didExitHierarchy() - - self.inHierarchyUpdated?(false) - } -} - -private final class VisibleVideoItem { - let file: TelegramMediaFile - let frame: CGRect - - init(file: TelegramMediaFile, frame: CGRect) { - self.file = file - self.frame = frame - } -} - -private enum UniformIndex: Int { - case Y = 0 - case UV - case RotationAngle - case ColorConversionMatrix -} - -private enum AttributeIndex: GLuint { - case Vertex = 0 - case TextureCoordinates - case NumAttributes -} - -private let colorConversion601: [GLfloat] = [ - 1.164, 1.164, 1.164, - 0.0, -0.392, 2.017, - 1.596, -0.813, 0.0 -] - -private let colorConversion709: [GLfloat] = [ - 1.164, 1.164, 1.164, - 0.0, -0.213, 2.112, - 1.793, -0.533, 0.0 -] - -private let fragmentShaderSource: Data = { - return try! Data(contentsOf: URL(fileURLWithPath: Bundle.main.path(forResource: "VTPlayer/VTPlayer_Shader", ofType: "fsh")!)) -}() - -private let vertexShaderSource: Data = { - return try! Data(contentsOf: URL(fileURLWithPath: Bundle.main.path(forResource: "VTPlayer/VTPlayer_Shader", ofType: "vsh")!)) -}() - -private final class SoftwareVideoFrames { - var frames: [MediaId: MediaTrackFrame] = [:] -} - -final class MultiplexedSoftwareVideoNode: UIView { - override class var layerClass: AnyClass { - return CAEAGLLayer.self - } - - private var eglLayer: CAEAGLLayer { - return self.layer as! CAEAGLLayer - } - - private let account: Account - private let scrollView: UIScrollView - private let trackingNode: MultiplexedSoftwareVideoTrackingNode - - private let context: EAGLContext - private var uniforms: [GLint] - private var program: GLuint = 0 - private var videoTextureCache: CVOpenGLESTextureCache? - - private var drawableSize: CGSize? - - private var frameBufferHandle: GLuint? - private var colorBufferHandle: GLuint? - - private var displayLink: CADisplayLink! - - var files: [TelegramMediaFile] = [] { - didSet { - self.updateVisibleItems() - } - } - private var displayItems: [VisibleVideoItem] = [] - private let sourceManager: MultiplexedSoftwareVideoSourceManager - - private let videoSourceQueue = Queue() - - init(account: Account, scrollView: UIScrollView) { - self.account = account - self.scrollView = scrollView - if #available(iOSApplicationExtension 11.0, *) { - self.scrollView.contentInsetAdjustmentBehavior = .never - } - self.trackingNode = MultiplexedSoftwareVideoTrackingNode() - self.sourceManager = MultiplexedSoftwareVideoSourceManager(queue: self.videoSourceQueue, account: account) - - self.context = EAGLContext(api: .openGLES2) - - var videoTextureCache: CVOpenGLESTextureCache? - CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, nil, self.context, nil, &videoTextureCache) - self.videoTextureCache = videoTextureCache - - self.uniforms = Array(repeating: 0, count: 4) - - super.init(frame: CGRect()) - - self.isUserInteractionEnabled = false - self.layer.contentsScale = 2.0 - self.isOpaque = true - - self.addSubnode(self.trackingNode) - - self.eglLayer.drawableProperties = [ - kEAGLDrawablePropertyRetainedBacking: false as NSNumber, - kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8 - ] - - EAGLContext.setCurrent(self.context) - - glDisable(GLenum(GL_DEPTH_TEST)) - - glEnableVertexAttribArray(AttributeIndex.Vertex.rawValue) - glVertexAttribPointer(AttributeIndex.Vertex.rawValue, 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(2 * MemoryLayout.size), nil) - - glEnableVertexAttribArray(AttributeIndex.TextureCoordinates.rawValue) - - glVertexAttribPointer(AttributeIndex.TextureCoordinates.rawValue, 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(2 * MemoryLayout.size), nil) - - self.loadShaders() - - glUseProgram(self.program) - - // 0 and 1 are the texture IDs of lumaTexture and chromaTexture respectively. - glUniform1i(self.uniforms[UniformIndex.Y.rawValue], 0) - glUniform1i(self.uniforms[UniformIndex.UV.rawValue], 1) - - glUniform1f(self.uniforms[UniformIndex.RotationAngle.rawValue], 0.0) - - glUniformMatrix3fv(self.uniforms[UniformIndex.ColorConversionMatrix.rawValue], 1, GLboolean(GL_FALSE), colorConversion709) - - class DisplayLinkProxy: NSObject { - weak var target: MultiplexedSoftwareVideoNode? - init(target: MultiplexedSoftwareVideoNode) { - self.target = target - } - - @objc func displayLinkEvent() { - self.target?.displayLinkEvent() - } - } - - self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent)) - self.displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes) - if #available(iOS 10.0, *) { - self.displayLink.preferredFramesPerSecond = 60 - } - self.displayLink.isPaused = true - - self.trackingNode.inHierarchyUpdated = { [weak self] value in - if let strongSelf = self { - strongSelf.displayLink.isPaused = !value - } - } - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - assert(Queue.mainQueue().isCurrent()) - - if var frameBufferHandle = self.frameBufferHandle { - glDeleteFramebuffers(1, &frameBufferHandle) - } - - if var colorBufferHandle = self.colorBufferHandle { - glDeleteFramebuffers(1, &colorBufferHandle) - } - } - - private func compileShaderWithType(shaderType: GLuint) -> GLuint? { - let source = shaderType == UInt32(GL_FRAGMENT_SHADER) ? fragmentShaderSource : vertexShaderSource - - let shader = glCreateShader(shaderType) - source.withUnsafeBytes { (bytes: UnsafePointer) -> Void in - var bytes: UnsafePointer? = bytes - var length = GLint(source.count) - withUnsafePointer(to: &bytes, { (bytesRef: UnsafePointer?>) -> Void in - glShaderSource(shader, 1, bytesRef, &length) - }) - } - - glCompileShader(shader) - - var logLength: GLsizei = 0 - glGetShaderInfoLog(shader, 0, &logLength, nil) - if logLength != 0 { - var log = Data(count: Int(logLength)) - - log.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in - glGetShaderInfoLog(shader, logLength, &logLength, bytes) - } - - if let logString = String(data: log, encoding: .utf8) { - print("Shader log: \(logString)") - } - } - - return shader - } - - private func loadShaders() { - self.program = glCreateProgram() - - guard let vertShader = self.compileShaderWithType(shaderType: GLuint(GL_VERTEX_SHADER)) else { - return - } - - guard let fragShader = self.compileShaderWithType(shaderType: GLuint(GL_FRAGMENT_SHADER)) else { - return - } - - glAttachShader(self.program, vertShader) - glAttachShader(self.program, fragShader) - - glBindAttribLocation(self.program, AttributeIndex.Vertex.rawValue, "position") - glBindAttribLocation(self.program, AttributeIndex.TextureCoordinates.rawValue, "texCoord") - - glLinkProgram(self.program) - - var status: GLint = 0 - glGetProgramiv(self.program, GLenum(GL_LINK_STATUS), &status) - let ok = (status != 0) - if (ok) { - self.uniforms[UniformIndex.Y.rawValue] = glGetUniformLocation(self.program, "SamplerY") - self.uniforms[UniformIndex.UV.rawValue] = glGetUniformLocation(self.program, "SamplerUV") - self.uniforms[UniformIndex.RotationAngle.rawValue] = glGetUniformLocation(self.program, "preferredRotation") - self.uniforms[UniformIndex.ColorConversionMatrix.rawValue] = glGetUniformLocation(self.program, "colorConversionMatrix") - } - - glDetachShader(self.program, vertShader) - glDeleteShader(vertShader) - - glDetachShader(self.program, fragShader) - glDeleteShader(fragShader) - - if (!ok) { - glDeleteProgram(self.program) - self.program = 0 - } - - assert(ok) - } - - override func layoutSubviews() { - super.layoutSubviews() - - self.updateDrawable() - } - - private func displayLinkEvent() { - self.draw() - } - - private var validVisibleItemsOffset: CGFloat? - private func updateImmediatelyVisibleItems() { - let visibleBounds = self.scrollView.bounds - if let validVisibleItemsOffset = self.validVisibleItemsOffset, validVisibleItemsOffset.isEqual(to: visibleBounds.origin.y) { - return - } - self.validVisibleItemsOffset = visibleBounds.origin.y - let minVisibleY = visibleBounds.minY - let maxVisibleY = visibleBounds.maxY - - var visibleItems: [TelegramMediaFile] = [] - for item in self.displayItems { - if item.frame.maxY < minVisibleY { - continue; - } - if item.frame.minY > maxVisibleY { - break; - } - visibleItems.append(item.file) - } - - self.sourceManager.updateVisibleItems(visibleItems) - } - - private func draw() { - EAGLContext.setCurrent(self.context) - - let timestamp = CACurrentMediaTime() - - self.updateImmediatelyVisibleItems() - self.sourceManager.update(at: timestamp) - - if let drawableSize = self.drawableSize, let frameBufferHandle = self.frameBufferHandle, let colorBufferHandle = self.colorBufferHandle { - glBindFramebuffer(GLenum(GL_FRAMEBUFFER), frameBufferHandle) - - let backingWidth = GLint(drawableSize.width * 2.0) - let backingHeight = GLint(drawableSize.height * 2.0) - - glViewport(0, 0, backingWidth, backingHeight) - - glClearColor(1.0, 1.0, 1.0, 1.0) - glClear(GLbitfield(GL_COLOR_BUFFER_BIT)) - - glUseProgram(self.program) - glUniform1f(self.uniforms[UniformIndex.RotationAngle.rawValue], 0.0) - glUniformMatrix3fv(self.uniforms[UniformIndex.ColorConversionMatrix.rawValue], 1, GLboolean(GL_FALSE), colorConversion709) - - glEnableVertexAttribArray(AttributeIndex.Vertex.rawValue) - glEnableVertexAttribArray(AttributeIndex.TextureCoordinates.rawValue) - - let visibleBounds = self.scrollView.bounds - let minVisibleY = visibleBounds.minY - let maxVisibleY = visibleBounds.maxY - let verticalOffset = visibleBounds.origin.y - for item in self.displayItems { - if item.frame.maxY < minVisibleY { - continue; - } - if item.frame.minY > maxVisibleY { - break; - } - - if let videoFrame = self.sourceManager.immediateVideoFrames[item.file.fileId], let imageBuffer = CMSampleBufferGetImageBuffer(videoFrame.sampleBuffer) { - - let frameSize = CVImageBufferGetEncodedSize(imageBuffer) - let frameWidth = GLsizei(frameSize.width) - let frameHeight = GLsizei(frameSize.height) - - var lumaTextureRef: CVOpenGLESTexture? - var chromaTextureRef: CVOpenGLESTexture? - - glActiveTexture(GLenum(GL_TEXTURE0)) - CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, self.videoTextureCache!, imageBuffer, nil, GLenum(GL_TEXTURE_2D), GL_RED_EXT, frameWidth, frameHeight, GLenum(GL_RED_EXT), GLenum(GL_UNSIGNED_BYTE), 0, &lumaTextureRef); - guard let lumaTexture = lumaTextureRef else { - print("error creating lumaTexture") - continue - } - - glBindTexture(CVOpenGLESTextureGetTarget(lumaTexture), CVOpenGLESTextureGetName(lumaTexture)) - glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR) - glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR) - glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_CLAMP_TO_EDGE) - glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_CLAMP_TO_EDGE) - - glActiveTexture(GLenum(GL_TEXTURE1)) - - CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, self.videoTextureCache!, imageBuffer, nil, GLenum(GL_TEXTURE_2D), GL_RG_EXT, frameWidth / 2, frameHeight / 2, GLenum(GL_RG_EXT), GLenum(GL_UNSIGNED_BYTE), 1, &chromaTextureRef) - - guard let chromaTexture = chromaTextureRef else { - print("error creating chromaTexture") - continue - } - - glBindTexture(CVOpenGLESTextureGetTarget(chromaTexture), CVOpenGLESTextureGetName(chromaTexture)) - glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR) - glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR) - glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_CLAMP_TO_EDGE) - glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_CLAMP_TO_EDGE) - } - - let normalSize = item.frame.size - let normalOrigin = CGPoint(x: item.frame.origin.x, y: drawableSize.height - (item.frame.origin.y - verticalOffset) - normalSize.height) - - //let normalOrigin = CGPoint(x: 375.0 - 10.0, y: drawableSize.height - 100.0 - 10.0) - //let normalSize = CGSize(width: 10.0, height: 10.0) - - let normalizedSamplingSize = CGSize(width: normalSize.width / drawableSize.width, height: normalSize.height / drawableSize.height) - let normalizedOffset = CGPoint(x: -1.0 + normalOrigin.x * 2.0 / drawableSize.width + normalizedSamplingSize.width, y: -1.0 + normalOrigin.y * 2.0 / drawableSize.height + normalizedSamplingSize.height) - - let quadVertexData: [GLfloat] = [ - Float(normalizedOffset.x - 1.0 * normalizedSamplingSize.width), Float(normalizedOffset.y - 1.0 * normalizedSamplingSize.height), - Float(normalizedOffset.x + normalizedSamplingSize.width), Float(normalizedOffset.y - 1.0 * normalizedSamplingSize.height), - Float(normalizedOffset.x - 1.0 * normalizedSamplingSize.width), Float(normalizedOffset.y + normalizedSamplingSize.height), - Float(normalizedOffset.x + normalizedSamplingSize.width), Float(normalizedOffset.y + normalizedSamplingSize.height) - ] - - glVertexAttribPointer(AttributeIndex.Vertex.rawValue, 2, GLenum(GL_FLOAT), 0, 0, quadVertexData) - - let textureSamplingRect = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0) - let quadTextureData: [GLfloat] = [ - Float(textureSamplingRect.minX), Float(textureSamplingRect.maxY), - Float(textureSamplingRect.maxX), Float(textureSamplingRect.maxY), - Float(textureSamplingRect.minX), Float(textureSamplingRect.minY), - Float(textureSamplingRect.maxX), Float(textureSamplingRect.minY) - ] - glVertexAttribPointer(AttributeIndex.TextureCoordinates.rawValue, 2, GLenum(GL_FLOAT), 0, 0, quadTextureData); - - glDrawArrays(GLenum(GL_TRIANGLE_STRIP), 0, 4) - } - - glBindRenderbuffer(GLenum(GL_RENDERBUFFER), colorBufferHandle) - self.context.presentRenderbuffer(Int(GL_RENDERBUFFER)) - } - } - - private func updateDrawable() { - if !self.bounds.size.width.isZero { - if self.drawableSize == nil || self.drawableSize! != self.bounds.size { - self.drawableSize = self.bounds.size - - if var frameBufferHandle = self.frameBufferHandle { - glDeleteFramebuffers(1, &frameBufferHandle) - self.frameBufferHandle = nil - } - - if var colorBufferHandle = self.colorBufferHandle { - glDeleteFramebuffers(1, &colorBufferHandle) - self.colorBufferHandle = nil - } - - var frameBufferHandle: GLuint = 0 - glGenFramebuffers(1, &frameBufferHandle); - glBindFramebuffer(GLenum(GL_FRAMEBUFFER), frameBufferHandle) - - var colorBufferHandle: GLuint = 0 - glGenRenderbuffers(1, &colorBufferHandle) - glBindRenderbuffer(GLenum(GL_RENDERBUFFER), colorBufferHandle) - self.context.renderbufferStorage(Int(GL_RENDERBUFFER), from: self.eglLayer) - - var backingWidth = GLint(self.bounds.size.width * 2.0) - var backingHeight = GLint(self.bounds.size.height * 2.0) - glGetRenderbufferParameteriv(GLenum(GL_RENDERBUFFER), GLenum(GL_RENDERBUFFER_WIDTH), &backingWidth) - glGetRenderbufferParameteriv(GLenum(GL_RENDERBUFFER), GLenum(GL_RENDERBUFFER_HEIGHT), &backingHeight) - - glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_RENDERBUFFER), colorBufferHandle) - - if glCheckFramebufferStatus(GLenum(GL_FRAMEBUFFER)) != UInt32(GL_FRAMEBUFFER_COMPLETE) { - assertionFailure() - } - - self.frameBufferHandle = frameBufferHandle - self.colorBufferHandle = colorBufferHandle - - self.updateVisibleItems() - } - } - } - - private func updateVisibleItems() { - if let drawableSize = self.drawableSize { - var displayItems: [VisibleVideoItem] = [] - - let idealHeight: CGFloat = 93.0 - - var weights: [Int] = [] - var totalItemSize: CGFloat = 0.0 - for item in self.files { - let aspectRatio: CGFloat - if let dimensions = item.dimensions { - aspectRatio = dimensions.width / dimensions.height - } else { - aspectRatio = 1.0 - } - weights.append(Int(aspectRatio * 100)) - totalItemSize += aspectRatio * idealHeight - } - - let numberOfRows = max(Int(round(totalItemSize / drawableSize.width)), 1) - - let partition = linearPartitionForWeights(weights, numberOfPartitions:numberOfRows) - - var i = 0 - var offset = CGPoint(x: 0.0, y: 0.0) - var previousItemSize: CGFloat = 0.0 - var contentMaxValueInScrollDirection: CGFloat = 0.0 - let maxWidth = drawableSize.width - - let minimumInteritemSpacing: CGFloat = 1.0 - let minimumLineSpacing: CGFloat = 1.0 - - let viewportWidth: CGFloat = drawableSize.width - - let preferredRowSize = idealHeight - - var rowIndex = -1 - for row in partition { - rowIndex += 1 - - var summedRatios: CGFloat = 0.0 - - var j = i - var n = i + row.count - - while j < n { - let aspectRatio: CGFloat - if let dimensions = self.files[j].dimensions { - aspectRatio = dimensions.width / dimensions.height - } else { - aspectRatio = 1.0 - } - - summedRatios += aspectRatio - - j += 1 - } - - var rowSize = drawableSize.width - (CGFloat(row.count - 1) * minimumInteritemSpacing) - - if rowIndex == partition.count - 1 { - if row.count < 2 { - rowSize = floor(viewportWidth / 3.0) - (CGFloat(row.count - 1) * minimumInteritemSpacing) - } else if row.count < 3 { - rowSize = floor(viewportWidth * 2.0 / 3.0) - (CGFloat(row.count - 1) * minimumInteritemSpacing) - } - } - - j = i - n = i + row.count - - while j < n { - let aspectRatio: CGFloat - if let dimensions = self.files[j].dimensions { - aspectRatio = dimensions.width / dimensions.height - } else { - aspectRatio = 1.0 - } - let preferredAspectRatio = aspectRatio - - let actualSize = CGSize(width: round(rowSize / summedRatios * (preferredAspectRatio)), height: preferredRowSize) - - var frame = CGRect(x: offset.x, y: offset.y, width: actualSize.width, height: actualSize.height) - if frame.origin.x + frame.size.width >= maxWidth - 2.0 { - frame.size.width = max(1.0, maxWidth - frame.origin.x) - } - - displayItems.append(VisibleVideoItem(file: self.files[j], frame: frame)) - - offset.x += actualSize.width + minimumInteritemSpacing - previousItemSize = actualSize.height - contentMaxValueInScrollDirection = frame.maxY - - j += 1 - } - - if row.count > 0 { - offset = CGPoint(x: 0.0, y: offset.y + previousItemSize + minimumLineSpacing) - } - - i += row.count - } - let contentSize = CGSize(width: drawableSize.width, height: contentMaxValueInScrollDirection) - self.scrollView.contentSize = contentSize - - self.displayItems = displayItems - - self.validVisibleItemsOffset = nil - self.updateImmediatelyVisibleItems() - } - } - -} - -private func NH_LP_TABLE_LOOKUP(_ table: inout [Int], _ i: Int, _ j: Int, _ rowsize: Int) -> Int { - return table[i * rowsize + j] -} - -private func NH_LP_TABLE_LOOKUP_SET(_ table: inout [Int], _ i: Int, _ j: Int, _ rowsize: Int, _ value: Int) { - table[i * rowsize + j] = value -} - -private func linearPartitionTable(_ weights: [Int], numberOfPartitions: Int) -> [Int] { - let n = weights.count - let k = numberOfPartitions - - let tableSize = n * k; - var tmpTable = Array(repeatElement(0, count: tableSize)) - - let solutionSize = (n - 1) * (k - 1) - var solution = Array(repeatElement(0, count: solutionSize)) - - for i in 0 ..< n { - let offset = i != 0 ? NH_LP_TABLE_LOOKUP(&tmpTable, i - 1, 0, k) : 0 - NH_LP_TABLE_LOOKUP_SET(&tmpTable, i, 0, k, Int(weights[i]) + offset) - } - - for j in 0 ..< k { - NH_LP_TABLE_LOOKUP_SET(&tmpTable, 0, j, k, Int(weights[0])) - } - - for i in 1 ..< n { - for j in 1 ..< k { - var currentMin = 0 - var minX = Int.max - - for x in 0 ..< i { - let c1 = NH_LP_TABLE_LOOKUP(&tmpTable, x, j - 1, k) - let c2 = NH_LP_TABLE_LOOKUP(&tmpTable, i, 0, k) - NH_LP_TABLE_LOOKUP(&tmpTable, x, 0, k) - let cost = max(c1, c2) - - if x == 0 || cost < currentMin { - currentMin = cost; - minX = x - } - } - - NH_LP_TABLE_LOOKUP_SET(&tmpTable, i, j, k, currentMin) - NH_LP_TABLE_LOOKUP_SET(&solution, i - 1, j - 1, k - 1, minX) - } - } - - return solution -} - -private func linearPartitionForWeights(_ weights: [Int], numberOfPartitions: Int) -> [[Int]] { - var n = weights.count - var k = numberOfPartitions - - if k <= 0 { - return [] - } - - if k >= n { - var partition: [[Int]] = [] - for weight in weights { - partition.append([weight]) - } - return partition - } - - if n == 1 { - return [weights] - } - - var solution = linearPartitionTable(weights, numberOfPartitions: numberOfPartitions) - let solutionRowSize = numberOfPartitions - 1 - - k = k - 2; - n = n - 1; - - var answer: [[Int]] = [] - - while k >= 0 { - if n < 1 { - answer.insert([], at: 0) - } else { - var currentAnswer: [Int] = [] - - var i = NH_LP_TABLE_LOOKUP(&solution, n - 1, k, solutionRowSize) + 1 - let range = n + 1 - while i < range { - currentAnswer.append(weights[i]) - i += 1 - } - - answer.insert(currentAnswer, at: 0) - - n = NH_LP_TABLE_LOOKUP(&solution, n - 1, k, solutionRowSize) - } - - k = k - 1 - } - - var currentAnswer: [Int] = [] - var i = 0 - let range = n + 1 - while i < range { - currentAnswer.append(weights[i]) - i += 1 - } - - answer.insert(currentAnswer, at: 0) - - return answer -} diff --git a/TelegramUI/MultiplexedVideoNode.swift b/TelegramUI/MultiplexedVideoNode.swift index 8aac487d66..653c217c73 100644 --- a/TelegramUI/MultiplexedVideoNode.swift +++ b/TelegramUI/MultiplexedVideoNode.swift @@ -206,7 +206,7 @@ final class MultiplexedVideoNode: UIScrollView, UIScrollViewDelegate { } } else { let layerHolder = takeSampleBufferLayer() - layerHolder.layer.videoGravity = AVLayerVideoGravityResizeAspectFill + layerHolder.layer.videoGravity = AVLayerVideoGravity.resizeAspectFill layerHolder.layer.frame = item.frame self.layer.addSublayer(layerHolder.layer) let manager = SoftwareVideoLayerFrameManager(account: self.account, resource: item.file.resource, layerHolder: layerHolder) diff --git a/TelegramUI/NativeVideoContent.swift b/TelegramUI/NativeVideoContent.swift new file mode 100644 index 0000000000..acf6fd851d --- /dev/null +++ b/TelegramUI/NativeVideoContent.swift @@ -0,0 +1,119 @@ +import Foundation +import AsyncDisplayKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore + +final class NativeVideoContent: UniversalVideoContent { + let id: AnyHashable + let file: TelegramMediaFile + let dimensions: CGSize + let duration: Int32 + + init(file: TelegramMediaFile) { + self.id = anyHashableFromMediaResourceId(file.resource.id) + self.file = file + self.dimensions = file.dimensions ?? CGSize(width: 128.0, height: 128.0) + self.duration = file.duration ?? 0 + } + + func makeContentNode(account: Account) -> UniversalVideoContentNode & ASDisplayNode { + return NativeVideoContentNode(account: account, audioSessionManager: account.telegramApplicationContext.mediaManager.audioSession, postbox: account.postbox, file: self.file) + } +} + +private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContentNode { + private let file: TelegramMediaFile + private let player: MediaPlayer + private let imageNode: TransformImageNode + private let playerNode: MediaPlayerNode + private let playbackCompletedListeners = Bag<() -> Void>() + + private var initializedStatus = false + private let _status = Promise() + var status: Signal { + return self._status.get() + } + + init(account: Account, audioSessionManager: ManagedAudioSession, postbox: Postbox, file: TelegramMediaFile) { + self.file = file + + self.imageNode = TransformImageNode() + + self.player = MediaPlayer(audioSessionManager: audioSessionManager, postbox: postbox, resource: file.resource, streamable: false, video: true, preferSoftwareDecoding: false, playAutomatically: false, enableSound: true) + var actionAtEndImpl: (() -> Void)? + self.player.actionAtEnd = .stop + self.playerNode = MediaPlayerNode(backgroundThread: false) + self.player.attachPlayerNode(self.playerNode) + + super.init() + + actionAtEndImpl = { [weak self] in + if let strongSelf = self { + for listener in strongSelf.playbackCompletedListeners.copyItems() { + listener() + } + } + } + + self.imageNode.setSignal(account: account, signal: mediaGridMessageVideo(account: account, video: file)) + + self.addSubnode(self.imageNode) + self.addSubnode(self.playerNode) + self._status.set(self.player.status) + } + + deinit { + self.player.pause() + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + if let dimensions = self.file.dimensions { + let imageSize = CGSize(width: floor(dimensions.width / 2.0), height: floor(dimensions.height / 2.0)) + let makeLayout = self.imageNode.asyncLayout() + let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())) + applyLayout() + } + + self.imageNode.frame = CGRect(origin: CGPoint(), size: size) + self.playerNode.frame = CGRect(origin: CGPoint(), size: size) + } + + func play() { + assert(Queue.mainQueue().isCurrent()) + self.player.play() + } + + func pause() { + assert(Queue.mainQueue().isCurrent()) + self.player.pause() + } + + func togglePlayPause() { + assert(Queue.mainQueue().isCurrent()) + self.player.togglePlayPause() + } + + func setSoundEnabled(_ value: Bool) { + assert(Queue.mainQueue().isCurrent()) + if value { + self.player.playOnceWithSound() + } else { + self.player.continuePlayingWithoutSound() + } + } + + func seek(_ timestamp: Double) { + assert(Queue.mainQueue().isCurrent()) + self.player.seek(timestamp: timestamp) + } + + func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int { + return self.playbackCompletedListeners.add(f) + } + + func removePlaybackCompleted(_ index: Int) { + self.playbackCompletedListeners.remove(index) + } +} diff --git a/TelegramUI/OngoingCallContext.swift b/TelegramUI/OngoingCallContext.swift index 55fef3ed95..ec237aa156 100644 --- a/TelegramUI/OngoingCallContext.swift +++ b/TelegramUI/OngoingCallContext.swift @@ -3,6 +3,8 @@ import SwiftSignalKit import TelegramCore import Postbox +import TelegramUIPrivateModule + private func callConnectionDescription(_ connection: CallSessionConnection) -> OngoingCallConnectionDescription { return OngoingCallConnectionDescription(connectionId: connection.id, ip: connection.ip, ipv6: connection.ipv6, port: connection.port, peerTag: connection.peerTag) } diff --git a/TelegramUI/OverlayUniversalVideoNode.swift b/TelegramUI/OverlayUniversalVideoNode.swift new file mode 100644 index 0000000000..f5656f7587 --- /dev/null +++ b/TelegramUI/OverlayUniversalVideoNode.swift @@ -0,0 +1,83 @@ +import Foundation +import AsyncDisplayKit +import SwiftSignalKit +import Display +import TelegramCore + +final class OverlayUniversalVideoNode: OverlayMediaItemNode { + private let content: UniversalVideoContent + private let videoNode: UniversalVideoNode + + private var validLayoutSize: CGSize? + + override var group: OverlayMediaItemNodeGroup? { + return OverlayMediaItemNodeGroup(rawValue: 0) + } + + init(account: Account, manager: UniversalVideoContentManager, content: UniversalVideoContent, expand: @escaping () -> Void, close: @escaping () -> Void) { + self.content = content + var togglePlayPauseImpl: (() -> Void)? + var closeImpl: (() -> Void)? + let decoration = OverlayVideoDecoration(togglePlayPause: { + togglePlayPauseImpl?() + }, expand: { + expand() + }, close: { + closeImpl?() + }) + self.videoNode = UniversalVideoNode(account: account, manager: manager, decoration: decoration, content: content, priority: .overlay) + + super.init() + + togglePlayPauseImpl = { [weak self] in + self?.videoNode.togglePlayPause() + } + closeImpl = { [weak self] in + if let strongSelf = self { + strongSelf.layer.animateScale(from: 1.0, to: 0.1, duration: 0.25, removeOnCompletion: false, completion: { _ in + self?.dismiss() + close() + }) + strongSelf.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) + } + } + + self.clipsToBounds = true + self.cornerRadius = 4.0 + + self.addSubnode(self.videoNode) + self.videoNode.ownsContentNodeUpdated = { [weak self] value in + if let strongSelf = self { + strongSelf.hasAttachedContext = value + strongSelf.hasAttachedContextUpdated?(value) + } + } + + self.videoNode.canAttachContent = true + } + + override func didLoad() { + super.didLoad() + } + + override func layout() { + self.updateLayout(self.bounds.size) + } + + override func preferredSizeForOverlayDisplay() -> CGSize { + return self.content.dimensions.aspectFitted(CGSize(width: 300.0, height: 300.0)) + } + + override func updateLayout(_ size: CGSize) { + if size != self.validLayoutSize { + self.updateLayoutImpl(size) + } + } + + private func updateLayoutImpl(_ size: CGSize) { + self.validLayoutSize = size + + self.videoNode.frame = CGRect(origin: CGPoint(), size: size) + self.videoNode.updateLayout(size: size, transition: .immediate) + } +} diff --git a/TelegramUI/OverlayVideoDecoration.swift b/TelegramUI/OverlayVideoDecoration.swift new file mode 100644 index 0000000000..ca1b687aa8 --- /dev/null +++ b/TelegramUI/OverlayVideoDecoration.swift @@ -0,0 +1,99 @@ +import Foundation +import AsyncDisplayKit +import Display +import SwiftSignalKit + +private let backgroundImage = UIImage(bundleImageName: "Chat/Message/OverlayPlainVideoShadow")?.precomposed().resizableImage(withCapInsets: UIEdgeInsets(top: 22.0, left: 25.0, bottom: 26.0, right: 25.0), resizingMode: .stretch) + +final class OverlayVideoDecoration: UniversalVideoDecoration { + let backgroundNode: ASDisplayNode? + let contentContainerNode: ASDisplayNode + let foregroundNode: ASDisplayNode? + + private let shadowNode: ASImageNode + private let controlsNode: PictureInPictureVideoControlsNode + + private var contentNode: (ASDisplayNode & UniversalVideoContentNode)? + + private var validLayoutSize: CGSize? + + init(togglePlayPause: @escaping () -> Void, expand: @escaping () -> Void, close: @escaping () -> Void) { + self.shadowNode = ASImageNode() + self.shadowNode.image = backgroundImage + self.backgroundNode = self.shadowNode + + self.contentContainerNode = ASDisplayNode() + self.contentContainerNode.backgroundColor = .black + + self.controlsNode = PictureInPictureVideoControlsNode(leave: { + expand() + }, playPause: { + togglePlayPause() + }, close: { + close() + }) + self.controlsNode.alpha = 0.0 + self.foregroundNode = self.controlsNode + + //self.controlsNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(controlsNodeTapGesture(_:)))) + } + + func updateContentNode(_ contentNode: (UniversalVideoContentNode & ASDisplayNode)?) { + if self.contentNode !== contentNode { + let previous = self.contentNode + self.contentNode = contentNode + + if let previous = previous { + if previous.supernode === self.contentContainerNode { + previous.removeFromSupernode() + } + } + + if let contentNode = contentNode { + if contentNode.supernode !== self.contentContainerNode { + self.contentContainerNode.addSubnode(contentNode) + if let validLayoutSize = self.validLayoutSize { + contentNode.frame = CGRect(origin: CGPoint(), size: validLayoutSize) + contentNode.updateLayout(size: validLayoutSize, transition: .immediate) + } + } + } + } + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + self.validLayoutSize = size + + let shadowInsets = UIEdgeInsets(top: 2.0, left: 3.0, bottom: 4.0, right: 3.0) + transition.updateFrame(node: self.shadowNode, frame: CGRect(origin: CGPoint(x: -shadowInsets.left, y: -shadowInsets.top), size: CGSize(width: size.width + shadowInsets.left + shadowInsets.right, height: size.height + shadowInsets.top + shadowInsets.bottom))) + + transition.updateFrame(node: self.controlsNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) + self.controlsNode.updateLayout(size: size, transition: transition) + + transition.updateFrame(node: self.contentContainerNode, frame: CGRect(origin: CGPoint(), size: size)) + if let contentNode = self.contentNode { + transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(), size: size)) + contentNode.updateLayout(size: size, transition: transition) + } + } + + func tap() { + if self.controlsNode.alpha.isZero { + self.controlsNode.alpha = 1.0 + self.controlsNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + } else { + self.controlsNode.alpha = 0.0 + self.controlsNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) + } + } + + func setStatus(_ status: Signal) { + self.controlsNode.status = status |> map { value -> MediaPlayerStatus in + if let value = value { + return value + } else { + return MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: 0.0, timestamp: 0.0, status: .paused) + } + } + } +} diff --git a/TelegramUI/PeerMediaCollectionController.swift b/TelegramUI/PeerMediaCollectionController.swift index 1e4c5dc899..21415476cc 100644 --- a/TelegramUI/PeerMediaCollectionController.swift +++ b/TelegramUI/PeerMediaCollectionController.swift @@ -195,16 +195,16 @@ public class PeerMediaCollectionController: ViewController { }, sendMessage: { _ in },sendSticker: { _ in }, sendGif: { _ in - }, requestMessageActionCallback: { _ in + }, requestMessageActionCallback: { _, _, _ in }, openUrl: { _ in }, shareCurrentLocation: { }, shareAccountContact: { }, sendBotCommand: { _, _ in }, openInstantPage: { _ in - }, openHashtag: {_ in + }, openHashtag: { _, _ in }, updateInputState: { _ in }, openMessageShareMenu: { _ in - }, presentController: { _ in + }, presentController: { _, _ in }, callPeer: { _ in }, longTap: { _ in }, openCheckoutOrReceipt: { _ in @@ -313,10 +313,10 @@ public class PeerMediaCollectionController: ViewController { }, navigateToMessage: { _ in }, openPeerInfo: { }, togglePeerNotifications: { - }, sendContextResult: { _ in - }, sendBotCommand: { _ in + }, sendContextResult: { _, _ in + }, sendBotCommand: { _, _ in }, sendBotStart: { _ in - }, botSwitchChatWithPayload: { _ in + }, botSwitchChatWithPayload: { _, _ in }, beginMediaRecording: { _ in }, finishMediaRecording: { _ in }, stopMediaRecording: { diff --git a/TelegramUI/PeerMediaCollectionControllerNode.swift b/TelegramUI/PeerMediaCollectionControllerNode.swift index 04e94f897f..7c83c39d44 100644 --- a/TelegramUI/PeerMediaCollectionControllerNode.swift +++ b/TelegramUI/PeerMediaCollectionControllerNode.swift @@ -43,7 +43,7 @@ class PeerMediaCollectionControllerNode: ASDisplayNode { private var containerLayout: (ContainerViewLayout, CGFloat)? var requestLayout: (ContainedViewLayoutTransition) -> Void = { _ in } - var requestUpdateMediaCollectionInterfaceState: (Bool, (PeerMediaCollectionInterfaceState) -> PeerMediaCollectionInterfaceState) -> Void = { _ in } + var requestUpdateMediaCollectionInterfaceState: (Bool, (PeerMediaCollectionInterfaceState) -> PeerMediaCollectionInterfaceState) -> Void = { _, _ in } private var mediaCollectionInterfaceState: PeerMediaCollectionInterfaceState diff --git a/TelegramUI/PeerMediaCollectionInterfaceStateButtons.swift b/TelegramUI/PeerMediaCollectionInterfaceStateButtons.swift index 0d8142b5bf..de65a082c0 100644 --- a/TelegramUI/PeerMediaCollectionInterfaceStateButtons.swift +++ b/TelegramUI/PeerMediaCollectionInterfaceStateButtons.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit enum PeerMediaCollectionNavigationButtonAction { case beginMessageSelection diff --git a/TelegramUI/PerformanceSpinner.swift b/TelegramUI/PerformanceSpinner.swift index db941d1098..5572752251 100644 --- a/TelegramUI/PerformanceSpinner.swift +++ b/TelegramUI/PerformanceSpinner.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import SwiftSignalKit private final class SpinnerThread: NSObject { diff --git a/TelegramUI/PictureInPictureVideoControlsNode.swift b/TelegramUI/PictureInPictureVideoControlsNode.swift index a7d355a2b2..237cbd40c5 100644 --- a/TelegramUI/PictureInPictureVideoControlsNode.swift +++ b/TelegramUI/PictureInPictureVideoControlsNode.swift @@ -126,4 +126,8 @@ final class PictureInPictureVideoControlsNode: ASDisplayNode { @objc func closePressed() { self.close() } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return super.hitTest(point, with: event) + } } diff --git a/TelegramUI/PresentationResourcesChat.swift b/TelegramUI/PresentationResourcesChat.swift index a315d38367..e2fdf9bccc 100644 --- a/TelegramUI/PresentationResourcesChat.swift +++ b/TelegramUI/PresentationResourcesChat.swift @@ -255,12 +255,12 @@ struct PresentationResourcesChat { context.setTextDrawingMode(.stroke) context.setLineWidth(0.65) - ("GIF" as NSString).draw(in: CGRect(origin: CGPoint(x: 6.0, y: 8.0), size: size), withAttributes: [NSFontAttributeName: Font.regular(8.0), NSForegroundColorAttributeName: theme.chat.inputMediaPanel.panelIconColor]) + ("GIF" as NSString).draw(in: CGRect(origin: CGPoint(x: 6.0, y: 8.0), size: size), withAttributes: [NSAttributedStringKey.font: Font.regular(8.0), NSAttributedStringKey.foregroundColor: theme.chat.inputMediaPanel.panelIconColor]) context.setTextDrawingMode(.fill) context.setLineWidth(0.8) - ("GIF" as NSString).draw(in: CGRect(origin: CGPoint(x: 6.0, y: 8.0), size: size), withAttributes: [NSFontAttributeName: Font.regular(8.0), NSForegroundColorAttributeName: theme.chat.inputMediaPanel.panelIconColor]) + ("GIF" as NSString).draw(in: CGRect(origin: CGPoint(x: 6.0, y: 8.0), size: size), withAttributes: [NSAttributedStringKey.font: Font.regular(8.0), NSAttributedStringKey.foregroundColor: theme.chat.inputMediaPanel.panelIconColor]) UIGraphicsPopContext() }) }) diff --git a/TelegramUI/PresentationStrings.swift b/TelegramUI/PresentationStrings.swift index 3cda7aa2bc..ba1cfc93ba 100644 --- a/TelegramUI/PresentationStrings.swift +++ b/TelegramUI/PresentationStrings.swift @@ -61,10 +61,10 @@ private func extractArgumentRanges(_ value: String) -> [(Int, NSRange)] { var index = 0 for match in matches { var currentIndex = index - if match.rangeAt(3).location != NSNotFound { - currentIndex = Int(string.substring(with: match.rangeAt(3)))! - 1 + if match.range(at: 3).location != NSNotFound { + currentIndex = Int(string.substring(with: match.range(at: 3)))! - 1 } - result.append((currentIndex, match.rangeAt(0))) + result.append((currentIndex, match.range(at: 0))) index += 1 } result.sort(by: { $0.1.location < $1.1.location }) diff --git a/TelegramUI/PresentationsResourceCache.swift b/TelegramUI/PresentationsResourceCache.swift index 7ceb00ac7a..d57953a843 100644 --- a/TelegramUI/PresentationsResourceCache.swift +++ b/TelegramUI/PresentationsResourceCache.swift @@ -1,5 +1,6 @@ import Foundation import SwiftSignalKit +import UIKit private final class PresentationsResourceCacheHolder { var images: [Int32: UIImage] = [:] diff --git a/TelegramUI/RadialProgressContentNode.swift b/TelegramUI/RadialProgressContentNode.swift index f387b10bde..2885aa14aa 100644 --- a/TelegramUI/RadialProgressContentNode.swift +++ b/TelegramUI/RadialProgressContentNode.swift @@ -58,7 +58,7 @@ private final class RadialProgressContentSpinnerNode: ASDisplayNode { animation.toValue = CGFloat(progress) as NSNumber animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) animation.duration = 0.2 - animation.completionBlock = { [weak self] _ in + animation.completionBlock = { [weak self] _, _ in self?.progressAnimationCompleted?() } self.pop_add(animation, forKey: "progress") diff --git a/TelegramUI/RadialProgressNode.swift b/TelegramUI/RadialProgressNode.swift index 21b29bb627..00048454d9 100644 --- a/TelegramUI/RadialProgressNode.swift +++ b/TelegramUI/RadialProgressNode.swift @@ -63,7 +63,7 @@ private class RadialProgressOverlayNode: ASDisplayNode { animation.toValue = CGFloat(progress) as NSNumber animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) animation.duration = 0.2 - animation.completionBlock = { [weak self] _ in + animation.completionBlock = { [weak self] _, _ in self?.progressAnimationCompleted?() } self.pop_removeAnimation(forKey: "progress") diff --git a/TelegramUI/StringPluralization.swift b/TelegramUI/StringPluralization.swift index 790f3991ba..18824ea0d2 100644 --- a/TelegramUI/StringPluralization.swift +++ b/TelegramUI/StringPluralization.swift @@ -1,5 +1,7 @@ import Foundation +import TelegramUIPrivateModule + enum PluralizationForm { case zero case one diff --git a/TelegramUI/StringWithAppliedEntities.swift b/TelegramUI/StringWithAppliedEntities.swift index 7af52c5167..f615c57125 100644 --- a/TelegramUI/StringWithAppliedEntities.swift +++ b/TelegramUI/StringWithAppliedEntities.swift @@ -3,7 +3,7 @@ import TelegramCore func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], baseColor: UIColor, linkColor: UIColor, baseFont: UIFont, boldFont: UIFont, fixedFont: UIFont) -> NSAttributedString { var nsString: NSString? - let string = NSMutableAttributedString(string: text, attributes: [NSFontAttributeName: baseFont, NSForegroundColorAttributeName: baseColor]) + let string = NSMutableAttributedString(string: text, attributes: [NSAttributedStringKey.font: baseFont, NSAttributedStringKey.foregroundColor: baseColor]) var skipEntity = false let stringLength = string.length for i in 0 ..< entities.count { @@ -22,35 +22,35 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba } switch entity.type { case .Url: - string.addAttribute(NSForegroundColorAttributeName, value: linkColor, range: range) + string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range) if nsString == nil { nsString = text as NSString } - string.addAttribute(TextNode.UrlAttribute, value: nsString!.substring(with: range), range: range) + string.addAttribute(NSAttributedStringKey(rawValue: TextNode.UrlAttribute), value: nsString!.substring(with: range), range: range) case .Email: - string.addAttribute(NSForegroundColorAttributeName, value: linkColor, range: range) + string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range) if nsString == nil { nsString = text as NSString } - string.addAttribute(TextNode.UrlAttribute, value: "mailto:\(nsString!.substring(with: range))", range: range) + string.addAttribute(NSAttributedStringKey(rawValue: TextNode.UrlAttribute), value: "mailto:\(nsString!.substring(with: range))", range: range) case let .TextUrl(url): - string.addAttribute(NSForegroundColorAttributeName, value: linkColor, range: range) + string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range) if nsString == nil { nsString = text as NSString } - string.addAttribute(TextNode.UrlAttribute, value: url, range: range) + string.addAttribute(NSAttributedStringKey(rawValue: TextNode.UrlAttribute), value: url, range: range) case .Bold: - string.addAttribute(NSFontAttributeName, value: boldFont, range: range) + string.addAttribute(NSAttributedStringKey.font, value: boldFont, range: range) case .Mention: - string.addAttribute(NSForegroundColorAttributeName, value: linkColor, range: range) + string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range) if nsString == nil { nsString = text as NSString } - string.addAttribute(TextNode.TelegramPeerTextMentionAttribute, value: nsString!.substring(with: range), range: range) + string.addAttribute(NSAttributedStringKey(rawValue: TextNode.TelegramPeerTextMentionAttribute), value: nsString!.substring(with: range), range: range) case let .TextMention(peerId): - string.addAttribute(NSForegroundColorAttributeName, value: linkColor, range: range) + string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range) let mention = nsString!.substring(with: range) - string.addAttribute(TextNode.TelegramPeerMentionAttribute, value: TelegramPeerMention(peerId: peerId, mention: mention), range: range) + string.addAttribute(NSAttributedStringKey(rawValue: TextNode.TelegramPeerMentionAttribute), value: TelegramPeerMention(peerId: peerId, mention: mention), range: range) case .Hashtag: if nsString == nil { nsString = text as NSString @@ -64,23 +64,23 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba skipEntity = true let combinedRange = NSRange(location: range.location, length: nextRange.location + nextRange.length - range.location) - string.addAttribute(NSForegroundColorAttributeName, value: linkColor, range: combinedRange) - string.addAttribute(TextNode.TelegramHashtagAttribute, value: TelegramHashtag(peerName: peerName, hashtag: hashtag), range: combinedRange) + string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: combinedRange) + string.addAttribute(NSAttributedStringKey(rawValue: TextNode.TelegramHashtagAttribute), value: TelegramHashtag(peerName: peerName, hashtag: hashtag), range: combinedRange) } } } if !skipEntity { - string.addAttribute(NSForegroundColorAttributeName, value: linkColor, range: range) - string.addAttribute(TextNode.TelegramHashtagAttribute, value: TelegramHashtag(peerName: nil, hashtag: hashtag), range: range) + string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range) + string.addAttribute(NSAttributedStringKey(rawValue: TextNode.TelegramHashtagAttribute), value: TelegramHashtag(peerName: nil, hashtag: hashtag), range: range) } case .BotCommand: - string.addAttribute(NSForegroundColorAttributeName, value: linkColor, range: range) + string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range) if nsString == nil { nsString = text as NSString } - string.addAttribute(TextNode.TelegramBotCommandAttribute, value: nsString!.substring(with: range), range: range) + string.addAttribute(NSAttributedStringKey(rawValue: TextNode.TelegramBotCommandAttribute), value: nsString!.substring(with: range), range: range) case .Code, .Pre: - string.addAttribute(NSFontAttributeName, value: fixedFont, range: range) + string.addAttribute(NSAttributedStringKey.font, value: fixedFont, range: range) default: break } diff --git a/TelegramUI/TelegramInitializeLegacyComponents.swift b/TelegramUI/TelegramInitializeLegacyComponents.swift index 6359485b50..37257a510a 100644 --- a/TelegramUI/TelegramInitializeLegacyComponents.swift +++ b/TelegramUI/TelegramInitializeLegacyComponents.swift @@ -236,7 +236,7 @@ private final class LegacyComponentsGlobalsProviderImpl: NSObject, LegacyCompone } } - public func makeHTTPRequestOperation(with request: URLRequest!) -> Operation! { + public func makeHTTPRequestOperation(with request: URLRequest!) -> (Operation & LegacyHTTPRequestOperation)! { return nil } diff --git a/TelegramUI/TelegramUI.h b/TelegramUI/TelegramUI.h index a79c43e4ff..6a7fcb9a8a 100644 --- a/TelegramUI/TelegramUI.h +++ b/TelegramUI/TelegramUI.h @@ -16,6 +16,3 @@ FOUNDATION_EXPORT const unsigned char TelegramUIVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import -#import -#import -#import diff --git a/TelegramUI/TelegramUIPrivate/module.modulemap b/TelegramUI/TelegramUIPrivate/module.modulemap index 5f7e21c55d..36fedb6b67 100644 --- a/TelegramUI/TelegramUIPrivate/module.modulemap +++ b/TelegramUI/TelegramUIPrivate/module.modulemap @@ -19,4 +19,7 @@ module TelegramUIPrivateModule { header "../STPPaymentConfiguration.h" header "../STPCard.h" header "../STPToken.h" + header "../OngoingCallThreadLocalContext.h" + header "../SecretChatKeyVisualization.h" + header "../NumberPluralizationForm.h" } diff --git a/TelegramUI/TextNode.swift b/TelegramUI/TextNode.swift index 5938fb4a78..a7a6e3d798 100644 --- a/TelegramUI/TextNode.swift +++ b/TelegramUI/TextNode.swift @@ -70,7 +70,7 @@ final class TextNodeLayout: NSObject { } } - func attributesAtPoint(_ point: CGPoint) -> (Int, [String: Any])? { + func attributesAtPoint(_ point: CGPoint) -> (Int, [NSAttributedStringKey: Any])? { if let attributedString = self.attributedString { let transformedPoint = CGPoint(x: point.x - self.insets.left, y: point.y - self.insets.top) for line in self.lines { @@ -126,7 +126,7 @@ final class TextNodeLayout: NSObject { func attributeRects(name: String, at index: Int) -> [CGRect]? { if let attributedString = self.attributedString { var range = NSRange() - let _ = attributedString.attribute(name, at: index, effectiveRange: &range) + let _ = attributedString.attribute(NSAttributedStringKey(rawValue: name), at: index, effectiveRange: &range) if range.length != 0 { var rects: [CGRect] = [] for line in self.lines { @@ -190,7 +190,7 @@ final class TextNode: ASDisplayNode { self.clipsToBounds = false } - func attributesAtPoint(_ point: CGPoint) -> (Int, [String: Any])? { + func attributesAtPoint(_ point: CGPoint) -> (Int, [NSAttributedStringKey: Any])? { if let cachedLayout = self.cachedLayout { return cachedLayout.attributesAtPoint(point) } else { @@ -212,7 +212,7 @@ final class TextNode: ASDisplayNode { let font: CTFont if stringLength != 0 { - if let stringFont = attributedString.attribute(kCTFontAttributeName as String, at: 0, effectiveRange: nil) { + if let stringFont = attributedString.attribute(NSAttributedStringKey.font, at: 0, effectiveRange: nil) { font = stringFont as! CTFont } else { font = defaultFont @@ -303,9 +303,9 @@ final class TextNode: ASDisplayNode { if CTLineGetTypographicBounds(originalLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(originalLine) < Double(constrainedSize.width) { coreTextLine = originalLine } else { - var truncationTokenAttributes: [String : AnyObject] = [:] - truncationTokenAttributes[kCTFontAttributeName as String] = font - truncationTokenAttributes[kCTForegroundColorFromContextAttributeName as String] = true as NSNumber + var truncationTokenAttributes: [NSAttributedStringKey : AnyObject] = [:] + truncationTokenAttributes[NSAttributedStringKey.font] = font + truncationTokenAttributes[NSAttributedStringKey(rawValue: kCTForegroundColorFromContextAttributeName as String)] = true as NSNumber let tokenString = "\u{2026}" let truncatedTokenString = NSAttributedString(string: tokenString, attributes: truncationTokenAttributes) let truncationToken = CTLineCreateWithAttributedString(truncatedTokenString) diff --git a/TelegramUI/UniversalVideoCalleryItem.swift b/TelegramUI/UniversalVideoCalleryItem.swift new file mode 100644 index 0000000000..4f1fc70d60 --- /dev/null +++ b/TelegramUI/UniversalVideoCalleryItem.swift @@ -0,0 +1,571 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import SwiftSignalKit +import TelegramCore +import Display + +class UniversalVideoGalleryItem: GalleryItem { + let account: Account + let theme: PresentationTheme + let strings: PresentationStrings + let content: UniversalVideoContent + let originData: GalleryItemOriginData? + let indexData: GalleryItemIndexData? + let caption: String + + init(account: Account, theme: PresentationTheme, strings: PresentationStrings, content: UniversalVideoContent, originData: GalleryItemOriginData?, indexData: GalleryItemIndexData?, caption: String) { + self.account = account + self.theme = theme + self.strings = strings + self.content = content + self.originData = originData + self.indexData = indexData + self.caption = caption + } + + func node() -> GalleryItemNode { + let node = UniversalVideoGalleryItemNode(account: self.account, theme: self.theme, strings: self.strings) + node.setupItem(self) + + /*for media in self.message.media { + if let file = media as? TelegramMediaFile, (file.isVideo || file.mimeType.hasPrefix("video/")) { + node.setFile(account: account, stableId: self.message.stableId, file: file, loopVideo: file.isAnimated || self.message.containsSecretMedia) + break + } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { + if let file = content.file, (file.isVideo || file.mimeType.hasPrefix("video/")) { + node.setFile(account: account, stableId: self.message.stableId, file: file, loopVideo: file.isAnimated || self.message.containsSecretMedia) + break + } + } + }*/ + + if let indexData = self.indexData { + node._title.set(.single("\(indexData.position + 1) of \(indexData.totalCount)")) + } + //node.setMessage(self.message) + + return node + } + + func updateNode(node: GalleryItemNode) { + if let node = node as? UniversalVideoGalleryItemNode { + if let indexData = self.indexData { + node._title.set(.single("\(indexData.position + 1) of \(indexData.totalCount)")) + } + node.setupItem(self) + //node.setMessage(self.message) + } + } +} + +private let pictureInPictureImage = UIImage(bundleImageName: "Media Gallery/PictureInPictureIcon")?.precomposed() +private let pictureInPictureButtonImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/PictureInPictureButton"), color: .white) +private let placeholderFont = Font.regular(16.0) + +private final class UniversalVideoGalleryItemPictureInPictureNode: ASDisplayNode { + private let iconNode: ASImageNode + private let textNode: ASTextNode + + init(strings: PresentationStrings) { + self.iconNode = ASImageNode() + self.iconNode.isLayerBacked = true + self.iconNode.displayWithoutProcessing = true + self.iconNode.displaysAsynchronously = false + self.iconNode.image = pictureInPictureImage + + self.textNode = ASTextNode() + self.textNode.isLayerBacked = true + self.textNode.displaysAsynchronously = false + self.textNode.attributedText = NSAttributedString(string: strings.Embed_PlayingInPIP, font: placeholderFont, textColor: UIColor(rgb: 0x8e8e93)) + + super.init() + + self.addSubnode(self.iconNode) + self.addSubnode(self.textNode) + } + + func updateLayout(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + let iconSize = self.iconNode.image?.size ?? CGSize() + let textSize = self.textNode.measure(CGSize(width: layout.size.width - 20.0, height: CGFloat.greatestFiniteMagnitude)) + let spacing: CGFloat = 10.0 + let contentHeight = iconSize.height + spacing + textSize.height + let contentVerticalOrigin = floor((layout.size.height - contentHeight) / 2.0) + transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: contentVerticalOrigin), size: iconSize)) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: contentVerticalOrigin + iconSize.height + spacing), size: textSize)) + } +} + +final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { + private let account: Account + private let strings: PresentationStrings + + fileprivate let _ready = Promise() + fileprivate let _title = Promise() + fileprivate let _titleView = Promise() + fileprivate let _rightBarButtonItem = Promise() + + private let scrubberView: ChatVideoGalleryItemScrubberView + private let footerContentNode: ChatItemGalleryFooterContentNode + + private var videoNode: UniversalVideoNode? + private var pictureInPictureNode: UniversalVideoGalleryItemPictureInPictureNode? + private let statusButtonNode: HighlightableButtonNode + private let statusNode: RadialStatusNode + + private var isCentral = false + private var validLayout: (ContainerViewLayout, CGFloat)? + + private var item: UniversalVideoGalleryItem? + + private let statusDisposable = MetaDisposable() + + init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { + self.account = account + self.strings = strings + self.scrubberView = ChatVideoGalleryItemScrubberView() + + self.footerContentNode = ChatItemGalleryFooterContentNode(account: account, theme: theme, strings: strings) + + self.statusButtonNode = HighlightableButtonNode() + self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5)) + + super.init() + + self._titleView.set(.single(self.scrubberView)) + self.scrubberView.seek = { [weak self] timestamp in + self?.videoNode?.seek(timestamp) + } + + self.statusButtonNode.addSubnode(self.statusNode) + self.statusButtonNode.addTarget(self, action: #selector(statusButtonPressed), forControlEvents: .touchUpInside) + + self.addSubnode(self.statusButtonNode) + self.statusNode.transitionToState(.play(.white), completion: {}) + + self.footerContentNode.playbackControl = { [weak self] in + if let strongSelf = self { + strongSelf.videoNode?.togglePlayPause() + } + } + } + + deinit { + self.statusDisposable.dispose() + } + + override func ready() -> Signal { + return self._ready.get() + } + + override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) + + self.validLayout = (layout, navigationBarHeight) + + let statusDiameter: CGFloat = 50.0 + let statusFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - statusDiameter) / 2.0), y: floor((layout.size.height - statusDiameter) / 2.0)), size: CGSize(width: statusDiameter, height: statusDiameter)) + transition.updateFrame(node: self.statusButtonNode, frame: statusFrame) + transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(), size: statusFrame.size)) + + if let pictureInPictureNode = self.pictureInPictureNode { + transition.updateFrame(node: pictureInPictureNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + pictureInPictureNode.updateLayout(layout, navigationBarHeight: navigationBarHeight, transition: transition) + } + } + + /*fileprivate func setMessage(_ message: Message) { + self.footerContentNode.setMessage(message) + + self.message = message + + var rightBarButtonItem: UIBarButtonItem? + for media in message.media { + if let file = media as? TelegramMediaFile { + if file.isVideo { + rightBarButtonItem = UIBarButtonItem(image: pictureInPictureButtonImage, style: .plain, target: self, action: #selector(self.pictureInPictureButtonPressed)) + break + } + } + } + self._rightBarButtonItem.set(.single(rightBarButtonItem)) + }*/ + + func setupItem(_ item: UniversalVideoGalleryItem) { + if self.item?.content.id != item.content.id { + if let videoNode = self.videoNode { + videoNode.canAttachContent = false + videoNode.removeFromSupernode() + } + + let videoNode = UniversalVideoNode(account: item.account, manager: item.account.telegramApplicationContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: item.content, priority: .gallery) + let videoSize = CGSize(width: item.content.dimensions.width * 2.0, height: item.content.dimensions.height * 2.0) + videoNode.updateLayout(size: videoSize, transition: .immediate) + videoNode.ownsContentNodeUpdated = { [weak self] value in + if let strongSelf = self { + strongSelf.updateDisplayPlaceholder(!value) + } + } + self.videoNode = videoNode + videoNode.backgroundColor = videoNode.ownsContentNode ? UIColor.black : UIColor(rgb: 0x333335) + videoNode.canAttachContent = true + self.updateDisplayPlaceholder(!videoNode.ownsContentNode) + + self.scrubberView.setStatusSignal(videoNode.status |> map { value -> MediaPlayerStatus in + if let value = value { + return value + } else { + return MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: Double(item.content.duration), timestamp: 0.0, status: .paused) + } + }) + + self.statusDisposable.set((videoNode.status |> deliverOnMainQueue).start(next: { [weak self] value in + if let strongSelf = self { + var isPaused = true + if let value = value { + switch value.status { + case .playing: + isPaused = false + case let .buffering(whilePlaying): + isPaused = !whilePlaying + default: + break + } + } + + strongSelf.statusButtonNode.isHidden = !isPaused + strongSelf.footerContentNode.content = isPaused ? .info : .playbackPause + } + })) + + self.zoomableContent = (videoSize, videoNode) + + let rightBarButtonItem = UIBarButtonItem(image: pictureInPictureButtonImage, style: .plain, target: self, action: #selector(self.pictureInPictureButtonPressed)) + self._rightBarButtonItem.set(.single(rightBarButtonItem)) + + self._ready.set(.single(Void())) + } + self.item = item + + self.footerContentNode.setup(origin: item.originData, caption: item.caption) + } + + private func updateDisplayPlaceholder(_ displayPlaceholder: Bool) { + if displayPlaceholder { + if self.pictureInPictureNode == nil { + let pictureInPictureNode = UniversalVideoGalleryItemPictureInPictureNode(strings: self.strings) + self.pictureInPictureNode = pictureInPictureNode + self.addSubnode(pictureInPictureNode) + if let validLayout = self.validLayout { + pictureInPictureNode.frame = CGRect(origin: CGPoint(), size: validLayout.0.size) + pictureInPictureNode.updateLayout(validLayout.0, navigationBarHeight: validLayout.1, transition: .immediate) + } + self.videoNode?.backgroundColor = UIColor(rgb: 0x333335) + } + } else if let pictureInPictureNode = self.pictureInPictureNode { + self.pictureInPictureNode = nil + pictureInPictureNode.removeFromSupernode() + self.videoNode?.backgroundColor = .black + } + } + + override func centralityUpdated(isCentral: Bool) { + super.centralityUpdated(isCentral: isCentral) + + if self.isCentral != isCentral { + self.isCentral = isCentral + + if let videoNode = self.videoNode { + if isCentral { + //videoNode.canAttachContent = true + } else if videoNode.ownsContentNode { + videoNode.pause() + } + } + } + } + + override func activateAsInitial() { + if self.isCentral { + self.videoNode?.play() + } + } + + override func animateIn(from node: ASDisplayNode) { + guard let videoNode = self.videoNode else { + return + } + + if let node = node as? TelegramVideoNode { + var transformedFrame = node.view.convert(node.view.bounds, to: videoNode.view) + let transformedSuperFrame = node.view.convert(node.view.bounds, to: videoNode.view.superview) + + videoNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: videoNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) + + transformedFrame.origin = CGPoint() + + let transform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0) + videoNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: videoNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25) + + self.account.telegramApplicationContext.mediaManager.setOverlayVideoNode(nil) + } else { + var transformedFrame = node.view.convert(node.view.bounds, to: videoNode.view) + let transformedSuperFrame = node.view.convert(node.view.bounds, to: videoNode.view.superview) + + videoNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: videoNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) + + transformedFrame.origin = CGPoint() + + let transform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0) + videoNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: videoNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25) + } + } + + override func animateOut(to node: ASDisplayNode, completion: @escaping () -> Void) { + guard let videoNode = self.videoNode else { + completion() + return + } + + var transformedFrame = node.view.convert(node.view.bounds, to: videoNode.view) + let transformedSuperFrame = node.view.convert(node.view.bounds, to: videoNode.view.superview) + let transformedSelfFrame = node.view.convert(node.view.bounds, to: self.view) + let transformedCopyViewInitialFrame = videoNode.view.convert(videoNode.view.bounds, to: self.view) + + var positionCompleted = false + var boundsCompleted = false + var copyCompleted = false + + let copyView = node.view.snapshotContentTree()! + + self.view.insertSubview(copyView, belowSubview: self.scrollView) + copyView.frame = transformedSelfFrame + + let intermediateCompletion = { [weak copyView] in + if positionCompleted && boundsCompleted && copyCompleted { + copyView?.removeFromSuperview() + completion() + } + } + + copyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, removeOnCompletion: false) + + copyView.layer.animatePosition(from: CGPoint(x: transformedCopyViewInitialFrame.midX, y: transformedCopyViewInitialFrame.midY), to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + let scale = CGSize(width: transformedCopyViewInitialFrame.size.width / transformedSelfFrame.size.width, height: transformedCopyViewInitialFrame.size.height / transformedSelfFrame.size.height) + copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in + copyCompleted = true + intermediateCompletion() + }) + + videoNode.layer.animatePosition(from: videoNode.layer.position, to: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in + positionCompleted = true + intermediateCompletion() + }) + + videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) + + self.statusButtonNode.layer.animatePosition(from: self.statusButtonNode.layer.position, to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in + //positionCompleted = true + //intermediateCompletion() + }) + self.statusButtonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) + self.statusButtonNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.25, removeOnCompletion: false) + + transformedFrame.origin = CGPoint() + + let transform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0) + videoNode.layer.animate(from: NSValue(caTransform3D: videoNode.layer.transform), to: NSValue(caTransform3D: transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in + boundsCompleted = true + intermediateCompletion() + }) + } + + func animateOut(toOverlay node: ASDisplayNode, completion: @escaping () -> Void) { + guard let videoNode = self.videoNode else { + completion() + return + } + + var transformedFrame = node.view.convert(node.view.bounds, to: videoNode.view) + let transformedSuperFrame = node.view.convert(node.view.bounds, to: videoNode.view.superview) + let transformedSelfFrame = node.view.convert(node.view.bounds, to: self.view) + let transformedCopyViewInitialFrame = videoNode.view.convert(videoNode.view.bounds, to: self.view) + let transformedSelfTargetSuperFrame = videoNode.view.convert(videoNode.view.bounds, to: node.view.superview) + + var positionCompleted = false + var boundsCompleted = false + var copyCompleted = false + var nodeCompleted = false + + let copyView = node.view.snapshotContentTree()! + + //self.view.insertSubview(copyView, belowSubview: self.scrollView) + videoNode.isHidden = true + copyView.frame = transformedSelfFrame + + let intermediateCompletion = { [weak copyView] in + if positionCompleted && boundsCompleted && copyCompleted && nodeCompleted { + copyView?.removeFromSuperview() + completion() + } + } + + copyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, removeOnCompletion: false) + + copyView.layer.animatePosition(from: CGPoint(x: transformedCopyViewInitialFrame.midX, y: transformedCopyViewInitialFrame.midY), to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + let scale = CGSize(width: transformedCopyViewInitialFrame.size.width / transformedSelfFrame.size.width, height: transformedCopyViewInitialFrame.size.height / transformedSelfFrame.size.height) + copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in + copyCompleted = true + intermediateCompletion() + }) + + videoNode.layer.animatePosition(from: videoNode.layer.position, to: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in + positionCompleted = true + intermediateCompletion() + }) + + videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) + + self.statusButtonNode.layer.animatePosition(from: self.statusButtonNode.layer.position, to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in + //positionCompleted = true + //intermediateCompletion() + }) + self.statusButtonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) + self.statusButtonNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.25, removeOnCompletion: false) + + transformedFrame.origin = CGPoint() + + let transform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0) + videoNode.layer.animate(from: NSValue(caTransform3D: videoNode.layer.transform), to: NSValue(caTransform3D: transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in + boundsCompleted = true + intermediateCompletion() + }) + + //node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + let nodeTransform = CATransform3DScale(node.layer.transform, videoNode.layer.bounds.size.width / transformedFrame.size.width, videoNode.layer.bounds.size.height / transformedFrame.size.height, 1.0) + node.layer.animatePosition(from: CGPoint(x: transformedSelfTargetSuperFrame.midX, y: transformedSelfTargetSuperFrame.midY), to: node.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) + node.layer.animate(from: NSValue(caTransform3D: nodeTransform), to: NSValue(caTransform3D: node.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in + nodeCompleted = true + intermediateCompletion() + }) + } + + override func title() -> Signal { + return .single("") + } + + override func titleView() -> Signal { + return self._titleView.get() + } + + override func rightBarButtonItem() -> Signal { + return self._rightBarButtonItem.get() + } + + /*private func activateVideo() { + if let (account, file, _) = self.accountAndFile { + if let resourceStatus = self.resourceStatus { + switch resourceStatus { + case .Fetching: + break + case .Local: + self.playVideo() + case .Remote: + self.fetchDisposable.set(account.postbox.mediaBox.fetchedResource(file.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .video)).start()) + } + } + } + }*/ + + @objc func statusButtonPressed() { + if let videoNode = self.videoNode { + videoNode.togglePlayPause() + } + /*if let (account, file, _) = self.accountAndFile { + if let resourceStatus = self.resourceStatus { + switch resourceStatus { + case .Fetching: + account.postbox.mediaBox.cancelInteractiveResourceFetch(file.resource) + case .Local: + self.playVideo() + case .Remote: + self.fetchDisposable.set(account.postbox.mediaBox.fetchedResource(file.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .video)).start()) + } + } + }*/ + } + + @objc func pictureInPictureButtonPressed() { + if let item = self.item, let _ = self.videoNode { + let account = self.account + let baseNavigationController = self.baseNavigationController() + let mediaManager = self.account.telegramApplicationContext.mediaManager + var expandImpl: (() -> Void)? + let overlayNode = OverlayUniversalVideoNode(account: self.account, manager: self.account.telegramApplicationContext.mediaManager.universalVideoManager, content: item.content, expand: { + expandImpl?() + }, close: { [weak mediaManager] in + mediaManager?.setOverlayVideoNode(nil) + }) + expandImpl = { [weak overlayNode] in + /*let gallery = GalleryController(account: account, messageId: message.id, replaceRootController: { controller, ready in + if let baseNavigationController = baseNavigationController { + baseNavigationController.replaceTopController(controller, animated: false, ready: ready) + } + }, baseNavigationController: baseNavigationController) + + (baseNavigationController?.topViewController as? ViewController)?.present(gallery, in: .window(.root), with: GalleryControllerPresentationArguments(transitionArguments: { _, _ in + if let overlayNode = overlayNode, let overlaySupernode = overlayNode.supernode { + return GalleryTransitionArguments(transitionNode: overlayNode, transitionContainerNode: overlaySupernode, transitionBackgroundNode: ASDisplayNode()) + } + return nil + }))*/ + } + account.telegramApplicationContext.mediaManager.setOverlayVideoNode(overlayNode) + if overlayNode.supernode != nil { + self.beginCustomDismiss() + self.animateOut(toOverlay: overlayNode, completion: { [weak self] in + self?.completeCustomDismiss() + }) + } + } + /*if let account = self.accountAndFile?.0, let message = self.message, let file = self.accountAndFile?.1 { + let overlayNode = TelegramVideoNode(manager: account.telegramApplicationContext.mediaManager, account: account, source: TelegramVideoNodeSource.messageMedia(stableId: message.stableId, file: file), priority: 1, withSound: true, withOverlayControls: true) + overlayNode.dismissed = { [weak account, weak overlayNode] in + if let account = account, let overlayNode = overlayNode { + if overlayNode.supernode != nil { + account.telegramApplicationContext.mediaManager.setOverlayVideoNode(nil) + } + } + } + let baseNavigationController = self.baseNavigationController() + overlayNode.unembed = { [weak account, weak overlayNode, weak baseNavigationController] in + if let account = account { + let gallery = GalleryController(account: account, messageId: message.id, replaceRootController: { controller, ready in + if let baseNavigationController = baseNavigationController { + baseNavigationController.replaceTopController(controller, animated: false, ready: ready) + } + }, baseNavigationController: baseNavigationController) + + (baseNavigationController?.topViewController as? ViewController)?.present(gallery, in: .window(.root), with: GalleryControllerPresentationArguments(transitionArguments: { _, _ in + if let overlayNode = overlayNode, let overlaySupernode = overlayNode.supernode { + return GalleryTransitionArguments(transitionNode: overlayNode, transitionContainerNode: overlaySupernode, transitionBackgroundNode: ASDisplayNode()) + } + return nil + })) + } + } + overlayNode.setShouldAcquireContext(true) + account.telegramApplicationContext.mediaManager.setOverlayVideoNode(overlayNode) + if overlayNode.supernode != nil { + self.beginCustomDismiss() + self.animateOut(toOverlay: overlayNode, completion: { [weak self] in + self?.completeCustomDismiss() + }) + } + }*/ + } + + override func footerContent() -> Signal { + return .single(self.footerContentNode) + } +} diff --git a/TelegramUI/UniversalVideoContentManager.swift b/TelegramUI/UniversalVideoContentManager.swift new file mode 100644 index 0000000000..d0e3ad38bb --- /dev/null +++ b/TelegramUI/UniversalVideoContentManager.swift @@ -0,0 +1,206 @@ +import Foundation +import AsyncDisplayKit +import SwiftSignalKit + +private final class UniversalVideoContentSubscriber { + let id: Int32 + let priority: UniversalVideoPriority + let update: ((UniversalVideoContentNode & ASDisplayNode)?) -> Void + var active: Bool = false + + init(id: Int32, priority: UniversalVideoPriority, update: @escaping ((UniversalVideoContentNode & ASDisplayNode)?) -> Void) { + self.id = id + self.priority = priority + self.update = update + } +} + +private final class UniversalVideoContentHolder { + private var nextId: Int32 = 0 + private var subscribers: [UniversalVideoContentSubscriber] = [] + let content: UniversalVideoContentNode & ASDisplayNode + + var statusDisposable: Disposable? + var statusValue: MediaPlayerStatus? + + init(content: UniversalVideoContentNode & ASDisplayNode, statusUpdated: @escaping (MediaPlayerStatus?) -> Void) { + self.content = content + + self.statusDisposable = (content.status |> deliverOn(Queue.mainQueue())).start(next: { [weak self] value in + if let strongSelf = self { + strongSelf.statusValue = value + statusUpdated(value) + } + }) + } + + deinit { + self.statusDisposable?.dispose() + } + + var isEmpty: Bool { + return self.subscribers.isEmpty + } + + func addSubscriber(priority: UniversalVideoPriority, update: @escaping ((UniversalVideoContentNode & ASDisplayNode)?) -> Void) -> Int32 { + let id = self.nextId + self.nextId += 1 + + self.subscribers.append(UniversalVideoContentSubscriber(id: id, priority: priority, update: update)) + self.subscribers.sort(by: { lhs, rhs in + if lhs.priority != rhs.priority { + return lhs.priority < rhs.priority + } + return lhs.id < rhs.id + }) + + return id + } + + func removeSubscriberAndUpdate(id: Int32) { + for i in 0 ..< self.subscribers.count { + if self.subscribers[i].id == id { + let subscriber = self.subscribers[i] + self.subscribers.remove(at: i) + if subscriber.active { + subscriber.update(nil) + self.update() + } + break + } + } + } + + func update() { + for i in (0 ..< self.subscribers.count) { + if i == self.subscribers.count - 1 { + if !self.subscribers[i].active { + self.subscribers[i].active = true + self.subscribers[i].update(self.content) + } + } else { + if self.subscribers[i].active { + self.subscribers[i].active = false + self.subscribers[i].update(nil) + } + } + } + } +} + +private final class UniversalVideoContentHolderCallbacks { + let playbackCompleted = Bag<() -> Void>() + let status = Bag<(MediaPlayerStatus?) -> Void>() + + var isEmpty: Bool { + return self.playbackCompleted.isEmpty && self.status.isEmpty + } +} + +final class UniversalVideoContentManager { + private var holders: [AnyHashable: UniversalVideoContentHolder] = [:] + private var holderCallbacks: [AnyHashable: UniversalVideoContentHolderCallbacks] = [:] + + func attachUniversalVideoContent(id: AnyHashable, priority: UniversalVideoPriority, create: () -> UniversalVideoContentNode & ASDisplayNode, update: @escaping ((UniversalVideoContentNode & ASDisplayNode)?) -> Void) -> Int32 { + assert(Queue.mainQueue().isCurrent()) + + let holder: UniversalVideoContentHolder + if let current = self.holders[id] { + holder = current + } else { + holder = UniversalVideoContentHolder(content: create(), statusUpdated: { [weak self] value in + if let strongSelf = self { + if let current = strongSelf.holderCallbacks[id] { + for subscriber in current.status.copyItems() { + subscriber(value) + } + } + } + }) + self.holders[id] = holder + } + + let id = holder.addSubscriber(priority: priority, update: update) + holder.update() + return id + } + + func detachUniversalVideoContent(id: AnyHashable, index: Int32) { + assert(Queue.mainQueue().isCurrent()) + + if let holder = self.holders[id] { + holder.removeSubscriberAndUpdate(id: index) + if holder.isEmpty { + //holder.content.dispose() + self.holders.removeValue(forKey: id) + + if let current = self.holderCallbacks[id] { + for subscriber in current.status.copyItems() { + subscriber(nil) + } + } + } + } + } + + func withUniversalVideoContent(id: AnyHashable, _ f: ((UniversalVideoContentNode & ASDisplayNode)?) -> Void) { + if let holder = self.holders[id] { + f(holder.content) + } else { + f(nil) + } + } + + func addPlaybackCompleted(id: AnyHashable, _ f: @escaping () -> Void) -> Int { + var callbacks: UniversalVideoContentHolderCallbacks + if let current = self.holderCallbacks[id] { + callbacks = current + } else { + callbacks = UniversalVideoContentHolderCallbacks() + self.holderCallbacks[id] = callbacks + } + return callbacks.playbackCompleted.add(f) + } + + func removePlaybackCompleted(id: AnyHashable, index: Int) { + if let current = self.holderCallbacks[id] { + current.playbackCompleted.remove(index) + if current.playbackCompleted.isEmpty { + self.holderCallbacks.removeValue(forKey: id) + } + } + } + + func statusSignal(id: AnyHashable) -> Signal { + return Signal { subscriber in + var callbacks: UniversalVideoContentHolderCallbacks + if let current = self.holderCallbacks[id] { + callbacks = current + } else { + callbacks = UniversalVideoContentHolderCallbacks() + self.holderCallbacks[id] = callbacks + } + + let index = callbacks.status.add({ value in + subscriber.putNext(value) + }) + + if let current = self.holders[id] { + subscriber.putNext(current.statusValue) + } else { + subscriber.putNext(nil) + } + + return ActionDisposable { + Queue.mainQueue().async { + if let current = self.holderCallbacks[id] { + current.status.remove(index) + if current.playbackCompleted.isEmpty { + self.holderCallbacks.removeValue(forKey: id) + } + } + } + } + } |> runOn(Queue.mainQueue()) + } +} diff --git a/TelegramUI/UniversalVideoNode.swift b/TelegramUI/UniversalVideoNode.swift new file mode 100644 index 0000000000..04d5d8bb69 --- /dev/null +++ b/TelegramUI/UniversalVideoNode.swift @@ -0,0 +1,235 @@ +import Foundation +import AsyncDisplayKit +import Postbox +import SwiftSignalKit +import TelegramCore +import Display + +protocol UniversalVideoContentNode: class { + var status: Signal { get } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) + + func play() + func pause() + func togglePlayPause() + func setSoundEnabled(_ value: Bool) + func seek(_ timestamp: Double) + func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int + func removePlaybackCompleted(_ index: Int) +} + +protocol UniversalVideoContent { + var id: AnyHashable { get } + var dimensions: CGSize { get } + var duration: Int32 { get } + func makeContentNode(account: Account) -> UniversalVideoContentNode & ASDisplayNode +} + +protocol UniversalVideoDecoration: class { + var backgroundNode: ASDisplayNode? { get } + var contentContainerNode: ASDisplayNode { get } + var foregroundNode: ASDisplayNode? { get } + + func setStatus(_ status: Signal) + + func updateContentNode(_ contentNode: (UniversalVideoContentNode & ASDisplayNode)?) + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) + func tap() +} + +enum UniversalVideoPriority: Int32, Comparable { + case embedded = 0 + case gallery = 1 + case overlay = 2 + + static func <(lhs: UniversalVideoPriority, rhs: UniversalVideoPriority) -> Bool { + return lhs.rawValue < rhs.rawValue + } + + static func ==(lhs: UniversalVideoPriority, rhs: UniversalVideoPriority) -> Bool { + return lhs.rawValue == rhs.rawValue + } +} + +final class UniversalVideoNode: ASDisplayNode { + private let account: Account + private let manager: UniversalVideoContentManager + private let content: UniversalVideoContent + private let priority: UniversalVideoPriority + private let decoration: UniversalVideoDecoration + + private var contentNode: (UniversalVideoContentNode & ASDisplayNode)? + private var contentNodeId: Int32? + + private var playbackCompletedIndex: Int? + private var contentRequestIndex: Int32? + + var playbackCompleted: (() -> Void)? + + private(set) var ownsContentNode: Bool = false + var ownsContentNodeUpdated: ((Bool) -> Void)? + + private let _status = Promise() + var status: Signal { + return self._status.get() + } + + var canAttachContent: Bool = false { + didSet { + if self.canAttachContent != oldValue { + if self.canAttachContent { + assert(self.contentRequestIndex == nil) + + let content = self.content + let account = self.account + self.contentRequestIndex = self.manager.attachUniversalVideoContent(id: self.content.id, priority: self.priority, create: { + return content.makeContentNode(account: account) + }, update: { [weak self] contentNode in + if let strongSelf = self { + strongSelf.updateContentNode(contentNode) + } + }) + } else { + assert(self.contentRequestIndex != nil) + if let contentRequestIndex = self.contentRequestIndex { + self.contentRequestIndex = nil + self.manager.detachUniversalVideoContent(id: self.content.id, index: contentRequestIndex) + } + } + } + } + } + + init(account: Account, manager: UniversalVideoContentManager, decoration: UniversalVideoDecoration, content: UniversalVideoContent, priority: UniversalVideoPriority) { + self.account = account + self.manager = manager + self.content = content + self.priority = priority + self.decoration = decoration + + super.init() + + self.playbackCompletedIndex = self.manager.addPlaybackCompleted(id: self.content.id, { [weak self] in + self?.playbackCompleted?() + }) + + self._status.set(self.manager.statusSignal(id: self.content.id)) + + self.decoration.setStatus(self.status) + + if let backgroundNode = self.decoration.backgroundNode { + self.addSubnode(backgroundNode) + } + + self.addSubnode(self.decoration.contentContainerNode) + + if let foregroundNode = self.decoration.foregroundNode { + self.addSubnode(foregroundNode) + } + } + + override func didLoad() { + super.didLoad() + + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + } + + deinit { + assert(Queue.mainQueue().isCurrent()) + + if let playbackCompletedIndex = self.playbackCompletedIndex { + self.manager.removePlaybackCompleted(id: self.content.id, index: playbackCompletedIndex) + } + + if let contentRequestIndex = self.contentRequestIndex { + self.contentRequestIndex = nil + self.manager.detachUniversalVideoContent(id: self.content.id, index: contentRequestIndex) + } + } + + private func updateContentNode(_ contentNode: (UniversalVideoContentNode & ASDisplayNode)?) { + let previous = self.contentNode + self.contentNode = contentNode + if previous !== contentNode { + if let previous = previous { + /*if let contextPlaybackEndedIndex = self.contextPlaybackEndedIndex { + previous.removePlaybackCompleted(contextPlaybackEndedIndex) + } + self.contextPlaybackEndedIndex = nil*/ + /*if let snapshotView = previous.playerNode.view.snapshotView(afterScreenUpdates: false) { + self.snapshotView = snapshotView + snapshotView.frame = self.imageNode.frame + self.view.addSubview(snapshotView) + }*/ + } + if let contentNode = contentNode { + /*self.contextPlaybackEndedIndex = context.addPlaybackCompleted { [weak self] in + self?.playbackEnded?() + }*/ + + } + self.decoration.updateContentNode(contentNode) + /*if self.hasAttachedContext != (context !== nil) { + self.hasAttachedContext = (context !== nil) + self.hasAttachedContextUpdated?(self.hasAttachedContext) + }*/ + + let ownsContentNode = contentNode !== nil + if self.ownsContentNode != ownsContentNode { + self.ownsContentNode = ownsContentNode + self.ownsContentNodeUpdated?(ownsContentNode) + } + } + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + self.decoration.updateLayout(size: size, transition: transition) + } + + func play() { + self.manager.withUniversalVideoContent(id: self.content.id, { contentNode in + if let contentNode = contentNode { + contentNode.play() + } + }) + } + + func pause() { + self.manager.withUniversalVideoContent(id: self.content.id, { contentNode in + if let contentNode = contentNode { + contentNode.pause() + } + }) + } + + func togglePlayPause() { + self.manager.withUniversalVideoContent(id: self.content.id, { contentNode in + if let contentNode = contentNode { + contentNode.togglePlayPause() + } + }) + } + + func setSoundEnabled(_ value: Bool) { + self.manager.withUniversalVideoContent(id: self.content.id, { contentNode in + if let contentNode = contentNode { + contentNode.setSoundEnabled(value) + } + }) + } + + func seek(_ timestamp: Double) { + self.manager.withUniversalVideoContent(id: self.content.id, { contentNode in + if let contentNode = contentNode { + contentNode.seek(timestamp) + } + }) + } + + @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.decoration.tap() + } + } +}