diff --git a/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/Contents.json b/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/Contents.json new file mode 100644 index 0000000000..8a22af5245 --- /dev/null +++ b/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ConversationInstantPageButtonIconIncoming@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "ConversationInstantPageButtonIconIncoming@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/ConversationInstantPageButtonIconIncoming@2x.png b/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/ConversationInstantPageButtonIconIncoming@2x.png new file mode 100644 index 0000000000..88d8b6e0a3 Binary files /dev/null and b/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/ConversationInstantPageButtonIconIncoming@2x.png differ diff --git a/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/ConversationInstantPageButtonIconIncoming@3x.png b/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/ConversationInstantPageButtonIconIncoming@3x.png new file mode 100644 index 0000000000..4797137de8 Binary files /dev/null and b/Images.xcassets/Chat/Message/AttachedContentInstantIcon.imageset/ConversationInstantPageButtonIconIncoming@3x.png differ diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index 5255da681f..ee1330a58a 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -128,6 +128,7 @@ D0C12A1D1F33A85600B3F66D /* ChatWallpaperBuiltin0.jpg in Resources */ = {isa = PBXBuildFile; fileRef = D0C12A1B1F33964900B3F66D /* ChatWallpaperBuiltin0.jpg */; }; D0C27B3B1F4B453700A4E170 /* InstantPagePlayableVideoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C27B3A1F4B453700A4E170 /* InstantPagePlayableVideoItem.swift */; }; D0C27B3D1F4B454800A4E170 /* InstantPagePlayableVideoNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C27B3C1F4B454800A4E170 /* InstantPagePlayableVideoNode.swift */; }; + D0CE67941F7DB45100FFB557 /* ChatMessageContactBubbleContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE67931F7DB45100FFB557 /* ChatMessageContactBubbleContentNode.swift */; }; D0CE8CE51F6F354400AA2DB0 /* ChatTextInputAccessoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE8CE41F6F354400AA2DB0 /* ChatTextInputAccessoryItem.swift */; }; D0CE8CE71F6F35A300AA2DB0 /* ChatTextInputPanelState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE8CE61F6F35A300AA2DB0 /* ChatTextInputPanelState.swift */; }; D0CE8CEC1F6FCCA300AA2DB0 /* TransformImageArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE8CEB1F6FCCA300AA2DB0 /* TransformImageArguments.swift */; }; @@ -642,9 +643,6 @@ D0EC6DEB1EB9F58900EBF1C3 /* ChatRequestInProgressTitlePanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02383781DDF1A4D004018B6 /* ChatRequestInProgressTitlePanelNode.swift */; }; D0EC6DEC1EB9F58900EBF1C3 /* ChatToastAlertPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D023837D1DDF50FD004018B6 /* ChatToastAlertPanelNode.swift */; }; D0EC6DED1EB9F58900EBF1C3 /* ChatHistoryNavigationButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E451D6B8B950046BCD6 /* ChatHistoryNavigationButtonNode.swift */; }; - D0EC6DEE1EB9F58900EBF1C3 /* ChatMediaActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E4A1D6B8BB20046BCD6 /* ChatMediaActionSheetController.swift */; }; - D0EC6DEF1EB9F58900EBF1C3 /* ChatMediaActionSheetRollItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E4B1D6B8BB20046BCD6 /* ChatMediaActionSheetRollItem.swift */; }; - D0EC6DF01EB9F58900EBF1C3 /* ActionSheetRollImageItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E481D6B8BAC0046BCD6 /* ActionSheetRollImageItem.swift */; }; D0EC6DF11EB9F58900EBF1C3 /* ShareController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00DE6971E8E8E33003F0D76 /* ShareController.swift */; }; D0EC6DF21EB9F58900EBF1C3 /* ShareControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00DE6991E8E8E43003F0D76 /* ShareControllerNode.swift */; }; D0EC6DF31EB9F58900EBF1C3 /* ShareControllerPeerGridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00DE69B1E8E8E97003F0D76 /* ShareControllerPeerGridItem.swift */; }; @@ -748,7 +746,6 @@ D0EC6E571EB9F58900EBF1C3 /* GroupsInCommonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099261E1E69791E00D95539 /* GroupsInCommonController.swift */; }; D0EC6E581EB9F58900EBF1C3 /* PeerSelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07CFF731DCA207200761F81 /* PeerSelectionController.swift */; }; D0EC6E591EB9F58900EBF1C3 /* PeerSelectionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07CFF751DCA224100761F81 /* PeerSelectionControllerNode.swift */; }; - D0EC6E5A1EB9F58900EBF1C3 /* ShareRecipientsActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2689C1D79D33E00C422DA /* ShareRecipientsActionSheetController.swift */; }; D0EC6E5B1EB9F58900EBF1C3 /* CallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EC6B3A1EB8CF2B00EBF1C3 /* CallController.swift */; }; D0EC6E5C1EB9F58900EBF1C3 /* CallControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EC6B3C1EB8CF3500EBF1C3 /* CallControllerNode.swift */; }; D0EC6E5D1EB9F58900EBF1C3 /* PrivacyAndSecurityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05A32DD1E6F0097002760B4 /* PrivacyAndSecurityController.swift */; }; @@ -922,6 +919,7 @@ D0F67FF21EE6B915000E5906 /* ChannelMembersSearchControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F67FF11EE6B915000E5906 /* ChannelMembersSearchControllerNode.swift */; }; D0F67FF41EE6C10F000E5906 /* ChannelMembersSearchContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F67FF31EE6C10F000E5906 /* ChannelMembersSearchContainerNode.swift */; }; D0F6800A1EE750EE000E5906 /* ChannelBannedMemberController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F680091EE750EE000E5906 /* ChannelBannedMemberController.swift */; }; + D0FB87B21F7C4C19004DE005 /* FetchMediaUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FB87B11F7C4C19004DE005 /* FetchMediaUtils.swift */; }; D0FC408E1D5B8E7500261D9D /* TelegramUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FC408D1D5B8E7500261D9D /* TelegramUITests.swift */; }; D0FC4FBB1F751E8900B7443F /* SelectablePeerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FC4FBA1F751E8900B7443F /* SelectablePeerNode.swift */; }; D0FE4DDC1F09AD0400E8A0B3 /* PresentationSurfaceLevels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FE4DDB1F09AD0400E8A0B3 /* PresentationSurfaceLevels.swift */; }; @@ -1399,6 +1397,7 @@ D0C932371E09E0EA0074F044 /* ChatBotInfoItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatBotInfoItem.swift; sourceTree = ""; }; D0C9323B1E0B4AE90074F044 /* DataAndStorageSettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataAndStorageSettingsController.swift; sourceTree = ""; }; D0CE1BD21E51BC6100404327 /* DebugController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugController.swift; sourceTree = ""; }; + D0CE67931F7DB45100FFB557 /* ChatMessageContactBubbleContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageContactBubbleContentNode.swift; sourceTree = ""; }; D0CE8CE41F6F354400AA2DB0 /* ChatTextInputAccessoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTextInputAccessoryItem.swift; sourceTree = ""; }; D0CE8CE61F6F35A300AA2DB0 /* ChatTextInputPanelState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTextInputPanelState.swift; sourceTree = ""; }; D0CE8CEB1F6FCCA300AA2DB0 /* TransformImageArguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransformImageArguments.swift; sourceTree = ""; }; @@ -1437,7 +1436,6 @@ D0D2686B1D788F8200C422DA /* ChatTitleAccessoryPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatTitleAccessoryPanelNode.swift; sourceTree = ""; }; D0D2686D1D7898A900C422DA /* ChatMessageSelectionNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessageSelectionNode.swift; sourceTree = ""; }; D0D268991D79CF9F00C422DA /* ChatPanelInterfaceInteraction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatPanelInterfaceInteraction.swift; sourceTree = ""; }; - D0D2689C1D79D33E00C422DA /* ShareRecipientsActionSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareRecipientsActionSheetController.swift; sourceTree = ""; }; D0D748051E7AF63800F4B1F6 /* StickerPackPreviewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPackPreviewController.swift; sourceTree = ""; }; D0D748071E7AF64400F4B1F6 /* StickerPackPreviewControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPackPreviewControllerNode.swift; sourceTree = ""; }; D0D7480E1E7B1BD600F4B1F6 /* StickerPackPreviewGridItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPackPreviewGridItem.swift; sourceTree = ""; }; @@ -2017,9 +2015,6 @@ D0F69E2C1D6B8B030046BCD6 /* ChatUnreadItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatUnreadItem.swift; sourceTree = ""; }; D0F69E401D6B8B7E0046BCD6 /* ChatTextInputPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatTextInputPanelNode.swift; sourceTree = ""; }; D0F69E451D6B8B950046BCD6 /* ChatHistoryNavigationButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryNavigationButtonNode.swift; sourceTree = ""; }; - D0F69E481D6B8BAC0046BCD6 /* ActionSheetRollImageItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetRollImageItem.swift; sourceTree = ""; }; - D0F69E4A1D6B8BB20046BCD6 /* ChatMediaActionSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMediaActionSheetController.swift; sourceTree = ""; }; - D0F69E4B1D6B8BB20046BCD6 /* ChatMediaActionSheetRollItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMediaActionSheetRollItem.swift; sourceTree = ""; }; D0F69E501D6B8BDA0046BCD6 /* GalleryController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GalleryController.swift; sourceTree = ""; }; D0F69E511D6B8BDA0046BCD6 /* GalleryControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GalleryControllerNode.swift; sourceTree = ""; }; D0F69E521D6B8BDA0046BCD6 /* GalleryItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GalleryItem.swift; sourceTree = ""; }; @@ -2068,6 +2063,7 @@ D0FA0AC41E77431A005BB9B7 /* InstalledStickerPacksController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstalledStickerPacksController.swift; sourceTree = ""; }; D0FA34FE1EA5834C00E56FFA /* ItemListControllerSegmentedTitleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListControllerSegmentedTitleView.swift; sourceTree = ""; }; D0FA35001EA6127000E56FFA /* StorageUsageController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageUsageController.swift; sourceTree = ""; }; + D0FB87B11F7C4C19004DE005 /* FetchMediaUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchMediaUtils.swift; sourceTree = ""; }; D0FC40821D5B8E7400261D9D /* TelegramUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TelegramUI.h; sourceTree = ""; }; D0FC40831D5B8E7400261D9D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D0FC40881D5B8E7500261D9D /* TelegramUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TelegramUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -3053,7 +3049,6 @@ children = ( D07CFF731DCA207200761F81 /* PeerSelectionController.swift */, D07CFF751DCA224100761F81 /* PeerSelectionControllerNode.swift */, - D0D2689C1D79D33E00C422DA /* ShareRecipientsActionSheetController.swift */, ); name = "Peer Selection"; sourceTree = ""; @@ -4259,7 +4254,6 @@ D0BA6F811D784C3A0034826E /* Input Panels */, D0D2686A1D788F6600C422DA /* Title Accessory Panels */, D0F69E441D6B8B850046BCD6 /* History Navigation */, - D0F69E471D6B8B9A0046BCD6 /* Input Media Action Sheet */, ); name = Chat; sourceTree = ""; @@ -4291,6 +4285,7 @@ D0754D1F1EEDEBA000884F6E /* ChatMessageGameBubbleContentNode.swift */, D0754D211EEDF89900884F6E /* ChatMessageInvoiceBubbleContentNode.swift */, D0754D1D1EEDDF6200884F6E /* ChatMessageAttachedContentNode.swift */, + D0CE67931F7DB45100FFB557 /* ChatMessageContactBubbleContentNode.swift */, D0F69E2C1D6B8B030046BCD6 /* ChatUnreadItem.swift */, D0F69E191D6B8AE60046BCD6 /* ChatHoleItem.swift */, D0D2686D1D7898A900C422DA /* ChatMessageSelectionNode.swift */, @@ -4328,16 +4323,6 @@ name = "History Navigation"; sourceTree = ""; }; - D0F69E471D6B8B9A0046BCD6 /* Input Media Action Sheet */ = { - isa = PBXGroup; - children = ( - D0F69E4A1D6B8BB20046BCD6 /* ChatMediaActionSheetController.swift */, - D0F69E4B1D6B8BB20046BCD6 /* ChatMediaActionSheetRollItem.swift */, - D0F69E481D6B8BAC0046BCD6 /* ActionSheetRollImageItem.swift */, - ); - name = "Input Media Action Sheet"; - sourceTree = ""; - }; D0F69E4E1D6B8BB90046BCD6 /* Media */ = { isa = PBXGroup; children = ( @@ -4504,6 +4489,7 @@ D0F3A8B91E831E6300B4C64C /* FetchVideoMediaResource.swift */, D06E4AC31E84806300627D1D /* FetchPhotoLibraryImageResource.swift */, D04B4D101EEA04D400711AF6 /* MapResources.swift */, + D0FB87B11F7C4C19004DE005 /* FetchMediaUtils.swift */, ); name = Resources; sourceTree = ""; @@ -4983,6 +4969,7 @@ D0EC6D201EB9F58800EBF1C3 /* PeerAvatar.swift in Sources */, D0EC6D211EB9F58800EBF1C3 /* FileResources.swift in Sources */, D0EC6FB31EBA114200EBF1C3 /* aec_core_neon.cc in Sources */, + D0CE67941F7DB45100FFB557 /* ChatMessageContactBubbleContentNode.swift in Sources */, D0EC6D221EB9F58800EBF1C3 /* PhotoResources.swift in Sources */, D0EC6FC11EBA132B00EBF1C3 /* nsx_core_neon.c in Sources */, D0EC6FA81EBA111500EBF1C3 /* sparse_fir_filter.cc in Sources */, @@ -5295,9 +5282,7 @@ D0EC6DEB1EB9F58900EBF1C3 /* ChatRequestInProgressTitlePanelNode.swift in Sources */, D0EC6DEC1EB9F58900EBF1C3 /* ChatToastAlertPanelNode.swift in Sources */, D0EC6DED1EB9F58900EBF1C3 /* ChatHistoryNavigationButtonNode.swift in Sources */, - D0EC6DEE1EB9F58900EBF1C3 /* ChatMediaActionSheetController.swift in Sources */, - D0EC6DEF1EB9F58900EBF1C3 /* ChatMediaActionSheetRollItem.swift in Sources */, - D0EC6DF01EB9F58900EBF1C3 /* ActionSheetRollImageItem.swift in Sources */, + D0FB87B21F7C4C19004DE005 /* FetchMediaUtils.swift in Sources */, D0E9BA0C1F04580700F079A4 /* BotCheckoutWebInteractionControllerNode.swift in Sources */, D0EC6DF11EB9F58900EBF1C3 /* ShareController.swift in Sources */, D0EC6FC21EBA135100EBF1C3 /* auto_corr_to_refl_coef.c in Sources */, @@ -5447,7 +5432,6 @@ D0EC6E571EB9F58900EBF1C3 /* GroupsInCommonController.swift in Sources */, D0EC6E581EB9F58900EBF1C3 /* PeerSelectionController.swift in Sources */, D0EC6E591EB9F58900EBF1C3 /* PeerSelectionControllerNode.swift in Sources */, - D0EC6E5A1EB9F58900EBF1C3 /* ShareRecipientsActionSheetController.swift in Sources */, D0EC6E5B1EB9F58900EBF1C3 /* CallController.swift in Sources */, D0EC6E5C1EB9F58900EBF1C3 /* CallControllerNode.swift in Sources */, D0EC6E5D1EB9F58900EBF1C3 /* PrivacyAndSecurityController.swift in Sources */, diff --git a/TelegramUI/ActionSheetRollImageItem.swift b/TelegramUI/ActionSheetRollImageItem.swift deleted file mode 100644 index 35df20523b..0000000000 --- a/TelegramUI/ActionSheetRollImageItem.swift +++ /dev/null @@ -1,81 +0,0 @@ -import Foundation -import Display -import AsyncDisplayKit -import Photos -import SwiftSignalKit - -private let testBackground = generateStretchableFilledCircleImage(radius: 8.0, color: UIColor.lightGray) - -final class ActionSheetRollImageItem: ListViewItem { - let asset: PHAsset - let selectedItem: () -> Void - - var selectable: Bool { - return true - } - - init(asset: PHAsset, selected: @escaping () -> Void) { - self.selectedItem = selected - self.asset = asset - } - - func nodeConfiguredForWidth(async: @escaping (@escaping () -> Void) -> Void, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, () -> Void)) -> Void) { - async { - let node = ActionSheetRollImageItemNode() - node.contentSize = CGSize(width: 84.0, height: 84.0) - node.insets = UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0) - node.updateAsset(asset: self.asset) - completion(node, { - return (nil, {}) - }) - } - } - - func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) { - completion(ListViewItemNodeLayout(contentSize: node.contentSize, insets: node.insets), { - }) - } - - func selected(listView: ListView) { - self.selectedItem() - } -} - -private final class ActionSheetRollImageItemNode: ListViewItemNode { - private let imageNode: ASImageNode - - init() { - self.imageNode = ASImageNode() - - self.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 84.0, height: 84.0)) - self.imageNode.displaysAsynchronously = true - self.imageNode.clipsToBounds = true - self.imageNode.cornerRadius = 8.0 - //self.imageNode.contentMode = .scaleToFill - //self.imageNode.contentsScale = UIScreenScale - - super.init(layerBacked: false, dynamicBounce: false) - - self.addSubnode(self.imageNode) - } - - func updateAsset(asset: PHAsset) { - let retinaSquare = CGSize(width: 84.0 * UIScreenScale, height: 84.0 * UIScreenScale) - - let cropToSquare = PHImageRequestOptions() - cropToSquare.resizeMode = .exact; - - let cropSideLength = min(asset.pixelWidth, asset.pixelHeight) - let square = CGRect(x: 0.0, y: 0.0, width: CGFloat(cropSideLength), height: CGFloat(cropSideLength)) - let cropRect = square.applying(CGAffineTransform(scaleX: 1.0 / CGFloat(asset.pixelWidth), y: 1.0 / CGFloat(asset.pixelHeight))) - - cropToSquare.normalizedCropRect = cropRect - - PHImageManager.default().requestImage(for: asset, targetSize: retinaSquare, contentMode: .aspectFit, options: cropToSquare, resultHandler: { [weak self] image, result in - if let strongSelf = self, let image = image, let cgImage = image.cgImage { - let orientedImage = UIImage(cgImage: cgImage, scale: image.scale, orientation: .right) - strongSelf.imageNode.image = orientedImage - } - }) - } -} diff --git a/TelegramUI/AvatarGalleryController.swift b/TelegramUI/AvatarGalleryController.swift index dc936f6873..89d945488f 100644 --- a/TelegramUI/AvatarGalleryController.swift +++ b/TelegramUI/AvatarGalleryController.swift @@ -203,7 +203,7 @@ class AvatarGalleryController: ViewController { if !self.entries.isEmpty { if centralItemNode.index == 0, let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]), !forceAway { animatedOutNode = false - centralItemNode.animateOut(to: transitionArguments.transitionNode, completion: { + centralItemNode.animateOut(to: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface, completion: { animatedOutNode = true completion() }) @@ -235,14 +235,14 @@ class AvatarGalleryController: ViewController { self.galleryNode.statusBar = self.statusBar self.galleryNode.navigationBar = self.navigationBar - self.galleryNode.transitionNodeForCentralItem = { [weak self] in + self.galleryNode.transitionDataForCentralItem = { [weak self] in if let strongSelf = self { if let centralItemNode = strongSelf.galleryNode.pager.centralItemNode(), let presentationArguments = strongSelf.presentationArguments as? AvatarGalleryControllerPresentationArguments { if centralItemNode.index != 0 { return nil } if let transitionArguments = presentationArguments.transitionArguments(strongSelf.entries[centralItemNode.index]) { - return transitionArguments.transitionNode + return (transitionArguments.transitionNode, transitionArguments.addToTransitionSurface) } } } @@ -294,7 +294,7 @@ class AvatarGalleryController: ViewController { if let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]) { nodeAnimatesItself = true - centralItemNode.animateIn(from: transitionArguments.transitionNode) + centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface) self._hiddenMedia.set(.single(self.entries[centralItemNode.index])) } diff --git a/TelegramUI/AvatarNode.swift b/TelegramUI/AvatarNode.swift index e30bb3f9e3..a80fd3871c 100644 --- a/TelegramUI/AvatarNode.swift +++ b/TelegramUI/AvatarNode.swift @@ -7,13 +7,13 @@ import TelegramCore import SwiftSignalKit private class AvatarNodeParameters: NSObject { - let account: Account - let peerId: PeerId + let accountPeerId: PeerId? + let peerId: PeerId? let letters: [String] let font: UIFont - init(account: Account, peerId: PeerId, letters: [String], font: UIFont) { - self.account = account + init(accountPeerId: PeerId?, peerId: PeerId?, letters: [String], font: UIFont) { + self.accountPeerId = accountPeerId self.peerId = peerId self.letters = letters self.font = font @@ -36,16 +36,19 @@ private let grayscaleColors: NSArray = [ ] private enum AvatarNodeState: Equatable { - case Empty - case PeerAvatar(PeerId, [String], TelegramMediaImageRepresentation?) + case empty + case peerAvatar(PeerId, [String], TelegramMediaImageRepresentation?) + case custom([String]) } private func ==(lhs: AvatarNodeState, rhs: AvatarNodeState) -> Bool { switch (lhs, rhs) { - case (.Empty, .Empty): + case (.empty, .empty): return true - case let (.PeerAvatar(lhsPeerId, lhsLetters, lhsPhotoRepresentations), .PeerAvatar(rhsPeerId, rhsLetters, rhsPhotoRepresentations)): + case let (.peerAvatar(lhsPeerId, lhsLetters, lhsPhotoRepresentations), .peerAvatar(rhsPeerId, rhsLetters, rhsPhotoRepresentations)): return lhsPeerId == rhsPeerId && lhsLetters == rhsLetters && lhsPhotoRepresentations == rhsPhotoRepresentations + case let (.custom(lhsLetters), .custom(rhsLetters)): + return lhsLetters == rhsLetters default: return false } @@ -56,7 +59,7 @@ public final class AvatarNode: ASDisplayNode { didSet { if oldValue !== font { if let parameters = self.parameters { - self.parameters = AvatarNodeParameters(account: parameters.account, peerId: parameters.peerId, letters: parameters.letters, font: self.font) + self.parameters = AvatarNodeParameters(accountPeerId: parameters.accountPeerId, peerId: parameters.peerId, letters: parameters.letters, font: self.font) } if !self.displaySuspended { @@ -68,7 +71,7 @@ public final class AvatarNode: ASDisplayNode { private var parameters: AvatarNodeParameters? let imageNode: ImageNode - private var state: AvatarNodeState = .Empty + private var state: AvatarNodeState = .empty private let imageReady = Promise(false) public var ready: Signal { @@ -115,11 +118,11 @@ public final class AvatarNode: ASDisplayNode { } else { representation = peer.smallProfileImage } - let updatedState = AvatarNodeState.PeerAvatar(peer.id, peer.displayLetters, representation) + let updatedState: AvatarNodeState = .peerAvatar(peer.id, peer.displayLetters, representation) if updatedState != self.state { self.state = updatedState - let parameters = AvatarNodeParameters(account: account, peerId: peer.id, letters: peer.displayLetters, font: self.font) + let parameters = AvatarNodeParameters(accountPeerId: account.peerId, peerId: peer.id, letters: peer.displayLetters, font: self.font) self.displaySuspended = true self.contents = nil @@ -138,6 +141,26 @@ public final class AvatarNode: ASDisplayNode { } } + public func setCustomLetters(_ letters: [String]) { + let updatedState: AvatarNodeState = .custom(letters) + if updatedState != self.state { + self.state = updatedState + + let parameters = AvatarNodeParameters(accountPeerId: nil, peerId: nil, letters: letters, font: self.font) + + self.displaySuspended = true + self.contents = nil + + self.imageReady.set(.single(true)) + self.displaySuspended = false + + if self.parameters == nil || self.parameters != parameters { + self.parameters = parameters + self.setNeedsDisplay() + } + } + } + override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol { return parameters ?? NSObject() } @@ -160,10 +183,10 @@ public final class AvatarNode: ASDisplayNode { let colorIndex: Int if let parameters = parameters as? AvatarNodeParameters { - if parameters.peerId.id == 0 { - colorIndex = -1 + if let accountPeerId = parameters.accountPeerId, let peerId = parameters.peerId { + colorIndex = abs(Int(accountPeerId.id + peerId.id)) } else { - colorIndex = abs(Int(parameters.account.peerId.id + parameters.peerId.id)) + colorIndex = -1 } } else { colorIndex = -1 @@ -176,7 +199,7 @@ public final class AvatarNode: ASDisplayNode { colorsArray = gradientColors[colorIndex % gradientColors.count] } - var locations: [CGFloat] = [1.0, 0.2]; + var locations: [CGFloat] = [1.0, 0.0] let colorSpace = CGColorSpaceCreateDeviceRGB() let gradient = CGGradient(colorsSpace: colorSpace, colors: colorsArray, locations: &locations)! @@ -210,7 +233,7 @@ public final class AvatarNode: ASDisplayNode { let currentState = node?.state let createNode = node == nil return { [weak node] account, peer, font in - let state: AvatarNodeState = .PeerAvatar(peer.id, peer.displayLetters, peer.smallProfileImage) + let state: AvatarNodeState = .peerAvatar(peer.id, peer.displayLetters, peer.smallProfileImage) if currentState != state { } diff --git a/TelegramUI/BotCheckoutController.swift b/TelegramUI/BotCheckoutController.swift index 7da0438ef7..1ee661b269 100644 --- a/TelegramUI/BotCheckoutController.swift +++ b/TelegramUI/BotCheckoutController.swift @@ -32,6 +32,8 @@ final class BotCheckoutController: ViewController { super.init(navigationBarTheme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme)) + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style + var title = self.presentationData.strings.Checkout_Title if invoice.flags.contains(.isTest) { title += " (Test)" diff --git a/TelegramUI/BotCheckoutPaymentMethodSheet.swift b/TelegramUI/BotCheckoutPaymentMethodSheet.swift index 222610ffcd..a8586fcb50 100644 --- a/TelegramUI/BotCheckoutPaymentMethodSheet.swift +++ b/TelegramUI/BotCheckoutPaymentMethodSheet.swift @@ -55,7 +55,7 @@ final class BotCheckoutPaymentMethodSheetController: ActionSheetController { self.theme = theme self.strings = strings - super.init() + super.init(theme: ActionSheetControllerTheme(presentationTheme: theme)) var items: [ActionSheetItem] = [] @@ -123,8 +123,8 @@ public class BotCheckoutPaymentMethodItem: ActionSheetItem { self.action = action } - public func node() -> ActionSheetItemNode { - let node = BotCheckoutPaymentMethodItemNode() + public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { + let node = BotCheckoutPaymentMethodItemNode(theme: theme) node.setItem(self) return node } @@ -139,19 +139,11 @@ public class BotCheckoutPaymentMethodItem: ActionSheetItem { } } -private let checkIcon = generateImage(CGSize(width: 14.0, height: 11.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setStrokeColor(UIColor(rgb: 0x007ee5).cgColor) - context.setLineWidth(2.0) - context.move(to: CGPoint(x: 12.0, y: 1.0)) - context.addLine(to: CGPoint(x: 4.16482734, y: 9.0)) - context.addLine(to: CGPoint(x: 1.0, y: 5.81145833)) - context.strokePath() -}) - public class BotCheckoutPaymentMethodItemNode: ActionSheetItemNode { public static let defaultFont: UIFont = Font.regular(20.0) + private let theme: ActionSheetControllerTheme + private var item: BotCheckoutPaymentMethodItem? private let button: HighlightTrackingButton @@ -159,7 +151,9 @@ public class BotCheckoutPaymentMethodItemNode: ActionSheetItemNode { private let iconNode: ASImageNode private let checkNode: ASImageNode - public override init() { + public override init(theme: ActionSheetControllerTheme) { + self.theme = theme + self.button = HighlightTrackingButton() self.titleNode = ASTextNode() @@ -175,9 +169,17 @@ public class BotCheckoutPaymentMethodItemNode: ActionSheetItemNode { self.checkNode.isUserInteractionEnabled = false self.checkNode.displayWithoutProcessing = true self.checkNode.displaysAsynchronously = false - self.checkNode.image = checkIcon + self.checkNode.image = generateImage(CGSize(width: 14.0, height: 11.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(theme.controlAccentColor.cgColor) + context.setLineWidth(2.0) + context.move(to: CGPoint(x: 12.0, y: 1.0)) + context.addLine(to: CGPoint(x: 4.16482734, y: 9.0)) + context.addLine(to: CGPoint(x: 1.0, y: 5.81145833)) + context.strokePath() + }) - super.init() + super.init(theme: theme) self.view.addSubview(self.button) self.addSubnode(self.titleNode) @@ -187,10 +189,10 @@ public class BotCheckoutPaymentMethodItemNode: ActionSheetItemNode { self.button.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { if highlighted { - strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor + strongSelf.backgroundNode.backgroundColor = theme.itemHighlightedBackgroundColor } else { UIView.animate(withDuration: 0.3, animations: { - strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor + strongSelf.backgroundNode.backgroundColor = theme.itemBackgroundColor }) } } @@ -202,7 +204,7 @@ public class BotCheckoutPaymentMethodItemNode: ActionSheetItemNode { func setItem(_ item: BotCheckoutPaymentMethodItem) { self.item = item - self.titleNode.attributedText = NSAttributedString(string: item.title, font: BotCheckoutPaymentMethodItemNode.defaultFont, textColor: .black) + self.titleNode.attributedText = NSAttributedString(string: item.title, font: BotCheckoutPaymentMethodItemNode.defaultFont, textColor: self.theme.primaryTextColor) self.iconNode.image = item.icon if let value = item.value { self.checkNode.isHidden = !value diff --git a/TelegramUI/BotCheckoutPaymentShippingOptionSheetController.swift b/TelegramUI/BotCheckoutPaymentShippingOptionSheetController.swift index 1f8785ea00..da0cb863a6 100644 --- a/TelegramUI/BotCheckoutPaymentShippingOptionSheetController.swift +++ b/TelegramUI/BotCheckoutPaymentShippingOptionSheetController.swift @@ -12,7 +12,7 @@ final class BotCheckoutPaymentShippingOptionSheetController: ActionSheetControll self.theme = theme self.strings = strings - super.init() + super.init(theme: ActionSheetControllerTheme(presentationTheme: theme)) var items: [ActionSheetItem] = [] @@ -69,16 +69,6 @@ final class BotCheckoutPaymentShippingOptionSheetController: ActionSheetControll } } -private let checkIcon = generateImage(CGSize(width: 14.0, height: 11.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setStrokeColor(UIColor(rgb: 0x007ee5).cgColor) - context.setLineWidth(2.0) - context.move(to: CGPoint(x: 12.0, y: 1.0)) - context.addLine(to: CGPoint(x: 4.16482734, y: 9.0)) - context.addLine(to: CGPoint(x: 1.0, y: 5.81145833)) - context.strokePath() -}) - public class BotCheckoutPaymentShippingOptionItem: ActionSheetItem { public let title: String public let label: String @@ -92,8 +82,8 @@ public class BotCheckoutPaymentShippingOptionItem: ActionSheetItem { self.action = action } - public func node() -> ActionSheetItemNode { - let node = BotCheckoutPaymentShippingOptionItemNode() + public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { + let node = BotCheckoutPaymentShippingOptionItemNode(theme: theme) node.setItem(self) return node } @@ -111,6 +101,8 @@ public class BotCheckoutPaymentShippingOptionItem: ActionSheetItem { public class BotCheckoutPaymentShippingOptionItemNode: ActionSheetItemNode { public static let defaultFont: UIFont = Font.regular(20.0) + private let theme: ActionSheetControllerTheme + private var item: BotCheckoutPaymentShippingOptionItem? private let button: HighlightTrackingButton @@ -118,7 +110,9 @@ public class BotCheckoutPaymentShippingOptionItemNode: ActionSheetItemNode { private let labelNode: ASTextNode private let checkNode: ASImageNode - public override init() { + public override init(theme: ActionSheetControllerTheme) { + self.theme = theme + self.button = HighlightTrackingButton() self.titleNode = ASTextNode() @@ -135,9 +129,17 @@ public class BotCheckoutPaymentShippingOptionItemNode: ActionSheetItemNode { self.checkNode.isUserInteractionEnabled = false self.checkNode.displayWithoutProcessing = true self.checkNode.displaysAsynchronously = false - self.checkNode.image = checkIcon + self.checkNode.image = generateImage(CGSize(width: 14.0, height: 11.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(theme.controlAccentColor.cgColor) + context.setLineWidth(2.0) + context.move(to: CGPoint(x: 12.0, y: 1.0)) + context.addLine(to: CGPoint(x: 4.16482734, y: 9.0)) + context.addLine(to: CGPoint(x: 1.0, y: 5.81145833)) + context.strokePath() + }) - super.init() + super.init(theme: theme) self.view.addSubview(self.button) self.addSubnode(self.titleNode) @@ -147,10 +149,10 @@ public class BotCheckoutPaymentShippingOptionItemNode: ActionSheetItemNode { self.button.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { if highlighted { - strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor + strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemHighlightedBackgroundColor } else { UIView.animate(withDuration: 0.3, animations: { - strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor + strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemBackgroundColor }) } } @@ -162,8 +164,8 @@ public class BotCheckoutPaymentShippingOptionItemNode: ActionSheetItemNode { func setItem(_ item: BotCheckoutPaymentShippingOptionItem) { self.item = item - self.titleNode.attributedText = NSAttributedString(string: item.title, font: BotCheckoutPaymentShippingOptionItemNode.defaultFont, textColor: .black) - self.labelNode.attributedText = NSAttributedString(string: item.label, font: BotCheckoutPaymentShippingOptionItemNode.defaultFont, textColor: .black) + self.titleNode.attributedText = NSAttributedString(string: item.title, font: BotCheckoutPaymentShippingOptionItemNode.defaultFont, textColor: self.theme.primaryTextColor) + self.labelNode.attributedText = NSAttributedString(string: item.label, font: BotCheckoutPaymentShippingOptionItemNode.defaultFont, textColor: self.theme.primaryTextColor) if let value = item.value { self.checkNode.isHidden = !value } else { diff --git a/TelegramUI/BotReceiptController.swift b/TelegramUI/BotReceiptController.swift index 687bd92d86..6ee2fd7100 100644 --- a/TelegramUI/BotReceiptController.swift +++ b/TelegramUI/BotReceiptController.swift @@ -32,6 +32,8 @@ final class BotReceiptController: ViewController { super.init(navigationBarTheme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme)) + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style + var title = self.presentationData.strings.Checkout_Receipt_Title if invoice.flags.contains(.isTest) { title += " (Test)" diff --git a/TelegramUI/CachedResourceRepresentations.swift b/TelegramUI/CachedResourceRepresentations.swift index d38e4a031e..018078801b 100644 --- a/TelegramUI/CachedResourceRepresentations.swift +++ b/TelegramUI/CachedResourceRepresentations.swift @@ -59,3 +59,23 @@ final class CachedVideoFirstFrameRepresentation: CachedMediaResourceRepresentati } } } + +final class CachedScaledVideoFirstFrameRepresentation: CachedMediaResourceRepresentation { + let size: CGSize + + var uniqueId: String { + return "scaled-frame-\(Int(self.size.width))x\(Int(self.size.height))" + } + + init(size: CGSize) { + self.size = size + } + + func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + if let to = to as? CachedScaledVideoFirstFrameRepresentation { + return self.size == to.size + } else { + return false + } + } +} diff --git a/TelegramUI/CallControllerNode.swift b/TelegramUI/CallControllerNode.swift index 76b3036ea5..a9e1dc6bfa 100644 --- a/TelegramUI/CallControllerNode.swift +++ b/TelegramUI/CallControllerNode.swift @@ -7,14 +7,6 @@ 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)) - context.setFillColor(color.cgColor) - let _ = try? drawSvgPath(context, path: "M10.6569398,0.0 L0.0,11 L10.6569398,22 L13,19.1782395 L5.07681762,11 L13,2.82176047 Z ") - }) -} - final class CallControllerNode: ASDisplayNode { private let account: Account @@ -72,7 +64,7 @@ final class CallControllerNode: ASDisplayNode { self.backButtonArrowNode = ASImageNode() self.backButtonArrowNode.displayWithoutProcessing = true self.backButtonArrowNode.displaysAsynchronously = false - self.backButtonArrowNode.image = generateBackArrowImage(color: .white) + self.backButtonArrowNode.image = NavigationBarTheme.generateBackArrowImage(color: .white) self.backButtonNode = HighlightableButtonNode() self.statusNode = CallControllerStatusNode() diff --git a/TelegramUI/CallListCallItem.swift b/TelegramUI/CallListCallItem.swift index 65e9b0de99..7164d0c326 100644 --- a/TelegramUI/CallListCallItem.swift +++ b/TelegramUI/CallListCallItem.swift @@ -122,6 +122,8 @@ class CallListCallItem: ListViewItem { private let separatorHeight = 1.0 / UIScreen.main.scale +private let avatarFont: UIFont = UIFont(name: "ArialRoundedMTBold", size: 15.0)! + class CallListCallItemNode: ItemListRevealOptionsItemNode { private let backgroundNode: ASDisplayNode private let separatorNode: ASDisplayNode @@ -149,7 +151,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - self.avatarNode = AvatarNode(font: Font.regular(15.0)) + self.avatarNode = AvatarNode(font: avatarFont) self.avatarNode.isLayerBacked = true self.titleNode = TextNode() diff --git a/TelegramUI/ChannelAdminsController.swift b/TelegramUI/ChannelAdminsController.swift index be0deeb0dd..f008daeb06 100644 --- a/TelegramUI/ChannelAdminsController.swift +++ b/TelegramUI/ChannelAdminsController.swift @@ -451,7 +451,7 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon let arguments = ChannelAdminsControllerArguments(account: account, updateCurrentAdministrationType: { let _ = (presentationDataSignal |> take(1) |> deliverOnMainQueue).start(next: { presentationData in - let actionSheet = ActionSheetController() + let actionSheet = ActionSheetController(presentationTheme: presentationData.theme) let result = ValuePromise() actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: presentationData.strings.ChannelMembers_WhoCanAddMembers_AllMembers, color: .accent, action: { [weak actionSheet] in diff --git a/TelegramUI/ChannelInfoController.swift b/TelegramUI/ChannelInfoController.swift index a362914079..8aab72f4c1 100644 --- a/TelegramUI/ChannelInfoController.swift +++ b/TelegramUI/ChannelInfoController.swift @@ -605,7 +605,7 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr presentControllerImpl?(channelVisibilityController(account: account, peerId: peerId, mode: .generic), ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet)) }, changeNotificationMuteSettings: { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let controller = ActionSheetController() + let controller = ActionSheetController(presentationTheme: presentationData.theme) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() } @@ -671,7 +671,7 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr }, leaveChannel: { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let controller = ActionSheetController() + let controller = ActionSheetController(presentationTheme: presentationData.theme) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() } @@ -847,7 +847,8 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr } } if let (node, _) = result { - return GalleryTransitionArguments(transitionNode: node, transitionContainerNode: controller.displayNode, transitionBackgroundNode: controller.displayNode) + return GalleryTransitionArguments(transitionNode: node, addToTransitionSurface: { _ in + }) } } return nil diff --git a/TelegramUI/ChatAvatarNavigationNode.swift b/TelegramUI/ChatAvatarNavigationNode.swift index 2127e7d990..590722b8aa 100644 --- a/TelegramUI/ChatAvatarNavigationNode.swift +++ b/TelegramUI/ChatAvatarNavigationNode.swift @@ -2,8 +2,8 @@ import Foundation import AsyncDisplayKit import Display -private let normalFont = Font.medium(16.0) -private let smallFont = Font.medium(12.0) +private let normalFont = UIFont(name: "ArialRoundedMTBold", size: 16.0)! +private let smallFont = UIFont(name: "ArialRoundedMTBold", size: 12.0)! final class ChatAvatarNavigationNode: ASDisplayNode { let avatarNode: AvatarNode diff --git a/TelegramUI/ChatBotStartInputPanelNode.swift b/TelegramUI/ChatBotStartInputPanelNode.swift index 0f7ca1080b..186df6dfe3 100644 --- a/TelegramUI/ChatBotStartInputPanelNode.swift +++ b/TelegramUI/ChatBotStartInputPanelNode.swift @@ -86,7 +86,7 @@ final class ChatBotStartInputPanelNode: ChatInputPanelNode { self.interfaceInteraction?.sendBotStart(presentationInterfaceState.botStartPayload) } - override func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { + override func updateLayout(width: CGFloat, maxHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { if self.presentationInterfaceState != interfaceState { let previousState = self.presentationInterfaceState self.presentationInterfaceState = interfaceState diff --git a/TelegramUI/ChatChannelSubscriberInputPanelNode.swift b/TelegramUI/ChatChannelSubscriberInputPanelNode.swift index 80615b1ef0..d9b5c6d025 100644 --- a/TelegramUI/ChatChannelSubscriberInputPanelNode.swift +++ b/TelegramUI/ChatChannelSubscriberInputPanelNode.swift @@ -108,7 +108,7 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { } } - override func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { + override func updateLayout(width: CGFloat, maxHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { self.layoutData = width if self.presentationInterfaceState != interfaceState { diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index b281bdbe6b..fd4205f040 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -132,6 +132,7 @@ public class ChatController: TelegramController { let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] id in if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(id) { var galleryMedia: Media? + var otherMedia: Media? for media in message.media { if let file = media as? TelegramMediaFile { if !file.isAnimated { @@ -147,6 +148,8 @@ public class ChatController: TelegramController { } } else if let mapMedia = media as? TelegramMediaMap { galleryMedia = mapMedia + } else if let contactMedia = media as? TelegramMediaContact { + otherMedia = contactMedia } } @@ -210,12 +213,66 @@ public class ChatController: TelegramController { } } if let transitionNode = transitionNode { - return GalleryTransitionArguments(transitionNode: transitionNode, transitionContainerNode: strongSelf.chatDisplayNode, transitionBackgroundNode: strongSelf.chatDisplayNode.historyNode) + return GalleryTransitionArguments(transitionNode: transitionNode, addToTransitionSurface: { view in + if let strongSelf = self { + strongSelf.chatDisplayNode.historyNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.chatDisplayNode.historyNode.view) + } + }) } } return nil })) } + } else if let contact = otherMedia as? TelegramMediaContact { + let _ = (strongSelf.account.postbox.modify { modifier -> Bool? in + if let peerId = contact.peerId { + return modifier.isPeerContact(peerId: peerId) + } else { + return nil + } + } |> deliverOnMainQueue).start(next: { isContact in + if let strongSelf = self { + let controller = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) + let dismissAction: () -> Void = { [weak controller] in + controller?.dismissAnimated() + } + var items: [ActionSheetItem] = [] + + if let peerId = contact.peerId { + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_SendMessage, action: { + dismissAction() + if let strongSelf = self { + strongSelf.controllerInteraction?.openPeer(peerId, .chat(textInputState: nil), nil) + } + })) + if let isContact = isContact, !isContact { + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_AddContact, action: { + dismissAction() + if let strongSelf = self { + let _ = addContactPeerInteractively(account: strongSelf.account, peerId: peerId, phone: contact.phoneNumber).start() + } + })) + } + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.UserInfo_TelegramCall, action: { + dismissAction() + if let strongSelf = self { + strongSelf.controllerInteraction?.callPeer(peerId) + } + })) + } + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.UserInfo_PhoneCall, action: { + dismissAction() + if let strongSelf = self { + strongSelf.account.telegramApplicationContext.applicationBindings.openUrl("tel:\(formatPhoneNumber(contact.phoneNumber).replacingOccurrences(of: " ", with: ""))") + } + })) + controller.setItemGroups([ + ActionSheetItemGroup(items: items), + ActionSheetItemGroup(items: [ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, action: { dismissAction() })]) + ]) + strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + } + }) } } }, openSecretMessagePreview: { [weak self] messageId in @@ -523,7 +580,7 @@ public class ChatController: TelegramController { if let strongSelf = self { switch action { case let .url(url): - let actionSheet = ActionSheetController() + let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetTextItem(title: url), ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in @@ -550,7 +607,7 @@ public class ChatController: TelegramController { strongSelf.chatDisplayNode.dismissInput() strongSelf.present(actionSheet, in: .window(.root)) case let .peerMention(peerId, mention): - let actionSheet = ActionSheetController() + let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) var items: [ActionSheetItem] = [] if !mention.isEmpty { items.append(ActionSheetTextItem(title: mention)) @@ -575,7 +632,7 @@ public class ChatController: TelegramController { strongSelf.chatDisplayNode.dismissInput() strongSelf.present(actionSheet, in: .window(.root)) case let .mention(mention): - let actionSheet = ActionSheetController() + let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetTextItem(title: mention), ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in @@ -596,7 +653,7 @@ public class ChatController: TelegramController { strongSelf.chatDisplayNode.dismissInput() strongSelf.present(actionSheet, in: .window(.root)) case let .command(command): - let actionSheet = ActionSheetController() + let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetTextItem(title: command), ActionSheetButtonItem(title: strongSelf.presentationData.strings.ShareMenu_Send, color: .accent, action: { [weak actionSheet] in @@ -617,7 +674,7 @@ public class ChatController: TelegramController { strongSelf.chatDisplayNode.dismissInput() strongSelf.present(actionSheet, in: .window(.root)) case let .hashtag(hashtag): - let actionSheet = ActionSheetController() + let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetTextItem(title: hashtag), ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in @@ -1179,7 +1236,7 @@ public class ChatController: TelegramController { stationaryItemRange = (maxInsertedItem + 1, Int.max) } - mappedTransition = (ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: deleteItems, insertItems: insertItems, updateItems: transition.updateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex), updateSizeAndInsets) + mappedTransition = (ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: deleteItems, insertItems: insertItems, updateItems: transition.updateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, animateIn: false), updateSizeAndInsets) }) if let mappedTransition = mappedTransition { @@ -1343,7 +1400,7 @@ public class ChatController: TelegramController { if let messageIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty { strongSelf.messageContextDisposable.set((chatDeleteMessagesOptions(account: strongSelf.account, messageIds: messageIds) |> deliverOnMainQueue).start(next: { options in if let strongSelf = self, !options.isEmpty { - let actionSheet = ActionSheetController() + let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) var items: [ActionSheetItem] = [] var personalPeerName: String? var isChannel = false @@ -1391,9 +1448,6 @@ public class ChatController: TelegramController { } }, forwardSelectedMessages: { [weak self] in if let strongSelf = self { - //let controller = ShareRecipientsActionSheetController() - //strongSelf.present(controller, in: .window(.root)) - if let forwardMessageIdsSet = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds { let forwardMessageIds = Array(forwardMessageIdsSet).sorted() @@ -2098,7 +2152,7 @@ public class ChatController: TelegramController { case .cancelMessageSelection: self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) case .clearHistory: - let actionSheet = ActionSheetController() + let actionSheet = ActionSheetController(presentationTheme: self.presentationData.theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ClearAll, color: .destructive, action: { [weak self, weak actionSheet] in actionSheet?.dismissAnimated() @@ -2169,7 +2223,7 @@ public class ChatController: TelegramController { }) } }) - let message: EnqueueMessage = .message(text: "", attributes: [], media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: venue), replyToMessageId: replyMessageId) + let message: EnqueueMessage = .message(text: "", attributes: [], media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: venue, liveBroadcastingTimeout: nil), replyToMessageId: replyMessageId) strongSelf.sendMessages([message]) } }), in: .window(.root)) @@ -2511,7 +2565,7 @@ public class ChatController: TelegramController { } else { title = self.presentationData.strings.Conversation_ReportSpamAndLeave } - let actionSheet = ActionSheetController() + let actionSheet = ActionSheetController(presentationTheme: self.presentationData.theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: title, color: .destructive, action: { [weak self, weak actionSheet] in actionSheet?.dismissAnimated() diff --git a/TelegramUI/ChatControllerNode.swift b/TelegramUI/ChatControllerNode.swift index 9ae2aa1439..451fcee465 100644 --- a/TelegramUI/ChatControllerNode.swift +++ b/TelegramUI/ChatControllerNode.swift @@ -292,32 +292,6 @@ class ChatControllerNode: ASDisplayNode { self.titleAccessoryPanelNode = nil } - var dismissedInputPanelNode: ASDisplayNode? - var dismissedAccessoryPanelNode: ASDisplayNode? - var dismissedInputContextPanelNode: ChatInputContextPanelNode? - - var inputPanelSize: CGSize? - var immediatelyLayoutInputPanelAndAnimateAppearance = false - if let inputPanelNode = inputPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, account: self.account, currentPanel: self.inputPanelNode, textInputPanelNode: self.textInputPanelNode, interfaceInteraction: self.interfaceInteraction) { - if inputPanelNode !== self.inputPanelNode { - if let inputTextPanelNode = self.inputPanelNode as? ChatTextInputPanelNode { - inputTextPanelNode.ensureUnfocused() - } - dismissedInputPanelNode = self.inputPanelNode - immediatelyLayoutInputPanelAndAnimateAppearance = true - let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, transition: .immediate, interfaceState: self.chatPresentationInterfaceState) - inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) - self.inputPanelNode = inputPanelNode - self.insertSubnode(inputPanelNode, aboveSubnode: self.navigateButtons) - } else { - let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, transition: transition, interfaceState: self.chatPresentationInterfaceState) - inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) - } - } else { - dismissedInputPanelNode = self.inputPanelNode - self.inputPanelNode = nil - } - var dismissedInputNode: ChatInputNode? var immediatelyLayoutInputNodeAndAnimateAppearance = false var inputNodeHeight: CGFloat? @@ -340,10 +314,6 @@ class ChatControllerNode: ASDisplayNode { self.inputNode = nil } - if let inputMediaNode = self.inputMediaNode, inputMediaNode != self.inputNode { - let _ = inputMediaNode.updateLayout(width: layout.size.width, transition: .immediate, interfaceState: self.chatPresentationInterfaceState) - } - var insets: UIEdgeInsets if let inputNodeHeight = inputNodeHeight { insets = layout.insets(options: []) @@ -353,6 +323,36 @@ class ChatControllerNode: ASDisplayNode { } insets.top += navigationBarHeight + var dismissedInputPanelNode: ASDisplayNode? + var dismissedAccessoryPanelNode: ASDisplayNode? + var dismissedInputContextPanelNode: ChatInputContextPanelNode? + + var inputPanelSize: CGSize? + var immediatelyLayoutInputPanelAndAnimateAppearance = false + if let inputPanelNode = inputPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, account: self.account, currentPanel: self.inputPanelNode, textInputPanelNode: self.textInputPanelNode, interfaceInteraction: self.interfaceInteraction) { + if inputPanelNode !== self.inputPanelNode { + if let inputTextPanelNode = self.inputPanelNode as? ChatTextInputPanelNode { + inputTextPanelNode.ensureUnfocused() + } + dismissedInputPanelNode = self.inputPanelNode + immediatelyLayoutInputPanelAndAnimateAppearance = true + let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, maxHeight: layout.size.height - insets.top - insets.bottom, transition: .immediate, interfaceState: self.chatPresentationInterfaceState) + inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) + self.inputPanelNode = inputPanelNode + self.insertSubnode(inputPanelNode, aboveSubnode: self.navigateButtons) + } else { + let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, maxHeight: layout.size.height - insets.top - insets.bottom, transition: transition, interfaceState: self.chatPresentationInterfaceState) + inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) + } + } else { + dismissedInputPanelNode = self.inputPanelNode + self.inputPanelNode = nil + } + + if let inputMediaNode = self.inputMediaNode, inputMediaNode != self.inputNode { + let _ = inputMediaNode.updateLayout(width: layout.size.width, transition: .immediate, interfaceState: self.chatPresentationInterfaceState) + } + transition.updateFrame(node: self.titleAccessoryPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: 56.0))) var titleAccessoryPanelFrame: CGRect? diff --git a/TelegramUI/ChatDateSelectionSheet.swift b/TelegramUI/ChatDateSelectionSheet.swift index 64db5c7c19..e5f93999c5 100644 --- a/TelegramUI/ChatDateSelectionSheet.swift +++ b/TelegramUI/ChatDateSelectionSheet.swift @@ -6,7 +6,6 @@ import SwiftSignalKit import Photos final class ChatDateSelectionSheet: ActionSheetController { - private let theme: PresentationTheme private let strings: PresentationStrings private let _ready = Promise() @@ -15,17 +14,16 @@ final class ChatDateSelectionSheet: ActionSheetController { } init(theme: PresentationTheme, strings: PresentationStrings, completion: @escaping (Int32) -> Void) { - self.theme = theme self.strings = strings - super.init() + super.init(theme: ActionSheetControllerTheme(presentationTheme: theme)) self._ready.set(.single(true)) var updatedValue: Int32? self.setItemGroups([ ActionSheetItemGroup(items: [ - ChatDateSelectorItem(theme: theme, strings: strings, valueChanged: { value in + ChatDateSelectorItem(strings: strings, valueChanged: { value in updatedValue = value }), ActionSheetButtonItem(title: strings.Common_Search, action: { [weak self] in @@ -49,19 +47,17 @@ final class ChatDateSelectionSheet: ActionSheetController { } private final class ChatDateSelectorItem: ActionSheetItem { - let theme: PresentationTheme let strings: PresentationStrings let valueChanged: (Int32) -> Void - init(theme: PresentationTheme, strings: PresentationStrings, valueChanged: @escaping (Int32) -> Void) { - self.theme = theme + init(strings: PresentationStrings, valueChanged: @escaping (Int32) -> Void) { self.strings = strings self.valueChanged = valueChanged } - func node() -> ActionSheetItemNode { - return ChatDateSelectorItemNode(theme: self.theme, strings: self.strings, valueChanged: self.valueChanged) + func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { + return ChatDateSelectorItemNode(theme: theme, strings: self.strings, valueChanged: self.valueChanged) } func updateNode(_ node: ActionSheetItemNode) { @@ -69,7 +65,7 @@ private final class ChatDateSelectorItem: ActionSheetItem { } private final class ChatDateSelectorItemNode: ActionSheetItemNode { - private let theme: PresentationTheme + private let theme: ActionSheetControllerTheme private let strings: PresentationStrings private let pickerView: UIDatePicker @@ -80,7 +76,7 @@ private final class ChatDateSelectorItemNode: ActionSheetItemNode { return Int32(self.pickerView.date.timeIntervalSince1970) } - init(theme: PresentationTheme, strings: PresentationStrings, valueChanged: @escaping (Int32) -> Void) { + init(theme: ActionSheetControllerTheme, strings: PresentationStrings, valueChanged: @escaping (Int32) -> Void) { self.theme = theme self.strings = strings self.valueChanged = valueChanged @@ -88,11 +84,12 @@ private final class ChatDateSelectorItemNode: ActionSheetItemNode { self.pickerView = UIDatePicker() self.pickerView.datePickerMode = .date self.pickerView.locale = Locale(identifier: strings.languageCode) + self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor") self.pickerView.maximumDate = Date(timeIntervalSinceNow: 2.0) self.pickerView.minimumDate = Date(timeIntervalSinceNow: 1376438400.0) - super.init() + super.init(theme: theme) self.view.addSubview(self.pickerView) self.pickerView.addTarget(self, action: #selector(self.pickerChanged), for: .valueChanged) diff --git a/TelegramUI/ChatDocumentGalleryItem.swift b/TelegramUI/ChatDocumentGalleryItem.swift index 71a5a239b3..e4a06855ab 100644 --- a/TelegramUI/ChatDocumentGalleryItem.swift +++ b/TelegramUI/ChatDocumentGalleryItem.swift @@ -158,7 +158,7 @@ class ChatDocumentGalleryItemNode: GalleryItemNode { return self._title.get() } - override func animateIn(from node: ASDisplayNode) { + override func animateIn(from node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void) { var transformedFrame = node.view.convert(node.view.bounds, to: self.webView) let transformedSuperFrame = node.view.convert(node.view.bounds, to: self.webView.superview) @@ -170,7 +170,7 @@ class ChatDocumentGalleryItemNode: GalleryItemNode { self.webView.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: self.webView.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25) } - override func animateOut(to node: ASDisplayNode, completion: @escaping () -> Void) { + override func animateOut(to node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) { var transformedFrame = node.view.convert(node.view.bounds, to: self.webView) let transformedSuperFrame = node.view.convert(node.view.bounds, to: self.webView.superview) let transformedSelfFrame = node.view.convert(node.view.bounds, to: self.view) diff --git a/TelegramUI/ChatHistoryEntriesForView.swift b/TelegramUI/ChatHistoryEntriesForView.swift index 8c27e75b09..b06f2ebe66 100644 --- a/TelegramUI/ChatHistoryEntriesForView.swift +++ b/TelegramUI/ChatHistoryEntriesForView.swift @@ -8,7 +8,9 @@ func chatHistoryEntriesForView(_ view: MessageHistoryView, includeUnreadEntry: B for entry in view.entries { switch entry { case let .HoleEntry(hole, _): - entries.append(.HoleEntry(hole, theme, strings)) + if view.tagMask == nil { + entries.append(.HoleEntry(hole, theme, strings)) + } case let .MessageEntry(message, read, _, monthLocation): var isClearHistory = false if !message.media.isEmpty { diff --git a/TelegramUI/ChatHistoryListNode.swift b/TelegramUI/ChatHistoryListNode.swift index 0f49219005..322273600f 100644 --- a/TelegramUI/ChatHistoryListNode.swift +++ b/TelegramUI/ChatHistoryListNode.swift @@ -80,6 +80,7 @@ struct ChatHistoryViewTransition { let cachedDataMessages: [MessageId: Message]? let readStateData: ChatHistoryCombinedInitialReadStateData? let scrolledToIndex: MessageIndex? + let animateIn: Bool } struct ChatHistoryListViewTransition { @@ -96,6 +97,7 @@ struct ChatHistoryListViewTransition { let cachedDataMessages: [MessageId: Message]? let readStateData: ChatHistoryCombinedInitialReadStateData? let scrolledToIndex: MessageIndex? + let animateIn: Bool } private func maxMessageIndexForEntries(_ entries: [ChatHistoryEntry], indexRange: (Int, Int)) -> (incoming: MessageIndex?, overall: MessageIndex?) { @@ -184,7 +186,7 @@ private func mappedUpdateEntries(account: Account, peerId: PeerId, controllerInt } private func mappedChatHistoryViewListTransition(account: Account, peerId: PeerId, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, transition: ChatHistoryViewTransition) -> ChatHistoryListViewTransition { - return ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex) + return ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, animateIn: transition.animateIn) } private final class ChatHistoryTransactionOpaqueState { @@ -719,6 +721,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { strongSelf._buttonKeyboardMessage.set(.single(transition.keyboardButtonsMessage)) } + if transition.animateIn { + strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + completion() } } diff --git a/TelegramUI/ChatHistoryViewForLocation.swift b/TelegramUI/ChatHistoryViewForLocation.swift index 2189ff83a6..0852abff87 100644 --- a/TelegramUI/ChatHistoryViewForLocation.swift +++ b/TelegramUI/ChatHistoryViewForLocation.swift @@ -90,7 +90,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocation, account: Accoun } } } - } else if let historyScrollState = (initialData?.chatInterfaceState as? ChatInterfaceState)?.historyScrollState { + } else if let historyScrollState = (initialData?.chatInterfaceState as? ChatInterfaceState)?.historyScrollState, tagMask == nil { scrollPosition = .positionRestoration(index: historyScrollState.messageIndex, relativeOffset: CGFloat(historyScrollState.relativeOffset)) } else { var messageCount = 0 diff --git a/TelegramUI/ChatImageGalleryItem.swift b/TelegramUI/ChatImageGalleryItem.swift index a3eddc7cc8..24c85b9890 100644 --- a/TelegramUI/ChatImageGalleryItem.swift +++ b/TelegramUI/ChatImageGalleryItem.swift @@ -135,25 +135,49 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { self.accountAndMedia = (account, file) } - override func animateIn(from node: ASDisplayNode) { + override func animateIn(from node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void) { var transformedFrame = node.view.convert(node.view.bounds, to: self.imageNode.view) let transformedSuperFrame = node.view.convert(node.view.bounds, to: self.imageNode.view.superview) let transformedSelfFrame = node.view.convert(node.view.bounds, to: self.view) let transformedCopyViewFinalFrame = self.imageNode.view.convert(self.imageNode.view.bounds, to: self.view) + let surfaceCopyView = node.view.snapshotContentTree()! let copyView = node.view.snapshotContentTree()! + addToTransitionSurface(surfaceCopyView) + + var transformedSurfaceFrame: CGRect? + var transformedSurfaceFinalFrame: CGRect? + if let contentSurface = surfaceCopyView.superview { + transformedSurfaceFrame = node.view.convert(node.view.bounds, to: contentSurface) + transformedSurfaceFinalFrame = self.imageNode.view.convert(self.imageNode.view.bounds, to: contentSurface) + } + + if let transformedSurfaceFrame = transformedSurfaceFrame { + surfaceCopyView.frame = transformedSurfaceFrame + } + self.view.insertSubview(copyView, belowSubview: self.scrollNode.view) copyView.frame = transformedSelfFrame - copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak copyView] _ in + copyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, removeOnCompletion: false) + + surfaceCopyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) + + copyView.layer.animatePosition(from: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak copyView] _ in copyView?.removeFromSuperview() }) - - copyView.layer.animatePosition(from: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) let scale = CGSize(width: transformedCopyViewFinalFrame.size.width / transformedSelfFrame.size.width, height: transformedCopyViewFinalFrame.size.height / transformedSelfFrame.size.height) copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false) + if let transformedSurfaceFrame = transformedSurfaceFrame, let transformedSurfaceFinalFrame = transformedSurfaceFinalFrame { + surfaceCopyView.layer.animatePosition(from: CGPoint(x: transformedSurfaceFrame.midX, y: transformedSurfaceFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak surfaceCopyView] _ in + surfaceCopyView?.removeFromSuperview() + }) + let scale = CGSize(width: transformedSurfaceFinalFrame.size.width / transformedSurfaceFrame.size.width, height: transformedSurfaceFinalFrame.size.height / transformedSurfaceFrame.size.height) + surfaceCopyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false) + } + self.imageNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: self.imageNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) self.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) @@ -161,7 +185,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { self.imageNode.layer.animateBounds(from: transformedFrame, to: self.imageNode.layer.bounds, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) } - override func animateOut(to node: ASDisplayNode, completion: @escaping () -> Void) { + override func animateOut(to node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) { var transformedFrame = node.view.convert(node.view.bounds, to: self.imageNode.view) let transformedSuperFrame = node.view.convert(node.view.bounds, to: self.imageNode.view.superview) let transformedSelfFrame = node.view.convert(node.view.bounds, to: self.view) @@ -172,18 +196,30 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { var copyCompleted = false let copyView = node.view.snapshotContentTree()! + let surfaceCopyView = node.view.snapshotContentTree()! + + addToTransitionSurface(surfaceCopyView) + + var transformedSurfaceFrame: CGRect? + var transformedSurfaceCopyViewInitialFrame: CGRect? + if let contentSurface = surfaceCopyView.superview { + transformedSurfaceFrame = node.view.convert(node.view.bounds, to: contentSurface) + transformedSurfaceCopyViewInitialFrame = self.imageNode.view.convert(self.imageNode.view.bounds, to: contentSurface) + } self.view.insertSubview(copyView, belowSubview: self.scrollNode.view) copyView.frame = transformedSelfFrame - let intermediateCompletion = { [weak copyView] in + let intermediateCompletion = { [weak copyView, weak surfaceCopyView] in if positionCompleted && boundsCompleted && copyCompleted { copyView?.removeFromSuperview() + surfaceCopyView?.removeFromSuperview() completion() } } - copyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, removeOnCompletion: false) + copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false) + surfaceCopyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, 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) @@ -192,12 +228,18 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { intermediateCompletion() }) + if let transformedSurfaceFrame = transformedSurfaceFrame, let transformedCopyViewInitialFrame = transformedSurfaceCopyViewInitialFrame { + surfaceCopyView.layer.animatePosition(from: CGPoint(x: transformedCopyViewInitialFrame.midX, y: transformedCopyViewInitialFrame.midY), to: CGPoint(x: transformedSurfaceFrame.midX, y: transformedSurfaceFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + let scale = CGSize(width: transformedCopyViewInitialFrame.size.width / transformedSurfaceFrame.size.width, height: transformedCopyViewInitialFrame.size.height / transformedSurfaceFrame.size.height) + surfaceCopyView.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) + } + self.imageNode.layer.animatePosition(from: self.imageNode.layer.position, to: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in positionCompleted = true intermediateCompletion() }) - self.imageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) + self.imageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) transformedFrame.origin = CGPoint() self.imageNode.layer.animateBounds(from: self.imageNode.layer.bounds, to: transformedFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in diff --git a/TelegramUI/ChatInputPanelNode.swift b/TelegramUI/ChatInputPanelNode.swift index 0625bcfc64..2ebd6c0361 100644 --- a/TelegramUI/ChatInputPanelNode.swift +++ b/TelegramUI/ChatInputPanelNode.swift @@ -8,7 +8,7 @@ class ChatInputPanelNode: ASDisplayNode { var account: Account? var interfaceInteraction: ChatPanelInterfaceInteraction? - func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { + func updateLayout(width: CGFloat, maxHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { return 0.0 } } diff --git a/TelegramUI/ChatItemGalleryFooterContentNode.swift b/TelegramUI/ChatItemGalleryFooterContentNode.swift index 7dc12c9620..9074da659b 100644 --- a/TelegramUI/ChatItemGalleryFooterContentNode.swift +++ b/TelegramUI/ChatItemGalleryFooterContentNode.swift @@ -239,7 +239,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { if let currentMessage = self.currentMessage { self.messageContextDisposable.set((chatDeleteMessagesOptions(account: self.account, messageIds: [currentMessage.id]) |> deliverOnMainQueue).start(next: { [weak self] options in if let strongSelf = self, let controllerInteration = strongSelf.controllerInteraction, !options.isEmpty { - let actionSheet = ActionSheetController() + let actionSheet = ActionSheetController(presentationTheme: strongSelf.theme) var items: [ActionSheetItem] = [] var personalPeerName: String? var isChannel = false @@ -292,164 +292,6 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode { var shareAction: (([PeerId]) -> Void)? let shareController = ShareController(account: self.account, subject: .message(currentMessage), saveToCameraRoll: true) controllerInteraction.presentController(shareController, nil) - /*saveToCameraRoll = { [weak shareController, weak self] in - shareController?.dismiss() - - if let strongSelf = self, let currentMessage = strongSelf.currentMessage { - var resource: (MediaResource, Bool)? - for media in currentMessage.media { - if let image = media as? TelegramMediaImage { - if let representation = largestImageRepresentation(image.representations) { - resource = (representation.resource, true) - } - break - } else if let file = media as? TelegramMediaFile { - if file.isVideo { - resource = (file.resource, false) - } else if file.mimeType.hasPrefix("image/") { - resource = (file.resource, true) - } - break - } - } - - if let (resource, isImage) = resource { - strongSelf.messageContextDisposable.set((strongSelf.account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: true)) |> take(1) |> deliverOnMainQueue).start(next: { data in - if data.complete { - let tempVideoPath = NSTemporaryDirectory() + "\(arc4random64()).mp4" - PHPhotoLibrary.shared().performChanges({ - if isImage { - if let data = try? Data(contentsOf: URL(fileURLWithPath: data.path)), let image = UIImage(data: data) { - PHAssetChangeRequest.creationRequestForAsset(from: image) - //PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: URL(fileURLWithPath: data.path)) - } - } else { - if let _ = try? FileManager.default.copyItem(atPath: data.path, toPath: tempVideoPath) { - PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: tempVideoPath)) - } - } - }, completionHandler: { _, error in - if let error = error { - print("\(error)") - } - let _ = try? FileManager.default.removeItem(atPath: tempVideoPath) - }) - } - })) - } - } - } - return - - let actionSheet = ActionSheetController() - var items: [ActionSheetItem] = [] - - var canSaveToCameraRoll = false - for media in currentMessage.media { - if let _ = media as? TelegramMediaImage { - canSaveToCameraRoll = true - break - } else if let file = media as? TelegramMediaFile { - if file.isVideo { - canSaveToCameraRoll = true - } else if file.mimeType.hasPrefix("image/") { - canSaveToCameraRoll = true - } - break - } - } - - if canSaveToCameraRoll { - items.append(ActionSheetButtonItem(title: "Save to Camera Roll", color: .accent, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self, let currentMessage = strongSelf.currentMessage { - var resource: (MediaResource, Bool)? - for media in currentMessage.media { - if let image = media as? TelegramMediaImage { - if let representation = largestImageRepresentation(image.representations) { - resource = (representation.resource, true) - } - break - } else if let file = media as? TelegramMediaFile { - if file.isVideo { - resource = (file.resource, false) - } else if file.mimeType.hasPrefix("image/") { - resource = (file.resource, true) - } - break - } - } - - if let (resource, isImage) = resource { - strongSelf.messageContextDisposable.set((strongSelf.account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: true)) |> take(1) |> deliverOnMainQueue).start(next: { data in - if data.complete { - let tempVideoPath = NSTemporaryDirectory() + "\(arc4random64()).mp4" - PHPhotoLibrary.shared().performChanges({ - if isImage { - if let data = try? Data(contentsOf: URL(fileURLWithPath: data.path)), let image = UIImage(data: data) { - PHAssetChangeRequest.creationRequestForAsset(from: image) - //PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: URL(fileURLWithPath: data.path)) - } - } else { - if let _ = try? FileManager.default.copyItem(atPath: data.path, toPath: tempVideoPath) { - PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: tempVideoPath)) - } - } - }, completionHandler: { _, error in - if let error = error { - print("\(error)") - } - let _ = try? FileManager.default.removeItem(atPath: tempVideoPath) - }) - } - })) - } - } - })) - } - - items.append(ActionSheetButtonItem(title: "Forward", color: .accent, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self, let currentMessage = strongSelf.currentMessage { - let forwardMessageIds = [currentMessage.id] - - let controller = PeerSelectionController(account: strongSelf.account) - controller.peerSelected = { [weak controller] peerId in - if let strongSelf = self, let strongController = controller { - let _ = (strongSelf.account.postbox.modify({ modifier -> Void in - modifier.updatePeerChatInterfaceState(peerId, update: { currentState in - if let currentState = currentState as? ChatInterfaceState { - return currentState.withUpdatedForwardMessageIds(forwardMessageIds) - } else { - return ChatInterfaceState().withUpdatedForwardMessageIds(forwardMessageIds) - } - }) - }) |> deliverOnMainQueue).start(completed: { - if let strongSelf = self { - let ready = ValuePromise() - - strongSelf.messageContextDisposable.set((ready.get() |> take(1) |> deliverOnMainQueue).start(next: { _ in - if let strongController = controller { - strongController.dismiss() - self?.controllerInteraction?.dismissController() - } - })) - - strongSelf.controllerInteraction?.replaceRootController(ChatController(account: strongSelf.account, peerId: peerId), ready) - } - }) - } - } - strongSelf.controllerInteraction?.presentController(controller, nil) - } - })) - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: "Cancel", color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - controllerInteraction.presentController(actionSheet, nil) - */ } } diff --git a/TelegramUI/ChatListController.swift b/TelegramUI/ChatListController.swift index 1608a1e70b..9eeb4bd9fa 100644 --- a/TelegramUI/ChatListController.swift +++ b/TelegramUI/ChatListController.swift @@ -184,7 +184,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD self.chatListDisplayNode.chatListNode.deletePeerChat = { [weak self] peerId in if let strongSelf = self { - let actionSheet = ActionSheetController() + let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Delete, color: .destructive, action: { [weak actionSheet] in actionSheet?.dismissAnimated() diff --git a/TelegramUI/ChatListNode.swift b/TelegramUI/ChatListNode.swift index b09d352fcf..208309b2d5 100644 --- a/TelegramUI/ChatListNode.swift +++ b/TelegramUI/ChatListNode.swift @@ -485,7 +485,7 @@ final class ChatListNode: ListView { } return EmptyDisposable - } |> runOn(Queue.mainQueue()) + } |> runOn(Queue.mainQueue()) } private func dequeueTransition() { @@ -523,7 +523,12 @@ final class ChatListNode: ListView { } } - self.transaction(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, updateOpaqueState: ChatListOpaqueTransactionState(chatListView: transition.chatListView), completion: completion) + var options = transition.options + if options.contains(.AnimateCrossfade) && !self.isDeceleratingAfterTracking { + options.insert(.PreferSynchronousDrawing) + } + + self.transaction(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, updateOpaqueState: ChatListOpaqueTransactionState(chatListView: transition.chatListView), completion: completion) } } diff --git a/TelegramUI/ChatListRecentPeersListItem.swift b/TelegramUI/ChatListRecentPeersListItem.swift index 40178ca71c..13c0128b4a 100644 --- a/TelegramUI/ChatListRecentPeersListItem.swift +++ b/TelegramUI/ChatListRecentPeersListItem.swift @@ -111,7 +111,7 @@ class ChatListRecentPeersListItemNode: ListViewItemNode { peersNode = currentPeersNode peersNode.updateThemeAndStrings(theme: item.theme, strings: item.strings) } else { - peersNode = ChatListSearchRecentPeersNode(account: item.account, theme: item.theme, strings: item.strings, peerSelected: { peer in + peersNode = ChatListSearchRecentPeersNode(account: item.account, theme: item.theme, mode: .list, strings: item.strings, peerSelected: { peer in self?.item?.peerSelected(peer) }, isPeerSelected: { _ in return false diff --git a/TelegramUI/ChatListSearchItem.swift b/TelegramUI/ChatListSearchItem.swift index 8bf0f1b17d..9508896e44 100644 --- a/TelegramUI/ChatListSearchItem.swift +++ b/TelegramUI/ChatListSearchItem.swift @@ -100,7 +100,9 @@ class ChatListSearchItemNode: ListViewItemNode { let placeholder = self.placeholder return { item, width, nextIsPinned in - let searchBarApply = searchBarNodeLayout(NSAttributedString(string: placeholder ?? "", font: searchBarFont, textColor: UIColor(rgb: 0x8e8e93)), CGSize(width: width - 16.0, height: CGFloat.greatestFiniteMagnitude), UIColor(rgb: 0x8e8e93), nextIsPinned ? item.theme.chatList.pinnedSearchBarColor : item.theme.chatList.regularSearchBarColor, nextIsPinned ? item.theme.chatList.itemBackgroundColor : item.theme.chatList.pinnedItemBackgroundColor) + let backgroundColor = nextIsPinned ? item.theme.chatList.pinnedItemBackgroundColor : item.theme.chatList.itemBackgroundColor + + let searchBarApply = searchBarNodeLayout(NSAttributedString(string: placeholder ?? "", font: searchBarFont, textColor: UIColor(rgb: 0x8e8e93)), CGSize(width: width - 16.0, height: CGFloat.greatestFiniteMagnitude), UIColor(rgb: 0x8e8e93), nextIsPinned ? item.theme.chatList.pinnedSearchBarColor : item.theme.chatList.regularSearchBarColor, backgroundColor) let layout = ListViewItemNodeLayout(contentSize: CGSize(width: width, height: 44.0 + 4.0), insets: UIEdgeInsets()) @@ -118,7 +120,7 @@ class ChatListSearchItemNode: ListViewItemNode { strongSelf.searchBarNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: width - 16.0, height: 28.0)) - transition.updateBackgroundColor(node: strongSelf, color: nextIsPinned ? item.theme.chatList.pinnedItemBackgroundColor : item.theme.chatList.itemBackgroundColor) + transition.updateBackgroundColor(node: strongSelf, color: backgroundColor) } }) } diff --git a/TelegramUI/ChatListSearchRecentPeersNode.swift b/TelegramUI/ChatListSearchRecentPeersNode.swift index b0510aaf51..24d1f253a8 100644 --- a/TelegramUI/ChatListSearchRecentPeersNode.swift +++ b/TelegramUI/ChatListSearchRecentPeersNode.swift @@ -26,6 +26,7 @@ private func calculateItemCustomWidth(width: CGFloat) -> CGFloat { final class ChatListSearchRecentPeersNode: ASDisplayNode { private var theme: PresentationTheme private var strings: PresentationStrings + private let mode: HorizontalPeerItemMode private let sectionHeaderNode: ListSectionHeaderNode private let listView: ListView private let share: Bool @@ -38,9 +39,10 @@ final class ChatListSearchRecentPeersNode: ASDisplayNode { private var items: [ListViewItem] = [] private var itemCustomWidth: CGFloat? - init(account: Account, theme: PresentationTheme, strings: PresentationStrings, peerSelected: @escaping (Peer) -> Void, isPeerSelected: @escaping (PeerId) -> Bool, share: Bool = false) { + init(account: Account, theme: PresentationTheme, mode: HorizontalPeerItemMode, strings: PresentationStrings, peerSelected: @escaping (Peer) -> Void, isPeerSelected: @escaping (PeerId) -> Bool, share: Bool = false) { self.theme = theme self.strings = strings + self.mode = mode self.share = share self.peerSelected = peerSelected self.isPeerSelected = isPeerSelected @@ -60,7 +62,7 @@ final class ChatListSearchRecentPeersNode: ASDisplayNode { if let strongSelf = self { var items: [ListViewItem] = [] for peer in peers { - items.append(HorizontalPeerItem(theme: strongSelf.theme, strings: strongSelf.strings, account: account, peer: peer, action: peerSelected, isPeerSelected: isPeerSelected, customWidth: strongSelf.itemCustomWidth)) + items.append(HorizontalPeerItem(theme: strongSelf.theme, strings: strongSelf.strings, mode: mode, account: account, peer: peer, action: peerSelected, isPeerSelected: isPeerSelected, customWidth: strongSelf.itemCustomWidth)) } strongSelf.items = items strongSelf.listView.transaction(deleteIndices: [], insertIndicesAndItems: (0 ..< items.count).map({ ListViewInsertItem(index: $0, previousIndex: nil, item: items[$0], directionHint: .Down) }), updateIndicesAndItems: [], options: [], updateOpaqueState: nil) @@ -110,7 +112,7 @@ final class ChatListSearchRecentPeersNode: ASDisplayNode { for i in 0 ..< self.items.count { if let item = self.items[i] as? HorizontalPeerItem { - self.items[i] = HorizontalPeerItem(theme: self.theme, strings: self.strings, account: item.account, peer: item.peer, action: self.peerSelected, isPeerSelected: self.isPeerSelected, customWidth: itemCustomWidth) + self.items[i] = HorizontalPeerItem(theme: self.theme, strings: self.strings, mode: self.mode, account: item.account, peer: item.peer, action: self.peerSelected, isPeerSelected: self.isPeerSelected, customWidth: itemCustomWidth) updateItems.append(ListViewUpdateItem(index: i, previousIndex: i, item: self.items[i], directionHint: nil)) } } diff --git a/TelegramUI/ChatListViewTransition.swift b/TelegramUI/ChatListViewTransition.swift index 97d1a243dd..09df736fdf 100644 --- a/TelegramUI/ChatListViewTransition.swift +++ b/TelegramUI/ChatListViewTransition.swift @@ -72,15 +72,32 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV let _ = options.insert(.LowLatency) let _ = options.insert(.Synchronous) case .interactiveChanges: - let _ = options.insert(.AnimateAlpha) - let _ = options.insert(.AnimateInsertion) - for (index, _, _) in indicesAndItems.sorted(by: { $0.0 > $1.0 }) { let adjustedIndex = updatedCount - 1 - index if adjustedIndex == maxAnimatedInsertionIndex + 1 { maxAnimatedInsertionIndex += 1 } } + + var minTimestamp: Int32 = Int32.max + var maxTimestamp: Int32 = 0 + for (_, item, _) in indicesAndItems { + let timestamp = item.index.messageIndex.timestamp + + if timestamp < minTimestamp { + minTimestamp = timestamp + } + if timestamp > maxTimestamp { + maxTimestamp = timestamp + } + } + + if abs(maxTimestamp - minTimestamp) > 60 * 60 { + let _ = options.insert(.AnimateCrossfade) + } else { + let _ = options.insert(.AnimateAlpha) + let _ = options.insert(.AnimateInsertion) + } case .reload: break case let .holeChanges(filledHoleDirections, removeHoleDirections): diff --git a/TelegramUI/ChatMediaActionSheetController.swift b/TelegramUI/ChatMediaActionSheetController.swift deleted file mode 100644 index 2642db656a..0000000000 --- a/TelegramUI/ChatMediaActionSheetController.swift +++ /dev/null @@ -1,63 +0,0 @@ -import Foundation -import Display -import AsyncDisplayKit -import UIKit -import SwiftSignalKit -import Photos - -final class ChatMediaActionSheetController: ActionSheetController { - private let _ready = Promise() - override var ready: Promise { - return self._ready - } - private var didSetReady = false - - var photo: (PHAsset) -> Void = { _ in } - var files: () -> Void = { } - var location: () -> Void = { } - var contacts: () -> Void = { } - - override init() { - super.init() - - self._ready.set(.single(true)) - - self.setItemGroups([ - ActionSheetItemGroup(items: [ - ChatMediaActionSheetRollItem(assetSelected: { [weak self] asset in - if let strongSelf = self { - self?.dismissAnimated() - strongSelf.photo(asset) - } - }), - ActionSheetButtonItem(title: "File", action: { [weak self] in - if let strongSelf = self { - self?.dismissAnimated() - strongSelf.files() - } - }), - ActionSheetButtonItem(title: "Location", action: { [weak self] in - self?.dismissAnimated() - if let location = self?.location { - location() - } - }), - ActionSheetButtonItem(title: "Contact", action: { [weak self] in - self?.dismissAnimated() - if let contacts = self?.contacts { - contacts() - } - }) - ]), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: "Cancel", action: { [weak self] in - self?.dismissAnimated() - }), - ]) - ]) - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/TelegramUI/ChatMediaActionSheetRollItem.swift b/TelegramUI/ChatMediaActionSheetRollItem.swift deleted file mode 100644 index b1a1fb91e1..0000000000 --- a/TelegramUI/ChatMediaActionSheetRollItem.swift +++ /dev/null @@ -1,122 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import Photos -import SwiftSignalKit - -final class ChatMediaActionSheetRollItem: ActionSheetItem { - private let assetSelected: (PHAsset) -> Void - - init(assetSelected: @escaping (PHAsset) -> Void) { - self.assetSelected = assetSelected - } - - func node() -> ActionSheetItemNode { - return ChatMediaActionSheetRollItemNode(assetSelected: self.assetSelected) - } - - func updateNode(_ node: ActionSheetItemNode) { - } -} - -private final class ChatMediaActionSheetRollItemNode: ActionSheetItemNode, PHPhotoLibraryChangeObserver { - private let listView: ListView - private let label: UILabel - private let button: HighlightTrackingButton - - private var assetCollection: PHAssetCollection? - private var fetchResult: PHFetchResult? - - private let assetSelected: (PHAsset) -> Void - - init(assetSelected: @escaping (PHAsset) -> Void) { - self.assetSelected = assetSelected - - self.listView = ListView() - self.listView.transform = CATransform3DMakeRotation(-CGFloat(M_PI / 2.0), 0.0, 0.0, 1.0) - - self.label = UILabel() - self.label.backgroundColor = nil - self.label.isOpaque = false - self.label.textColor = UIColor(rgb: 0x007ee5) - self.label.text = "Photo or Video" - self.label.font = Font.regular(20.0) - self.label.sizeToFit() - - self.button = HighlightTrackingButton() - - super.init() - - self.button.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor - } else { - UIView.animate(withDuration: 0.3, animations: { - strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor - }) - } - } - } - self.view.addSubview(self.button) - - self.view.addSubview(self.label) - self.addSubnode(self.listView) - - PHPhotoLibrary.requestAuthorization({ _ in - - }) - - let allPhotosOptions = PHFetchOptions() - allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] - - self.fetchResult = PHAsset.fetchAssets(with: .image, options: allPhotosOptions) - - var items: [ListViewItem] = [] - if let fetchResult = self.fetchResult { - for i in 0 ..< fetchResult.count { - let asset = fetchResult.object(at: i) - items.append(ActionSheetRollImageItem(asset: asset, selected: { [weak self] in - if let strongSelf = self { - strongSelf.assetSelected(asset) - } - })) - } - } - - if !items.isEmpty { - self.listView.transaction(deleteIndices: [], insertIndicesAndItems: (0 ..< items.count).map({ ListViewInsertItem(index: $0, previousIndex: nil, item: items[$0], directionHint: .Down) }), updateIndicesAndItems: [], options: [], updateOpaqueState: nil) - } - - //PHPhotoLibrary.shared().register(self) - } - - override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - return CGSize(width: constrainedSize.width, height: 157.0) - } - - override func layout() { - super.layout() - - let bounds = self.bounds - - self.button.frame = CGRect(origin: CGPoint(), size: bounds.size) - - self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 84.0, height: bounds.size.width) - self.listView.position = CGPoint(x: bounds.size.width / 2.0, y: 84.0 / 2.0 + 8.0) - - let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: CGSize(width: 84.0, height: bounds.size.width), insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), duration: 0.0, curve: .Default) - - let labelSize = self.label.bounds.size - self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.size.width - labelSize.width) / 2.0), y: 84.0 + 16.0 + floorToScreenPixels((bounds.height - 84.0 - 16.0 - labelSize.height) / 2.0)), size: labelSize) - } - - func photoLibraryDidChange(_ changeInstance: PHChange) { - Queue.concurrentDefaultQueue().async { - //let collectionChanges = changeInstance.changeDetailsForFetchResult(self.fetchResult) - //self.fetchResult = collectionChanges.fetchResultAfterChanges() - - } - } -} diff --git a/TelegramUI/ChatMediaInputStickerGridItem.swift b/TelegramUI/ChatMediaInputStickerGridItem.swift index 42e908d825..691c6186eb 100644 --- a/TelegramUI/ChatMediaInputStickerGridItem.swift +++ b/TelegramUI/ChatMediaInputStickerGridItem.swift @@ -140,7 +140,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 != stickerItem { if let dimensions = stickerItem.file.dimensions { self.imageNode.setSignal(account: account, signal: chatMessageSticker(account: account, file: stickerItem.file, small: true)) - self.stickerFetchedDisposable.set(fileInteractiveFetched(account: account, file: stickerItem.file).start()) + self.stickerFetchedDisposable.set(freeMediaFileInteractiveFetched(account: account, file: stickerItem.file).start()) self.currentState = (account, stickerItem, dimensions) self.setNeedsLayout() diff --git a/TelegramUI/ChatMediaInputStickerPackItem.swift b/TelegramUI/ChatMediaInputStickerPackItem.swift index ab2ef367be..0b9433b1d4 100644 --- a/TelegramUI/ChatMediaInputStickerPackItem.swift +++ b/TelegramUI/ChatMediaInputStickerPackItem.swift @@ -108,7 +108,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingImageSize, intrinsicInsets: UIEdgeInsets())) imageApply() self.imageNode.setSignal(account: account, signal: chatMessageSticker(account: account, file: item.file, small: true)) - self.stickerFetchedDisposable.set(fileInteractiveFetched(account: account, file: item.file).start()) + self.stickerFetchedDisposable.set(freeMediaFileInteractiveFetched(account: account, file: item.file).start()) self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize) } diff --git a/TelegramUI/ChatMessageAttachedContentNode.swift b/TelegramUI/ChatMessageAttachedContentNode.swift index e98f8a83b5..b7485f53c7 100644 --- a/TelegramUI/ChatMessageAttachedContentNode.swift +++ b/TelegramUI/ChatMessageAttachedContentNode.swift @@ -12,6 +12,10 @@ private let textBoldFont: UIFont = Font.semibold(15.0) private let textFixedFont: UIFont = Font.regular(15.0) private let buttonFont: UIFont = Font.semibold(13.0) +enum ChatMessageAttachedContentActionIcon { + case instant +} + struct ChatMessageAttachedContentNodeMediaFlags: OptionSet { var rawValue: Int32 @@ -29,11 +33,14 @@ struct ChatMessageAttachedContentNodeMediaFlags: OptionSet { private final class ChatMessageAttachedContentButtonNode: HighlightTrackingButtonNode { private let textNode: TextNode + private let iconNode: ASImageNode private let highlightedTextNode: TextNode private let backgroundNode: ASImageNode private var regularImage: UIImage? private var highlightedImage: UIImage? + private var regularIconImage: UIImage? + private var highlightedIconImage: UIImage? var pressed: (() -> Void)? @@ -48,6 +55,11 @@ private final class ChatMessageAttachedContentButtonNode: HighlightTrackingButto self.backgroundNode.displayWithoutProcessing = true self.backgroundNode.displaysAsynchronously = false + self.iconNode = ASImageNode() + self.iconNode.isLayerBacked = true + self.iconNode.displayWithoutProcessing = true + self.iconNode.displaysAsynchronously = false + super.init() self.addSubnode(self.backgroundNode) @@ -59,11 +71,13 @@ private final class ChatMessageAttachedContentButtonNode: HighlightTrackingButto if let strongSelf = self { if highlighted { strongSelf.backgroundNode.image = strongSelf.highlightedImage + strongSelf.iconNode.image = strongSelf.highlightedIconImage strongSelf.textNode.isHidden = true strongSelf.highlightedTextNode.isHidden = false } else { UIView.transition(with: strongSelf.view, duration: 0.2, options: [.transitionCrossDissolve], animations: { strongSelf.backgroundNode.image = strongSelf.regularImage + strongSelf.iconNode.image = strongSelf.regularIconImage strongSelf.textNode.isHidden = false strongSelf.highlightedTextNode.isHidden = true }, completion: nil) @@ -78,14 +92,16 @@ private final class ChatMessageAttachedContentButtonNode: HighlightTrackingButto self.pressed?() } - static func asyncLayout(_ current: ChatMessageAttachedContentButtonNode?) -> (_ width: CGFloat, _ regularImage: UIImage, _ highlightedImage: UIImage, _ title: String, _ titleColor: UIColor, _ highlightedTitleColor: UIColor) -> (CGFloat, (CGFloat) -> (CGSize, () -> ChatMessageAttachedContentButtonNode)) { + static func asyncLayout(_ current: ChatMessageAttachedContentButtonNode?) -> (_ width: CGFloat, _ regularImage: UIImage, _ highlightedImage: UIImage, _ iconImage: UIImage?, _ highlightedIconImage: UIImage?, _ title: String, _ titleColor: UIColor, _ highlightedTitleColor: UIColor) -> (CGFloat, (CGFloat) -> (CGSize, () -> ChatMessageAttachedContentButtonNode)) { let previousRegularImage = current?.regularImage let previousHighlightedImage = current?.highlightedImage + let previousRegularIconImage = current?.regularIconImage + let previousHighlightedIconImage = current?.highlightedIconImage let maybeMakeTextLayout = (current?.textNode).flatMap(TextNode.asyncLayout) let maybeMakeHighlightedTextLayout = (current?.highlightedTextNode).flatMap(TextNode.asyncLayout) - return { width, regularImage, highlightedImage, title, titleColor, highlightedTitleColor in + return { width, regularImage, highlightedImage, iconImage, highlightedIconImage, title, titleColor, highlightedTitleColor in let targetNode: ChatMessageAttachedContentButtonNode if let current = current { targetNode = current @@ -117,9 +133,24 @@ private final class ChatMessageAttachedContentButtonNode: HighlightTrackingButto updatedHighlightedImage = highlightedImage } + var updatedRegularIconImage: UIImage? + if iconImage !== previousRegularIconImage { + updatedRegularIconImage = iconImage + } + + var updatedHighlightedIconImage: UIImage? + if highlightedIconImage !== previousHighlightedIconImage { + updatedHighlightedIconImage = highlightedIconImage + } + + var iconWidth: CGFloat = 0.0 + if let iconImage = iconImage { + iconWidth = iconImage.size.width + 5.0 + } + let labelInset: CGFloat = 8.0 - let (textSize, textApply) = makeTextLayout(NSAttributedString(string: title, font: buttonFont, textColor: titleColor), nil, 1, .end, CGSize(width: max(1.0, width - labelInset * 2.0), height: CGFloat.greatestFiniteMagnitude), .left, nil, UIEdgeInsets()) + let (textSize, textApply) = makeTextLayout(NSAttributedString(string: title, font: buttonFont, textColor: titleColor), nil, 1, .end, CGSize(width: max(1.0, width - labelInset * 2.0 - iconWidth), height: CGFloat.greatestFiniteMagnitude), .left, nil, UIEdgeInsets()) let (_, highlightedTextApply) = makeHighlightedTextLayout(NSAttributedString(string: title, font: buttonFont, textColor: highlightedTitleColor), nil, 1, .end, CGSize(width: max(1.0, width - labelInset * 2.0), height: CGFloat.greatestFiniteMagnitude), .left, nil, UIEdgeInsets()) @@ -137,12 +168,35 @@ private final class ChatMessageAttachedContentButtonNode: HighlightTrackingButto targetNode.backgroundNode.image = updatedHighlightedImage } } + if let updatedRegularIconImage = updatedRegularIconImage { + targetNode.regularIconImage = updatedRegularIconImage + if !targetNode.textNode.isHidden { + targetNode.iconNode.image = updatedRegularIconImage + } + } + if let updatedHighlightedIconImage = updatedHighlightedIconImage { + targetNode.highlightedIconImage = updatedHighlightedIconImage + if targetNode.iconNode.isHidden { + targetNode.iconNode.image = updatedHighlightedIconImage + } + } let _ = textApply() let _ = highlightedTextApply() targetNode.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: refinedWidth, height: 33.0)) - targetNode.textNode.frame = CGRect(origin: CGPoint(x: floor((refinedWidth - textSize.size.width) / 2.0), y: floor((33.0 - textSize.size.height) / 2.0)), size: textSize.size) + var textFrame = CGRect(origin: CGPoint(x: floor((refinedWidth - textSize.size.width) / 2.0), y: floor((33.0 - textSize.size.height) / 2.0)), size: textSize.size) + if let image = targetNode.iconNode.image { + textFrame.origin.x += floor(image.size.width / 2.0) + targetNode.iconNode.frame = CGRect(origin: CGPoint(x: textFrame.minX - image.size.width - 5.0, y: textFrame.minY + 3.0), size: image.size) + if targetNode.iconNode.supernode == nil { + targetNode.addSubnode(targetNode.iconNode) + } + } else if targetNode.iconNode.supernode != nil { + targetNode.iconNode.removeFromSupernode() + } + + targetNode.textNode.frame = textFrame targetNode.highlightedTextNode.frame = targetNode.textNode.frame return targetNode @@ -201,7 +255,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { self.addSubnode(self.statusNode) } - func asyncLayout() -> (_ theme: PresentationTheme, _ strings: PresentationStrings, _ automaticDownloadSettings: AutomaticMediaDownloadSettings, _ account: Account, _ message: Message, _ messageRead: Bool, _ title: String?, _ subtitle: String?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ position: ChatMessageBubbleContentPosition, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))) { + func asyncLayout() -> (_ theme: PresentationTheme, _ strings: PresentationStrings, _ automaticDownloadSettings: AutomaticMediaDownloadSettings, _ account: Account, _ message: Message, _ messageRead: Bool, _ title: String?, _ subtitle: String?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ position: ChatMessageBubbleContentPosition, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))) { let textAsyncLayout = TextNode.asyncLayout(self.textNode) let currentImage = self.media as? TelegramMediaImage let imageLayout = self.inlineImageNode.asyncLayout() @@ -211,10 +265,10 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { let makeButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.buttonNode) - return { theme, strings, automaticDownloadSettings, account, message, messageRead, title, subtitle, text, entities, mediaAndFlags, actionTitle, displayLine, layoutConstants, position, constrainedSize in + return { theme, strings, automaticDownloadSettings, account, message, messageRead, title, subtitle, text, entities, mediaAndFlags, actionIcon, actionTitle, displayLine, layoutConstants, position, constrainedSize in let incoming = message.effectivelyIncoming - var insets = UIEdgeInsets(top: 0.0, left: 8.0, bottom: 5.0, right: 8.0) + var insets = UIEdgeInsets(top: 0.0, left: 12.0, bottom: 5.0, right: 12.0) switch position.top { case .None: insets.top += 8.0 @@ -222,7 +276,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { break } if displayLine { - insets.left += 11.0 + insets.left += 10.0 } var preferMediaBeforeText = false @@ -470,20 +524,30 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { if let actionTitle = actionTitle { let buttonImage: UIImage let buttonHighlightedImage: UIImage + var buttonIconImage: UIImage? + var buttonHighlightedIconImage: UIImage? let titleColor: UIColor let titleHighlightedColor: UIColor if incoming { buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonIncoming(theme)! buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIncoming(theme)! + if let actionIcon = actionIcon, case .instant = actionIcon { + buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconInstantIncoming(theme)! + buttonHighlightedIconImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIconInstantIncoming(theme)! + } titleColor = theme.chat.bubble.incomingAccentColor titleHighlightedColor = theme.chat.bubble.incomingFillColor } else { buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonOutgoing(theme)! buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonOutgoing(theme)! + if let actionIcon = actionIcon, case .instant = actionIcon { + buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconInstantOutgoing(theme)! + buttonHighlightedIconImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIconInstantOutgoing(theme)! + } titleColor = theme.chat.bubble.outgoingAccentColor titleHighlightedColor = theme.chat.bubble.outgoingFillColor } - let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, buttonImage, buttonHighlightedImage, actionTitle, titleColor, titleHighlightedColor) + let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, buttonImage, buttonHighlightedImage, buttonIconImage, buttonHighlightedIconImage, actionTitle, titleColor, titleHighlightedColor) boundingSize.width = max(buttonWidth, boundingSize.width) continueActionButtonLayout = continueLayout } @@ -534,7 +598,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { var actionButtonSizeAndApply: ((CGSize, () -> ChatMessageAttachedContentButtonNode))? if let continueActionButtonLayout = continueActionButtonLayout { - let (size, apply) = continueActionButtonLayout(boundingWidth - 9.0 - insets.right) + let (size, apply) = continueActionButtonLayout(boundingWidth - 13.0 - insets.right) actionButtonSizeAndApply = (size, apply) adjustedBoundingSize.height += 7.0 + size.height } @@ -555,7 +619,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } strongSelf.lineNode.image = lineImage - strongSelf.lineNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 0.0), size: CGSize(width: 2.0, height: adjustedLineHeight - insets.top - insets.bottom - 2.0)) + strongSelf.lineNode.frame = CGRect(origin: CGPoint(x: 13.0, y: 0.0), size: CGSize(width: 2.0, height: adjustedLineHeight - insets.top - insets.bottom - 2.0)) strongSelf.lineNode.isHidden = !displayLine let _ = textApply() @@ -655,7 +719,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } } } - buttonNode.frame = CGRect(origin: CGPoint(x: 9.0, y: adjustedLineHeight - insets.top - insets.bottom - 2.0 + 6.0), size: size) + buttonNode.frame = CGRect(origin: CGPoint(x: 13.0, y: adjustedLineHeight - insets.top - insets.bottom - 2.0 + 6.0), size: size) } else if let buttonNode = strongSelf.buttonNode { buttonNode.removeFromSupernode() strongSelf.buttonNode = nil diff --git a/TelegramUI/ChatMessageAvatarAccessoryItem.swift b/TelegramUI/ChatMessageAvatarAccessoryItem.swift index ba1bcf5e86..62dc59e223 100644 --- a/TelegramUI/ChatMessageAvatarAccessoryItem.swift +++ b/TelegramUI/ChatMessageAvatarAccessoryItem.swift @@ -3,6 +3,8 @@ import Postbox import Display import TelegramCore +private let avatarFont: UIFont = UIFont(name: "ArialRoundedMTBold", size: 15.0)! + final class ChatMessageAvatarAccessoryItem: ListViewAccessoryItem { private let account: Account private let peerId: PeerId @@ -38,7 +40,7 @@ final class ChatMessageAvatarAccessoryItemNode: ListViewAccessoryItemNode { let avatarNode: AvatarNode override init() { - self.avatarNode = AvatarNode(font: Font.regular(14.0)) + self.avatarNode = AvatarNode(font: avatarFont) self.avatarNode.isLayerBacked = true self.avatarNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0)) diff --git a/TelegramUI/ChatMessageBubbleItemNode.swift b/TelegramUI/ChatMessageBubbleItemNode.swift index 22f451aeb0..f60481e44f 100644 --- a/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/TelegramUI/ChatMessageBubbleItemNode.swift @@ -28,6 +28,8 @@ private func contentNodeClassesForItem(_ item: ChatMessageItem) -> [AnyClass] { skipText = true result.append(ChatMessageInvoiceBubbleContentNode.self) break + } else if let _ = media as? TelegramMediaContact { + result.append(ChatMessageContactBubbleContentNode.self) } } diff --git a/TelegramUI/ChatMessageContactBubbleContentNode.swift b/TelegramUI/ChatMessageContactBubbleContentNode.swift new file mode 100644 index 0000000000..ad05efea74 --- /dev/null +++ b/TelegramUI/ChatMessageContactBubbleContentNode.swift @@ -0,0 +1,255 @@ +import Foundation +import AsyncDisplayKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore + +private let avatarFont: UIFont = UIFont(name: "ArialRoundedMTBold", size: 15.0)! + +private let titleFont = Font.medium(14.0) +private let textFont = Font.regular(14.0) + +class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { + private let avatarNode: AvatarNode + private let dateAndStatusNode: ChatMessageDateAndStatusNode + private let titleNode: TextNode + private let textNode: TextNode + + private var item: ChatMessageItem? + private var contact: TelegramMediaContact? + private var contactPhone: String? + + required init() { + self.avatarNode = AvatarNode(font: avatarFont) + self.dateAndStatusNode = ChatMessageDateAndStatusNode() + self.titleNode = TextNode() + self.textNode = TextNode() + + super.init() + + self.addSubnode(self.avatarNode) + self.addSubnode(self.titleNode) + self.addSubnode(self.textNode) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func didLoad() { + super.didLoad() + + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.contactTap(_:))) + self.view.addGestureRecognizer(tapRecognizer) + } + + override func asyncLayoutContent() -> (_ item: ChatMessageItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ position: ChatMessageBubbleContentPosition, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))) { + let statusLayout = self.dateAndStatusNode.asyncLayout() + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let makeTextLayout = TextNode.asyncLayout(self.textNode) + + let previousContact = self.contact + let previousContactPhone = self.contactPhone + + return { item, layoutConstants, position, constrainedSize in + var selectedContact: TelegramMediaContact? + for media in item.message.media { + if let media = media as? TelegramMediaContact { + selectedContact = media + } + } + + var titleString: NSAttributedString? + var textString: NSAttributedString? + var updatedPhone: String? + + if let selectedContact = selectedContact { + let displayName: String + if !selectedContact.firstName.isEmpty && !selectedContact.lastName.isEmpty { + displayName = "\(selectedContact.firstName) \(selectedContact.lastName)" + } else if !selectedContact.firstName.isEmpty { + displayName = selectedContact.firstName + } else { + displayName = selectedContact.lastName + } + titleString = NSAttributedString(string: displayName, font: titleFont, textColor: item.message.effectivelyIncoming ? item.theme.chat.bubble.incomingAccentColor : item.theme.chat.bubble.outgoingAccentColor) + + let phone: String + if let previousContact = previousContact, previousContact.isEqual(selectedContact), let contactPhone = previousContactPhone { + phone = contactPhone + } else { + phone = formatPhoneNumber(selectedContact.phoneNumber) + } + updatedPhone = phone + textString = NSAttributedString(string: phone, font: textFont, textColor: item.message.effectivelyIncoming ? item.theme.chat.bubble.incomingPrimaryTextColor : item.theme.chat.bubble.outgoingPrimaryTextColor) + } else { + updatedPhone = nil + } + + return (CGFloat.greatestFiniteMagnitude, { constrainedSize in + let avatarSize = CGSize(width: 40.0, height: 40.0) + + let maxTextWidth = max(1.0, constrainedSize.width - avatarSize.width - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right) + let (titleLayout, titleApply) = makeTitleLayout(titleString, nil, 1, .end, CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), .natural, nil, UIEdgeInsets()) + let (textLayout, textApply) = makeTextLayout(textString, nil, 2, .end, CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), .natural, nil, UIEdgeInsets()) + + var t = Int(item.message.timestamp) + var timeinfo = tm() + localtime_r(&t, &timeinfo) + + var edited = false + var sentViaBot = false + var viewCount: Int? + for attribute in item.message.attributes { + if let _ = attribute as? EditedMessageAttribute { + edited = true + } else if let attribute = attribute as? ViewCountMessageAttribute { + viewCount = attribute.count + } else if let _ = attribute as? InlineBotMessageAttribute { + sentViaBot = true + } + } + + var dateText = String(format: "%02d:%02d", arguments: [Int(timeinfo.tm_hour), Int(timeinfo.tm_min)]) + + if let author = item.message.author as? TelegramUser { + if author.botInfo != nil { + sentViaBot = true + } + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { + dateText = "\(author.displayTitle), \(dateText)" + } + } + + let statusType: ChatMessageDateAndStatusType? + if case .None = position.bottom { + if item.message.effectivelyIncoming { + statusType = .BubbleIncoming + } else { + if item.message.flags.contains(.Failed) { + statusType = .BubbleOutgoing(.Failed) + } else if item.message.flags.isSending { + statusType = .BubbleOutgoing(.Sending) + } else { + statusType = .BubbleOutgoing(.Sent(read: item.read)) + } + } + } else { + statusType = nil + } + + var statusSize = CGSize() + var statusApply: ((Bool) -> Void)? + + if let statusType = statusType { + let (size, apply) = statusLayout(item.theme, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude)) + statusSize = size + statusApply = apply + } + + let contentWidth = avatarSize.width + max(statusSize.width, max(titleLayout.size.width, textLayout.size.width)) + layoutConstants.text.bubbleInsets.right + 8.0 + + return (contentWidth, { boundingWidth in + let layoutSize: CGSize + let statusFrame: CGRect + + let baseAvatarFrame = CGRect(origin: CGPoint(), size: avatarSize) + + layoutSize = CGSize(width: contentWidth, height: 63.0) + statusFrame = CGRect(origin: CGPoint(x: boundingWidth - statusSize.width - layoutConstants.text.bubbleInsets.right, y: layoutSize.height - statusSize.height - 5.0), size: statusSize) + let avatarFrame = baseAvatarFrame.offsetBy(dx: 5.0, dy: 5.0) + + var customLetters: [String] = [] + if let selectedContact = selectedContact, selectedContact.peerId == nil { + let firstName = selectedContact.firstName + let lastName = selectedContact.lastName + if !firstName.isEmpty && !lastName.isEmpty { + customLetters = [firstName.substring(to: firstName.index(after: firstName.startIndex)).uppercased(), lastName.substring(to: lastName.index(after: lastName.startIndex)).uppercased()] + } else if !firstName.isEmpty { + customLetters = [firstName.substring(to: firstName.index(after: firstName.startIndex)).uppercased()] + } else if !lastName.isEmpty { + customLetters = [lastName.substring(to: lastName.index(after: lastName.startIndex)).uppercased()] + } + } + + return (layoutSize, { [weak self] animation in + if let strongSelf = self { + strongSelf.item = item + strongSelf.contact = selectedContact + strongSelf.contactPhone = updatedPhone + + strongSelf.avatarNode.frame = avatarFrame + + let _ = titleApply() + let _ = textApply() + + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: avatarFrame.maxX + 7.0, y: avatarFrame.minY + 1.0), size: titleLayout.size) + strongSelf.textNode.frame = CGRect(origin: CGPoint(x: avatarFrame.maxX + 7.0, y: avatarFrame.minY + 20.0), size: textLayout.size) + + if let statusApply = statusApply { + if strongSelf.dateAndStatusNode.supernode == nil { + strongSelf.addSubnode(strongSelf.dateAndStatusNode) + } + var hasAnimation = true + if case .None = animation { + hasAnimation = false + } + statusApply(hasAnimation) + strongSelf.dateAndStatusNode.frame = statusFrame + } else if strongSelf.dateAndStatusNode.supernode != nil { + strongSelf.dateAndStatusNode.removeFromSupernode() + } + + if let _ = titleString { + if strongSelf.titleNode.supernode == nil { + strongSelf.addSubnode(strongSelf.titleNode) + } + if strongSelf.textNode.supernode == nil { + strongSelf.addSubnode(strongSelf.textNode) + } + } else { + if strongSelf.titleNode.supernode != nil { + strongSelf.titleNode.removeFromSupernode() + } + if strongSelf.textNode.supernode != nil { + strongSelf.textNode.removeFromSupernode() + } + } + + if let peerId = selectedContact?.peerId, let peer = item.message.peers[peerId] { + strongSelf.avatarNode.setPeer(account: item.account, peer: peer) + } else { + strongSelf.avatarNode.setCustomLetters(customLetters) + } + } + }) + }) + }) + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + override func animateAdded(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + } + + override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction { + return .none + } + + @objc func contactTap(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + if let item = self.item { + self.controllerInteraction?.openMessage(item.message.id) + } + } + } +} diff --git a/TelegramUI/ChatMessageGameBubbleContentNode.swift b/TelegramUI/ChatMessageGameBubbleContentNode.swift index 17dd2ac176..3d2d803cd9 100644 --- a/TelegramUI/ChatMessageGameBubbleContentNode.swift +++ b/TelegramUI/ChatMessageGameBubbleContentNode.swift @@ -71,7 +71,7 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode { } } - let (initialWidth, continueLayout) = contentNodeLayout(item.theme, item.strings, item.controllerInteraction.automaticMediaDownloadSettings, item.account, item.message, item.read, title, subtitle, item.message.text.isEmpty ? text : item.message.text, item.message.text.isEmpty ? nil : messageEntities, mediaAndFlags, nil, true, layoutConstants, position, constrainedSize) + let (initialWidth, continueLayout) = contentNodeLayout(item.theme, item.strings, item.controllerInteraction.automaticMediaDownloadSettings, item.account, item.message, item.read, title, subtitle, item.message.text.isEmpty ? text : item.message.text, item.message.text.isEmpty ? nil : messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, position, constrainedSize) return (initialWidth, { constrainedSize in let (refinedWidth, finalizeLayout) = continueLayout(constrainedSize) diff --git a/TelegramUI/ChatMessageInstantVideoItemNode.swift b/TelegramUI/ChatMessageInstantVideoItemNode.swift index e15553f3b6..e1912d834b 100644 --- a/TelegramUI/ChatMessageInstantVideoItemNode.swift +++ b/TelegramUI/ChatMessageInstantVideoItemNode.swift @@ -117,14 +117,14 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { var updatedPlaybackStatus: Signal? if let updatedFile = updatedFile, updatedMedia { - updatedPlaybackStatus = combineLatest(fileMediaResourceStatus(account: item.account, file: updatedFile, message: item.message), item.account.pendingMessageManager.pendingMessageStatus(item.message.id)) + updatedPlaybackStatus = combineLatest(messageFileMediaResourceStatus(account: item.account, file: updatedFile, message: item.message), item.account.pendingMessageManager.pendingMessageStatus(item.message.id)) |> map { resourceStatus, pendingStatus -> FileMediaResourceStatus in if let pendingStatus = pendingStatus { var progress = pendingStatus.progress if pendingStatus.isRunning { progress = max(progress, 0.27) } - return .fetchStatus(.Fetching(progress: progress)) + return .fetchStatus(.Fetching(isActive: pendingStatus.isRunning, progress: progress)) } else { return resourceStatus } @@ -300,8 +300,12 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { switch status { case let .fetchStatus(fetchStatus): switch fetchStatus { - case let .Fetching(progress): - state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, value: CGFloat(progress), cancelEnabled: true) + case let .Fetching(isActive, progress): + var adjustedProgress = progress + if isActive { + adjustedProgress = max(adjustedProgress, 0.027) + } + state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, value: CGFloat(adjustedProgress), cancelEnabled: true) case .Local: state = .none /*if isSecretMedia && secretProgressIcon != nil { diff --git a/TelegramUI/ChatMessageInteractiveFileNode.swift b/TelegramUI/ChatMessageInteractiveFileNode.swift index 14111ffb68..2dfde1bccb 100644 --- a/TelegramUI/ChatMessageInteractiveFileNode.swift +++ b/TelegramUI/ChatMessageInteractiveFileNode.swift @@ -140,17 +140,18 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { } if mediaUpdated { + let messageId = message.id updatedFetchControls = FetchControls(fetch: { [weak self] in if let strongSelf = self { - strongSelf.fetchDisposable.set(chatMessageFileInteractiveFetched(account: account, file: file).start()) + strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(account: account, messageId: messageId, file: file).start()) } }, cancel: { - chatMessageFileCancelInteractiveFetch(account: account, file: file) + messageMediaFileCancelInteractiveFetch(account: account, messageId: messageId, file: file) }) } if statusUpdated { - updatedStatusSignal = fileMediaResourceStatus(account: account, file: file, message: message) + updatedStatusSignal = messageFileMediaResourceStatus(account: account, file: file, message: message) } var statusSize: CGSize? @@ -397,8 +398,12 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { switch status { case let .fetchStatus(fetchStatus): switch fetchStatus { - case let .Fetching(progress): - state = .progress(color: statusForegroundColor, value: CGFloat(progress), cancelEnabled: true) + case let .Fetching(isActive, progress): + var adjustedProgress = progress + if isActive { + adjustedProgress = max(adjustedProgress, 0.027) + } + state = .progress(color: statusForegroundColor, value: CGFloat(adjustedProgress), cancelEnabled: true) case .Local: if isAudio { state = .play(statusForegroundColor) diff --git a/TelegramUI/ChatMessageInteractiveMediaNode.swift b/TelegramUI/ChatMessageInteractiveMediaNode.swift index a8dfa3fe5d..ce5348cc82 100644 --- a/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -265,12 +265,13 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { } } + let messageId = message.id updatedFetchControls = FetchControls(fetch: { if let strongSelf = self { - strongSelf.fetchDisposable.set(chatMessageFileInteractiveFetched(account: account, file: file).start()) + strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(account: account, messageId: messageId, file: file).start()) } }, cancel: { - chatMessageFileCancelInteractiveFetch(account: account, file: file) + messageMediaFileCancelInteractiveFetch(account: account, messageId: messageId, file: file) }) } } @@ -285,7 +286,7 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { if pendingStatus.isRunning { progress = max(progress, 0.027) } - return .Fetching(progress: progress) + return .Fetching(isActive: pendingStatus.isRunning, progress: progress) } else { return resourceStatus } @@ -294,14 +295,14 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { updatedStatusSignal = chatMessagePhotoStatus(account: account, photo: image) } } else if let file = media as? TelegramMediaFile { - updatedStatusSignal = combineLatest(chatMessageFileStatus(account: account, file: file), account.pendingMessageManager.pendingMessageStatus(message.id)) + updatedStatusSignal = combineLatest(messageMediaFileStatus(account: account, messageId: message.id, file: file), account.pendingMessageManager.pendingMessageStatus(message.id)) |> map { resourceStatus, pendingStatus -> MediaResourceStatus in if let pendingStatus = pendingStatus { var progress = pendingStatus.progress if pendingStatus.isRunning { progress = max(progress, 0.027) } - return .Fetching(progress: progress) + return .Fetching(isActive: pendingStatus.isRunning, progress: progress) } else { return resourceStatus } @@ -420,8 +421,12 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { var state: RadialStatusNodeState let bubbleTheme = theme.chat.bubble switch status { - case let .Fetching(progress): - state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, value: CGFloat(progress), cancelEnabled: true) + case let .Fetching(isActive, progress): + var adjustedProgress = progress + if isActive { + adjustedProgress = max(adjustedProgress, 0.027) + } + state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, value: CGFloat(adjustedProgress), cancelEnabled: true) case .Local: state = .none if isSecretMedia && secretProgressIcon != nil { diff --git a/TelegramUI/ChatMessageInvoiceBubbleContentNode.swift b/TelegramUI/ChatMessageInvoiceBubbleContentNode.swift index 54b90ce815..0b8f648933 100644 --- a/TelegramUI/ChatMessageInvoiceBubbleContentNode.swift +++ b/TelegramUI/ChatMessageInvoiceBubbleContentNode.swift @@ -59,7 +59,7 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode { } } - let (initialWidth, continueLayout) = contentNodeLayout(item.theme, item.strings, item.controllerInteraction.automaticMediaDownloadSettings, item.account, item.message, item.read, title, subtitle, text, nil, mediaAndFlags, nil, false, layoutConstants, position, constrainedSize) + let (initialWidth, continueLayout) = contentNodeLayout(item.theme, item.strings, item.controllerInteraction.automaticMediaDownloadSettings, item.account, item.message, item.read, title, subtitle, text, nil, mediaAndFlags, nil, nil, false, layoutConstants, position, constrainedSize) return (initialWidth, { constrainedSize in let (refinedWidth, finalizeLayout) = continueLayout(constrainedSize) diff --git a/TelegramUI/ChatMessageReplyInfoNode.swift b/TelegramUI/ChatMessageReplyInfoNode.swift index 28b070d076..d0670d6c0d 100644 --- a/TelegramUI/ChatMessageReplyInfoNode.swift +++ b/TelegramUI/ChatMessageReplyInfoNode.swift @@ -82,6 +82,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { private var titleNode: TextNode? private var textNode: TextNode? private var imageNode: TransformImageNode? + private var overlayIconNode: ASImageNode? private var previousMedia: Media? override init() { @@ -129,6 +130,8 @@ class ChatMessageReplyInfoNode: ASDisplayNode { var leftInset: CGFloat = 10.0 + var overlayIcon: UIImage? + var updatedMedia: Media? var imageDimensions: CGSize? for media in message.media { @@ -138,11 +141,14 @@ class ChatMessageReplyInfoNode: ASDisplayNode { imageDimensions = representation.dimensions } break - } else if let file = media as? TelegramMediaFile { + } else if let file = media as? TelegramMediaFile, file.isVideo { updatedMedia = file - if let representation = largestImageRepresentation(file.previewRepresentations), !file.isSticker { + if let dimensions = file.dimensions { + imageDimensions = dimensions + } else if let representation = largestImageRepresentation(file.previewRepresentations), !file.isSticker { imageDimensions = representation.dimensions } + overlayIcon = PresentationResourcesChat.chatBubbleReplyThumbnailPlayImage(theme) break } } @@ -166,7 +172,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { if let image = updatedMedia as? TelegramMediaImage { updateImageSignal = chatMessagePhotoThumbnail(account: account, photo: image) } else if let file = updatedMedia as? TelegramMediaFile { - + updateImageSignal = chatMessageVideoThumbnail(account: account, file: file) } } @@ -204,6 +210,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { node.contentNode.addSubnode(textNode) } + var imageFrame: CGRect? if let applyImage = applyImage { let imageNode = applyImage() if node.imageNode == nil { @@ -211,6 +218,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { node.addSubnode(imageNode) node.imageNode = imageNode } + imageFrame = CGRect(origin: CGPoint(x: 8.0, y: 3.0), size: CGSize(width: 30.0, height: 30.0)) imageNode.frame = CGRect(origin: CGPoint(x: 8.0, y: 3.0), size: CGSize(width: 30.0, height: 30.0)) if let updateImageSignal = updateImageSignal { @@ -221,6 +229,25 @@ class ChatMessageReplyInfoNode: ASDisplayNode { node.imageNode = nil } + if let overlayIcon = overlayIcon, let imageFrame = imageFrame { + let overlayIconNode: ASImageNode + if let current = node.overlayIconNode { + overlayIconNode = current + } else { + overlayIconNode = ASImageNode() + overlayIconNode.isLayerBacked = true + overlayIconNode.displayWithoutProcessing = true + overlayIconNode.displaysAsynchronously = false + node.overlayIconNode = overlayIconNode + node.addSubnode(overlayIconNode) + } + overlayIconNode.image = overlayIcon + overlayIconNode.frame = CGRect(origin: CGPoint(x: imageFrame.minX + floor((imageFrame.size.width - overlayIcon.size.width) / 2.0), y: imageFrame.minY + floor((imageFrame.size.height - overlayIcon.size.height) / 2.0)), size: overlayIcon.size) + } else if let overlayIconNode = node.overlayIconNode { + overlayIconNode.removeFromSupernode() + node.overlayIconNode = nil + } + titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: titleLayout.size) textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: titleLayout.size.height), size: textLayout.size) diff --git a/TelegramUI/ChatMessageSelectionInputPanelNode.swift b/TelegramUI/ChatMessageSelectionInputPanelNode.swift index a210d4f7cb..cb67916c3f 100644 --- a/TelegramUI/ChatMessageSelectionInputPanelNode.swift +++ b/TelegramUI/ChatMessageSelectionInputPanelNode.swift @@ -61,7 +61,7 @@ final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { self.interfaceInteraction?.forwardSelectedMessages() } - override func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { + override func updateLayout(width: CGFloat, maxHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { if self.presentationInterfaceState != interfaceState { self.presentationInterfaceState = interfaceState diff --git a/TelegramUI/ChatMessageStickerItemNode.swift b/TelegramUI/ChatMessageStickerItemNode.swift index 63c1a779bf..8c7127b220 100644 --- a/TelegramUI/ChatMessageStickerItemNode.swift +++ b/TelegramUI/ChatMessageStickerItemNode.swift @@ -16,6 +16,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { private let fetchDisposable = MetaDisposable() + private let dateAndStatusNode: ChatMessageDateAndStatusNode private var replyInfoNode: ChatMessageReplyInfoNode? private var replyBackgroundNode: ASImageNode? @@ -23,11 +24,13 @@ class ChatMessageStickerItemNode: ChatMessageItemView { required init() { self.imageNode = TransformImageNode() + self.dateAndStatusNode = ChatMessageDateAndStatusNode() super.init(layerBacked: false) self.imageNode.displaysAsynchronously = false self.addSubnode(self.imageNode) + self.addSubnode(self.dateAndStatusNode) } required init?(coder aDecoder: NSCoder) { @@ -58,7 +61,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { let signal = chatMessageSticker(account: item.account, file: telegramFile, small: false) self.imageNode.setSignal(account: item.account, signal: signal) - self.fetchDisposable.set(fileInteractiveFetched(account: item.account, file: telegramFile).start()) + self.fetchDisposable.set(freeMediaFileInteractiveFetched(account: item.account, file: telegramFile).start()) } break @@ -71,6 +74,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { let telegramFile = self.telegramFile let layoutConstants = self.layoutConstants let imageLayout = self.imageNode.asyncLayout() + let makeDateAndStatusLayout = self.dateAndStatusNode.asyncLayout() let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode) let currentReplyBackgroundNode = self.replyBackgroundNode @@ -111,12 +115,57 @@ class ChatMessageStickerItemNode: ChatMessageItemView { layoutInsets.top += layoutConstants.timestampHeaderHeight } + let displayLeftInset = layoutConstants.bubble.edgeInset + avatarInset + let imageFrame = CGRect(origin: CGPoint(x: (incoming ? (layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (width - imageSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left)), y: 0.0), size: imageSize) let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: imageFrame.size, boundingSize: imageFrame.size, intrinsicInsets: UIEdgeInsets()) let imageApply = imageLayout(arguments) + let statusType: ChatMessageDateAndStatusType + if item.message.effectivelyIncoming { + statusType = .FreeIncoming + } else { + if item.message.flags.contains(.Failed) { + statusType = .FreeOutgoing(.Failed) + } else if item.message.flags.isSending { + statusType = .FreeOutgoing(.Sending) + } else { + statusType = .FreeOutgoing(.Sent(read: item.read)) + } + } + + var t = Int(item.message.timestamp) + var timeinfo = tm() + localtime_r(&t, &timeinfo) + + var edited = false + var sentViaBot = false + var viewCount: Int? + for attribute in item.message.attributes { + if let _ = attribute as? EditedMessageAttribute { + edited = true + } else if let attribute = attribute as? ViewCountMessageAttribute { + viewCount = attribute.count + } else if let _ = attribute as? InlineBotMessageAttribute { + sentViaBot = true + } + } + + var dateText = String(format: "%02d:%02d", arguments: [Int(timeinfo.tm_hour), Int(timeinfo.tm_min)]) + + if let author = item.message.author as? TelegramUser { + if author.botInfo != nil { + sentViaBot = true + } + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { + dateText = "\(author.displayTitle), \(dateText)" + } + } + + let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.theme, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)) + var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)? var updatedReplyBackgroundNode: ASImageNode? var replyBackgroundImage: UIImage? @@ -141,6 +190,9 @@ class ChatMessageStickerItemNode: ChatMessageItemView { strongSelf.progressNode?.position = strongSelf.imageNode.position imageApply() + dateAndStatusApply(false) + strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: max(displayLeftInset, imageFrame.maxX - dateAndStatusSize.width - 4.0), y: imageFrame.maxY - dateAndStatusSize.height), size: dateAndStatusSize) + if let updatedReplyBackgroundNode = updatedReplyBackgroundNode { if strongSelf.replyBackgroundNode == nil { strongSelf.replyBackgroundNode = updatedReplyBackgroundNode diff --git a/TelegramUI/ChatMessageWebpageBubbleContentNode.swift b/TelegramUI/ChatMessageWebpageBubbleContentNode.swift index a6630c73bf..e47dcd9809 100644 --- a/TelegramUI/ChatMessageWebpageBubbleContentNode.swift +++ b/TelegramUI/ChatMessageWebpageBubbleContentNode.swift @@ -74,6 +74,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { var subtitle: String? var text: String? var mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? + var actionIcon: ChatMessageAttachedContentActionIcon? var actionTitle: String? if let webpage = webPageContent { @@ -110,6 +111,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } if let _ = webpage.instantPage { + actionIcon = .instant actionTitle = item.strings.Conversation_InstantPagePreview } else if let type = webpage.type { switch type { @@ -123,7 +125,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } } - let (initialWidth, continueLayout) = contentNodeLayout(item.theme, item.strings, item.controllerInteraction.automaticMediaDownloadSettings, item.account, item.message, item.read, title, subtitle, text, nil, mediaAndFlags, actionTitle, true, layoutConstants, position, constrainedSize) + let (initialWidth, continueLayout) = contentNodeLayout(item.theme, item.strings, item.controllerInteraction.automaticMediaDownloadSettings, item.account, item.message, item.read, title, subtitle, text, nil, mediaAndFlags, actionIcon, actionTitle, true, layoutConstants, position, constrainedSize) return (initialWidth, { constrainedSize in let (refinedWidth, finalizeLayout) = continueLayout(constrainedSize) diff --git a/TelegramUI/ChatSearchInputPanelNode.swift b/TelegramUI/ChatSearchInputPanelNode.swift index 90a690ef5f..3a8f33be87 100644 --- a/TelegramUI/ChatSearchInputPanelNode.swift +++ b/TelegramUI/ChatSearchInputPanelNode.swift @@ -79,7 +79,7 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode { self.interfaceInteraction?.openCalendarSearch() } - override func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { + override func updateLayout(width: CGFloat, maxHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { if self.presentationInterfaceState != interfaceState { let themeUpdated = self.presentationInterfaceState?.theme !== interfaceState.theme diff --git a/TelegramUI/ChatSecretAutoremoveTimerActionSheet.swift b/TelegramUI/ChatSecretAutoremoveTimerActionSheet.swift index 8013bff008..b466721ab5 100644 --- a/TelegramUI/ChatSecretAutoremoveTimerActionSheet.swift +++ b/TelegramUI/ChatSecretAutoremoveTimerActionSheet.swift @@ -18,14 +18,14 @@ final class ChatSecretAutoremoveTimerActionSheetController: ActionSheetControlle self.theme = theme self.strings = strings - super.init() + super.init(theme: ActionSheetControllerTheme(presentationTheme: theme)) self._ready.set(.single(true)) var updatedValue = currentValue self.setItemGroups([ ActionSheetItemGroup(items: [ - AutoremoveTimeoutSelectorItem(theme: theme, strings: strings, currentValue: currentValue, valueChanged: { value in + AutoremoveTimeoutSelectorItem(strings: strings, currentValue: currentValue, valueChanged: { value in updatedValue = value }), ActionSheetButtonItem(title: strings.Wallpaper_Set, action: { [weak self] in @@ -47,21 +47,19 @@ final class ChatSecretAutoremoveTimerActionSheetController: ActionSheetControlle } private final class AutoremoveTimeoutSelectorItem: ActionSheetItem { - let theme: PresentationTheme let strings: PresentationStrings let currentValue: Int32 let valueChanged: (Int32) -> Void - init(theme: PresentationTheme, strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) { - self.theme = theme + init(strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) { self.strings = strings self.currentValue = currentValue self.valueChanged = valueChanged } - func node() -> ActionSheetItemNode { - return AutoremoveTimeoutSelectorItemNode(theme: self.theme, strings: self.strings, currentValue: self.currentValue, valueChanged: self.valueChanged) + func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { + return AutoremoveTimeoutSelectorItemNode(theme: theme, strings: self.strings, currentValue: self.currentValue, valueChanged: self.valueChanged) } func updateNode(_ node: ActionSheetItemNode) { @@ -93,20 +91,20 @@ private let timeoutValues: [Int32] = [ ] private final class AutoremoveTimeoutSelectorItemNode: ActionSheetItemNode, UIPickerViewDelegate, UIPickerViewDataSource { - private let theme: PresentationTheme + private let theme: ActionSheetControllerTheme private let strings: PresentationStrings private let valueChanged: (Int32) -> Void private let pickerView: UIPickerView - init(theme: PresentationTheme, strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) { + init(theme: ActionSheetControllerTheme, strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) { self.theme = theme self.strings = strings self.valueChanged = valueChanged self.pickerView = UIPickerView() - super.init() + super.init(theme: theme) self.pickerView.delegate = self self.pickerView.dataSource = self diff --git a/TelegramUI/ChatTextInputMediaRecordingButton.swift b/TelegramUI/ChatTextInputMediaRecordingButton.swift index 834a4a2b9a..80245473e1 100644 --- a/TelegramUI/ChatTextInputMediaRecordingButton.swift +++ b/TelegramUI/ChatTextInputMediaRecordingButton.swift @@ -217,8 +217,9 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto self.theme = theme self.innerIconView = UIImageView() self.presentController = presentController - - super.init(frame: CGRect()) + + let mediaRecordingControl = theme.chat.inputPanel.mediaRecordingControl + super.init(theme: TGModernConversationInputMicButtonTheme(buttonColor: mediaRecordingControl.buttonColor, micLevel: mediaRecordingControl.micLevelColor, buttonControlColor: mediaRecordingControl.activeIconColor, panelControlFill: mediaRecordingControl.panelControlFillColor, panelControlStroke: mediaRecordingControl.panelControlStrokeColor, panelControlContentPrimaryColor: mediaRecordingControl.panelControlContentPrimaryColor, panelControlContentAccentColor: mediaRecordingControl.panelControlContentAccentColor)) self.insertSubview(self.innerIconView, at: 0) diff --git a/TelegramUI/ChatTextInputPanelNode.swift b/TelegramUI/ChatTextInputPanelNode.swift index e915cd6241..bd57692f03 100644 --- a/TelegramUI/ChatTextInputPanelNode.swift +++ b/TelegramUI/ChatTextInputPanelNode.swift @@ -98,6 +98,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { private var accessoryItemButtons: [(ChatTextInputAccessoryItem, AccessoryItemIconButton)] = [] + private var validLayout: (CGFloat, CGFloat)? + var displayAttachmentMenu: () -> Void = { } var sendMessage: () -> Void = { } var updateHeight: () -> Void = { } @@ -216,7 +218,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } self.micButton.offsetRecordingControls = { [weak self] in if let strongSelf = self, let presentationInterfaceState = strongSelf.presentationInterfaceState { - let _ = strongSelf.updateLayout(width: strongSelf.bounds.size.width, transition: .immediate, interfaceState: presentationInterfaceState) + if let (width, maxHeight) = strongSelf.validLayout { + let _ = strongSelf.updateLayout(width: width, maxHeight: maxHeight, transition: .immediate, interfaceState: presentationInterfaceState) + } } } self.micButton.stopRecording = { [weak self] in @@ -279,7 +283,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { keyboardAppearance = .dark } } - textInputNode.typingAttributes = [NSAttributedStringKey.font.rawValue: Font.regular(17.0), NSAttributedStringKey.foregroundColor.rawValue: textColor] + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineSpacing = 1.0 + paragraphStyle.lineHeightMultiple = 1.0 + paragraphStyle.paragraphSpacing = 1.0 + paragraphStyle.maximumLineHeight = 20.0 + paragraphStyle.minimumLineHeight = 20.0 + + textInputNode.typingAttributes = [NSAttributedStringKey.font.rawValue: Font.regular(17.0), NSAttributedStringKey.foregroundColor.rawValue: textColor, NSAttributedStringKey.paragraphStyle.rawValue: paragraphStyle] textInputNode.clipsToBounds = true textInputNode.delegate = self textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0) @@ -302,10 +314,16 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { textInputNode.view.addGestureRecognizer(recognizer) } - private func calculateTextFieldMetrics(width: CGFloat) -> (accessoryButtonsWidth: CGFloat, textFieldHeight: CGFloat) { + private func textFieldMaxHeight(_ maxHeight: CGFloat) -> CGFloat { + return max(33.0, maxHeight - (self.textFieldInsets.top + self.textFieldInsets.bottom + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom)) + } + + private func calculateTextFieldMetrics(width: CGFloat, maxHeight: CGFloat) -> (accessoryButtonsWidth: CGFloat, textFieldHeight: CGFloat) { let accessoryButtonInset = self.accessoryButtonInset let accessoryButtonSpacing = self.accessoryButtonSpacing + let fieldMaxHeight = textFieldMaxHeight(maxHeight) + var accessoryButtonsWidth: CGFloat = 0.0 var firstButton = true for (_, button) in self.accessoryItemButtons { @@ -322,7 +340,14 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { if let textInputNode = self.textInputNode { let unboundTextFieldHeight = max(33.0, ceil(textInputNode.measure(CGSize(width: width - self.textFieldInsets.left - self.textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right - accessoryButtonsWidth, height: CGFloat.greatestFiniteMagnitude)).height)) - textFieldHeight = min(114.0, unboundTextFieldHeight) + let maxNumberOfLines = min(12, (Int(fieldMaxHeight - 11.0) - 33) / 22) + + //let numberOfLines = Int((unboundTextFieldHeight - 11.0) + 11.0) / 22 + //unboundTextFieldHeight = CGFloat(numberOfLines) * 22.0 + 11.0 + + let updatedMaxHeight = (CGFloat(maxNumberOfLines) * 22.0 + 10.0) + + textFieldHeight = min(updatedMaxHeight, unboundTextFieldHeight) } else { textFieldHeight = 33.0 } @@ -334,7 +359,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { return textFieldHeight + self.textFieldInsets.top + self.textFieldInsets.bottom + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom } - override func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { + override func updateLayout(width: CGFloat, maxHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { + self.validLayout = (width, maxHeight) if self.presentationInterfaceState != interfaceState { let previousState = self.presentationInterfaceState self.presentationInterfaceState = interfaceState @@ -460,7 +486,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { self.accessoryItemButtons = updatedButtons } - let (accessoryButtonsWidth, textFieldHeight) = self.calculateTextFieldMetrics(width: width) + let (accessoryButtonsWidth, textFieldHeight) = self.calculateTextFieldMetrics(width: width, maxHeight: maxHeight) let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight) self.micButton.updateMode(mode: interfaceState.interfaceState.mediaRecordingMode, animated: transition.isAnimated) @@ -661,13 +687,13 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { let _ = placeholderApply() - contextPlaceholderNode.frame = CGRect(origin: CGPoint(x: self.textFieldInsets.left + self.textInputViewInternalInsets.left, y: self.textFieldInsets.top + self.textInputViewInternalInsets.top + self.textInputViewRealInsets.top + UIScreenPixel + audioRecordingItemsVerticalOffset), size: placeholderSize.size) + contextPlaceholderNode.frame = CGRect(origin: CGPoint(x: self.textFieldInsets.left + self.textInputViewInternalInsets.left, y: self.textFieldInsets.top + self.textInputViewInternalInsets.top + self.textInputViewRealInsets.top + audioRecordingItemsVerticalOffset), size: placeholderSize.size) } else if let contextPlaceholderNode = self.contextPlaceholderNode { self.contextPlaceholderNode = nil contextPlaceholderNode.removeFromSupernode() } - transition.updateFrame(node: self.textPlaceholderNode, frame: CGRect(origin: CGPoint(x: self.textFieldInsets.left + self.textInputViewInternalInsets.left, y: self.textFieldInsets.top + self.textInputViewInternalInsets.top + self.textInputViewRealInsets.top + UIScreenPixel + audioRecordingItemsVerticalOffset), size: self.textPlaceholderNode.frame.size)) + transition.updateFrame(node: self.textPlaceholderNode, frame: CGRect(origin: CGPoint(x: self.textFieldInsets.left + self.textInputViewInternalInsets.left, y: self.textFieldInsets.top + self.textInputViewInternalInsets.top + self.textInputViewRealInsets.top + audioRecordingItemsVerticalOffset), size: self.textPlaceholderNode.frame.size)) transition.updateFrame(layer: self.textInputBackgroundView.layer, frame: CGRect(x: self.textFieldInsets.left, y: self.textFieldInsets.top + audioRecordingItemsVerticalOffset, width: width - self.textFieldInsets.left - self.textFieldInsets.right + textInputBackgroundWidthOffset, height: panelHeight - self.textFieldInsets.top - self.textFieldInsets.bottom)) @@ -820,10 +846,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } } - let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: self.bounds.size.width) - let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight) - if !self.bounds.size.height.isEqual(to: panelHeight) { - self.updateHeight() + if let (width, maxHeight) = self.validLayout { + let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: width, maxHeight: maxHeight) + let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight) + if !self.bounds.size.height.isEqual(to: panelHeight) { + self.updateHeight() + } } } diff --git a/TelegramUI/ChatTitleView.swift b/TelegramUI/ChatTitleView.swift index 479fb4b504..9fff43278e 100644 --- a/TelegramUI/ChatTitleView.swift +++ b/TelegramUI/ChatTitleView.swift @@ -59,7 +59,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView { } if self.typingIndicator == nil { let typingIndicator = TGModernConversationTitleActivityIndicator() - //typingIndicator.setColor(self.theme.rootController.navigationBar.accentTextColor) + typingIndicator.setColor(self.theme.rootController.navigationBar.accentTextColor) self.addSubview(typingIndicator) self.typingIndicator = typingIndicator } diff --git a/TelegramUI/ChatUnblockInputPanelNode.swift b/TelegramUI/ChatUnblockInputPanelNode.swift index fda2b78132..0ac2749eef 100644 --- a/TelegramUI/ChatUnblockInputPanelNode.swift +++ b/TelegramUI/ChatUnblockInputPanelNode.swift @@ -80,7 +80,7 @@ final class ChatUnblockInputPanelNode: ChatInputPanelNode { self.interfaceInteraction?.unblockPeer() } - override func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { + override func updateLayout(width: CGFloat, maxHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { if self.presentationInterfaceState != interfaceState { self.presentationInterfaceState = interfaceState } diff --git a/TelegramUI/ChatVideoGalleryItem.swift b/TelegramUI/ChatVideoGalleryItem.swift index 182abb37ca..3aa7cb57b3 100644 --- a/TelegramUI/ChatVideoGalleryItem.swift +++ b/TelegramUI/ChatVideoGalleryItem.swift @@ -154,7 +154,7 @@ final class ChatVideoGalleryItemNode: ZoomableContentGalleryItemNode { if let strongSelf = self { strongSelf.resourceStatus = status switch status { - case let .Fetching(progress): + case let .Fetching(_, progress): strongSelf.progressNode.state = .Fetching(progress: progress) strongSelf.progressButtonNode.isHidden = false case .Local: @@ -201,7 +201,7 @@ final class ChatVideoGalleryItemNode: ZoomableContentGalleryItemNode { if let strongSelf = self { strongSelf.resourceStatus = status switch status { - case let .Fetching(progress): + case let .Fetching(_, progress): strongSelf.progressNode.state = .Fetching(progress: progress) strongSelf.progressButtonNode.isHidden = false case .Local: @@ -243,7 +243,7 @@ final class ChatVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } - override func animateIn(from node: ASDisplayNode) { + override func animateIn(from node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void) { guard let videoNode = self.videoNode else { return } @@ -273,7 +273,7 @@ final class ChatVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } - override func animateOut(to node: ASDisplayNode, completion: @escaping () -> Void) { + override func animateOut(to node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) { guard let videoNode = self.videoNode else { completion() return @@ -465,7 +465,8 @@ final class ChatVideoGalleryItemNode: ZoomableContentGalleryItemNode { (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 GalleryTransitionArguments(transitionNode: overlayNode, addToTransitionSurface: { _ in + }) } return nil })) diff --git a/TelegramUI/CommandChatInputContextPanelNode.swift b/TelegramUI/CommandChatInputContextPanelNode.swift index 60ac2bda05..b4ca1ae387 100644 --- a/TelegramUI/CommandChatInputContextPanelNode.swift +++ b/TelegramUI/CommandChatInputContextPanelNode.swift @@ -142,6 +142,7 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { //options.insert(.LowLatency) } else { options.insert(.AnimateTopItemPosition) + options.insert(.AnimateCrossfade) } var insets = UIEdgeInsets() diff --git a/TelegramUI/CommandChatInputPanelItem.swift b/TelegramUI/CommandChatInputPanelItem.swift index a3259a0196..9559a1447f 100644 --- a/TelegramUI/CommandChatInputPanelItem.swift +++ b/TelegramUI/CommandChatInputPanelItem.swift @@ -68,7 +68,7 @@ final class CommandChatInputPanelItem: ListViewItem { } } -private let avatarFont = Font.regular(16.0) +private let avatarFont: UIFont = UIFont(name: "ArialRoundedMTBold", size: 16.0)! private let textFont = Font.medium(14.0) private let descriptionFont = Font.regular(14.0) private let descriptionColor = UIColor(rgb: 0x9099A2) diff --git a/TelegramUI/CompomentsThemes.swift b/TelegramUI/CompomentsThemes.swift index fd4880d261..e3f4d38ac5 100644 --- a/TelegramUI/CompomentsThemes.swift +++ b/TelegramUI/CompomentsThemes.swift @@ -14,3 +14,16 @@ extension NavigationBarTheme { self.init(buttonColor: theme.buttonColor, primaryTextColor: theme.primaryTextColor, backgroundColor: theme.backgroundColor, separatorColor: theme.separatorColor) } } + +extension ActionSheetControllerTheme { + convenience init(presentationTheme: PresentationTheme) { + let actionSheet = presentationTheme.actionSheet + self.init(dimColor: actionSheet.dimColor, backgroundType: actionSheet.backgroundType == .light ? .light : .dark, itemBackgroundColor: actionSheet.itemBackgroundColor, itemHighlightedBackgroundColor: actionSheet.itemHighlightedBackgroundColor, standardActionTextColor: actionSheet.standardActionTextColor, destructiveActionTextColor: actionSheet.destructiveActionTextColor, disabledActionTextColor: actionSheet.disabledActionTextColor, primaryTextColor: actionSheet.primaryTextColor, secondaryTextColor: actionSheet.secondaryTextColor, controlAccentColor: actionSheet.controlAccentColor) + } +} + +extension ActionSheetController { + convenience init(presentationTheme: PresentationTheme) { + self.init(theme: ActionSheetControllerTheme(presentationTheme: presentationTheme)) + } +} diff --git a/TelegramUI/ContactsPeerItem.swift b/TelegramUI/ContactsPeerItem.swift index 7297e5da39..de508d35b5 100644 --- a/TelegramUI/ContactsPeerItem.swift +++ b/TelegramUI/ContactsPeerItem.swift @@ -180,6 +180,8 @@ class ContactsPeerItem: ListViewItem { private let separatorHeight = 1.0 / UIScreen.main.scale +private let avatarFont: UIFont = UIFont(name: "ArialRoundedMTBold", size: 15.0)! + class ContactsPeerItemNode: ItemListRevealOptionsItemNode { private let backgroundNode: ASDisplayNode private let separatorNode: ASDisplayNode @@ -212,7 +214,7 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode { self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - self.avatarNode = AvatarNode(font: Font.regular(15.0)) + self.avatarNode = AvatarNode(font: avatarFont) self.avatarNode.isLayerBacked = true self.titleNode = TextNode() diff --git a/TelegramUI/ContactsVCardItem.swift b/TelegramUI/ContactsVCardItem.swift index 022ecfb6eb..6a0a73eae6 100644 --- a/TelegramUI/ContactsVCardItem.swift +++ b/TelegramUI/ContactsVCardItem.swift @@ -65,6 +65,8 @@ class ContactsVCardItem: ListViewItem { private let separatorHeight = 1.0 / UIScreen.main.scale +private let avatarFont: UIFont = UIFont(name: "ArialRoundedMTBold", size: 15.0)! + class ContactsVCardItemNode: ListViewItemNode { private let separatorNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode @@ -85,7 +87,7 @@ class ContactsVCardItemNode: ListViewItemNode { self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true - self.avatarNode = AvatarNode(font: Font.regular(15.0)) + self.avatarNode = AvatarNode(font: avatarFont) self.avatarNode.isLayerBacked = true self.titleNode = TextNode() diff --git a/TelegramUI/DebugController.swift b/TelegramUI/DebugController.swift index 17678bb548..2756d15aff 100644 --- a/TelegramUI/DebugController.swift +++ b/TelegramUI/DebugController.swift @@ -80,7 +80,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { func item(_ arguments: DebugControllerArguments) -> ListViewItem { switch self { case let .sendLogs(theme): - return ItemListDisclosureItem(theme: theme, title: "Seng Logs", label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(theme: theme, title: "Send Logs", label: "", sectionId: self.section, style: .blocks, action: { let _ = (Logger.shared.collectLogs() |> deliverOnMainQueue).start(next: { logs in let controller = PeerSelectionController(account: arguments.account) diff --git a/TelegramUI/DefaultDarkPresentationTheme.swift b/TelegramUI/DefaultDarkPresentationTheme.swift index 878cef3502..9e8ec6baf5 100644 --- a/TelegramUI/DefaultDarkPresentationTheme.swift +++ b/TelegramUI/DefaultDarkPresentationTheme.swift @@ -1,7 +1,7 @@ import Foundation import UIKit -private let accentColor: UIColor = UIColor(rgb: 0xb2b2b2) +private let accentColor: UIColor = UIColor(rgb: 0xffffff) private let destructiveColor: UIColor = .red private let rootStatusBar = PresentationThemeRootNavigationStatusBar( @@ -9,25 +9,26 @@ private let rootStatusBar = PresentationThemeRootNavigationStatusBar( ) private let rootTabBar = PresentationThemeRootTabBar( - backgroundColor: UIColor(rgb: 0x121212), - separatorColor: UIColor(rgb: 0x1f1f1f), - iconColor: UIColor(rgb: 0x5e5e5e), + backgroundColor: UIColor(rgb: 0x1c1c1d), + separatorColor: UIColor(rgb: 0x000000), + iconColor: UIColor(rgb: 0x808080), selectedIconColor: accentColor, - textColor: UIColor(rgb: 0x5e5e5e), + textColor: UIColor(rgb: 0x929292), selectedTextColor: accentColor, - badgeBackgroundColor: UIColor(rgb: 0xff3600), - badgeTextColor: .white) + badgeBackgroundColor: .red, //!!! + badgeTextColor: .white //!!! +) private let rootNavigationBar = PresentationThemeRootNavigationBar( buttonColor: accentColor, primaryTextColor: accentColor, secondaryTextColor: UIColor(rgb: 0x5e5e5e), - controlColor: UIColor(rgb: 0x5e5e5e), + controlColor: accentColor, accentTextColor: accentColor, - backgroundColor: UIColor(rgb: 0x121212), - separatorColor: UIColor(rgb: 0x1a1a1a), - badgeBackgroundColor: UIColor(rgb: 0xff3600), - badgeTextColor: .white + backgroundColor: UIColor(rgb: 0x1c1c1d), + separatorColor: UIColor(rgb: 0x000000), + badgeBackgroundColor: UIColor(rgb: 0xffffff), + badgeTextColor: UIColor(rgb: 0x1c1c1d) ) private let activeNavigationSearchBar = PresentationThemeActiveNavigationSearchBar( @@ -55,122 +56,133 @@ private let switchColors = PresentationThemeSwitch( ) private let list = PresentationThemeList( - blocksBackgroundColor: UIColor(rgb: 0x121212), - plainBackgroundColor: UIColor(rgb: 0x121212), - itemPrimaryTextColor: UIColor(rgb: 0xb2b2b2), - itemSecondaryTextColor: UIColor(rgb: 0x545454), - itemDisabledTextColor: UIColor(rgb: 0x4d4d4d), + blocksBackgroundColor: UIColor(rgb: 0x000000), + plainBackgroundColor: UIColor(rgb: 0x000000), + itemPrimaryTextColor: UIColor(rgb: 0xffffff), + itemSecondaryTextColor: UIColor(rgb: 0x545454), //!!! + itemDisabledTextColor: UIColor(rgb: 0x4d4d4d), //!!! itemAccentColor: accentColor, itemDestructiveColor: destructiveColor, - itemPlaceholderTextColor: UIColor(rgb: 0x4d4d4d), - itemBackgroundColor: UIColor(rgb: 0x121212), - itemHighlightedBackgroundColor: UIColor(rgb: 0x1b1b1b), - itemSeparatorColor: UIColor(rgb: 0x1a1a1a), - disclosureArrowColor: UIColor(rgb: 0x545454), - sectionHeaderTextColor: UIColor(rgb: 0x545454), - freeTextColor: UIColor(rgb: 0x545454), - freeTextErrorColor: UIColor(rgb: 0xcf3030), - freeTextSuccessColor: UIColor(rgb: 0x30cf30), + itemPlaceholderTextColor: UIColor(rgb: 0x4d4d4d), //!!! + itemBackgroundColor: UIColor(rgb: 0x1c1c1d), + itemHighlightedBackgroundColor: UIColor(rgb: 0x1b1b1b), //!!! + itemSeparatorColor: UIColor(rgb: 0x000000), + disclosureArrowColor: UIColor(rgb: 0x545454), //!!! + sectionHeaderTextColor: UIColor(rgb: 0x8d8e93), + freeTextColor: UIColor(rgb: 0x8d8e93), + freeTextErrorColor: UIColor(rgb: 0xcf3030), //!!! + freeTextSuccessColor: UIColor(rgb: 0x30cf30), //!!! itemSwitchColors: switchColors ) private let chatList = PresentationThemeChatList( - backgroundColor: UIColor(rgb: 0x121212), - itemSeparatorColor: UIColor(rgb: 0x1a1a1a), - itemBackgroundColor: UIColor(rgb: 0x121212), - pinnedItemBackgroundColor: UIColor(rgb: 0x121212), - itemHighlightedBackgroundColor: UIColor(rgb: 0x1b1b1b), - titleColor: UIColor(rgb: 0xb2b2b2), - secretTitleColor: UIColor(rgb: 0xb2b2b2), - dateTextColor: UIColor(rgb: 0x545454), - authorNameColor: UIColor(rgb: 0xb2b2b2), - messageTextColor: UIColor(rgb: 0x545454), - messageDraftTextColor: UIColor(rgb: 0xdd4b39), - checkmarkColor: UIColor(rgb: 0x545454), - pendingIndicatorColor: UIColor(rgb: 0x545454), - muteIconColor: UIColor(rgb: 0x626262), - unreadBadgeActiveBackgroundColor: UIColor(rgb: 0xb2b2b2), - unreadBadgeActiveTextColor: UIColor(rgb: 0x121212), - unreadBadgeInactiveBackgroundColor: UIColor(rgb: 0x626262), - unreadBadgeInactiveTextColor:UIColor(rgb: 0x121212), - pinnedBadgeColor: UIColor(rgb: 0x121212), - pinnedSearchBarColor: UIColor(rgb: 0x545454), - regularSearchBarColor: UIColor(rgb: 0x545454), - sectionHeaderFillColor: UIColor(rgb: 0x000000), - sectionHeaderTextColor: UIColor(rgb: 0x545454), + backgroundColor: UIColor(rgb: 0x000000), + itemSeparatorColor: UIColor(rgb: 0x252525), + itemBackgroundColor: UIColor(rgb: 0x000000), + pinnedItemBackgroundColor: UIColor(rgb: 0x1c1c1d), + itemHighlightedBackgroundColor: UIColor(rgb: 0x1b1b1b), //!!! + titleColor: UIColor(rgb: 0xffffff), + secretTitleColor: UIColor(rgb: 0xb2b2b2), //!!! + dateTextColor: UIColor(rgb: 0x8e8e93), + authorNameColor: UIColor(rgb: 0xffffff), + messageTextColor: UIColor(rgb: 0x8e8e93), + messageDraftTextColor: UIColor(rgb: 0xdd4b39), //!!! + checkmarkColor: UIColor(rgb: 0xffffff), + pendingIndicatorColor: UIColor(rgb: 0xffffff), //!!! + muteIconColor: UIColor(rgb: 0x8e8e93), //!!! + unreadBadgeActiveBackgroundColor: UIColor(rgb: 0xffffff), + unreadBadgeActiveTextColor: UIColor(rgb: 0x000000), + unreadBadgeInactiveBackgroundColor: UIColor(rgb: 0x666666), + unreadBadgeInactiveTextColor:UIColor(rgb: 0x000000), + pinnedBadgeColor: UIColor(rgb: 0x767677), + pinnedSearchBarColor: UIColor(rgb: 0x272728), + regularSearchBarColor: UIColor(rgb: 0x272728), //!!! + sectionHeaderFillColor: UIColor(rgb: 0x000000), //!!! + sectionHeaderTextColor: UIColor(rgb: 0x545454), //!!! searchBarKeyboardColor: .dark ) private let bubble = PresentationThemeChatBubble( - incomingFillColor: UIColor(rgb: 0x1b1b1b), - incomingFillHighlightedColor: UIColor(rgb: 0x4b4b4b), - incomingStrokeColor: UIColor(rgb: 0x000000), - outgoingFillColor: UIColor(rgb: 0x1b1b1b), - outgoingFillHighlightedColor: UIColor(rgb: 0x4b4b4b), + incomingFillColor: UIColor(rgb: 0x1f1f1f), + incomingFillHighlightedColor: UIColor(rgb: 0x4b4b4b), //!!! + incomingStrokeColor: UIColor(rgb: 0x000000), //!!! + outgoingFillColor: UIColor(rgb: 0x313131), + outgoingFillHighlightedColor: UIColor(rgb: 0x4b4b4b), //!!! outgoingStrokeColor: UIColor(rgb: 0x000000), - freeformFillColor: UIColor(rgb: 0x1b1b1b), - freeformFillHighlightedColor: UIColor(rgb: 0x4b4b4b), + freeformFillColor: UIColor(rgb: 0x1f1f1f), + freeformFillHighlightedColor: UIColor(rgb: 0x4b4b4b), //!!! freeformStrokeColor: UIColor(rgb: 0x000000), - infoFillColor: UIColor(rgb: 0x1b1b1b), + infoFillColor: UIColor(rgb: 0x1f1f1f), infoStrokeColor: UIColor(rgb: 0x000000), - incomingPrimaryTextColor: UIColor(rgb: 0xb2b2b2), - incomingSecondaryTextColor: UIColor(rgb: 0x545454), - incomingLinkTextColor: accentColor, - incomingLinkHighlightColor: accentColor.withAlphaComponent(0.5), - outgoingPrimaryTextColor: UIColor(rgb: 0xb2b2b2), - outgoingSecondaryTextColor: UIColor(rgb: 0x545454), - outgoingLinkTextColor: accentColor, - outgoingLinkHighlightColor: accentColor.withAlphaComponent(0.5), - infoPrimaryTextColor: UIColor(rgb: 0xb2b2b2), - infoLinkTextColor: accentColor, - incomingAccentColor: accentColor, - outgoingAccentColor: accentColor, - outgoingCheckColor: UIColor(rgb: 0x545454), - incomingPendingActivityColor: UIColor(rgb: 0x545454), - outgoingPendingActivityColor: UIColor(rgb: 0x545454), - mediaDateAndStatusFillColor: UIColor(white: 0.0, alpha: 0.5), + incomingPrimaryTextColor: UIColor(rgb: 0xffffff), + incomingSecondaryTextColor: UIColor(rgb: 0xacacac), //!!! + incomingLinkTextColor: accentColor, //!!! + incomingLinkHighlightColor: accentColor.withAlphaComponent(0.5), //!!! + outgoingPrimaryTextColor: UIColor(rgb: 0xffffff), //!!! + outgoingSecondaryTextColor: UIColor(rgb: 0xacacac), //!!! + outgoingLinkTextColor: accentColor, //!!! + outgoingLinkHighlightColor: accentColor.withAlphaComponent(0.5), //!!! + infoPrimaryTextColor: UIColor(rgb: 0xffffff), + infoLinkTextColor: accentColor, //!!! + incomingAccentColor: UIColor(rgb: 0xacacac), //!!! + outgoingAccentColor: UIColor(rgb: 0xacacac), + outgoingCheckColor: UIColor(rgb: 0xacacac), + incomingPendingActivityColor: UIColor(rgb: 0xacacac), //!!! + outgoingPendingActivityColor: UIColor(rgb: 0xacacac), + mediaDateAndStatusFillColor: UIColor(white: 0.0, alpha: 0.5), //!!! mediaDateAndStatusTextColor: .white, - incomingFileTitleColor: UIColor(rgb: 0xb2b2b2), - outgoingFileTitleColor: UIColor(rgb: 0xb2b2b2), - incomingFileDescriptionColor: UIColor(rgb: 0x545454), - outgoingFileDescriptionColor: UIColor(rgb: 0x545454), - incomingFileDurationColor: UIColor(rgb: 0x545454), - outgoingFileDurationColor: UIColor(rgb: 0x545454), - shareButtonFillColor: UIColor(rgb: 0xffffff, alpha: 0.2), - shareButtonForegroundColor: UIColor(rgb: 0xb2b2b2), - mediaOverlayControlBackgroundColor: UIColor(white: 0.0, alpha: 0.6), - mediaOverlayControlForegroundColor: UIColor(white: 1.0, alpha: 0.6), - actionButtonsFillColor: UIColor(rgb: 0x1b1b1b), - actionButtonsTextColor: UIColor(rgb: 0xb2b2b2) + incomingFileTitleColor: UIColor(rgb: 0xffffff), + outgoingFileTitleColor: UIColor(rgb: 0xffffff), + incomingFileDescriptionColor: UIColor(rgb: 0xacacac), //!!! + outgoingFileDescriptionColor: UIColor(rgb: 0xacacac), + incomingFileDurationColor: UIColor(rgb: 0xacacac), + outgoingFileDurationColor: UIColor(rgb: 0xacacac), + shareButtonFillColor: UIColor(rgb: 0xffffff, alpha: 0.2), //!!! + shareButtonForegroundColor: UIColor(rgb: 0xb2b2b2), //!!! + mediaOverlayControlBackgroundColor: UIColor(white: 0.0, alpha: 0.6), //!!! + mediaOverlayControlForegroundColor: UIColor(white: 1.0, alpha: 0.6), //!!! + actionButtonsFillColor: UIColor(rgb: 0x1b1b1b), //!!! + actionButtonsTextColor: UIColor(rgb: 0xb2b2b2) //!!! ) private let serviceMessage = PresentationThemeServiceMessage( - serviceMessageFillColor: UIColor(rgb: 0xffffff, alpha: 0.2), - serviceMessagePrimaryTextColor: UIColor(rgb: 0xb2b2b2), - serviceMessageLinkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.2), - unreadBarFillColor: UIColor(rgb: 0x1b1b1b), + serviceMessageFillColor: UIColor(rgb: 0x1f1f1f, alpha: 1.0), + serviceMessagePrimaryTextColor: UIColor(rgb: 0xffffff), + serviceMessageLinkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.12), //!!! + unreadBarFillColor: UIColor(rgb: 0x1b1b1b), //!!! unreadBarStrokeColor: UIColor(rgb: 0x000000), - unreadBarTextColor: UIColor(rgb: 0xb2b2b2), - dateFillStaticColor: UIColor(rgb: 0xffffff, alpha: 0.2), + unreadBarTextColor: UIColor(rgb: 0xb2b2b2), //!!! + dateFillStaticColor: UIColor(rgb: 0x1f1f1f, alpha: 1.0), dateFillFloatingColor: UIColor(rgb: 0xffffff, alpha: 0.2), dateTextColor: UIColor(rgb: 0xb2b2b2) ) +private let inputPanelMediaRecordingControl = PresentationThemeChatInputPanelMediaRecordingControl( + buttonColor: accentColor, + micLevelColor: accentColor.withAlphaComponent(0.2), + activeIconColor: .black, + panelControlFillColor: UIColor(rgb: 0x1C1C1D), + panelControlStrokeColor: UIColor(rgb: 0x1C1C1D), + panelControlContentPrimaryColor: UIColor(rgb: 0x9597a0), //!!! + panelControlContentAccentColor: accentColor +) + private let inputPanel = PresentationThemeChatInputPanel( - panelBackgroundColor: UIColor(rgb: 0x1b1b1b), + panelBackgroundColor: UIColor(rgb: 0x1c1c1d), panelStrokeColor: UIColor(rgb: 0x000000), panelControlAccentColor: accentColor, - panelControlColor: UIColor(rgb: 0x545454), - panelControlDisabledColor: UIColor(rgb: 0x545454, alpha: 0.5), - panelControlDestructiveColor: UIColor(rgb: 0xff3b30), - inputBackgroundColor: UIColor(rgb: 0x121212), - inputStrokeColor: UIColor(rgb: 0x000000), - inputPlaceholderColor: UIColor(rgb: 0xb2b2b2, alpha: 0.5), - inputTextColor: UIColor(rgb: 0xb2b2b2), - inputControlColor: UIColor(rgb: 0xb2b2b2, alpha: 0.5), - primaryTextColor: UIColor(rgb: 0xb2b2b2), + panelControlColor: UIColor(rgb: 0x808080), + panelControlDisabledColor: UIColor(rgb: 0x808080, alpha: 0.5), //!!! + panelControlDestructiveColor: UIColor(rgb: 0xff3b30), //!!! + inputBackgroundColor: UIColor(rgb: 0x060606), + inputStrokeColor: UIColor(rgb: 0x060606), + inputPlaceholderColor: UIColor(rgb: 0x7b7b7b), + inputTextColor: UIColor(rgb: 0xffffff), + inputControlColor: UIColor(rgb: 0xb2b2b2, alpha: 0.5), //!!! + primaryTextColor: UIColor(rgb: 0xffffff), mediaRecordingDotColor: .white, - keyboardColor: .dark + keyboardColor: .dark, + mediaRecordingControl: inputPanelMediaRecordingControl ) private let inputMediaPanel = PresentationThemeInputMediaPanel( @@ -209,9 +221,23 @@ private let chat = PresentationThemeChat( historyNavigation: historyNavigation ) +private let actionSheet = PresentationThemeActionSheet( + dimColor: UIColor(white: 0.0, alpha: 0.5), + backgroundType: .dark, + itemBackgroundColor: UIColor(rgb: 0x1c1c1d, alpha: 0.8), //!!! + itemHighlightedBackgroundColor: UIColor(rgb: 0x000000, alpha: 0.5), //!!! + standardActionTextColor: accentColor, + destructiveActionTextColor: destructiveColor, + disabledActionTextColor: UIColor(rgb: 0x4d4d4d), //!!! + primaryTextColor: .white, + secondaryTextColor: UIColor(rgb: 0x5e5e5e), //!!! + controlAccentColor: accentColor +) + let defaultDarkPresentationTheme = PresentationTheme( rootController: rootController, list: list, chatList: chatList, - chat: chat + chat: chat, + actionSheet: actionSheet ) diff --git a/TelegramUI/DefaultPresentationTheme.swift b/TelegramUI/DefaultPresentationTheme.swift index 819a8d5673..4b41deea58 100644 --- a/TelegramUI/DefaultPresentationTheme.swift +++ b/TelegramUI/DefaultPresentationTheme.swift @@ -157,6 +157,16 @@ private let serviceMessage = PresentationThemeServiceMessage( dateTextColor: .white ) +private let inputPanelMediaRecordingControl = PresentationThemeChatInputPanelMediaRecordingControl( + buttonColor: accentColor, + micLevelColor: accentColor.withAlphaComponent(0.2), + activeIconColor: .white, + panelControlFillColor: UIColor(rgb: 0xf7f7f7), + panelControlStrokeColor: UIColor(rgb: 0xb2b2b2), + panelControlContentPrimaryColor: UIColor(rgb: 0x9597a0), + panelControlContentAccentColor: accentColor +) + private let inputPanel = PresentationThemeChatInputPanel( panelBackgroundColor: UIColor(rgb: 0xf2f4f6), panelStrokeColor: UIColor(rgb: 0xbdc2c7), @@ -171,7 +181,8 @@ private let inputPanel = PresentationThemeChatInputPanel( inputControlColor: UIColor(rgb: 0x9099A2, alpha: 0.6), primaryTextColor: .black, mediaRecordingDotColor: UIColor(rgb: 0xed2521), - keyboardColor: .light + keyboardColor: .light, + mediaRecordingControl: inputPanelMediaRecordingControl ) private let inputMediaPanel = PresentationThemeInputMediaPanel( @@ -210,9 +221,23 @@ private let chat = PresentationThemeChat( historyNavigation: historyNavigation ) +private let actionSheet = PresentationThemeActionSheet( + dimColor: UIColor(white: 0.0, alpha: 0.4), + backgroundType: .light, + itemBackgroundColor: UIColor(white: 1.0, alpha: 0.8), + itemHighlightedBackgroundColor: UIColor(white: 0.9, alpha: 0.7), + standardActionTextColor: accentColor, + destructiveActionTextColor: destructiveColor, + disabledActionTextColor: UIColor(rgb: 0x4d4d4d), + primaryTextColor: .black, + secondaryTextColor: UIColor(rgb: 0x5e5e5e), + controlAccentColor: accentColor +) + let defaultPresentationTheme = PresentationTheme( rootController: rootController, list: list, chatList: chatList, - chat: chat + chat: chat, + actionSheet: actionSheet ) diff --git a/TelegramUI/DeleteChatInputPanelNode.swift b/TelegramUI/DeleteChatInputPanelNode.swift index ba7b94446b..05b9962724 100644 --- a/TelegramUI/DeleteChatInputPanelNode.swift +++ b/TelegramUI/DeleteChatInputPanelNode.swift @@ -33,7 +33,7 @@ final class DeleteChatInputPanelNode: ChatInputPanelNode { self.interfaceInteraction?.deleteChat() } - override func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { + override func updateLayout(width: CGFloat, maxHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { if self.presentationInterfaceState != interfaceState { self.presentationInterfaceState = interfaceState diff --git a/TelegramUI/EmbedGalleryVideoItem.swift b/TelegramUI/EmbedGalleryVideoItem.swift index 6295db70a5..f0c0735a85 100644 --- a/TelegramUI/EmbedGalleryVideoItem.swift +++ b/TelegramUI/EmbedGalleryVideoItem.swift @@ -194,7 +194,7 @@ final class EmbedVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } - override func animateIn(from node: ASDisplayNode) { + override func animateIn(from node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void) { guard let videoNode = self.videoNode else { return } @@ -224,7 +224,7 @@ final class EmbedVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } - override func animateOut(to node: ASDisplayNode, completion: @escaping () -> Void) { + override func animateOut(to node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) { guard let videoNode = self.videoNode else { completion() return @@ -398,7 +398,8 @@ final class EmbedVideoGalleryItemNode: ZoomableContentGalleryItemNode { (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 GalleryTransitionArguments(transitionNode: overlayNode, addToTransitionSurface: { _ in + }) } return nil })) diff --git a/TelegramUI/FetchCachedRepresentations.swift b/TelegramUI/FetchCachedRepresentations.swift index 1080198af6..5e85b5525c 100644 --- a/TelegramUI/FetchCachedRepresentations.swift +++ b/TelegramUI/FetchCachedRepresentations.swift @@ -14,6 +14,8 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR return fetchCachedScaledImageRepresentation(account: account, resource: resource, resourceData: resourceData, representation: representation) } else if let representation = representation as? CachedVideoFirstFrameRepresentation { return fetchCachedVideoFirstFrameRepresentation(account: account, resource: resource, resourceData: resourceData, representation: representation) + } else if let representation = representation as? CachedScaledVideoFirstFrameRepresentation { + return fetchCachedScaledVideoFirstFrameRepresentation(account: account, resource: resource, resourceData: resourceData, representation: representation) } return .never() } @@ -173,3 +175,41 @@ private func fetchCachedVideoFirstFrameRepresentation(account: Account, resource return EmptyDisposable } |> runOn(account.graphicsThreadPool) } + +private func fetchCachedScaledVideoFirstFrameRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedScaledVideoFirstFrameRepresentation) -> Signal { + return account.postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedVideoFirstFrameRepresentation(), complete: true) |> mapToSignal { firstFrame -> Signal in + return Signal({ subscriber in + if let data = try? Data(contentsOf: URL(fileURLWithPath: firstFrame.path), options: [.mappedIfSafe]) { + if let image = UIImage(data: data) { + var randomId: Int64 = 0 + arc4random_buf(&randomId, 8) + let path = NSTemporaryDirectory() + "\(randomId)" + let url = URL(fileURLWithPath: path) + + let size = representation.size + + let colorImage = generateImage(size, contextGenerator: { size, context in + context.setBlendMode(.copy) + context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size)) + }, scale: 1.0)! + + if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { + CGImageDestinationSetProperties(colorDestination, nil) + + let colorQuality: Float = 0.5 + + let options = NSMutableDictionary() + options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + + CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary) + if CGImageDestinationFinalize(colorDestination) { + subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + subscriber.putCompletion() + } + } + } + } + return EmptyDisposable + }) |> runOn(account.graphicsThreadPool) + } +} diff --git a/TelegramUI/FetchManager.swift b/TelegramUI/FetchManager.swift index a72e13e1c0..1acacc845e 100644 --- a/TelegramUI/FetchManager.swift +++ b/TelegramUI/FetchManager.swift @@ -2,16 +2,21 @@ import Foundation import Postbox import TelegramCore import SwiftSignalKit +import Postbox private struct FetchManagerLocationEntryId: Hashable { + let location: FetchManagerLocation let resourceId: MediaResourceId let locationKey: FetchManagerLocationKey static func ==(lhs: FetchManagerLocationEntryId, rhs: FetchManagerLocationEntryId) -> Bool { + if lhs.location != rhs.location { + return false + } if !lhs.resourceId.isEqual(to: rhs.resourceId) { return false } - if !lhs.locationKey.isEqual(to: rhs.locationKey) { + if lhs.locationKey != rhs.locationKey { return false } return true @@ -24,44 +29,93 @@ private struct FetchManagerLocationEntryId: Hashable { private final class FetchManagerLocationEntry { let id: FetchManagerLocationEntryId + let episode: Int32 let resource: MediaResource + let fetchTag: MediaResourceFetchTag? var referenceCount: Int32 = 0 var elevatedPriorityReferenceCount: Int32 = 0 var userInitiatedPriorityIndices: [Int32] = [] var priorityKey: FetchManagerPriorityKey? { - if self.referenceCount > 0 { + if self.referenceCount >= 0 { return FetchManagerPriorityKey(locationKey: self.id.locationKey, hasElevatedPriority: self.elevatedPriorityReferenceCount > 0, userInitiatedPriority: userInitiatedPriorityIndices.last) } else { return nil } } - init(id: FetchManagerLocationEntryId, resource: MediaResource) { + init(id: FetchManagerLocationEntryId, episode: Int32, resource: MediaResource, fetchTag: MediaResourceFetchTag?) { self.id = id + self.episode = episode self.resource = resource + self.fetchTag = fetchTag } } -private final class FetchManagerCategoryLocationContext { +private final class FetchManagerActiveContext { + var disposable: Disposable? +} + +private final class FetchManagerStatusContext { + var disposable: Disposable? + var originalStatus: MediaResourceStatus? + var subscribers = Bag<(MediaResourceStatus) -> Void>() + + var hasEntry = false + + var isEmpty: Bool { + return !self.hasEntry && self.subscribers.isEmpty + } + + var combinedStatus: MediaResourceStatus? { + if let originalStatus = self.originalStatus { + if originalStatus == .Remote && self.hasEntry { + return .Fetching(isActive: false, progress: 0.0) + } else { + return originalStatus + } + } else { + return nil + } + } +} + +private final class FetchManagerCategoryContext { + private let postbox: Postbox + private let entryCompleted: (FetchManagerLocationEntryId) -> Void + private var topEntryIdAndPriority: (FetchManagerLocationEntryId, FetchManagerPriorityKey)? private var entries: [FetchManagerLocationEntryId: FetchManagerLocationEntry] = [:] - func withEntry(id: FetchManagerLocationEntryId, resource: MediaResource, _ f: (FetchManagerLocationEntry) -> Void) { + private var activeContexts: [FetchManagerLocationEntryId: FetchManagerActiveContext] = [:] + private var statusContexts: [FetchManagerLocationEntryId: FetchManagerStatusContext] = [:] + + init(postbox: Postbox, entryCompleted: @escaping (FetchManagerLocationEntryId) -> Void) { + self.postbox = postbox + self.entryCompleted = entryCompleted + } + + func withEntry(id: FetchManagerLocationEntryId, takeNew: (() -> (MediaResource, MediaResourceFetchTag?, Int32))?, _ f: (FetchManagerLocationEntry) -> Void) { let entry: FetchManagerLocationEntry let previousPriorityKey: FetchManagerPriorityKey? + if let current = self.entries[id] { entry = current previousPriorityKey = entry.priorityKey - } else { + } else if let takeNew = takeNew { previousPriorityKey = nil - entry = FetchManagerLocationEntry(id: id, resource: resource) + let (resource, fetchTag, episode) = takeNew() + entry = FetchManagerLocationEntry(id: id, episode: episode, resource: resource, fetchTag: fetchTag) self.entries[id] = entry + } else { + return } f(entry) + var removedEntries = false + let updatedPriorityKey = entry.priorityKey if previousPriorityKey != updatedPriorityKey { if let updatedPriorityKey = updatedPriorityKey { @@ -79,9 +133,70 @@ private final class FetchManagerCategoryLocationContext { self.topEntryIdAndPriority = nil } self.entries.removeValue(forKey: id) + removedEntries = true } } + self.maybeFindAndActivateNewTopEntry() + + if removedEntries { + var removedIds: [FetchManagerLocationEntryId] = [] + for (entryId, activeContext) in self.activeContexts { + if self.entries[entryId] == nil { + removedIds.append(entryId) + activeContext.disposable?.dispose() + } + } + for entryId in removedIds { + self.activeContexts.removeValue(forKey: entryId) + } + } + + if let activeContext = self.activeContexts[id] { + if activeContext.disposable == nil { + if let entry = self.entries[id] { + let entryCompleted = self.entryCompleted + activeContext.disposable = self.postbox.mediaBox.fetchedResource(entry.resource, tag: entry.fetchTag, implNext: true).start(next: { value in + entryCompleted(id) + }) + } else { + assertionFailure() + } + } + } + + if (previousPriorityKey != nil) != (updatedPriorityKey != nil) { + if let statusContext = self.statusContexts[id] { + if updatedPriorityKey != nil { + if !statusContext.hasEntry { + let previousStatus = statusContext.combinedStatus + statusContext.hasEntry = true + if let combinedStatus = statusContext.combinedStatus, combinedStatus != previousStatus { + for f in statusContext.subscribers.copyItems() { + f(combinedStatus) + } + } + } else { + assertionFailure() + } + } else { + if statusContext.hasEntry { + let previousStatus = statusContext.combinedStatus + statusContext.hasEntry = false + if let combinedStatus = statusContext.combinedStatus, combinedStatus != previousStatus { + for f in statusContext.subscribers.copyItems() { + f(combinedStatus) + } + } + } else { + assertionFailure() + } + } + } + } + } + + func maybeFindAndActivateNewTopEntry() { if self.topEntryIdAndPriority == nil && !self.entries.isEmpty { var topEntryIdAndPriority: (FetchManagerLocationEntryId, FetchManagerPriorityKey)? for (id, entry) in self.entries { @@ -100,54 +215,242 @@ private final class FetchManagerCategoryLocationContext { self.topEntryIdAndPriority = topEntryIdAndPriority } + + if let topEntryId = self.topEntryIdAndPriority?.0, self.activeContexts[topEntryId] == nil { + if let entry = self.entries[topEntryId] { + let activeContext = FetchManagerActiveContext() + self.activeContexts[topEntryId] = activeContext + let entryCompleted = self.entryCompleted + activeContext.disposable = self.postbox.mediaBox.fetchedResource(entry.resource, tag: entry.fetchTag, implNext: true).start(next: { value in + entryCompleted(topEntryId) + }) + } else { + assertionFailure() + } + } + } + + func cancelEntry(_ id: FetchManagerLocationEntryId) { + if let _ = self.entries[id] { + self.entries.removeValue(forKey: id) + + if let statusContext = self.statusContexts[id] { + if statusContext.hasEntry { + let previousStatus = statusContext.combinedStatus + statusContext.hasEntry = false + if let combinedStatus = statusContext.combinedStatus, combinedStatus != previousStatus { + for f in statusContext.subscribers.copyItems() { + f(combinedStatus) + } + } + } else { + assertionFailure() + } + } + } + + if let activeContext = self.activeContexts[id] { + activeContext.disposable?.dispose() + activeContext.disposable = nil + self.activeContexts.removeValue(forKey: id) + } + + if self.topEntryIdAndPriority?.0 == id { + self.topEntryIdAndPriority = nil + } + + self.maybeFindAndActivateNewTopEntry() + } + + func withFetchStatusContext(_ id: FetchManagerLocationEntryId, _ f: (FetchManagerStatusContext) -> Void) { + let statusContext: FetchManagerStatusContext + if let current = self.statusContexts[id] { + statusContext = current + } else { + statusContext = FetchManagerStatusContext() + self.statusContexts[id] = statusContext + if self.entries[id] != nil { + statusContext.hasEntry = true + } + } + + f(statusContext) + + if statusContext.isEmpty { + statusContext.disposable?.dispose() + self.statusContexts.removeValue(forKey: id) + } } var isEmpty: Bool { - return self.entries.isEmpty + return self.entries.isEmpty && self.activeContexts.isEmpty && self.statusContexts.isEmpty } } final class FetchManager { private let queue = Queue() - private let network: Network + private let postbox: Postbox + private var nextEpisodeId: Int32 = 0 + private var nextUserInitiatedIndex: Int32 = 0 - private var categoryLocationContexts: [FetchManagerCategoryLocationKey: FetchManagerCategoryLocationContext] = [:] + private var categoryContexts: [FetchManagerCategory: FetchManagerCategoryContext] = [:] - init(network: Network) { - self.network = network + init(postbox: Postbox) { + self.postbox = postbox } - private func withLocationContext(_ key: FetchManagerCategoryLocationKey, _ f: (FetchManagerCategoryLocationContext) -> Void) { + private func takeNextEpisodeId() -> Int32 { + let value = self.nextEpisodeId + self.nextEpisodeId += 1 + return value + } + + private func takeNextUserInitiatedIndex() -> Int32 { + let value = self.nextUserInitiatedIndex + self.nextUserInitiatedIndex += 1 + return value + } + + private func withCategoryContext(_ key: FetchManagerCategory, _ f: (FetchManagerCategoryContext) -> Void) { assert(self.queue.isCurrent()) - let context: FetchManagerCategoryLocationContext - if let current = self.categoryLocationContexts[key] { + let context: FetchManagerCategoryContext + if let current = self.categoryContexts[key] { context = current } else { - context = FetchManagerCategoryLocationContext() - self.categoryLocationContexts[key] = context + let queue = self.queue + context = FetchManagerCategoryContext(postbox: self.postbox, entryCompleted: { [weak self] id in + queue.async { + if let strongSelf = self { + strongSelf.withCategoryContext(key, { context in + context.cancelEntry(id) + }) + } + } + }) + self.categoryContexts[key] = context } f(context) if context.isEmpty { - self.categoryLocationContexts.removeValue(forKey: key) + self.categoryContexts.removeValue(forKey: key) } } - func interactivelyFetched(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, resource: MediaResource, elevatedPriority: Bool, userInitiated: Bool) -> Signal { + func interactivelyFetched(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, resource: MediaResource, fetchTag: MediaResourceFetchTag?, elevatedPriority: Bool, userInitiated: Bool) -> Signal { let queue = self.queue return Signal { [weak self] subscriber in if let strongSelf = self { - strongSelf.withLocationContext(FetchManagerCategoryLocationKey(location: location, category: category), { context in - context.withEntry(id: FetchManagerLocationEntryId(resourceId: resource.id, locationKey: locationKey), resource: resource, { entry in - + var assignedEpisode: Int32? + var assignedUserInitiatedIndex: Int32? + + strongSelf.withCategoryContext(category, { context in + context.withEntry(id: FetchManagerLocationEntryId(location: location, resourceId: resource.id, locationKey: locationKey), takeNew: { return (resource, fetchTag, strongSelf.takeNextEpisodeId()) }, { entry in + assignedEpisode = entry.episode + entry.referenceCount += 1 + if elevatedPriority { + entry.elevatedPriorityReferenceCount += 1 + } + if userInitiated { + let userInitiatedIndex = strongSelf.takeNextUserInitiatedIndex() + assignedUserInitiatedIndex = userInitiatedIndex + entry.userInitiatedPriorityIndices.append(userInitiatedIndex) + entry.userInitiatedPriorityIndices.sort() + } }) }) return ActionDisposable { queue.async { if let strongSelf = self { - + strongSelf.withCategoryContext(category, { context in + context.withEntry(id: FetchManagerLocationEntryId(location: location, resourceId: resource.id, locationKey: locationKey), takeNew: nil, { entry in + if entry.episode == assignedEpisode { + entry.referenceCount -= 1 + assert(entry.referenceCount >= 0) + if elevatedPriority { + entry.elevatedPriorityReferenceCount -= 1 + assert(entry.elevatedPriorityReferenceCount >= 0) + } + if let userInitiatedIndex = assignedUserInitiatedIndex { + if let index = entry.userInitiatedPriorityIndices.index(of: userInitiatedIndex) { + entry.userInitiatedPriorityIndices.remove(at: index) + } else { + assertionFailure() + } + } + } + }) + }) + } + } + } + } else { + return EmptyDisposable + } + } |> runOn(self.queue) + } + + func cancelInteractiveFetches(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, resource: MediaResource) { + self.queue.async { + self.withCategoryContext(category, { context in + context.cancelEntry(FetchManagerLocationEntryId(location: location, resourceId: resource.id, locationKey: locationKey)) + }) + } + } + + func fetchStatus(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, resource: MediaResource) -> Signal { + let queue = self.queue + return Signal { [weak self] subscriber in + if let strongSelf = self { + var assignedIndex: Int? + + let entryId = FetchManagerLocationEntryId(location: location, resourceId: resource.id, locationKey: locationKey) + strongSelf.withCategoryContext(category, { context in + context.withFetchStatusContext(entryId, { statusContext in + assignedIndex = statusContext.subscribers.add({ status in + subscriber.putNext(status) + if case .Local = status { + subscriber.putCompletion() + } + }) + if let status = statusContext.combinedStatus { + subscriber.putNext(status) + if case .Local = status { + subscriber.putCompletion() + } + } + if statusContext.disposable == nil { + statusContext.disposable = strongSelf.postbox.mediaBox.resourceStatus(resource).start(next: { status in + queue.async { + if let strongSelf = self { + strongSelf.withCategoryContext(category, { context in + context.withFetchStatusContext(entryId, { statusContext in + statusContext.originalStatus = status + if let combinedStatus = statusContext.combinedStatus { + for f in statusContext.subscribers.copyItems() { + f(combinedStatus) + } + } + }) + }) + } + } + }) + } + }) + }) + + return ActionDisposable { + queue.async { + if let strongSelf = self { + strongSelf.withCategoryContext(category, { context in + context.withFetchStatusContext(entryId, { statusContext in + if let assignedIndex = assignedIndex { + statusContext.subscribers.remove(assignedIndex) + } + }) + }) } } } diff --git a/TelegramUI/FetchManagerLocation.swift b/TelegramUI/FetchManagerLocation.swift index f7bc4f175d..6b7d01623f 100644 --- a/TelegramUI/FetchManagerLocation.swift +++ b/TelegramUI/FetchManagerLocation.swift @@ -6,28 +6,51 @@ enum FetchManagerCategory: Int32 { case file } -protocol FetchManagerLocationKey: class { - func isEqual(to: FetchManagerLocationKey) -> Bool - func isLess(than: FetchManagerLocationKey) -> Bool - var hashValue: Int { get } -} - -struct FetchManagerCategoryLocationKey: Hashable { - let location: FetchManagerLocation - let category: FetchManagerCategory +enum FetchManagerLocationKey: Comparable, Hashable { + case messageId(MessageId) + case free - var hashValue: Int { - return self.location.hashValue &* 31 &+ self.category.hashValue + static func ==(lhs: FetchManagerLocationKey, rhs: FetchManagerLocationKey) -> Bool { + switch lhs { + case let .messageId(id): + if case .messageId(id) = rhs { + return true + } else { + return false + } + case .free: + if case .free = rhs { + return true + } else { + return false + } + } } - static func ==(lhs: FetchManagerCategoryLocationKey, rhs: FetchManagerCategoryLocationKey) -> Bool { - if lhs.location != rhs.location { - return false + static func <(lhs: FetchManagerLocationKey, rhs: FetchManagerLocationKey) -> Bool { + switch lhs { + case let .messageId(lhsId): + if case let .messageId(rhsId) = rhs { + return lhsId < rhsId + } else { + return true + } + case .free: + if case .free = rhs { + return false + } else { + return false + } } - if lhs.category != rhs.category { - return false + } + + var hashValue: Int { + switch self { + case let .messageId(id): + return id.hashValue + case .free: + return 1 } - return true } } @@ -37,7 +60,7 @@ struct FetchManagerPriorityKey: Comparable { let userInitiatedPriority: Int32? static func ==(lhs: FetchManagerPriorityKey, rhs: FetchManagerPriorityKey) -> Bool { - if !lhs.locationKey.isEqual(to: rhs.locationKey) { + if lhs.locationKey != rhs.locationKey { return false } if lhs.hasElevatedPriority != rhs.hasElevatedPriority { @@ -52,7 +75,7 @@ struct FetchManagerPriorityKey: Comparable { static func <(lhs: FetchManagerPriorityKey, rhs: FetchManagerPriorityKey) -> Bool { if let lhsUserInitiatedPriority = lhs.userInitiatedPriority, let rhsUserInitiatedPriority = rhs.userInitiatedPriority { if lhsUserInitiatedPriority != rhsUserInitiatedPriority { - if lhsUserInitiatedPriority < rhsUserInitiatedPriority { + if lhsUserInitiatedPriority > rhsUserInitiatedPriority { return false } else { return true @@ -74,7 +97,7 @@ struct FetchManagerPriorityKey: Comparable { } } - return lhs.locationKey.isLess(than: rhs.locationKey) + return lhs.locationKey < rhs.locationKey } } diff --git a/TelegramUI/FetchMediaUtils.swift b/TelegramUI/FetchMediaUtils.swift new file mode 100644 index 0000000000..b1ab03cc78 --- /dev/null +++ b/TelegramUI/FetchMediaUtils.swift @@ -0,0 +1,25 @@ +import Foundation +import TelegramCore +import Postbox +import SwiftSignalKit + +func freeMediaFileInteractiveFetched(account: Account, file: TelegramMediaFile) -> Signal { + return account.postbox.mediaBox.fetchedResource(file.resource, tag: TelegramMediaResourceFetchTag(statsCategory: file.isVideo ? .video : .file)) +} + +func cancelFreeMediaFileInteractiveFetch(account: Account, file: TelegramMediaFile) { + account.postbox.mediaBox.cancelInteractiveResourceFetch(file.resource) +} + +func messageMediaFileInteractiveFetched(account: Account, messageId: MessageId, file: TelegramMediaFile) -> Signal { + return account.telegramApplicationContext.fetchManager.interactivelyFetched(category: .file, location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: file.resource, fetchTag: TelegramMediaResourceFetchTag(statsCategory: file.isVideo ? .video : .file), elevatedPriority: false, userInitiated: true) +} + +func messageMediaFileCancelInteractiveFetch(account: Account, messageId: MessageId, file: TelegramMediaFile) { + account.telegramApplicationContext.fetchManager.cancelInteractiveFetches(category: .file, location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: file.resource) + +} + +func messageMediaFileStatus(account: Account, messageId: MessageId, file: TelegramMediaFile) -> Signal { + return account.telegramApplicationContext.fetchManager.fetchStatus(category: .file, location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: file.resource) +} diff --git a/TelegramUI/FileMediaResourceStatus.swift b/TelegramUI/FileMediaResourceStatus.swift index 53b0b3e898..1a5eecf6dd 100644 --- a/TelegramUI/FileMediaResourceStatus.swift +++ b/TelegramUI/FileMediaResourceStatus.swift @@ -13,7 +13,7 @@ enum FileMediaResourceStatus { case playbackStatus(FileMediaResourcePlaybackStatus) } -func fileMediaResourceStatus(account: Account, file: TelegramMediaFile, message: Message) -> Signal { +func messageFileMediaResourceStatus(account: Account, file: TelegramMediaFile, message: Message) -> Signal { let playbackStatus: Signal if let applicationContext = account.applicationContext as? TelegramApplicationContext, let (playlistId, itemId) = peerMessageAudioPlaylistAndItemIds(message) { playbackStatus = applicationContext.mediaManager.filteredPlaylistPlayerStateAndStatus(playlistId: playlistId, itemId: itemId) @@ -35,7 +35,7 @@ func fileMediaResourceStatus(account: Account, file: TelegramMediaFile, message: } if message.flags.isSending { - return combineLatest(chatMessageFileStatus(account: account, file: file), account.pendingMessageManager.pendingMessageStatus(message.id), playbackStatus) + return combineLatest(messageMediaFileStatus(account: account, messageId: message.id, file: file), account.pendingMessageManager.pendingMessageStatus(message.id), playbackStatus) |> map { resourceStatus, pendingStatus, playbackStatus -> FileMediaResourceStatus in if let playbackStatus = playbackStatus { switch playbackStatus { @@ -51,13 +51,13 @@ func fileMediaResourceStatus(account: Account, file: TelegramMediaFile, message: } } } else if let pendingStatus = pendingStatus { - return .fetchStatus(.Fetching(progress: pendingStatus.progress)) + return .fetchStatus(.Fetching(isActive: pendingStatus.isRunning, progress: pendingStatus.progress)) } else { return .fetchStatus(resourceStatus) } } } else { - return combineLatest(chatMessageFileStatus(account: account, file: file), playbackStatus) + return combineLatest(messageMediaFileStatus(account: account, messageId: message.id, file: file), playbackStatus) |> map { resourceStatus, playbackStatus -> FileMediaResourceStatus in if let playbackStatus = playbackStatus { switch playbackStatus { diff --git a/TelegramUI/FileResources.swift b/TelegramUI/FileResources.swift index 8b09e12db2..0c59337af3 100644 --- a/TelegramUI/FileResources.swift +++ b/TelegramUI/FileResources.swift @@ -2,11 +2,3 @@ import Foundation import Postbox import SwiftSignalKit import TelegramCore - -func fileInteractiveFetched(account: Account, file: TelegramMediaFile) -> Signal { - return account.postbox.mediaBox.fetchedResource(file.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .file)) -} - -func fileCancelInteractiveFetch(account: Account, file: TelegramMediaFile) { - account.postbox.mediaBox.cancelInteractiveResourceFetch(file.resource) -} diff --git a/TelegramUI/GalleryController.swift b/TelegramUI/GalleryController.swift index 7a023c6f60..8627774553 100644 --- a/TelegramUI/GalleryController.swift +++ b/TelegramUI/GalleryController.swift @@ -101,13 +101,11 @@ func galleryItemForEntry(account: Account, theme: PresentationTheme, strings: Pr final class GalleryTransitionArguments { let transitionNode: ASDisplayNode - let transitionContainerNode: ASDisplayNode - let transitionBackgroundNode: ASDisplayNode + let addToTransitionSurface: (UIView) -> Void - init(transitionNode: ASDisplayNode, transitionContainerNode: ASDisplayNode, transitionBackgroundNode: ASDisplayNode) { + init(transitionNode: ASDisplayNode, addToTransitionSurface: @escaping (UIView) -> Void) { self.transitionNode = transitionNode - self.transitionContainerNode = transitionContainerNode - self.transitionBackgroundNode = transitionBackgroundNode + self.addToTransitionSurface = addToTransitionSurface } } @@ -292,7 +290,7 @@ class GalleryController: ViewController { if case let .MessageEntry(message, _, _, _) = self.entries[centralItemNode.index] { if let media = mediaForMessage(message: message), let transitionArguments = presentationArguments.transitionArguments(message.id, media), !forceAway { animatedOutNode = false - centralItemNode.animateOut(to: transitionArguments.transitionNode, completion: { + centralItemNode.animateOut(to: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface, completion: { animatedOutNode = true completion() }) @@ -324,12 +322,12 @@ class GalleryController: ViewController { self.galleryNode.statusBar = self.statusBar self.galleryNode.navigationBar = self.navigationBar - self.galleryNode.transitionNodeForCentralItem = { [weak self] in + self.galleryNode.transitionDataForCentralItem = { [weak self] in if let strongSelf = self { if let centralItemNode = strongSelf.galleryNode.pager.centralItemNode(), let presentationArguments = strongSelf.presentationArguments as? GalleryControllerPresentationArguments { if case let .MessageEntry(message, _, _, _) = strongSelf.entries[centralItemNode.index] { if let media = mediaForMessage(message: message), let transitionArguments = presentationArguments.transitionArguments(message.id, media) { - return transitionArguments.transitionNode + return (transitionArguments.transitionNode, transitionArguments.addToTransitionSurface) } } } @@ -424,7 +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) + centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface) self._hiddenMedia.set(.single((message.id, media))) } diff --git a/TelegramUI/GalleryControllerNode.swift b/TelegramUI/GalleryControllerNode.swift index 87781af6b1..d0096fb1b8 100644 --- a/TelegramUI/GalleryControllerNode.swift +++ b/TelegramUI/GalleryControllerNode.swift @@ -8,7 +8,7 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate { var navigationBar: NavigationBar? let footerNode: GalleryFooterNode var toolbarNode: ASDisplayNode? - var transitionNodeForCentralItem: (() -> ASDisplayNode?)? + var transitionDataForCentralItem: (() -> (ASDisplayNode?, (UIView) -> Void)?)? var dismiss: (() -> Void)? var containerLayout: (CGFloat, ContainerViewLayout)? @@ -208,9 +208,9 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - if let centralItemNode = self.pager.centralItemNode(), let transitionNodeForCentralItem = self.transitionNodeForCentralItem, let node = transitionNodeForCentralItem() { + if let centralItemNode = self.pager.centralItemNode(), let (transitionNodeForCentralItem, addToTransitionSurface) = self.transitionDataForCentralItem?(), let node = transitionNodeForCentralItem { contentAnimationCompleted = false - centralItemNode.animateOut(to: node, completion: { + centralItemNode.animateOut(to: node, addToTransitionSurface: addToTransitionSurface, completion: { contentAnimationCompleted = true completion() }) diff --git a/TelegramUI/GalleryItemNode.swift b/TelegramUI/GalleryItemNode.swift index f220a488f8..3ec07d955e 100644 --- a/TelegramUI/GalleryItemNode.swift +++ b/TelegramUI/GalleryItemNode.swift @@ -68,9 +68,9 @@ open class GalleryItemNode: ASDisplayNode { open func visibilityUpdated(isVisible: Bool) { } - open func animateIn(from node: ASDisplayNode) { + open func animateIn(from node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void) { } - open func animateOut(to node: ASDisplayNode, completion: @escaping () -> Void) { + open func animateOut(to node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) { } } diff --git a/TelegramUI/GridMessageItem.swift b/TelegramUI/GridMessageItem.swift index 8fd60ce747..270f2caa8d 100644 --- a/TelegramUI/GridMessageItem.swift +++ b/TelegramUI/GridMessageItem.swift @@ -196,8 +196,12 @@ final class GridMessageItemNode: GridItemNode { if let strongSelf = self { strongSelf.resourceStatus = status switch status { - case let .Fetching(progress): - strongSelf.progressNode.state = .Fetching(progress: progress) + case let .Fetching(isActive, progress): + var adjustedProgress = progress + if isActive { + adjustedProgress = max(adjustedProgress, 0.027) + } + strongSelf.progressNode.state = .Fetching(progress: adjustedProgress) strongSelf.progressNode.isHidden = false case .Local: strongSelf.progressNode.state = .None @@ -303,11 +307,11 @@ final class GridMessageItemNode: GridItemNode { if let resourceStatus = self.resourceStatus { switch resourceStatus { case .Fetching: - account.postbox.mediaBox.cancelInteractiveResourceFetch(file.resource) + messageMediaFileCancelInteractiveFetch(account: account, messageId: messageId, file: file) case .Local: controllerInteraction.openMessage(messageId) case .Remote: - self.fetchDisposable.set(account.postbox.mediaBox.fetchedResource(file.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .file)).start()) + self.fetchDisposable.set(messageMediaFileInteractiveFetched(account: account, messageId: messageId, file: file).start()) } } } else { diff --git a/TelegramUI/GroupInfoController.swift b/TelegramUI/GroupInfoController.swift index 1736381190..36136b69d6 100644 --- a/TelegramUI/GroupInfoController.swift +++ b/TelegramUI/GroupInfoController.swift @@ -1003,7 +1003,8 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl }, presentController: { controller, presentationArguments in presentControllerImpl?(controller, presentationArguments) }, changeNotificationMuteSettings: { - let controller = ActionSheetController() + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + let controller = ActionSheetController(presentationTheme: presentationData.theme) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() } @@ -1020,28 +1021,28 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl } controller.setItemGroups([ ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: "Enable", action: { + ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsEnable, action: { dismissAction() notificationAction(0) }), - ActionSheetButtonItem(title: "Mute for 1 hour", action: { + ActionSheetButtonItem(title: muteForIntervalString(strings: presentationData.strings, value: 1 * 60 * 60), action: { dismissAction() notificationAction(1 * 60 * 60) }), - ActionSheetButtonItem(title: "Mute for 8 hours", action: { + ActionSheetButtonItem(title: muteForIntervalString(strings: presentationData.strings, value: 8 * 60 * 60), action: { dismissAction() notificationAction(8 * 60 * 60) }), - ActionSheetButtonItem(title: "Mute for 2 days", action: { + ActionSheetButtonItem(title: muteForIntervalString(strings: presentationData.strings, value: 2 * 24 * 60 * 60), action: { dismissAction() notificationAction(2 * 24 * 60 * 60) }), - ActionSheetButtonItem(title: "Disable", action: { + ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsDisable, action: { dismissAction() notificationAction(Int32.max) }) ]), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: "Cancel", action: { dismissAction() })]) + ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ]) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }, changeNotificationSoundSettings: { @@ -1182,7 +1183,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let actionSheet = ActionSheetController() + let actionSheet = ActionSheetController(presentationTheme: presentationData.theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: "Remove \(peer.displayTitle)?", color: .destructive, action: { [weak actionSheet] in actionSheet?.dismissAnimated() @@ -1236,7 +1237,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl pushControllerImpl?(convertToSupergroupController(account: account, peerId: peerId)) }, leave: { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let controller = ActionSheetController() + let controller = ActionSheetController(presentationTheme: presentationData.theme) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() } @@ -1379,7 +1380,8 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl } } if let (node, _) = result { - return GalleryTransitionArguments(transitionNode: node, transitionContainerNode: controller.displayNode, transitionBackgroundNode: controller.displayNode) + return GalleryTransitionArguments(transitionNode: node, addToTransitionSurface: { _ in + }) } } return nil diff --git a/TelegramUI/HashtagChatInputContextPanelNode.swift b/TelegramUI/HashtagChatInputContextPanelNode.swift index fda290eec7..9f2841f7af 100644 --- a/TelegramUI/HashtagChatInputContextPanelNode.swift +++ b/TelegramUI/HashtagChatInputContextPanelNode.swift @@ -138,6 +138,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { //options.insert(.LowLatency) } else { options.insert(.AnimateTopItemPosition) + options.insert(.AnimateCrossfade) } var insets = UIEdgeInsets() diff --git a/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift b/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift index 54d6d49b9b..bba51a1756 100644 --- a/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift +++ b/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift @@ -129,6 +129,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont //options.insert(.LowLatency) } else { //options.insert(.AnimateTopItemPosition) + //options.insert(.AnimateCrossfade) } self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in diff --git a/TelegramUI/HorizontalPeerItem.swift b/TelegramUI/HorizontalPeerItem.swift index 9a3959fa96..c5c1edf6cc 100644 --- a/TelegramUI/HorizontalPeerItem.swift +++ b/TelegramUI/HorizontalPeerItem.swift @@ -5,18 +5,25 @@ import AsyncDisplayKit import TelegramCore import SwiftSignalKit +enum HorizontalPeerItemMode { + case list + case actionSheet +} + final class HorizontalPeerItem: ListViewItem { let theme: PresentationTheme let strings: PresentationStrings + let mode: HorizontalPeerItemMode let account: Account let peer: Peer let action: (Peer) -> Void let isPeerSelected: (PeerId) -> Bool let customWidth: CGFloat? - init(theme: PresentationTheme, strings: PresentationStrings, account: Account, peer: Peer, action: @escaping (Peer) -> Void, isPeerSelected: @escaping (PeerId) -> Bool, customWidth: CGFloat?) { + init(theme: PresentationTheme, strings: PresentationStrings, mode: HorizontalPeerItemMode, account: Account, peer: Peer, action: @escaping (Peer) -> Void, isPeerSelected: @escaping (PeerId) -> Bool, customWidth: CGFloat?) { self.theme = theme self.strings = strings + self.mode = mode self.account = account self.peer = peer self.action = action @@ -87,9 +94,17 @@ final class HorizontalPeerItemNode: ListViewItemNode { return { [weak self] item, width in let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: 92.0, height: item.customWidth ?? 80.0), insets: UIEdgeInsets()) return (itemLayout, { animated in + let textColor: UIColor + switch item.mode { + case .list: + textColor = item.theme.list.itemPrimaryTextColor + case .actionSheet: + textColor = .black + } + if let strongSelf = self { strongSelf.item = item - + strongSelf.peerNode.textColor = textColor strongSelf.peerNode.setup(account: item.account, peer: item.peer, chatPeer: nil, numberOfLines: 1) strongSelf.peerNode.frame = CGRect(origin: CGPoint(), size: itemLayout.size) strongSelf.peerNode.updateSelection(selected: item.isPeerSelected(item.peer.id), animated: false) diff --git a/TelegramUI/HorizontalStickerGridItem.swift b/TelegramUI/HorizontalStickerGridItem.swift index a0493a3e7f..491381f458 100644 --- a/TelegramUI/HorizontalStickerGridItem.swift +++ b/TelegramUI/HorizontalStickerGridItem.swift @@ -66,7 +66,7 @@ final class HorizontalStickerGridItemNode: GridItemNode { if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1.id != file.id { if let dimensions = file.dimensions { self.imageNode.setSignal(account: account, signal: chatMessageSticker(account: account, file: file, small: true)) - self.stickerFetchedDisposable.set(fileInteractiveFetched(account: account, file: file).start()) + self.stickerFetchedDisposable.set(freeMediaFileInteractiveFetched(account: account, file: file).start()) self.currentState = (account, file, dimensions) self.setNeedsLayout() diff --git a/TelegramUI/InstalledStickerPacksController.swift b/TelegramUI/InstalledStickerPacksController.swift index 3727b6b294..7716034dff 100644 --- a/TelegramUI/InstalledStickerPacksController.swift +++ b/TelegramUI/InstalledStickerPacksController.swift @@ -367,7 +367,8 @@ public func installedStickerPacksController(account: Account, mode: InstalledSti } } }, removePack: { id in - let controller = ActionSheetController() + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + let controller = ActionSheetController(presentationTheme: presentationData.theme) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() } diff --git a/TelegramUI/InstantImageGalleryItem.swift b/TelegramUI/InstantImageGalleryItem.swift index a8492afe34..b4e75a8802 100644 --- a/TelegramUI/InstantImageGalleryItem.swift +++ b/TelegramUI/InstantImageGalleryItem.swift @@ -118,7 +118,7 @@ final class InstantImageGalleryItemNode: ZoomableContentGalleryItemNode { self.accountAndMedia = (account, file) } - override func animateIn(from node: ASDisplayNode) { + override func animateIn(from node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void) { var transformedFrame = node.view.convert(node.view.bounds, to: self.imageNode.view) let transformedSuperFrame = node.view.convert(node.view.bounds, to: self.imageNode.view.superview) let transformedSelfFrame = node.view.convert(node.view.bounds, to: self.view) @@ -144,7 +144,7 @@ final class InstantImageGalleryItemNode: ZoomableContentGalleryItemNode { self.imageNode.layer.animateBounds(from: transformedFrame, to: self.imageNode.layer.bounds, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) } - override func animateOut(to node: ASDisplayNode, completion: @escaping () -> Void) { + override func animateOut(to node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) { var transformedFrame = node.view.convert(node.view.bounds, to: self.imageNode.view) let transformedSuperFrame = node.view.convert(node.view.bounds, to: self.imageNode.view.superview) let transformedSelfFrame = node.view.convert(node.view.bounds, to: self.view) diff --git a/TelegramUI/InstantPageController.swift b/TelegramUI/InstantPageController.swift index cbbb4dc3ec..4b0fb7173a 100644 --- a/TelegramUI/InstantPageController.swift +++ b/TelegramUI/InstantPageController.swift @@ -72,7 +72,7 @@ final class InstantPageController: ViewController { } override public func loadDisplayNode() { - self.displayNode = InstantPageControllerNode(account: self.account, settings: self.settings, strings: self.presentationData.strings, statusBar: self.statusBar, present: { [weak self] c, a in + self.displayNode = InstantPageControllerNode(account: self.account, settings: self.settings, presentationTheme: self.presentationData.theme, strings: self.presentationData.strings, statusBar: self.statusBar, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) }, openPeer: { [weak self] peerId in if let strongSelf = self { diff --git a/TelegramUI/InstantPageControllerNode.swift b/TelegramUI/InstantPageControllerNode.swift index 9a852c6e1e..82dcc5f5fd 100644 --- a/TelegramUI/InstantPageControllerNode.swift +++ b/TelegramUI/InstantPageControllerNode.swift @@ -9,6 +9,7 @@ import SafariServices final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { private let account: Account private var settings: InstantPagePresentationSettings? + private var presentationTheme: PresentationTheme private var strings: PresentationStrings private var theme: InstantPageTheme? private let present: (ViewController, Any?) -> Void @@ -43,8 +44,9 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { private let hiddenMediaDisposable = MetaDisposable() private let resolveUrlDisposable = MetaDisposable() - init(account: Account, settings: InstantPagePresentationSettings?, strings: PresentationStrings, statusBar: StatusBar, present: @escaping (ViewController, Any?) -> Void, openPeer: @escaping (PeerId) -> Void, navigateBack: @escaping () -> Void) { + init(account: Account, settings: InstantPagePresentationSettings?, presentationTheme: PresentationTheme, strings: PresentationStrings, statusBar: StatusBar, present: @escaping (ViewController, Any?) -> Void, openPeer: @escaping (PeerId) -> Void, navigateBack: @escaping () -> Void) { self.account = account + self.presentationTheme = presentationTheme self.strings = strings self.settings = settings self.theme = settings.flatMap(instantPageThemeForSettings) @@ -545,7 +547,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } case .longTap: if let url = self.urlForTapLocation(location) { - let actionSheet = ActionSheetController() + let actionSheet = ActionSheetController(presentationTheme: self.presentationTheme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetTextItem(title: url.url), ActionSheetButtonItem(title: self.self.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak self, weak actionSheet] in @@ -726,7 +728,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { for (_, itemNode) in strongSelf.visibleItemsWithViews { if let itemNode = itemNode as? InstantPageNode { if let transitionNode = itemNode.transitionNode(media: entry.media) { - return GalleryTransitionArguments(transitionNode: transitionNode, transitionContainerNode: strongSelf, transitionBackgroundNode: strongSelf) + return GalleryTransitionArguments(transitionNode: transitionNode, addToTransitionSurface: { _ in + }) } } } diff --git a/TelegramUI/InstantPageGalleryController.swift b/TelegramUI/InstantPageGalleryController.swift index 4bdf71b8b7..a8bcd2f123 100644 --- a/TelegramUI/InstantPageGalleryController.swift +++ b/TelegramUI/InstantPageGalleryController.swift @@ -148,7 +148,7 @@ class InstantPageGalleryController: ViewController { if !self.entries.isEmpty { if let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]), !forceAway { animatedOutNode = false - centralItemNode.animateOut(to: transitionArguments.transitionNode, completion: { + centralItemNode.animateOut(to: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface, completion: { animatedOutNode = true completion() }) @@ -180,11 +180,11 @@ class InstantPageGalleryController: ViewController { self.galleryNode.statusBar = self.statusBar self.galleryNode.navigationBar = self.navigationBar - self.galleryNode.transitionNodeForCentralItem = { [weak self] in + self.galleryNode.transitionDataForCentralItem = { [weak self] in if let strongSelf = self { if let centralItemNode = strongSelf.galleryNode.pager.centralItemNode(), let presentationArguments = strongSelf.presentationArguments as? InstantPageGalleryControllerPresentationArguments { if let transitionArguments = presentationArguments.transitionArguments(strongSelf.entries[centralItemNode.index]) { - return transitionArguments.transitionNode + return (transitionArguments.transitionNode, transitionArguments.addToTransitionSurface) } } } @@ -237,7 +237,7 @@ class InstantPageGalleryController: ViewController { if let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]) { nodeAnimatesItself = true - centralItemNode.animateIn(from: transitionArguments.transitionNode) + centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface) self._hiddenMedia.set(.single(self.entries[centralItemNode.index])) } diff --git a/TelegramUI/InstantPagePlayableVideoNode.swift b/TelegramUI/InstantPagePlayableVideoNode.swift index fb91356a26..3ae996225e 100644 --- a/TelegramUI/InstantPagePlayableVideoNode.swift +++ b/TelegramUI/InstantPagePlayableVideoNode.swift @@ -37,7 +37,7 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode { if let file = media.media as? TelegramMediaFile { self.imageNode.setSignal(account: account, signal: chatMessageVideo(account: account, video: file)) - self.fetchedDisposable.set(chatMessageFileInteractiveFetched(account: account, file: file).start()) + self.fetchedDisposable.set(freeMediaFileInteractiveFetched(account: account, file: file).start()) } } diff --git a/TelegramUI/InstantVideoNode.swift b/TelegramUI/InstantVideoNode.swift index aabaab8921..63150c2c13 100644 --- a/TelegramUI/InstantVideoNode.swift +++ b/TelegramUI/InstantVideoNode.swift @@ -163,12 +163,6 @@ final class InstantVideoNode: OverlayMediaItemNode { self.imageNode.setSignal(account: account, signal: chatMessageVideo(account: account, video: source.file)) - self.statusDisposable = (chatMessageFileStatus(account: account, file: source.file) |> deliverOnMainQueue).start(next: { [weak self] status in - if let strongSelf = self { - - } - }) - self.manager.sharedVideoContextManager.withSharedVideoContext(id: self.source.id, { [weak self] context in if let strongSelf = self, let context = context as? SharedInstantVideoContext { context.addPlaybackCompleted { diff --git a/TelegramUI/ItemListAvatarAndNameItem.swift b/TelegramUI/ItemListAvatarAndNameItem.swift index 3b61e710d7..164dea4784 100644 --- a/TelegramUI/ItemListAvatarAndNameItem.swift +++ b/TelegramUI/ItemListAvatarAndNameItem.swift @@ -160,6 +160,7 @@ class ItemListAvatarAndNameInfoItem: ListViewItem, ItemListItem { } } +private let avatarFont: UIFont = UIFont(name: "ArialRoundedMTBold", size: 28.0)! private let nameFont = Font.medium(19.0) private let statusFont = Font.regular(15.0) @@ -198,7 +199,7 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode { self.bottomStripeNode = ASDisplayNode() self.bottomStripeNode.isLayerBacked = true - self.avatarNode = AvatarNode(font: Font.regular(28.0)) + self.avatarNode = AvatarNode(font: avatarFont) self.updatingAvatarOverlay = ASImageNode() self.updatingAvatarOverlay.displayWithoutProcessing = true diff --git a/TelegramUI/ItemListPeerItem.swift b/TelegramUI/ItemListPeerItem.swift index f4382ed417..e84100861d 100644 --- a/TelegramUI/ItemListPeerItem.swift +++ b/TelegramUI/ItemListPeerItem.swift @@ -128,7 +128,7 @@ private let titleBoldFont = Font.medium(17.0) private let statusFont = Font.regular(14.0) private let labelFont = Font.regular(13.0) private let labelDisclosureFont = Font.regular(17.0) -private let avatarFont = Font.regular(17.0) +private let avatarFont: UIFont = UIFont(name: "ArialRoundedMTBold", size: 17.0)! class ItemListPeerItemNode: ItemListRevealOptionsItemNode { private let backgroundNode: ASDisplayNode diff --git a/TelegramUI/LegacyInstantVideoController.swift b/TelegramUI/LegacyInstantVideoController.swift index 8c6e285010..2aa72bbf0a 100644 --- a/TelegramUI/LegacyInstantVideoController.swift +++ b/TelegramUI/LegacyInstantVideoController.swift @@ -89,7 +89,8 @@ func legacyInstantVideoController(theme: PresentationTheme, panelFrame: CGRect, legacyController.bind(controller: baseController) legacyController.presentationCompleted = { [weak legacyController, weak baseController] in if let legacyController = legacyController, let baseController = baseController { - let controller = TGVideoMessageCaptureController(context: legacyController.context, assets: TGVideoMessageCaptureControllerAssets(send: PresentationResourcesChat.chatInputPanelSendButtonImage(theme)!, slideToCancel:PresentationResourcesChat.chatInputPanelMediaRecordingCancelArrowImage(theme)!, actionDelete: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Acessory Panels/MessageSelectionThrash"), color: theme.chat.inputPanel.panelControlAccentColor))!, transitionInView: { + let controllerTheme = TGVideoMessageCaptureControllerTheme(darkBackground: theme.rootController.statusBar.style.style == .White, panelSeparatorColor: theme.chat.inputPanel.panelStrokeColor, panelTime: theme.chat.inputPanel.primaryTextColor, panelDotColor: theme.chat.inputPanel.mediaRecordingDotColor, panelAccentColor: theme.chat.inputPanel.panelControlAccentColor) + let controller = TGVideoMessageCaptureController(context: legacyController.context, assets: TGVideoMessageCaptureControllerAssets(send: PresentationResourcesChat.chatInputPanelSendButtonImage(theme)!, slideToCancel:PresentationResourcesChat.chatInputPanelMediaRecordingCancelArrowImage(theme)!, actionDelete: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Acessory Panels/MessageSelectionThrash"), color: theme.chat.inputPanel.panelControlAccentColor), theme: controllerTheme)!, transitionInView: { return nil }, parentController: baseController, controlsFrame: panelFrame, isAlreadyLocked: { return false diff --git a/TelegramUI/LegacyLocationController.swift b/TelegramUI/LegacyLocationController.swift index 1fc268325a..33edc0cd4c 100644 --- a/TelegramUI/LegacyLocationController.swift +++ b/TelegramUI/LegacyLocationController.swift @@ -22,7 +22,7 @@ func legacyLocationController(message: Message, mapMedia: TelegramMediaMap, acco legacyLocation.latitude = mapMedia.latitude legacyLocation.longitude = mapMedia.longitude if let venue = mapMedia.venue { - legacyLocation.venue = TGVenueAttachment(title: venue.title, address: venue.address, provider: venue.provider, venueId: venue.id) + legacyLocation.venue = TGVenueAttachment(title: venue.title, address: venue.address, provider: venue.provider, venueId: venue.id, type: venue.type) } let legacyController = LegacyController(presentation: .modal(animateIn: true)) diff --git a/TelegramUI/LegacyLocationPicker.swift b/TelegramUI/LegacyLocationPicker.swift index b1039eb7b8..9170c76979 100644 --- a/TelegramUI/LegacyLocationPicker.swift +++ b/TelegramUI/LegacyLocationPicker.swift @@ -13,7 +13,7 @@ func legacyLocationPickerController(sendLocation: @escaping (CLLocationCoordinat legacyController.bind(controller: navigationController) controller.locationPicked = { [weak legacyController] coordinate, venue in sendLocation(coordinate, venue.flatMap { venue in - return MapVenue(title: venue.title, address: venue.address, provider: venue.provider, id: venue.venueId) + return MapVenue(title: venue.title, address: venue.address, provider: venue.provider, id: venue.venueId, type: venue.type) }) legacyController?.dismiss() } diff --git a/TelegramUI/ListMessageFileItemNode.swift b/TelegramUI/ListMessageFileItemNode.swift index dcf4f25b6f..5b0717f35b 100644 --- a/TelegramUI/ListMessageFileItemNode.swift +++ b/TelegramUI/ListMessageFileItemNode.swift @@ -330,17 +330,18 @@ final class ListMessageFileItemNode: ListMessageNode { if let selectedMedia = selectedMedia { if mediaUpdated { let account = item.account + let messageId = message.id updatedFetchControls = FetchControls(fetch: { [weak self] in if let strongSelf = self { - strongSelf.fetchDisposable.set(chatMessageFileInteractiveFetched(account: account, file: selectedMedia).start()) + strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(account: account, messageId: messageId, file: selectedMedia).start()) } }, cancel: { - chatMessageFileCancelInteractiveFetch(account: account, file: selectedMedia) + messageMediaFileCancelInteractiveFetch(account: account, messageId: messageId, file: selectedMedia) }) } if statusUpdated { - updatedStatusSignal = fileMediaResourceStatus(account: item.account, file: selectedMedia, message: message) + updatedStatusSignal = messageFileMediaResourceStatus(account: item.account, file: selectedMedia, message: message) if isAudio { if let currentUpdatedStatusSignal = updatedStatusSignal { @@ -481,8 +482,12 @@ final class ListMessageFileItemNode: ListMessageNode { switch status { case let .fetchStatus(fetchStatus): switch fetchStatus { - case let .Fetching(progress): - strongSelf.progressNode.state = .Fetching(progress: progress) + case let .Fetching(isActive, progress): + var adjustedProgress = progress + if isActive { + adjustedProgress = max(adjustedProgress, 0.027) + } + strongSelf.progressNode.state = .Fetching(progress: adjustedProgress) case .Local: if isAudio { strongSelf.progressNode.state = .Play @@ -584,7 +589,7 @@ final class ListMessageFileItemNode: ListMessageNode { } switch maybeFetchStatus { - case let .Fetching(progress): + case let .Fetching(_, progress): let progressFrame = CGRect(x: 65.0, y: size.height - 2.0, width: floor((size.width - 65.0) * CGFloat(progress)), height: 2.0) if self.linearProgressNode.supernode == nil { self.addSubnode(self.linearProgressNode) diff --git a/TelegramUI/MentionChatInputContextPanelNode.swift b/TelegramUI/MentionChatInputContextPanelNode.swift index 7fd4624edd..6183a569b5 100644 --- a/TelegramUI/MentionChatInputContextPanelNode.swift +++ b/TelegramUI/MentionChatInputContextPanelNode.swift @@ -128,6 +128,7 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { //options.insert(.LowLatency) } else { options.insert(.AnimateTopItemPosition) + options.insert(.AnimateCrossfade) } var insets = UIEdgeInsets() diff --git a/TelegramUI/MentionChatInputPanelItem.swift b/TelegramUI/MentionChatInputPanelItem.swift index c8992a3ea0..4e30a93958 100644 --- a/TelegramUI/MentionChatInputPanelItem.swift +++ b/TelegramUI/MentionChatInputPanelItem.swift @@ -68,7 +68,7 @@ final class MentionChatInputPanelItem: ListViewItem { } } -private let avatarFont = Font.regular(16.0) +private let avatarFont: UIFont = UIFont(name: "ArialRoundedMTBold", size: 16.0)! private let textFont = Font.medium(14.0) final class MentionChatInputPanelItemNode: ListViewItemNode { diff --git a/TelegramUI/NetworkUsageStatsController.swift b/TelegramUI/NetworkUsageStatsController.swift index b15e32426d..08576b4623 100644 --- a/TelegramUI/NetworkUsageStatsController.swift +++ b/TelegramUI/NetworkUsageStatsController.swift @@ -382,7 +382,7 @@ func networkUsageStatsController(account: Account) -> ViewController { let arguments = NetworkUsageStatsControllerArguments(resetStatistics: { [weak stats] section in let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let controller = ActionSheetController() + let controller = ActionSheetController(presentationTheme: presentationData.theme) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() } diff --git a/TelegramUI/NotificationsAndSounds.swift b/TelegramUI/NotificationsAndSounds.swift index 4b79e96766..d884322442 100644 --- a/TelegramUI/NotificationsAndSounds.swift +++ b/TelegramUI/NotificationsAndSounds.swift @@ -376,7 +376,7 @@ public func notificationsAndSoundsController(account: Account) -> ViewController }).start() }, resetNotifications: { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let actionSheet = ActionSheetController() + let actionSheet = ActionSheetController(presentationTheme: presentationData.theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: presentationData.strings.Notifications_Reset, color: .destructive, action: { [weak actionSheet] in actionSheet?.dismissAnimated() diff --git a/TelegramUI/PasscodeOptionsController.swift b/TelegramUI/PasscodeOptionsController.swift index 8b7fdb39b8..b7e946d370 100644 --- a/TelegramUI/PasscodeOptionsController.swift +++ b/TelegramUI/PasscodeOptionsController.swift @@ -238,9 +238,10 @@ func passcodeOptionsController(account: Account) -> ViewController { legacyController?.dismiss() } }, turnPasscodeOff: { - let actionSheet = ActionSheetController() + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + let actionSheet = ActionSheetController(presentationTheme: presentationData.theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: "Turn Passcode Off", color: .destructive, action: { [weak actionSheet] in + ActionSheetButtonItem(title: presentationData.strings.PasscodeSettings_TurnPasscodeOff, color: .destructive, action: { [weak actionSheet] in actionSheet?.dismissAnimated() let challenge = PostboxAccessChallengeData.none @@ -253,7 +254,7 @@ func passcodeOptionsController(account: Account) -> ViewController { }) }) ]), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: "Cancel", color: .accent, action: { [weak actionSheet] in + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() }) ])]) @@ -286,7 +287,8 @@ func passcodeOptionsController(account: Account) -> ViewController { legacyController?.dismiss() } }, changePasscodeTimeout: { - let actionSheet = ActionSheetController() + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + let actionSheet = ActionSheetController(presentationTheme: presentationData.theme) var items: [ActionSheetItem] = [] let setAction: (Int32?) -> Void = { value in let _ = (passcodeOptionsDataPromise.get() |> take(1)).start(next: { [weak passcodeOptionsDataPromise] data in @@ -317,7 +319,7 @@ func passcodeOptionsController(account: Account) -> ViewController { } actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: "Cancel", color: .accent, action: { [weak actionSheet] in + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() }) ])]) diff --git a/TelegramUI/PeerAvatarImageGalleryItem.swift b/TelegramUI/PeerAvatarImageGalleryItem.swift index 7b2c461e87..b3c53ee0cf 100644 --- a/TelegramUI/PeerAvatarImageGalleryItem.swift +++ b/TelegramUI/PeerAvatarImageGalleryItem.swift @@ -96,7 +96,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { } } - override func animateIn(from node: ASDisplayNode) { + override func animateIn(from node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void) { var transformedFrame = node.view.convert(node.view.bounds, to: self.imageNode.view) let transformedSuperFrame = node.view.convert(node.view.bounds, to: self.imageNode.view.superview) let transformedSelfFrame = node.view.convert(node.view.bounds, to: self.view) @@ -132,7 +132,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { }) } - override func animateOut(to node: ASDisplayNode, completion: @escaping () -> Void) { + override func animateOut(to node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) { var transformedFrame = node.view.convert(node.view.bounds, to: self.imageNode.view) let transformedSuperFrame = node.view.convert(node.view.bounds, to: self.imageNode.view.superview) let transformedSelfFrame = node.view.convert(node.view.bounds, to: self.view) diff --git a/TelegramUI/PeerMediaCollectionController.swift b/TelegramUI/PeerMediaCollectionController.swift index 9a6725d0a6..583808613f 100644 --- a/TelegramUI/PeerMediaCollectionController.swift +++ b/TelegramUI/PeerMediaCollectionController.swift @@ -104,7 +104,8 @@ public class PeerMediaCollectionController: ViewController { strongSelf.present(gallery, in: .window(.root), with: GalleryControllerPresentationArguments(transitionArguments: { [weak self] messageId, media in if let strongSelf = self { if let transitionNode = strongSelf.mediaCollectionDisplayNode.transitionNodeForGallery(messageId: messageId, media: media) { - return GalleryTransitionArguments(transitionNode: transitionNode, transitionContainerNode: strongSelf.mediaCollectionDisplayNode, transitionBackgroundNode: strongSelf.mediaCollectionDisplayNode.historyNode as! ASDisplayNode) + return GalleryTransitionArguments(transitionNode: transitionNode, addToTransitionSurface: { _ in + }) } } return nil @@ -195,7 +196,7 @@ public class PeerMediaCollectionController: ViewController { if let messageIds = strongSelf.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty { strongSelf.messageContextDisposable.set((combineLatest(chatDeleteMessagesOptions(account: strongSelf.account, messageIds: messageIds), strongSelf.peer.get() |> take(1)) |> deliverOnMainQueue).start(next: { options, peer in if let strongSelf = self, let peer = peer, !options.isEmpty { - let actionSheet = ActionSheetController() + let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) var items: [ActionSheetItem] = [] var personalPeerName: String? var isChannel = false diff --git a/TelegramUI/PeerMediaCollectionControllerNode.swift b/TelegramUI/PeerMediaCollectionControllerNode.swift index 4787196e50..434545feb0 100644 --- a/TelegramUI/PeerMediaCollectionControllerNode.swift +++ b/TelegramUI/PeerMediaCollectionControllerNode.swift @@ -194,14 +194,14 @@ class PeerMediaCollectionControllerNode: ASDisplayNode { if let selectionPanel = self.selectionPanel { selectionPanel.selectedMessageCount = selectionState.selectedIds.count - let panelHeight = selectionPanel.updateLayout(width: layout.size.width, transition: transition, interfaceState: interfaceState) + let panelHeight = selectionPanel.updateLayout(width: layout.size.width, maxHeight: 0.0, transition: transition, interfaceState: interfaceState) transition.updateFrame(node: selectionPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight))) } else { let selectionPanel = ChatMessageSelectionInputPanelNode(theme: self.chatPresentationInterfaceState.theme) selectionPanel.interfaceInteraction = self.interfaceInteraction selectionPanel.selectedMessageCount = selectionState.selectedIds.count selectionPanel.backgroundColor = self.presentationData.theme.chat.inputPanel.panelBackgroundColor - let panelHeight = selectionPanel.updateLayout(width: layout.size.width, transition: .immediate, interfaceState: interfaceState) + let panelHeight = selectionPanel.updateLayout(width: layout.size.width, maxHeight: 0.0, transition: .immediate, interfaceState: interfaceState) self.selectionPanel = selectionPanel self.addSubnode(selectionPanel) selectionPanel.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom), size: CGSize(width: layout.size.width, height: panelHeight)) diff --git a/TelegramUI/PeerMediaCollectionSectionsNode.swift b/TelegramUI/PeerMediaCollectionSectionsNode.swift index bdb3c6b943..4928346d2a 100644 --- a/TelegramUI/PeerMediaCollectionSectionsNode.swift +++ b/TelegramUI/PeerMediaCollectionSectionsNode.swift @@ -23,6 +23,7 @@ final class PeerMediaCollectionSectionsNode: ASDisplayNode { strings.SharedMedia_CategoryOther ]) self.segmentedControl.selectedSegmentIndex = 0 + self.segmentedControl.tintColor = theme.rootController.navigationBar.accentTextColor self.separatorNode = ASDisplayNode() self.separatorNode.isLayerBacked = true diff --git a/TelegramUI/PhotoResources.swift b/TelegramUI/PhotoResources.swift index 65b3d10fdf..e4c38bcd9b 100644 --- a/TelegramUI/PhotoResources.swift +++ b/TelegramUI/PhotoResources.swift @@ -125,12 +125,12 @@ private func chatMessageFileDatas(account: Account, file: TelegramMediaFile, pat return signal } -private func chatMessageVideoDatas(account: Account, file: TelegramMediaFile) -> Signal<(Data?, (Data, String)?, Bool), NoError> { +private func chatMessageVideoDatas(account: Account, file: TelegramMediaFile, thumbnailSize: Bool = false) -> Signal<(Data?, (Data, String)?, Bool), NoError> { if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations) { let thumbnailResource = smallestRepresentation.resource let fullSizeResource = file.resource - let maybeFullSize = account.postbox.mediaBox.cachedResourceRepresentation(fullSizeResource, representation: CachedVideoFirstFrameRepresentation(), complete: false) + let maybeFullSize = account.postbox.mediaBox.cachedResourceRepresentation(fullSizeResource, representation: thumbnailSize ? CachedScaledVideoFirstFrameRepresentation(size: CGSize(width: 160.0, height: 160.0)) : CachedVideoFirstFrameRepresentation(), complete: false) let signal = maybeFullSize |> take(1) |> mapToSignal { maybeData -> Signal<(Data?, (Data, String)?, Bool), NoError> in if maybeData.complete { @@ -521,20 +521,21 @@ func chatMessagePhoto(account: Account, photo: TelegramMediaImage) -> Signal<(Tr if let fullSizeImage = blurSourceImage { let thumbnailSize = CGSize(width: fullSizeImage.width, height: fullSizeImage.height) - let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 154.0, height: 154.0)) + let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 74.0, height: 74.0)) let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) thumbnailContext.withFlippedContext { c in c.interpolationQuality = .none c.draw(fullSizeImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) } telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - //telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) if let blurredImage = thumbnailContext.generateImage() { let filledSize = thumbnailSize.aspectFilled(arguments.drawingRect.size) + c.interpolationQuality = .medium c.draw(blurredImage.cgImage!, in: CGRect(origin: CGPoint(x: (arguments.drawingRect.width - filledSize.width) / 2.0, y: (arguments.drawingRect.height - filledSize.height) / 2.0), size: filledSize)) c.setBlendMode(.normal) - c.setFillColor(UIColor(white: 1.0, alpha: 0.6).cgColor) + c.setFillColor(UIColor(white: 1.0, alpha: 0.5).cgColor) c.fill(arguments.drawingRect) c.setBlendMode(.copy) } @@ -629,12 +630,90 @@ func chatMessagePhotoThumbnail(account: Account, photo: TelegramMediaImage) -> S var fullSizeImage: CGImage? if let fullSizeData = fullSizeData { if fullSizeComplete { - /*let options = NSMutableDictionary() - options.setValue(max(fittedSize.width * context.scale, fittedSize.height * context.scale) as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) - options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) - if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) { - fullSizeImage = image - }*/ + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { + fullSizeImage = image + } + } else { + let imageSource = CGImageSourceCreateIncremental(nil) + CGImageSourceUpdateData(imageSource, fullSizeData as CFData, fullSizeComplete) + + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + if let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { + fullSizeImage = image + } + } + } + + var thumbnailImage: CGImage? + if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { + thumbnailImage = image + } + + var blurredThumbnailImage: UIImage? + if let thumbnailImage = thumbnailImage { + let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) + let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + blurredThumbnailImage = thumbnailContext.generateImage() + } + + context.withFlippedContext { c in + c.setBlendMode(.copy) + if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height { + //c.setFillColor(UIColor(white: 0.0, alpha: 0.4).cgColor) + c.fill(arguments.drawingRect) + } + + c.setBlendMode(.copy) + if let blurredThumbnailImage = blurredThumbnailImage, let cgImage = blurredThumbnailImage.cgImage { + c.interpolationQuality = .low + c.draw(cgImage, in: fittedRect) + c.setBlendMode(.normal) + } + + if let fullSizeImage = fullSizeImage { + c.interpolationQuality = .medium + c.draw(fullSizeImage, in: fittedRect) + } + } + + addCorners(context, arguments: arguments) + + return context + } + } +} + +func chatMessageVideoThumbnail(account: Account, file: TelegramMediaFile) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + let signal = chatMessageVideoDatas(account: account, file: file, thumbnailSize: true) + + return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in + return { arguments in + let context = DrawingContext(size: arguments.drawingSize, clear: true) + + let drawingRect = arguments.drawingRect + var fittedSize = arguments.imageSize + if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) { + fittedSize.width = arguments.boundingSize.width + } + if abs(fittedSize.height - arguments.boundingSize.height).isLessThanOrEqualTo(CGFloat(1.0)) { + fittedSize.height = arguments.boundingSize.height + } + + let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize) + + var fullSizeImage: CGImage? + if let fullSizeData = fullSizeData?.0 { + if fullSizeComplete { let options = NSMutableDictionary() options[kCGImageSourceShouldCache as NSString] = false as NSNumber if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { @@ -1011,21 +1090,25 @@ func mediaGridMessageVideo(account: Account, video: TelegramMediaFile) -> Signal context.withFlippedContext { c in c.setBlendMode(.copy) if arguments.boundingSize != arguments.imageSize { - if let fullSizeImage = fullSizeImage { + let blurSourceImage = thumbnailImage ?? fullSizeImage + + if let fullSizeImage = blurSourceImage { let thumbnailSize = CGSize(width: fullSizeImage.width, height: fullSizeImage.height) - let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 100.0, height: 100.0)) + let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 74.0, height: 74.0)) let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) thumbnailContext.withFlippedContext { c in c.interpolationQuality = .none c.draw(fullSizeImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) } telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) if let blurredImage = thumbnailContext.generateImage() { let filledSize = thumbnailSize.aspectFilled(arguments.drawingRect.size) + c.interpolationQuality = .medium c.draw(blurredImage.cgImage!, in: CGRect(origin: CGPoint(x: (arguments.drawingRect.width - filledSize.width) / 2.0, y: (arguments.drawingRect.height - filledSize.height) / 2.0), size: filledSize)) c.setBlendMode(.normal) - c.setFillColor(UIColor(white: 1.0, alpha: 0.1).cgColor) + c.setFillColor(UIColor(white: 1.0, alpha: 0.5).cgColor) c.fill(arguments.drawingRect) c.setBlendMode(.copy) } @@ -1350,18 +1433,6 @@ func chatMessageImageFile(account: Account, file: TelegramMediaFile, progressive } } -func chatMessageFileStatus(account: Account, file: TelegramMediaFile) -> Signal { - return account.postbox.mediaBox.resourceStatus(file.resource) -} - -func chatMessageFileInteractiveFetched(account: Account, file: TelegramMediaFile) -> Signal { - return account.postbox.mediaBox.fetchedResource(file.resource, tag: TelegramMediaResourceFetchTag(statsCategory: .file)) -} - -func chatMessageFileCancelInteractiveFetch(account: Account, file: TelegramMediaFile) { - account.postbox.mediaBox.cancelInteractiveResourceFetch(file.resource) -} - private func avatarGalleryPhotoDatas(account: Account, representations: [TelegramMediaImageRepresentation], autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> { if let smallestRepresentation = smallestImageRepresentation(representations), let largestRepresentation = largestImageRepresentation(representations) { let maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource) diff --git a/TelegramUI/PreparedChatHistoryViewTransition.swift b/TelegramUI/PreparedChatHistoryViewTransition.swift index a98c8bce15..1d226ced2c 100644 --- a/TelegramUI/PreparedChatHistoryViewTransition.swift +++ b/TelegramUI/PreparedChatHistoryViewTransition.swift @@ -24,6 +24,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie let updatedCount = toView.filteredEntries.count var options: ListViewDeleteAndInsertOptions = [] + var animateIn = false var maxAnimatedInsertionIndex = -1 var stationaryItemRange: (Int, Int)? var scrollToItem: ListViewScrollToItem? @@ -31,7 +32,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie switch reason { case let .Initial(fadeIn): if fadeIn { - let _ = options.insert(.AnimateAlpha) + animateIn = true } else { let _ = options.insert(.LowLatency) let _ = options.insert(.Synchronous) @@ -183,7 +184,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie } } - subscriber.putNext(ChatHistoryViewTransition(historyView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, initialData: initialData, keyboardButtonsMessage: keyboardButtonsMessage, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData, scrolledToIndex: scrolledToIndex)) + subscriber.putNext(ChatHistoryViewTransition(historyView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, initialData: initialData, keyboardButtonsMessage: keyboardButtonsMessage, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData, scrolledToIndex: scrolledToIndex, animateIn: animateIn)) subscriber.putCompletion() return EmptyDisposable diff --git a/TelegramUI/PresentationResourceKey.swift b/TelegramUI/PresentationResourceKey.swift index ebf8010638..af4f8fef7a 100644 --- a/TelegramUI/PresentationResourceKey.swift +++ b/TelegramUI/PresentationResourceKey.swift @@ -83,6 +83,8 @@ enum PresentationResourceKey: Int32 { case chatBubbleActionButtonBottomRightImage case chatBubbleActionButtonBottomSingleImage + case chatBubbleReplyThumbnailPlayImage + case chatInfoItemBackgroundImage case chatEmptyItemBackgroundImage case chatEmptyItemIconImage @@ -135,6 +137,11 @@ enum PresentationResourceKey: Int32 { case chatMessageAttachedContentButtonOutgoing case chatMessageAttachedContentHighlightedButtonOutgoing + case chatMessageAttachedContentButtonIconInstantIncoming + case chatMessageAttachedContentHighlightedButtonIconInstantIncoming + case chatMessageAttachedContentButtonIconInstantOutgoing + case chatMessageAttachedContentHighlightedButtonIconInstantOutgoing + case sharedMediaFileDownloadStartIcon case sharedMediaFileDownloadPauseIcon diff --git a/TelegramUI/PresentationResourcesChat.swift b/TelegramUI/PresentationResourcesChat.swift index 2461dc234b..3ac3f09817 100644 --- a/TelegramUI/PresentationResourcesChat.swift +++ b/TelegramUI/PresentationResourcesChat.swift @@ -347,13 +347,13 @@ struct PresentationResourcesChat { static func chatInputPanelVoiceActiveButtonImage(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatInputPanelVoiceActiveButtonImage.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconMicrophone"), color: .white) + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconMicrophone"), color: theme.chat.inputPanel.mediaRecordingControl.activeIconColor) }) } static func chatInputPanelVideoActiveButtonImage(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatInputPanelVideoActiveButtonImage.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconVideo"), color: .white) + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconVideo"), color: theme.chat.inputPanel.mediaRecordingControl.activeIconColor) }) } @@ -591,9 +591,65 @@ struct PresentationResourcesChat { }) } + static func chatMessageAttachedContentButtonIconInstantIncoming(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatMessageAttachedContentButtonIconInstantIncoming.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/AttachedContentInstantIcon"), color: theme.chat.bubble.incomingAccentColor) + }) + } + + static func chatMessageAttachedContentHighlightedButtonIconInstantIncoming(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatMessageAttachedContentHighlightedButtonIconInstantIncoming.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/AttachedContentInstantIcon"), color: theme.chat.bubble.incomingFillColor) + }) + } + + static func chatMessageAttachedContentButtonIconInstantOutgoing(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatMessageAttachedContentButtonIconInstantOutgoing.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/AttachedContentInstantIcon"), color: theme.chat.bubble.outgoingAccentColor) + }) + } + + static func chatMessageAttachedContentHighlightedButtonIconInstantOutgoing(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatMessageAttachedContentHighlightedButtonIconInstantOutgoing.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/AttachedContentInstantIcon"), color: theme.chat.bubble.outgoingFillColor) + }) + } + static func chatLoadingIndicatorBackgroundImage(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatLoadingIndicatorBackgroundImage.rawValue, { theme in return generateStretchableFilledCircleImage(diameter: 30.0, color: theme.chat.serviceMessage.serviceMessageFillColor) }) } + + static func chatBubbleReplyThumbnailPlayImage(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatBubbleReplyThumbnailPlayImage.rawValue, { theme in + return generateImage(CGSize(width: 16.0, height: 16.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor(white: 0.0, alpha: 0.5).cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + + let diameter = size.width + + context.setFillColor(UIColor(white: 1.0, alpha: 1.0).cgColor) + + context.translateBy(x: -2.0, y: -1.0) + + let size = CGSize(width: 10.0, height: 13.0) + context.translateBy(x: (diameter - size.width) / 2.0 + 1.5, y: (diameter - size.height) / 2.0) + + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) + context.scaleBy(x: 0.4, y: 0.4) + context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + + let _ = try? drawSvgPath(context, path: "M1.71891969,0.209353049 C0.769586558,-0.350676705 0,0.0908839327 0,1.18800046 L0,16.8564753 C0,17.9569971 0.750549162,18.357187 1.67393713,17.7519379 L14.1073836,9.60224049 C15.0318735,8.99626906 15.0094718,8.04970371 14.062401,7.49100858 L1.71891969,0.209353049 ") + context.fillPath() + + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) + context.scaleBy(x: 1.0 / 0.4, y: 1.0 / 0.4) + context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + + context.translateBy(x: -(diameter - size.width) / 2.0 - 1.5, y: -(diameter - size.height) / 2.0) + }) + }) + } } diff --git a/TelegramUI/PresentationTheme.swift b/TelegramUI/PresentationTheme.swift index 20d8fbfaf1..26bfe4889d 100644 --- a/TelegramUI/PresentationTheme.swift +++ b/TelegramUI/PresentationTheme.swift @@ -231,6 +231,64 @@ public final class PresentationThemeRootController { } } +public enum PresentationThemeActionSheetBackgroundType: Int32 { + case light + case dark +} + +public final class PresentationThemeActionSheet { + public let dimColor: UIColor + public let backgroundType: PresentationThemeActionSheetBackgroundType + public let itemBackgroundColor: UIColor + public let itemHighlightedBackgroundColor: UIColor + public let standardActionTextColor: UIColor + public let destructiveActionTextColor: UIColor + public let disabledActionTextColor: UIColor + public let primaryTextColor: UIColor + public let secondaryTextColor: UIColor + public let controlAccentColor: UIColor + + init(dimColor: UIColor, backgroundType: PresentationThemeActionSheetBackgroundType, itemBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, standardActionTextColor: UIColor, destructiveActionTextColor: UIColor, disabledActionTextColor: UIColor, primaryTextColor: UIColor, secondaryTextColor: UIColor, controlAccentColor: UIColor) { + self.dimColor = dimColor + self.backgroundType = backgroundType + self.itemBackgroundColor = itemBackgroundColor + self.itemHighlightedBackgroundColor = itemHighlightedBackgroundColor + self.standardActionTextColor = standardActionTextColor + self.destructiveActionTextColor = destructiveActionTextColor + self.disabledActionTextColor = disabledActionTextColor + self.primaryTextColor = primaryTextColor + self.secondaryTextColor = secondaryTextColor + self.controlAccentColor = controlAccentColor + } + + public init(decoder: PostboxDecoder) throws { + self.dimColor = try parseColor(decoder, "dimColor") + self.backgroundType = PresentationThemeActionSheetBackgroundType(rawValue: decoder.decodeInt32ForKey("backgroundType", orElse: 0)) ?? .light + self.itemBackgroundColor = try parseColor(decoder, "itemBackgroundColor") + self.itemHighlightedBackgroundColor = try parseColor(decoder, "itemHighlightedBackgroundColor") + self.standardActionTextColor = try parseColor(decoder, "standardActionTextColor") + self.destructiveActionTextColor = try parseColor(decoder, "destructiveActionTextColor") + self.disabledActionTextColor = try parseColor(decoder, "disabledActionTextColor") + self.primaryTextColor = try parseColor(decoder, "primaryTextColor") + self.secondaryTextColor = try parseColor(decoder, "secondaryTextColor") + self.controlAccentColor = try parseColor(decoder, "controlAccentColor") + } + + public func encode(_ encoder: PostboxEncoder) { + for child in Mirror(reflecting: self).children { + if let label = child.label { + if let value = child.value as? UIColor { + encoder.encodeInt32(Int32(bitPattern: value.argb), forKey: label) + } else if let value = child.value as? PresentationThemeActionSheetBackgroundType { + encoder.encodeInt32(value.rawValue, forKey: label) + } else { + assertionFailure() + } + } + } + } +} + public final class PresentationThemeSwitch { public let frameColor: UIColor public let handleColor: UIColor @@ -674,6 +732,48 @@ public enum PresentationThemeKeyboardColor: Int32 { } } +public final class PresentationThemeChatInputPanelMediaRecordingControl { + public let buttonColor: UIColor + public let micLevelColor: UIColor + public let activeIconColor: UIColor + public let panelControlFillColor: UIColor + public let panelControlStrokeColor: UIColor + public let panelControlContentPrimaryColor: UIColor + public let panelControlContentAccentColor: UIColor + + init(buttonColor: UIColor, micLevelColor: UIColor, activeIconColor: UIColor, panelControlFillColor: UIColor, panelControlStrokeColor: UIColor, panelControlContentPrimaryColor: UIColor, panelControlContentAccentColor: UIColor) { + self.buttonColor = buttonColor + self.micLevelColor = micLevelColor + self.activeIconColor = activeIconColor + self.panelControlFillColor = panelControlFillColor + self.panelControlStrokeColor = panelControlStrokeColor + self.panelControlContentPrimaryColor = panelControlContentPrimaryColor + self.panelControlContentAccentColor = panelControlContentAccentColor + } + + public init(decoder: PostboxDecoder) throws { + self.buttonColor = try parseColor(decoder, "buttonColor") + self.micLevelColor = try parseColor(decoder, "micLevelColor") + self.activeIconColor = try parseColor(decoder, "activeIconColor") + self.panelControlFillColor = try parseColor(decoder, "panelControlFillColor") + self.panelControlStrokeColor = try parseColor(decoder, "panelControlStrokeColor") + self.panelControlContentPrimaryColor = try parseColor(decoder, "panelControlContentPrimaryColor") + self.panelControlContentAccentColor = try parseColor(decoder, "panelControlContentAccentColor") + } + + public func encode(_ encoder: PostboxEncoder) { + for child in Mirror(reflecting: self).children { + if let label = child.label { + if let value = child.value as? UIColor { + encoder.encodeInt32(Int32(bitPattern: value.argb), forKey: label) + } else { + assertionFailure() + } + } + } + } +} + public final class PresentationThemeChatInputPanel { public let panelBackgroundColor: UIColor public let panelStrokeColor: UIColor @@ -689,8 +789,9 @@ public final class PresentationThemeChatInputPanel { public let primaryTextColor: UIColor public let mediaRecordingDotColor: UIColor public let keyboardColor: PresentationThemeKeyboardColor + public let mediaRecordingControl: PresentationThemeChatInputPanelMediaRecordingControl - public init(panelBackgroundColor: UIColor, panelStrokeColor: UIColor, panelControlAccentColor: UIColor, panelControlColor: UIColor, panelControlDisabledColor: UIColor, panelControlDestructiveColor: UIColor, inputBackgroundColor: UIColor, inputStrokeColor: UIColor, inputPlaceholderColor: UIColor, inputTextColor: UIColor, inputControlColor: UIColor, primaryTextColor: UIColor, mediaRecordingDotColor: UIColor, keyboardColor: PresentationThemeKeyboardColor) { + public init(panelBackgroundColor: UIColor, panelStrokeColor: UIColor, panelControlAccentColor: UIColor, panelControlColor: UIColor, panelControlDisabledColor: UIColor, panelControlDestructiveColor: UIColor, inputBackgroundColor: UIColor, inputStrokeColor: UIColor, inputPlaceholderColor: UIColor, inputTextColor: UIColor, inputControlColor: UIColor, primaryTextColor: UIColor, mediaRecordingDotColor: UIColor, keyboardColor: PresentationThemeKeyboardColor, mediaRecordingControl: PresentationThemeChatInputPanelMediaRecordingControl) { self.panelBackgroundColor = panelBackgroundColor self.panelStrokeColor = panelStrokeColor self.panelControlAccentColor = panelControlAccentColor @@ -705,6 +806,7 @@ public final class PresentationThemeChatInputPanel { self.primaryTextColor = primaryTextColor self.mediaRecordingDotColor = mediaRecordingDotColor self.keyboardColor = keyboardColor + self.mediaRecordingControl = mediaRecordingControl } public init(decoder: PostboxDecoder) throws { @@ -726,6 +828,11 @@ public final class PresentationThemeChatInputPanel { } else { throw PresentationThemeParsingError.generic } + if let mediaRecordingControl = (try? decoder.decodeObjectForKeyThrowing("mediaRecordingControl", decoder: { try PresentationThemeChatInputPanelMediaRecordingControl(decoder: $0) })) as? PresentationThemeChatInputPanelMediaRecordingControl { + self.mediaRecordingControl = mediaRecordingControl + } else { + throw PresentationThemeParsingError.generic + } } public func encode(_ encoder: PostboxEncoder) { @@ -737,6 +844,8 @@ public final class PresentationThemeChatInputPanel { encoder.encodeObjectWithEncoder(value, encoder: { value.encode($0) }, forKey: label) } else if let value = child.value as? PresentationThemeKeyboardColor { encoder.encodeInt32(value.rawValue, forKey: label) + } else if let value = child.value as? PresentationThemeChatInputPanelMediaRecordingControl { + encoder.encodeObjectWithEncoder(value, encoder: { value.encode($0) }, forKey: label) } else { assertionFailure() } @@ -935,14 +1044,16 @@ public final class PresentationTheme: Equatable { public let list: PresentationThemeList public let chatList: PresentationThemeChatList public let chat: PresentationThemeChat + public let actionSheet: PresentationThemeActionSheet public let resourceCache: PresentationsResourceCache = PresentationsResourceCache() - public init(rootController: PresentationThemeRootController, list: PresentationThemeList, chatList: PresentationThemeChatList, chat: PresentationThemeChat) { + public init(rootController: PresentationThemeRootController, list: PresentationThemeList, chatList: PresentationThemeChatList, chat: PresentationThemeChat, actionSheet: PresentationThemeActionSheet) { self.rootController = rootController self.list = list self.chatList = chatList self.chat = chat + self.actionSheet = actionSheet } public init(decoder: PostboxDecoder) throws { @@ -966,6 +1077,11 @@ public final class PresentationTheme: Equatable { } else { throw PresentationThemeParsingError.generic } + if let actionSheet = (try? decoder.decodeObjectForKeyThrowing("actionSheet", decoder: { try PresentationThemeActionSheet(decoder: $0) })) as? PresentationThemeActionSheet { + self.actionSheet = actionSheet + } else { + throw PresentationThemeParsingError.generic + } } public func encode(_ encoder: PostboxEncoder) { @@ -973,6 +1089,7 @@ public final class PresentationTheme: Equatable { encoder.encodeObjectWithEncoder(self.list, encoder: { self.list.encode($0) }, forKey: "list") encoder.encodeObjectWithEncoder(self.chatList, encoder: { self.chatList.encode($0) }, forKey: "chatList") encoder.encodeObjectWithEncoder(self.chat, encoder: { self.chat.encode($0) }, forKey: "chat") + encoder.encodeObjectWithEncoder(self.actionSheet, encoder: { self.actionSheet.encode($0) }, forKey: "actionSheet") } public static func ==(lhs: PresentationTheme, rhs: PresentationTheme) -> Bool { diff --git a/TelegramUI/PrivacyAndSecurityController.swift b/TelegramUI/PrivacyAndSecurityController.swift index 7046b99c9f..8271416f6c 100644 --- a/TelegramUI/PrivacyAndSecurityController.swift +++ b/TelegramUI/PrivacyAndSecurityController.swift @@ -418,7 +418,7 @@ public func privacyAndSecurityController(account: Account, initialSettings: Sign updateAccountTimeoutDisposable.set(signal.start(next: { [weak updateAccountTimeoutDisposable] privacySettingsValue in if let _ = privacySettingsValue { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let controller = ActionSheetController() + let controller = ActionSheetController(presentationTheme: presentationData.theme) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() } diff --git a/TelegramUI/RadialProgressContentNode.swift b/TelegramUI/RadialProgressContentNode.swift index 2885aa14aa..69229e13f5 100644 --- a/TelegramUI/RadialProgressContentNode.swift +++ b/TelegramUI/RadialProgressContentNode.swift @@ -144,6 +144,7 @@ private final class RadialProgressContentSpinnerNode: ASDisplayNode { basicAnimation.toValue = NSNumber(value: Float.pi * 2.0) basicAnimation.repeatCount = Float.infinity basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) + basicAnimation.beginTime = 1.0 self.layer.add(basicAnimation, forKey: "progressRotation") } diff --git a/TelegramUI/RecentSessionsController.swift b/TelegramUI/RecentSessionsController.swift index 65d80e5b22..4620e4c580 100644 --- a/TelegramUI/RecentSessionsController.swift +++ b/TelegramUI/RecentSessionsController.swift @@ -327,13 +327,14 @@ public func recentSessionsController(account: Account) -> ViewController { } })) }, terminateOtherSessions: { - let controller = ActionSheetController() + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + let controller = ActionSheetController(presentationTheme: presentationData.theme) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() } controller.setItemGroups([ ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: "Terminate all other sessions", color: .destructive, action: { + ActionSheetButtonItem(title: presentationData.strings.AuthSessions_TerminateOtherSessions, color: .destructive, action: { dismissAction() updateState { @@ -364,7 +365,7 @@ public func recentSessionsController(account: Account) -> ViewController { })) }) ]), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: "Cancel", action: { dismissAction() })]) + ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ]) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }) diff --git a/TelegramUI/SearchBarPlaceholderNode.swift b/TelegramUI/SearchBarPlaceholderNode.swift index 2b0ef3ec18..628cfc6db8 100644 --- a/TelegramUI/SearchBarPlaceholderNode.swift +++ b/TelegramUI/SearchBarPlaceholderNode.swift @@ -33,6 +33,7 @@ class SearchBarPlaceholderNode: ASDisplayNode, ASEditableTextNodeDelegate { var activate: (() -> Void)? let backgroundNode: ASImageNode + private var fillBackgroundColor: UIColor private var foregroundColor: UIColor private var iconColor: UIColor let iconNode: ASImageNode @@ -46,10 +47,11 @@ class SearchBarPlaceholderNode: ASDisplayNode, ASEditableTextNodeDelegate { self.backgroundNode.displaysAsynchronously = false self.backgroundNode.displayWithoutProcessing = true + self.fillBackgroundColor = UIColor.white self.foregroundColor = UIColor(rgb: 0xededed) self.iconColor = UIColor(rgb: 0x000000, alpha: 0.0) - self.backgroundNode.image = generateBackground(backgroundColor: UIColor.white, foregroundColor: self.foregroundColor) + self.backgroundNode.image = generateBackground(backgroundColor: self.fillBackgroundColor, foregroundColor: self.foregroundColor) self.iconNode = ASImageNode() self.iconNode.isLayerBacked = true @@ -78,6 +80,7 @@ class SearchBarPlaceholderNode: ASDisplayNode, ASEditableTextNodeDelegate { func asyncLayout() -> (_ placeholderString: NSAttributedString?, _ constrainedSize: CGSize, _ iconColor: UIColor, _ foregroundColor: UIColor, _ backgroundColor: UIColor) -> (() -> Void) { let labelLayout = TextNode.asyncLayout(self.labelNode) + let currentFillBackgroundColor = self.fillBackgroundColor let currentForegroundColor = self.foregroundColor let currentIconColor = self.iconColor @@ -86,7 +89,7 @@ class SearchBarPlaceholderNode: ASDisplayNode, ASEditableTextNodeDelegate { var updatedBackgroundImage: UIImage? var updatedIconImage: UIImage? - if !currentForegroundColor.isEqual(foregroundColor) { + if !currentFillBackgroundColor.isEqual(backgroundColor) || !currentForegroundColor.isEqual(foregroundColor) { updatedBackgroundImage = generateBackground(backgroundColor: backgroundColor, foregroundColor: foregroundColor) } if !currentIconColor.isEqual(iconColor) { @@ -97,6 +100,7 @@ class SearchBarPlaceholderNode: ASDisplayNode, ASEditableTextNodeDelegate { if let strongSelf = self { let _ = labelApply() + strongSelf.fillBackgroundColor = backgroundColor strongSelf.foregroundColor = foregroundColor strongSelf.iconColor = iconColor if let updatedBackgroundImage = updatedBackgroundImage { diff --git a/TelegramUI/SecretChatHandshakeStatusInputPanelNode.swift b/TelegramUI/SecretChatHandshakeStatusInputPanelNode.swift index 126407891e..e51ebe7940 100644 --- a/TelegramUI/SecretChatHandshakeStatusInputPanelNode.swift +++ b/TelegramUI/SecretChatHandshakeStatusInputPanelNode.swift @@ -39,7 +39,7 @@ final class SecretChatHandshakeStatusInputPanelNode: ChatInputPanelNode { self.interfaceInteraction?.unblockPeer() } - override func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { + override func updateLayout(width: CGFloat, maxHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { if self.presentationInterfaceState != interfaceState { self.presentationInterfaceState = interfaceState diff --git a/TelegramUI/SelectablePeerNode.swift b/TelegramUI/SelectablePeerNode.swift index a25f0a396f..1f5277576e 100644 --- a/TelegramUI/SelectablePeerNode.swift +++ b/TelegramUI/SelectablePeerNode.swift @@ -32,8 +32,24 @@ final class SelectablePeerNode: ASDisplayNode { private var peer: Peer? private var chatPeer: Peer? - var textColor: UIColor = .black - var selectedColor: UIColor = UIColor(rgb: 0x007ee5) + var textColor: UIColor = .black { + didSet { + if !self.textColor.isEqual(oldValue) { + if let peer = self.peer { + self.textNode.attributedText = NSAttributedString(string: peer.displayTitle, font: textFont, textColor: self.currentSelected ? self.selectedColor : self.textColor, paragraphAlignment: .center) + } + } + } + } + var selectedColor: UIColor = UIColor(rgb: 0x007ee5) { + didSet { + if !self.selectedColor.isEqual(oldValue) { + if let peer = self.peer { + self.textNode.attributedText = NSAttributedString(string: peer.displayTitle, font: textFont, textColor: self.currentSelected ? self.selectedColor : self.textColor, paragraphAlignment: .center) + } + } + } + } override init() { self.avatarNodeContainer = ASDisplayNode() @@ -66,14 +82,14 @@ final class SelectablePeerNode: ASDisplayNode { self.peer = peer self.chatPeer = chatPeer - var defaultColor: UIColor = .black + var defaultColor: UIColor = self.textColor if let chatPeer = chatPeer, chatPeer.id.namespace == Namespaces.Peer.SecretChat { defaultColor = UIColor(rgb: 0x149a1f) } let text = peer.displayTitle self.textNode.maximumNumberOfLines = UInt(numberOfLines) - self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: self.currentSelected ? UIColor(rgb: 0x007ee5) : defaultColor, paragraphAlignment: .center) + self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: self.currentSelected ? self.selectedColor : defaultColor, paragraphAlignment: .center) self.avatarNode.setPeer(account: account, peer: peer) self.setNeedsLayout() } @@ -83,7 +99,7 @@ final class SelectablePeerNode: ASDisplayNode { self.currentSelected = selected if let peer = self.peer { - self.textNode.attributedText = NSAttributedString(string: peer.displayTitle, font: textFont, textColor: selected ? self.selectedColor : textColor, paragraphAlignment: .center) + self.textNode.attributedText = NSAttributedString(string: peer.displayTitle, font: textFont, textColor: selected ? self.selectedColor : self.textColor, paragraphAlignment: .center) } if selected { diff --git a/TelegramUI/SettingsController.swift b/TelegramUI/SettingsController.swift index 8566620a72..8256d78755 100644 --- a/TelegramUI/SettingsController.swift +++ b/TelegramUI/SettingsController.swift @@ -602,7 +602,8 @@ public func settingsController(account: Account, accountManager: AccountManager) } } if let (node, _) = result { - return GalleryTransitionArguments(transitionNode: node, transitionContainerNode: controller.displayNode, transitionBackgroundNode: controller.displayNode) + return GalleryTransitionArguments(transitionNode: node, addToTransitionSurface: { _ in + }) } } return nil diff --git a/TelegramUI/ShareController.swift b/TelegramUI/ShareController.swift index 86c77dadd4..4771c8dbe3 100644 --- a/TelegramUI/ShareController.swift +++ b/TelegramUI/ShareController.swift @@ -51,7 +51,7 @@ public final class ShareController: ViewController { public var dismissed: (() -> Void)? - public init(account: Account, subject: ShareControllerSubject, saveToCameraRoll: Bool = false ,externalShare: Bool = true) { + public init(account: Account, subject: ShareControllerSubject, saveToCameraRoll: Bool = false, externalShare: Bool = true) { self.account = account self.externalShare = externalShare self.subject = subject diff --git a/TelegramUI/ShareControllerNode.swift b/TelegramUI/ShareControllerNode.swift index f329e271e8..25597d60fd 100644 --- a/TelegramUI/ShareControllerNode.swift +++ b/TelegramUI/ShareControllerNode.swift @@ -5,27 +5,6 @@ import SwiftSignalKit import Postbox import TelegramCore -private let defaultBackgroundColor: UIColor = UIColor(white: 1.0, alpha: 1.0) -private let highlightedBackgroundColor: UIColor = UIColor(white: 0.9, alpha: 1.0) -private let separatorColor: UIColor = UIColor(rgb: 0xbcbbc1) - -private let roundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: .white) -private let highlightedRoundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: highlightedBackgroundColor) - -private let halfRoundedBackground = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(UIColor.white.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) - context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height / 2.0))) -})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1) - -private let highlightedHalfRoundedBackground = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(highlightedBackgroundColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) - context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height / 2.0))) -})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1) - final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate { private let account: Account private var presentationData: PresentationData @@ -78,6 +57,23 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate self.defaultAction = defaultAction self.requestLayout = requestLayout + let roundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: .white) + let highlightedRoundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: UIColor(white: 0.9, alpha: 1.0)) + + let halfRoundedBackground = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor.white.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) + context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height / 2.0))) + })?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1) + + let highlightedHalfRoundedBackground = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor(white: 0.9, alpha: 1.0).cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) + context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height / 2.0))) + })?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1) + self.wrappingScrollNode = ASScrollNode() self.wrappingScrollNode.view.alwaysBounceVertical = true self.wrappingScrollNode.view.delaysContentTouches = false @@ -117,7 +113,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate self.actionSeparatorNode = ASDisplayNode() self.actionSeparatorNode.isLayerBacked = true self.actionSeparatorNode.displaysAsynchronously = false - self.actionSeparatorNode.backgroundColor = separatorColor + self.actionSeparatorNode.backgroundColor = UIColor(white: 0.9, alpha: 1.0) super.init() diff --git a/TelegramUI/ShareControllerRecentPeersGridItem.swift b/TelegramUI/ShareControllerRecentPeersGridItem.swift index 2577f02aaa..8042f3d66a 100644 --- a/TelegramUI/ShareControllerRecentPeersGridItem.swift +++ b/TelegramUI/ShareControllerRecentPeersGridItem.swift @@ -56,7 +56,7 @@ final class ShareControllerRecentPeersGridItemNode: GridItemNode { peersNode = currentPeersNode peersNode.updateThemeAndStrings(theme: theme, strings: strings) } else { - peersNode = ChatListSearchRecentPeersNode(account: account, theme: theme, strings: strings, peerSelected: { [weak self] peer in + peersNode = ChatListSearchRecentPeersNode(account: account, theme: theme, mode: .actionSheet, strings: strings, peerSelected: { [weak self] peer in self?.controllerInteraction?.togglePeer(peer) }, isPeerSelected: { [weak self] peerId in return self?.controllerInteraction?.selectedPeerIds.contains(peerId) ?? false diff --git a/TelegramUI/ShareRecipientsActionSheetController.swift b/TelegramUI/ShareRecipientsActionSheetController.swift deleted file mode 100644 index 71bad690b1..0000000000 --- a/TelegramUI/ShareRecipientsActionSheetController.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation -import Display -import AsyncDisplayKit -import UIKit -import SwiftSignalKit - -final class ShareRecipientsActionSheetController: ActionSheetController { - private let _ready = Promise() - override var ready: Promise { - return self._ready - } - private var didSetReady = false - - var location: () -> Void = { } - var contacts: () -> Void = { } - - override init() { - super.init() - - self._ready.set(.single(true)) - - self.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: "Cancel", action: { [weak self] in - self?.dismissAnimated() - }), - ]) - ]) - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/TelegramUI/StickerPackPreviewGridItem.swift b/TelegramUI/StickerPackPreviewGridItem.swift index fd5718d850..7c6c6b5d28 100644 --- a/TelegramUI/StickerPackPreviewGridItem.swift +++ b/TelegramUI/StickerPackPreviewGridItem.swift @@ -101,7 +101,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode { self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: .black, paragraphAlignment: .right) if let dimensions = stickerItem.file.dimensions { self.imageNode.setSignal(account: account, signal: chatMessageSticker(account: account, file: stickerItem.file, small: true)) - self.stickerFetchedDisposable.set(fileInteractiveFetched(account: account, file: stickerItem.file).start()) + self.stickerFetchedDisposable.set(freeMediaFileInteractiveFetched(account: account, file: stickerItem.file).start()) self.currentState = (account, stickerItem, dimensions) self.setNeedsLayout() diff --git a/TelegramUI/StorageUsageController.swift b/TelegramUI/StorageUsageController.swift index 3f81ab809c..93df70b5fa 100644 --- a/TelegramUI/StorageUsageController.swift +++ b/TelegramUI/StorageUsageController.swift @@ -217,7 +217,7 @@ func storageUsageController(account: Account) -> ViewController { let arguments = StorageUsageControllerArguments(account: account, updateKeepMedia: { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let controller = ActionSheetController() + let controller = ActionSheetController(presentationTheme: presentationData.theme) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() } @@ -247,7 +247,7 @@ func storageUsageController(account: Account) -> ViewController { if let result = result, case let .result(stats) = result { if let categories = stats.media[peerId] { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let controller = ActionSheetController() + let controller = ActionSheetController(presentationTheme: presentationData.theme) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() } diff --git a/TelegramUI/TelegramApplicationContext.swift b/TelegramUI/TelegramApplicationContext.swift index 2aadeb2510..c2764b0619 100644 --- a/TelegramUI/TelegramApplicationContext.swift +++ b/TelegramUI/TelegramApplicationContext.swift @@ -23,6 +23,7 @@ public final class TelegramApplicationBindings { public final class TelegramApplicationContext { public let applicationBindings: TelegramApplicationBindings public let accountManager: AccountManager + let fetchManager: FetchManager public var callManager: PresentationCallManager? public let mediaManager = MediaManager() @@ -47,9 +48,10 @@ public final class TelegramApplicationContext { public var navigateToCurrentCall: (() -> Void)? public var hasOngoingCall: Signal? - public init(applicationBindings: TelegramApplicationBindings, accountManager: AccountManager, currentPresentationData: PresentationData, presentationData: Signal, currentMediaDownloadSettings: AutomaticMediaDownloadSettings, automaticMediaDownloadSettings: Signal) { + public init(applicationBindings: TelegramApplicationBindings, accountManager: AccountManager, currentPresentationData: PresentationData, presentationData: Signal, currentMediaDownloadSettings: AutomaticMediaDownloadSettings, automaticMediaDownloadSettings: Signal, postbox: Postbox) { self.applicationBindings = applicationBindings self.accountManager = accountManager + self.fetchManager = FetchManager(postbox: postbox) self.currentPresentationData = Atomic(value: currentPresentationData) self.currentAutomaticMediaDownloadSettings = Atomic(value: currentMediaDownloadSettings) self._presentationData.set(.single(currentPresentationData) |> then(presentationData)) diff --git a/TelegramUI/TelegramInitializeLegacyComponents.swift b/TelegramUI/TelegramInitializeLegacyComponents.swift index 1ceca26944..a3d4f9938f 100644 --- a/TelegramUI/TelegramInitializeLegacyComponents.swift +++ b/TelegramUI/TelegramInitializeLegacyComponents.swift @@ -4,8 +4,10 @@ import UIKit import TelegramCore import SwiftSignalKit import MtProtoKitDynamic +import Display var legacyComponentsApplication: UIApplication! +private var legacyComponentsAccount: Account? private var legacyLocalization = TGLocalization(version: 0, code: "en", dict: [:], isActive: true) @@ -13,6 +15,10 @@ func updateLegacyLocalization(strings: PresentationStrings) { legacyLocalization = TGLocalization(version: 0, code: strings.languageCode, dict: strings.dict, isActive: true) } +public func updateLegacyComponentsAccount(_ account: Account?) { + legacyComponentsAccount = account +} + private var legacyDocumentsStorePath: String? private var legacyCanOpenUrl: (URL) -> Bool = { _ in return false } private var legacyOpenUrl: (URL) -> Void = { _ in } @@ -184,11 +190,46 @@ private final class LegacyComponentsGlobalsProviderImpl: NSObject, LegacyCompone } public func currentWallpaperInfo() -> TGWallpaperInfo! { - return nil + if let account = legacyComponentsAccount { + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + switch presentationData.chatWallpaper { + case .builtin: + return TGBuiltinWallpaperInfo() + case let .color(color): + return TGColorWallpaperInfo(color: UInt32(bitPattern: color)) + case let .image(representations): + if let resource = largestImageRepresentation(representations)?.resource, let path = account.postbox.mediaBox.completedResourcePath(resource), let image = UIImage(contentsOfFile: path) { + return TGCustomImageWallpaperInfo(image: image) + } else { + return TGBuiltinWallpaperInfo() + } + } + } else { + return TGBuiltinWallpaperInfo() + } } public func currentWallpaperImage() -> UIImage! { - return nil + if let account = legacyComponentsAccount { + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + switch presentationData.chatWallpaper { + case .builtin: + return nil + case let .color(color): + return generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in + context.setFillColor(UIColor(rgb: UInt32(bitPattern: color)).cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + }) + case let .image(representations): + if let resource = largestImageRepresentation(representations)?.resource, let path = account.postbox.mediaBox.completedResourcePath(resource), let image = UIImage(contentsOfFile: path) { + return image + } else { + return nil + } + } + } else { + return nil + } } public func sharedMediaImageProcessingThreadPool() -> SThreadPool! { diff --git a/TelegramUI/TelegramVideoNode.swift b/TelegramUI/TelegramVideoNode.swift index 3b8972e9c5..21b9d7f6b9 100644 --- a/TelegramUI/TelegramVideoNode.swift +++ b/TelegramUI/TelegramVideoNode.swift @@ -239,12 +239,6 @@ final class TelegramVideoNode: OverlayMediaItemNode { } self.imageNode.setSignal(account: account, signal: chatMessageVideo(account: account, video: source.file)) - - self.statusDisposable = (chatMessageFileStatus(account: account, file: source.file) |> deliverOnMainQueue).start(next: { [weak self] status in - if let strongSelf = self { - - } - }) } deinit { diff --git a/TelegramUI/ThemeGalleryController.swift b/TelegramUI/ThemeGalleryController.swift index 578d41dec9..2dd0589001 100644 --- a/TelegramUI/ThemeGalleryController.swift +++ b/TelegramUI/ThemeGalleryController.swift @@ -161,7 +161,7 @@ class ThemeGalleryController: ViewController { if !self.entries.isEmpty { if centralItemNode.index == 0, let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]), !forceAway { animatedOutNode = false - centralItemNode.animateOut(to: transitionArguments.transitionNode, completion: { + centralItemNode.animateOut(to: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface, completion: { animatedOutNode = true completion() }) @@ -193,11 +193,11 @@ class ThemeGalleryController: ViewController { //self.galleryNode.statusBar = self.statusBar self.galleryNode.navigationBar = self.navigationBar - self.galleryNode.transitionNodeForCentralItem = { [weak self] in + self.galleryNode.transitionDataForCentralItem = { [weak self] in if let strongSelf = self { if let centralItemNode = strongSelf.galleryNode.pager.centralItemNode(), let presentationArguments = strongSelf.presentationArguments as? ThemePreviewControllerPresentationArguments { if let transitionArguments = presentationArguments.transitionArguments(strongSelf.entries[centralItemNode.index]) { - return transitionArguments.transitionNode + return (transitionArguments.transitionNode, transitionArguments.addToTransitionSurface) } } } @@ -248,7 +248,7 @@ class ThemeGalleryController: ViewController { wallpaper = value } let _ = (updatePresentationThemeSettingsInteractively(postbox: strongSelf.account.postbox, { current in - if case .color(0x121212) = wallpaper { + if case .color(0x000000) = wallpaper { return PresentationThemeSettings(chatWallpaper: wallpaper, theme: .builtin(.dark)) } @@ -280,7 +280,7 @@ class ThemeGalleryController: ViewController { if let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]) { nodeAnimatesItself = true - centralItemNode.animateIn(from: transitionArguments.transitionNode) + centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface) self._hiddenMedia.set(.single(self.entries[centralItemNode.index])) } diff --git a/TelegramUI/ThemeGalleryItem.swift b/TelegramUI/ThemeGalleryItem.swift index 7c8548072e..2d747e95c8 100644 --- a/TelegramUI/ThemeGalleryItem.swift +++ b/TelegramUI/ThemeGalleryItem.swift @@ -103,7 +103,7 @@ final class ThemeGalleryItemNode: ZoomableContentGalleryItemNode { } } - override func animateIn(from node: ASDisplayNode) { + override func animateIn(from node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void) { var transformedFrame = node.view.convert(node.view.bounds, to: self.imageNode.view) let transformedSuperFrame = node.view.convert(node.view.bounds, to: self.imageNode.view.superview) let transformedSelfFrame = node.view.convert(node.view.bounds, to: self.view) @@ -139,7 +139,7 @@ final class ThemeGalleryItemNode: ZoomableContentGalleryItemNode { }) } - override func animateOut(to node: ASDisplayNode, completion: @escaping () -> Void) { + override func animateOut(to node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) { var transformedFrame = node.view.convert(node.view.bounds, to: self.imageNode.view) let transformedSuperFrame = node.view.convert(node.view.bounds, to: self.imageNode.view.superview) let transformedSelfFrame = node.view.convert(node.view.bounds, to: self.view) diff --git a/TelegramUI/UniversalVideoCalleryItem.swift b/TelegramUI/UniversalVideoCalleryItem.swift index e9139e0ab1..c0aa16301d 100644 --- a/TelegramUI/UniversalVideoCalleryItem.swift +++ b/TelegramUI/UniversalVideoCalleryItem.swift @@ -216,10 +216,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.updateDisplayPlaceholder(!videoNode.ownsContentNode) self.scrubberView.setStatusSignal(videoNode.status |> map { value -> MediaPlayerStatus in - if let value = value { + if let value = value, !value.duration.isZero { return value } else { - return MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: Double(item.content.duration), timestamp: 0.0, status: .paused) + return MediaPlayerStatus(generationTimestamp: 0.0, duration: max(Double(item.content.duration), 0.01), timestamp: 0.0, status: .paused) } }) @@ -299,7 +299,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } - override func animateIn(from node: ASDisplayNode) { + override func animateIn(from node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void) { guard let videoNode = self.videoNode else { return } @@ -319,7 +319,47 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } else { 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 transformedCopyViewFinalFrame = videoNode.view.convert(videoNode.view.bounds, to: self.view) + let surfaceCopyView = node.view.snapshotContentTree()! + let copyView = node.view.snapshotContentTree()! + + addToTransitionSurface(surfaceCopyView) + + var transformedSurfaceFrame: CGRect? + var transformedSurfaceFinalFrame: CGRect? + if let contentSurface = surfaceCopyView.superview { + transformedSurfaceFrame = node.view.convert(node.view.bounds, to: contentSurface) + transformedSurfaceFinalFrame = videoNode.view.convert(videoNode.view.bounds, to: contentSurface) + } + + if let transformedSurfaceFrame = transformedSurfaceFrame { + surfaceCopyView.frame = transformedSurfaceFrame + } + + self.view.insertSubview(copyView, belowSubview: self.scrollNode.view) + copyView.frame = transformedSelfFrame + + copyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, removeOnCompletion: false) + + surfaceCopyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) + + copyView.layer.animatePosition(from: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak copyView] _ in + copyView?.removeFromSuperview() + }) + let scale = CGSize(width: transformedCopyViewFinalFrame.size.width / transformedSelfFrame.size.width, height: transformedCopyViewFinalFrame.size.height / transformedSelfFrame.size.height) + copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false) + + if let transformedSurfaceFrame = transformedSurfaceFrame, let transformedSurfaceFinalFrame = transformedSurfaceFinalFrame { + surfaceCopyView.layer.animatePosition(from: CGPoint(x: transformedSurfaceFrame.midX, y: transformedSurfaceFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak surfaceCopyView] _ in + surfaceCopyView?.removeFromSuperview() + }) + let scale = CGSize(width: transformedSurfaceFinalFrame.size.width / transformedSurfaceFrame.size.width, height: transformedSurfaceFinalFrame.size.height / transformedSurfaceFrame.size.height) + surfaceCopyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false) + } + + videoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) videoNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: videoNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) transformedFrame.origin = CGPoint() @@ -332,12 +372,13 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { let transform = CATransform3DScale(pictureInPictureNode.layer.transform, transformedPlaceholderFrame.size.width / pictureInPictureNode.layer.bounds.size.width, transformedPlaceholderFrame.size.height / pictureInPictureNode.layer.bounds.size.height, 1.0) pictureInPictureNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: pictureInPictureNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25) + pictureInPictureNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) pictureInPictureNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: pictureInPictureNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) } } } - override func animateOut(to node: ASDisplayNode, completion: @escaping () -> Void) { + override func animateOut(to node: ASDisplayNode, addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) { guard let videoNode = self.videoNode else { completion() return @@ -353,18 +394,30 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { var copyCompleted = false let copyView = node.view.snapshotContentTree()! + let surfaceCopyView = node.view.snapshotContentTree()! + + addToTransitionSurface(surfaceCopyView) + + var transformedSurfaceFrame: CGRect? + var transformedSurfaceCopyViewInitialFrame: CGRect? + if let contentSurface = surfaceCopyView.superview { + transformedSurfaceFrame = node.view.convert(node.view.bounds, to: contentSurface) + transformedSurfaceCopyViewInitialFrame = videoNode.view.convert(videoNode.view.bounds, to: contentSurface) + } self.view.insertSubview(copyView, belowSubview: self.scrollNode.view) copyView.frame = transformedSelfFrame - let intermediateCompletion = { [weak copyView] in + let intermediateCompletion = { [weak copyView, weak surfaceCopyView] in if positionCompleted && boundsCompleted && copyCompleted { copyView?.removeFromSuperview() + surfaceCopyView?.removeFromSuperview() completion() } } - copyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, removeOnCompletion: false) + copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false) + surfaceCopyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, 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) @@ -373,12 +426,18 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { intermediateCompletion() }) + if let transformedSurfaceFrame = transformedSurfaceFrame, let transformedCopyViewInitialFrame = transformedSurfaceCopyViewInitialFrame { + surfaceCopyView.layer.animatePosition(from: CGPoint(x: transformedCopyViewInitialFrame.midX, y: transformedCopyViewInitialFrame.midY), to: CGPoint(x: transformedSurfaceFrame.midX, y: transformedSurfaceFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + let scale = CGSize(width: transformedCopyViewInitialFrame.size.width / transformedSurfaceFrame.size.width, height: transformedCopyViewInitialFrame.size.height / transformedSurfaceFrame.size.height) + surfaceCopyView.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) + } + 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) + videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, 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 diff --git a/TelegramUI/UniversalVideoContentManager.swift b/TelegramUI/UniversalVideoContentManager.swift index afa5166941..d24c11fb55 100644 --- a/TelegramUI/UniversalVideoContentManager.swift +++ b/TelegramUI/UniversalVideoContentManager.swift @@ -188,7 +188,7 @@ final class UniversalVideoContentManager { if let current = self.holders[content.id] { subscriber.putNext(current.statusValue) } else { - subscriber.putNext(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(content.duration), timestamp: 0.0, status: .paused)) + subscriber.putNext(nil) } return ActionDisposable { diff --git a/TelegramUI/UserInfoController.swift b/TelegramUI/UserInfoController.swift index 0bbb5ca8d2..db1c545db4 100644 --- a/TelegramUI/UserInfoController.swift +++ b/TelegramUI/UserInfoController.swift @@ -523,7 +523,7 @@ public func userInfoController(account: Account, peerId: PeerId) -> ViewControll openChatImpl?() }, changeNotificationMuteSettings: { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let controller = ActionSheetController() + let controller = ActionSheetController(presentationTheme: presentationData.theme) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() } @@ -595,7 +595,8 @@ public func userInfoController(account: Account, peerId: PeerId) -> ViewControll return modifier.getPeer(peerId) } |> deliverOnMainQueue).start(next: { peer in if let peer = peer as? TelegramUser, let peerPhoneNumber = peer.phone, formatPhoneNumber(number) == formatPhoneNumber(peerPhoneNumber) { - let controller = ActionSheetController() + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + let controller = ActionSheetController(presentationTheme: presentationData.theme) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() } @@ -758,7 +759,8 @@ public func userInfoController(account: Account, peerId: PeerId) -> ViewControll } } if let (node, _) = result { - return GalleryTransitionArguments(transitionNode: node, transitionContainerNode: controller.displayNode, transitionBackgroundNode: controller.displayNode) + return GalleryTransitionArguments(transitionNode: node, addToTransitionSurface: { _ in + }) } } return nil diff --git a/TelegramUI/VerticalListContextResultsChatInputContextPanelNode.swift b/TelegramUI/VerticalListContextResultsChatInputContextPanelNode.swift index 2f709796b4..d1c34f2763 100644 --- a/TelegramUI/VerticalListContextResultsChatInputContextPanelNode.swift +++ b/TelegramUI/VerticalListContextResultsChatInputContextPanelNode.swift @@ -184,6 +184,7 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex //options.insert(.LowLatency) } else { options.insert(.AnimateTopItemPosition) + options.insert(.AnimateCrossfade) } var insets = UIEdgeInsets() diff --git a/TelegramUI/Wallpapers.swift b/TelegramUI/Wallpapers.swift index 4b05ce1c4c..662b18f518 100644 --- a/TelegramUI/Wallpapers.swift +++ b/TelegramUI/Wallpapers.swift @@ -63,7 +63,7 @@ func telegramWallpapers(account: Account) -> Signal<[TelegramWallpaper], NoError return account.postbox.modify { modifier -> [TelegramWallpaper] in let items = modifier.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudWallpapers) if items.count == 0 { - return [.builtin, .color(0x121212)] + return [.color(0x000000), .builtin] } else { return items.map { $0.contents as! TelegramWallpaper } } @@ -81,8 +81,8 @@ func telegramWallpapers(account: Account) -> Signal<[TelegramWallpaper], NoError } } items.removeFirst() - items.insert(.builtin, at: 0) - items.insert(.color(0x121212), at: 1) + items.insert(.color(0x000000), at: 0) + items.insert(.builtin, at: 1) if items == list { return .complete()