From 80e00293b69ae827c9424508f809c56d85785cfa Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 2 Aug 2021 09:53:00 +0300 Subject: [PATCH 1/2] Temporarily rollback audio session setup for video recording --- .../Sources/TelegramInitializeLegacyComponents.swift | 2 +- .../TelegramAudio/Sources/ManagedAudioSession.swift | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift b/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift index 4016d8ce82..91974df539 100644 --- a/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift +++ b/submodules/LegacyUI/Sources/TelegramInitializeLegacyComponents.swift @@ -182,7 +182,7 @@ private final class LegacyComponentsGlobalsProviderImpl: NSObject, LegacyCompone let convertedType: ManagedAudioSessionType switch type { case TGAudioSessionTypePlayAndRecord, TGAudioSessionTypePlayAndRecordHeadphones: - convertedType = .recordWithOthers + convertedType = .record(speaker: false) default: convertedType = .play } diff --git a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift index f9cd5d88a9..c1f5311f2a 100644 --- a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift +++ b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift @@ -39,7 +39,7 @@ private func nativeCategoryForType(_ type: ManagedAudioSessionType, headphones: return .ambient case .play: return .playback - case .record, .recordWithOthers, .voiceCall, .videoCall: + case .record, .recordWithOthers, .voiceCall, .videoCall: return .playAndRecord case .playWithPossiblePortOverride: if headphones { @@ -568,7 +568,7 @@ public final class ManagedAudioSession { index += 1 } - let lastIsRecordWithOthers = self.holders.last?.audioSessionType == .recordWithOthers + let lastIsRecordWithOthers = false // self.holders.last?.audioSessionType == .recordWithOthers if !deactivating { if let activeIndex = activeIndex { var deactivate = false @@ -745,9 +745,9 @@ public final class ManagedAudioSession { case .videoCall: mode = .videoChat options.insert(.mixWithOthers) - case .recordWithOthers: - mode = .videoRecording - options.insert(.mixWithOthers) +// case .recordWithOthers: +// mode = .videoRecording +// options.insert(.mixWithOthers) default: mode = .default } From c187f16728100557af7bf1e444259e0a80b3f6e9 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 3 Aug 2021 20:30:56 +0300 Subject: [PATCH 2/2] Various Fixes --- .../Sources/PeerSelectionController.swift | 4 +- .../Sources/ChatListSearchListPaneNode.swift | 63 +++-- .../Items/UniversalVideoGalleryItem.swift | 9 +- .../LegacyComponents/TGCameraController.h | 2 +- .../Sources/TGAttachmentCameraView.m | 3 + .../Sources/TGCameraController.m | 35 ++- .../Sources/TGCameraPreviewView.m | 22 +- .../Sources/TGMediaAvatarMenuMixin.m | 2 +- submodules/ShareController/BUILD | 1 + .../Sources/ShareControllerPeerGridItem.swift | 76 +++++- .../Sources/ShareSearchContainerNode.swift | 50 ++-- ...ediaNavigationAccessoryContainerNode.swift | 2 +- .../MediaNavigationAccessoryHeaderNode.swift | 251 ++++++++++++++++-- .../MediaNavigationAccessoryPanel.swift | 24 +- .../Sources/TelegramBaseController.swift | 61 +++-- .../Sources/VoiceChatController.swift | 4 +- .../TelegramUI/Sources/ChatController.swift | 2 +- .../ChatMessageAnimatedStickerItemNode.swift | 6 +- .../ChatMessageInstantVideoItemNode.swift | 46 ++-- .../Sources/ChatMessageStickerItemNode.swift | 2 +- .../InstantVideoRadialStatusNode.swift | 74 +++++- .../PeerInfo/Panes/PeerInfoListPaneNode.swift | 61 +++-- .../Sources/PeerSelectionController.swift | 4 +- .../Sources/PeerSelectionControllerNode.swift | 20 +- 24 files changed, 626 insertions(+), 198 deletions(-) diff --git a/submodules/AccountContext/Sources/PeerSelectionController.swift b/submodules/AccountContext/Sources/PeerSelectionController.swift index 34a9371976..e4f30c083b 100644 --- a/submodules/AccountContext/Sources/PeerSelectionController.swift +++ b/submodules/AccountContext/Sources/PeerSelectionController.swift @@ -40,8 +40,9 @@ public final class PeerSelectionControllerParams { public let createNewGroup: (() -> Void)? public let pretendPresentedInModal: Bool public let multipleSelection: Bool + public let forwardedMessagesCount: Int - public init(context: AccountContext, filter: ChatListNodePeersFilter = [.onlyWriteable], hasChatListSelector: Bool = true, hasContactSelector: Bool = true, hasGlobalSearch: Bool = true, title: String? = nil, attemptSelection: ((Peer) -> Void)? = nil, createNewGroup: (() -> Void)? = nil, pretendPresentedInModal: Bool = false, multipleSelection: Bool = false) { + public init(context: AccountContext, filter: ChatListNodePeersFilter = [.onlyWriteable], hasChatListSelector: Bool = true, hasContactSelector: Bool = true, hasGlobalSearch: Bool = true, title: String? = nil, attemptSelection: ((Peer) -> Void)? = nil, createNewGroup: (() -> Void)? = nil, pretendPresentedInModal: Bool = false, multipleSelection: Bool = false, forwardedMessagesCount: Int = 0) { self.context = context self.filter = filter self.hasChatListSelector = hasChatListSelector @@ -52,6 +53,7 @@ public final class PeerSelectionControllerParams { self.createNewGroup = createNewGroup self.pretendPresentedInModal = pretendPresentedInModal self.multipleSelection = multipleSelection + self.forwardedMessagesCount = forwardedMessagesCount } } diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index b227158f6a..2342d7bce9 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -1727,32 +1727,28 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context, displayBackground: true) mediaAccessoryPanel.containerNode.headerNode.displayScrubber = item.playbackData?.type != .instantVideo + mediaAccessoryPanel.getController = { [weak self] in + return self?.navigationController?.topViewController as? ViewController + } + mediaAccessoryPanel.presentInGlobalOverlay = { [weak self] c in + (self?.navigationController?.topViewController as? ViewController)?.presentInGlobalOverlay(c) + } mediaAccessoryPanel.close = { [weak self] in if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause)) } } - mediaAccessoryPanel.toggleRate = { - [weak self] in + mediaAccessoryPanel.setRate = { [weak self] rate in guard let strongSelf = self else { return } let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> AudioPlaybackRate in let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings) as? MusicPlaybackSettings ?? MusicPlaybackSettings.defaultSettings - - let nextRate: AudioPlaybackRate - switch settings.voicePlaybackRate { - case .x1: - nextRate = .x2 - case .x2: - nextRate = .x1 - default: - nextRate = .x1 - } + transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in - return settings.withUpdatedVoicePlaybackRate(nextRate) + return settings.withUpdatedVoicePlaybackRate(rate) }) - return nextRate + return rate } |> deliverOnMainQueue).start(next: { baseRate in guard let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType else { @@ -1771,22 +1767,31 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }) let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let slowdown = baseRate == .x1 - controller.present( - UndoOverlayController( - presentationData: presentationData, - content: .audioRate( - slowdown: slowdown, - text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp + let slowdown: Bool? + if baseRate == .x1 { + slowdown = true + } else if baseRate == .x2 { + slowdown = false + } else { + slowdown = nil + } + if let slowdown = slowdown { + controller.present( + UndoOverlayController( + presentationData: presentationData, + content: .audioRate( + slowdown: slowdown, + text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp + ), + elevatedLayout: false, + animateInAsReplacement: hasTooltip, + action: { action in + return true + } ), - elevatedLayout: false, - animateInAsReplacement: hasTooltip, - action: { action in - return true - } - ), - in: .current - ) + in: .current + ) + } } }) } diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 62880ca214..56578535cd 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -524,6 +524,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { private let isInteractingPromise = ValuePromise(false, ignoreRepeated: true) private let controlsVisiblePromise = ValuePromise(true, ignoreRepeated: true) private let isShowingContextMenuPromise = ValuePromise(false, ignoreRepeated: true) + private let hasExpandedCaptionPromise = ValuePromise(false, ignoreRepeated: true) private var hideControlsDisposable: Disposable? var playbackCompleted: (() -> Void)? @@ -701,12 +702,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.titleContentView = GalleryTitleView(frame: CGRect()) self._titleView.set(.single(self.titleContentView)) - let shouldHideControlsSignal: Signal = combineLatest(self.isPlayingPromise.get(), self.isInteractingPromise.get(), self.controlsVisiblePromise.get(), self.isShowingContextMenuPromise.get()) - |> mapToSignal { isPlaying, isIntracting, controlsVisible, isShowingContextMenu -> Signal in - if isShowingContextMenu { + let shouldHideControlsSignal: Signal = combineLatest(self.isPlayingPromise.get(), self.isInteractingPromise.get(), self.controlsVisiblePromise.get(), self.isShowingContextMenuPromise.get(), self.hasExpandedCaptionPromise.get()) + |> mapToSignal { isPlaying, isInteracting, controlsVisible, isShowingContextMenu, hasExpandedCaptionPromise -> Signal in + if isShowingContextMenu || hasExpandedCaptionPromise { return .complete() } - if isPlaying && !isIntracting && controlsVisible { + if isPlaying && !isInteracting && controlsVisible { return .single(Void()) |> delay(4.0, queue: Queue.mainQueue()) } else { diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraController.h index da8b0b9bf3..c08deb5ed3 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraController.h @@ -76,6 +76,6 @@ typedef enum { + (UIInterfaceOrientation)_interfaceOrientationForDeviceOrientation:(UIDeviceOrientation)orientation; -+ (bool)useLegacyCamera; ++ (UIImage *)startImage; @end diff --git a/submodules/LegacyComponents/Sources/TGAttachmentCameraView.m b/submodules/LegacyComponents/Sources/TGAttachmentCameraView.m index 30821b674e..3cea35671f 100644 --- a/submodules/LegacyComponents/Sources/TGAttachmentCameraView.m +++ b/submodules/LegacyComponents/Sources/TGAttachmentCameraView.m @@ -4,6 +4,7 @@ #import #import "TGAttachmentMenuCell.h" +#import "TGCameraController.h" #import #import @@ -46,6 +47,8 @@ _camera = camera; _previewView = [[TGCameraPreviewView alloc] initWithFrame:CGRectMake(0, 0, 84.0f, 84.0f)]; + [_previewView fadeInAnimated:false]; + [_previewView beginTransitionWithSnapshotImage:[TGCameraController startImage] animated:false]; [_wrapperView addSubview:_previewView]; [camera attachPreviewView:_previewView]; diff --git a/submodules/LegacyComponents/Sources/TGCameraController.m b/submodules/LegacyComponents/Sources/TGCameraController.m index 0cae82eb74..7e9deb2fc0 100644 --- a/submodules/LegacyComponents/Sources/TGCameraController.m +++ b/submodules/LegacyComponents/Sources/TGCameraController.m @@ -2251,6 +2251,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus _dismissing = true; self.view.userInteractionEnabled = false; + _focusControl.active = false; _rectangleView.hidden = true; @@ -2267,6 +2268,15 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus referenceFrame = self.beginTransitionOut(); __weak TGCameraController *weakSelf = self; + [_camera captureNextFrameCompletion:^(UIImage *frameImage) { + CGFloat minSize = MIN(frameImage.size.width, frameImage.size.height); + CGFloat maxSize = MAX(frameImage.size.width, frameImage.size.height); + + UIImage *image = TGPhotoEditorCrop(frameImage, nil, UIImageOrientationUp, 0.0f, CGRectMake((maxSize - minSize) / 2.0f, 0.0f, minSize, minSize), false, CGSizeMake(240.0f, 240.0f), frameImage.size, true); + + UIImage *startImage = TGSecretBlurredAttachmentImage(image, image.size, NULL, false, 0); + [TGCameraController saveStartImage:startImage]; + }]; if (_standalone) { [self simpleTransitionOutWithVelocity:velocity completion:^ @@ -2757,11 +2767,6 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus } } -+ (bool)useLegacyCamera -{ - return false; -} - + (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext currentItem:(id)currentItem storeAssets:(bool)storeAssets saveEditedPhotos:(bool)saveEditedPhotos descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *))descriptionGenerator { NSMutableArray *signals = [[NSMutableArray alloc] init]; @@ -3144,4 +3149,24 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus return value; } +#pragma mark - Start Image + +static UIImage *startImage = nil; + ++ (UIImage *)startImage +{ + if (startImage == nil) + startImage = TGComponentsImageNamed (@"VideoMessagePlaceholder.jpg"); + + return startImage; +} + ++ (void)saveStartImage:(UIImage *)image +{ + if (image == nil) + return; + + startImage = image; +} + @end diff --git a/submodules/LegacyComponents/Sources/TGCameraPreviewView.m b/submodules/LegacyComponents/Sources/TGCameraPreviewView.m index b49624e7a9..abed3a0e18 100644 --- a/submodules/LegacyComponents/Sources/TGCameraPreviewView.m +++ b/submodules/LegacyComponents/Sources/TGCameraPreviewView.m @@ -129,10 +129,15 @@ if (strongSelf == nil) return; - if (resume) + if (resume) { [strongSelf endResetTransitionAnimated:true]; - else - [strongSelf fadeInAnimated:true]; + } else { + if (strongSelf->_snapshotView != nil) { + [strongSelf endTransitionAnimated:true]; + } else { + [strongSelf fadeInAnimated:true]; + } + } }; camera.captureStopped = ^(bool pause) @@ -262,6 +267,10 @@ } } +- (bool)hasTransitionSnapshot { + return _snapshotView != nil; +} + - (void)beginResetTransitionAnimated:(bool)animated { if (iosMajorVersion() < 7) @@ -319,7 +328,12 @@ if (_snapshotView != nil) { - CGSize size = TGScaleToFill(_snapshotView.frame.size, _wrapperView.frame.size); + CGSize imageSize = _snapshotView.frame.size; + if ([_snapshotView isKindOfClass:[UIImageView class]]) { + imageSize = ((UIImageView *)_snapshotView).image.size; + } + + CGSize size = TGScaleToFill(imageSize, _wrapperView.frame.size); _snapshotView.frame = CGRectMake(floor((self.frame.size.width - size.width) / 2.0f), floor((self.frame.size.height - size.height) / 2.0f), size.width, size.height); } } diff --git a/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m b/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m index 12b4f56a8a..c482f564ce 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m +++ b/submodules/LegacyComponents/Sources/TGMediaAvatarMenuMixin.m @@ -53,7 +53,7 @@ _hasSearchButton = hasSearchButton; _hasDeleteButton = hasDeleteButton; _hasViewButton = hasViewButton; - _personalPhoto = ![TGCameraController useLegacyCamera] ? personalPhoto : false; + _personalPhoto = personalPhoto; _isVideo = isVideo; _signup = signup; } diff --git a/submodules/ShareController/BUILD b/submodules/ShareController/BUILD index 44333ca34d..e6fa68bd0b 100644 --- a/submodules/ShareController/BUILD +++ b/submodules/ShareController/BUILD @@ -27,6 +27,7 @@ swift_library( "//submodules/AccountContext:AccountContext", "//submodules/SegmentedControlNode:SegmentedControlNode", "//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode", + "//submodules/ShimmerEffect:ShimmerEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/ShareController/Sources/ShareControllerPeerGridItem.swift b/submodules/ShareController/Sources/ShareControllerPeerGridItem.swift index f531a0330f..596e2c2231 100644 --- a/submodules/ShareController/Sources/ShareControllerPeerGridItem.swift +++ b/submodules/ShareController/Sources/ShareControllerPeerGridItem.swift @@ -10,6 +10,7 @@ import TelegramStringFormatting import SelectablePeerNode import PeerPresenceStatusManager import AccountContext +import ShimmerEffect final class ShareControllerInteraction { var foundPeers: [RenderedPeer] = [] @@ -89,14 +90,14 @@ final class ShareControllerPeerGridItem: GridItem { let context: AccountContext let theme: PresentationTheme let strings: PresentationStrings - let peer: RenderedPeer + let peer: RenderedPeer? let presence: PeerPresence? let controllerInteraction: ShareControllerInteraction let search: Bool let section: GridSection? - init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: RenderedPeer, presence: PeerPresence?, controllerInteraction: ShareControllerInteraction, sectionTitle: String? = nil, search: Bool = false) { + init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: RenderedPeer?, presence: PeerPresence?, controllerInteraction: ShareControllerInteraction, sectionTitle: String? = nil, search: Bool = false) { self.context = context self.theme = theme self.strings = strings @@ -130,12 +131,15 @@ final class ShareControllerPeerGridItem: GridItem { } final class ShareControllerPeerGridItemNode: GridItemNode { - private var currentState: (AccountContext, PresentationTheme, PresentationStrings, RenderedPeer, Bool, PeerPresence?)? + private var currentState: (AccountContext, PresentationTheme, PresentationStrings, RenderedPeer?, Bool, PeerPresence?)? private let peerNode: SelectablePeerNode private var presenceManager: PeerPresenceStatusManager? var controllerInteraction: ShareControllerInteraction? + private var placeholderNode: ShimmerEffectNode? + private var absoluteLocation: (CGRect, CGSize)? + override init() { self.peerNode = SelectablePeerNode() @@ -143,7 +147,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode { self.peerNode.toggleSelection = { [weak self] in if let strongSelf = self { - if let (_, _, _, peer, search, _) = strongSelf.currentState { + if let (_, _, _, maybePeer, search, _) = strongSelf.currentState, let peer = maybePeer { if let _ = peer.peers[peer.peerId] { strongSelf.controllerInteraction?.togglePeer(peer, search) } @@ -159,13 +163,21 @@ final class ShareControllerPeerGridItemNode: GridItemNode { }) } - func setup(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: RenderedPeer, presence: PeerPresence?, search: Bool, synchronousLoad: Bool, force: Bool) { + override func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) { + var rect = absoluteRect + self.absoluteLocation = (rect, containerSize) + if let shimmerNode = self.placeholderNode { + shimmerNode.updateAbsoluteRect(rect, within: containerSize) + } + } + + func setup(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: RenderedPeer?, presence: PeerPresence?, search: Bool, synchronousLoad: Bool, force: Bool) { if force || self.currentState == nil || self.currentState!.0 !== context || self.currentState!.3 != peer || !arePeerPresencesEqual(self.currentState!.5, presence) { let itemTheme = SelectablePeerNodeTheme(textColor: theme.actionSheet.primaryTextColor, secretTextColor: theme.chatList.secretTitleColor, selectedTextColor: theme.actionSheet.controlAccentColor, checkBackgroundColor: theme.actionSheet.opaqueItemBackgroundColor, checkFillColor: theme.actionSheet.controlAccentColor, checkColor: theme.actionSheet.checkContentColor, avatarPlaceholderColor: theme.list.mediaPlaceholderColor) let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) var online = false - if let peer = peer.peer as? TelegramUser, let presence = presence as? TelegramUserPresence, !isServicePeer(peer) && !peer.flags.contains(.isSupport) && peer.id != context.account.peerId { + if let peer = peer?.peer as? TelegramUser, let presence = presence as? TelegramUserPresence, !isServicePeer(peer) && !peer.flags.contains(.isSupport) && peer.id != context.account.peerId { let relativeStatus = relativeUserPresenceStatus(presence, relativeTo: timestamp) if case .online = relativeStatus { online = true @@ -173,7 +185,39 @@ final class ShareControllerPeerGridItemNode: GridItemNode { } self.peerNode.theme = itemTheme - self.peerNode.setup(context: context, theme: theme, strings: strings, peer: EngineRenderedPeer(peer), online: online, synchronousLoad: synchronousLoad) + if let peer = peer { + self.peerNode.setup(context: context, theme: theme, strings: strings, peer: EngineRenderedPeer(peer), online: online, synchronousLoad: synchronousLoad) + if let shimmerNode = self.placeholderNode { + self.placeholderNode = nil + shimmerNode.removeFromSupernode() + } + } else { + let shimmerNode: ShimmerEffectNode + if let current = self.placeholderNode { + shimmerNode = current + } else { + shimmerNode = ShimmerEffectNode() + self.placeholderNode = shimmerNode + self.addSubnode(shimmerNode) + } + shimmerNode.frame = self.bounds + if let (rect, size) = self.absoluteLocation { + shimmerNode.updateAbsoluteRect(rect, within: size) + } + + var shapes: [ShimmerEffectNode.Shape] = [] + + let titleLineWidth: CGFloat = 56.0 + let lineDiameter: CGFloat = 10.0 + + let iconFrame = CGRect(x: 13.0, y: 4.0, width: 60.0, height: 60.0) + shapes.append(.circle(iconFrame)) + + let titleFrame = CGRect(x: 15.0, y: 70.0, width: 56.0, height: 10.0) + shapes.append(.roundedRectLine(startPoint: CGPoint(x: titleFrame.minX, y: titleFrame.minY + floor((titleFrame.height - lineDiameter) / 2.0)), width: titleLineWidth, diameter: lineDiameter)) + + shimmerNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, horizontal: true, size: self.bounds.size) + } self.currentState = (context, theme, strings, peer, search, presence) self.setNeedsLayout() if let presence = presence as? TelegramUserPresence { @@ -185,7 +229,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode { func updateSelection(animated: Bool) { var selected = false - if let controllerInteraction = self.controllerInteraction, let (_, _, _, peer, _, _) = self.currentState { + if let controllerInteraction = self.controllerInteraction, let (_, _, _, maybePeer, _, _) = self.currentState, let peer = maybePeer { selected = controllerInteraction.selectedPeerIds.contains(peer.peerId) } @@ -197,5 +241,21 @@ final class ShareControllerPeerGridItemNode: GridItemNode { let bounds = self.bounds self.peerNode.frame = bounds + self.placeholderNode?.frame = bounds + + if let (_, theme, _, _, _, _) = self.currentState, let shimmerNode = self.placeholderNode { + var shapes: [ShimmerEffectNode.Shape] = [] + + let titleLineWidth: CGFloat = 56.0 + let lineDiameter: CGFloat = 10.0 + + let iconFrame = CGRect(x: (bounds.width - 60.0) / 2.0, y: 4.0, width: 60.0, height: 60.0) + shapes.append(.circle(iconFrame)) + + let titleFrame = CGRect(x: (bounds.width - titleLineWidth) / 2.0, y: 70.0, width: titleLineWidth, height: 10.0) + shapes.append(.roundedRectLine(startPoint: CGPoint(x: titleFrame.minX, y: titleFrame.minY + floor((titleFrame.height - lineDiameter) / 2.0)), width: titleLineWidth, diameter: lineDiameter)) + + shimmerNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, horizontal: true, size: self.bounds.size) + } } } diff --git a/submodules/ShareController/Sources/ShareSearchContainerNode.swift b/submodules/ShareController/Sources/ShareSearchContainerNode.swift index 6580453523..6480675655 100644 --- a/submodules/ShareController/Sources/ShareSearchContainerNode.swift +++ b/submodules/ShareController/Sources/ShareSearchContainerNode.swift @@ -110,13 +110,17 @@ private enum ShareSearchRecentEntry: Comparable, Identifiable { private struct ShareSearchPeerEntry: Comparable, Identifiable { let index: Int32 - let peer: RenderedPeer + let peer: RenderedPeer? let presence: PeerPresence? let theme: PresentationTheme let strings: PresentationStrings var stableId: Int64 { - return self.peer.peerId.toInt64() + if let peer = self.peer { + return peer.peerId.toInt64() + } else { + return Int64(index) + } } static func ==(lhs: ShareSearchPeerEntry, rhs: ShareSearchPeerEntry) -> Bool { @@ -134,7 +138,7 @@ private struct ShareSearchPeerEntry: Comparable, Identifiable { } func item(context: AccountContext, interfaceInteraction: ShareControllerInteraction) -> GridItem { - return ShareControllerPeerGridItem(context: context, theme: self.theme, strings: self.strings, peer: peer, presence: self.presence, controllerInteraction: interfaceInteraction, search: true) + return ShareControllerPeerGridItem(context: context, theme: self.theme, strings: self.strings, peer: self.peer, presence: self.presence, controllerInteraction: interfaceInteraction, search: true) } } @@ -246,10 +250,13 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { if !query.isEmpty { let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> take(1) let foundLocalPeers = context.account.postbox.searchPeers(query: query.lowercased()) - let foundRemotePeers: Signal<([FoundPeer], [FoundPeer]), NoError> = .single(([], [])) + let foundRemotePeers: Signal<([FoundPeer], [FoundPeer], Bool), NoError> = .single(([], [], true)) |> then( context.engine.peers.searchPeers(query: query) |> delay(0.2, queue: Queue.concurrentDefaultQueue()) + |> map { a, b -> ([FoundPeer], [FoundPeer], Bool) in + return (a, b, false) + } ) return combineLatest(accountPeer, foundLocalPeers, foundRemotePeers) @@ -278,21 +285,28 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { } } - for foundPeer in foundRemotePeers.0 { - let peer = foundPeer.peer - if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) { - existingPeerIds.insert(peer.id) - entries.append(ShareSearchPeerEntry(index: index, peer: RenderedPeer(peer: foundPeer.peer), presence: nil, theme: theme, strings: strings)) + if foundRemotePeers.2 { + for _ in 0 ..< 4 { + entries.append(ShareSearchPeerEntry(index: index, peer: nil, presence: nil, theme: theme, strings: strings)) index += 1 } - } - - for foundPeer in foundRemotePeers.1 { - let peer = foundPeer.peer - if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) { - existingPeerIds.insert(peer.id) - entries.append(ShareSearchPeerEntry(index: index, peer: RenderedPeer(peer: peer), presence: nil, theme: theme, strings: strings)) - index += 1 + } else { + for foundPeer in foundRemotePeers.0 { + let peer = foundPeer.peer + if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) { + existingPeerIds.insert(peer.id) + entries.append(ShareSearchPeerEntry(index: index, peer: RenderedPeer(peer: foundPeer.peer), presence: nil, theme: theme, strings: strings)) + index += 1 + } + } + + for foundPeer in foundRemotePeers.1 { + let peer = foundPeer.peer + if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) { + existingPeerIds.insert(peer.id) + entries.append(ShareSearchPeerEntry(index: index, peer: RenderedPeer(peer: peer), presence: nil, theme: theme, strings: strings)) + index += 1 + } } } @@ -436,7 +450,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { var scrollToItem: GridNodeScrollToItem? if !self.contentGridNode.isHidden, let ensurePeerVisibleOnLayout = self.ensurePeerVisibleOnLayout { self.ensurePeerVisibleOnLayout = nil - if let index = self.entries.firstIndex(where: { $0.peer.peerId == ensurePeerVisibleOnLayout }) { + if let index = self.entries.firstIndex(where: { $0.peer?.peerId == ensurePeerVisibleOnLayout }) { scrollToItem = GridNodeScrollToItem(index: index, position: .visible, transition: transition, directionHint: .up, adjustForSection: false) } } diff --git a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryContainerNode.swift b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryContainerNode.swift index ba6070ab82..db322c564d 100644 --- a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryContainerNode.swift +++ b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryContainerNode.swift @@ -24,7 +24,7 @@ public final class MediaNavigationAccessoryContainerNode: ASDisplayNode, UIGestu self.backgroundNode = ASDisplayNode() self.separatorNode = ASDisplayNode() - self.headerNode = MediaNavigationAccessoryHeaderNode(presentationData: self.presentationData) + self.headerNode = MediaNavigationAccessoryHeaderNode(context: context) super.init() diff --git a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift index f7cbb240a7..396dfa7ffc 100644 --- a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift +++ b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift @@ -10,6 +10,7 @@ import UniversalMediaPlayer import AccountContext import TelegramStringFormatting import ManagedAnimationNode +import ContextUI private let titleFont = Font.regular(12.0) private let subtitleFont = Font.regular(10.0) @@ -130,6 +131,7 @@ private func generateMaskImage(color: UIColor) -> UIImage? { public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollViewDelegate { public static let minimizedHeight: CGFloat = 37.0 + private let context: AccountContext private var theme: PresentationTheme private var strings: PresentationStrings private var dateTimeFormat: PresentationDateTimeFormat @@ -148,7 +150,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi private let closeButton: HighlightableButtonNode private let actionButton: HighlightTrackingButtonNode private let playPauseIconNode: PlayPauseIconNode - private let rateButton: HighlightableButtonNode + private let rateButton: RateButton private let accessibilityAreaNode: AccessibilityAreaNode private let scrubbingNode: MediaPlayerScrubbingNode @@ -167,24 +169,31 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi public var tapAction: (() -> Void)? public var close: (() -> Void)? - public var toggleRate: (() -> Void)? + public var setRate: ((AudioPlaybackRate) -> Void)? public var togglePlayPause: (() -> Void)? public var playPrevious: (() -> Void)? public var playNext: (() -> Void)? + public var getController: (() -> ViewController?)? + public var presentInGlobalOverlay: ((ViewController) -> Void)? + public var playbackBaseRate: AudioPlaybackRate? = nil { didSet { guard self.playbackBaseRate != oldValue, let playbackBaseRate = self.playbackBaseRate else { return } switch playbackBaseRate { + case .x0_5: + self.rateButton.setContent(.image(optionsRateImage(rate: "0.5x", isLarge: false, color: self.theme.rootController.navigationBar.controlColor))) case .x1: - self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: []) + self.rateButton.setContent(.image(optionsRateImage(rate: "1x", isLarge: false, color: self.theme.rootController.navigationBar.controlColor))) self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateNormal self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange + case .x1_5: + self.rateButton.setContent(.image(optionsRateImage(rate: "1.5x", isLarge: false, color: self.theme.rootController.navigationBar.controlColor))) case .x2: - self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: []) + self.rateButton.setContent(.image(optionsRateImage(rate: "2x", isLarge: false, color: self.theme.rootController.navigationBar.controlColor))) self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateFast self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange @@ -208,7 +217,9 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi } } - public init(presentationData: PresentationData) { + public init(context: AccountContext) { + self.context = context + let presentationData = context.sharedContext.currentPresentationData.with { $0 } self.theme = presentationData.theme self.strings = presentationData.strings self.dateTimeFormat = presentationData.dateTimeFormat @@ -236,7 +247,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi self.closeButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 2.0) self.closeButton.displaysAsynchronously = false - self.rateButton = HighlightableButtonNode() + self.rateButton = RateButton() self.rateButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -4.0, bottom: -8.0, right: -4.0) self.rateButton.displaysAsynchronously = false @@ -265,9 +276,6 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi self.scrollNode.addSubnode(self.previousItemNode) self.scrollNode.addSubnode(self.nextItemNode) - //self.addSubnode(self.leftMaskNode) - //self.addSubnode(self.rightMaskNode) - self.addSubnode(self.closeButton) self.addSubnode(self.rateButton) self.addSubnode(self.accessibilityAreaNode) @@ -276,9 +284,13 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi self.addSubnode(self.actionButton) self.closeButton.addTarget(self, action: #selector(self.closeButtonPressed), forControlEvents: .touchUpInside) - self.rateButton.addTarget(self, action: #selector(self.rateButtonPressed), forControlEvents: .touchUpInside) self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside) + self.rateButton.addTarget(self, action: #selector(self.rateButtonPressed), forControlEvents: .touchUpInside) + self.rateButton.contextAction = { [weak self] sourceNode, gesture in + self?.openRateMenu(sourceNode: sourceNode, gesture: gesture) + } + self.addSubnode(self.scrubbingNode) self.addSubnode(self.separatorNode) @@ -300,13 +312,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi return } if let status = status { - let baseRate: AudioPlaybackRate - if status.baseRate.isEqual(to: 1.0) { - baseRate = .x1 - } else { - baseRate = .x2 - } - strongSelf.playbackBaseRate = baseRate + strongSelf.playbackBaseRate = AudioPlaybackRate(status.baseRate) } else { strongSelf.playbackBaseRate = .x1 } @@ -365,10 +371,14 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi if let playbackBaseRate = self.playbackBaseRate { switch playbackBaseRate { + case .x0_5: + self.rateButton.setContent(.image(optionsRateImage(rate: "0.5x", isLarge: false, color: self.theme.rootController.navigationBar.controlColor))) case .x1: - self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: []) + self.rateButton.setContent(.image(optionsRateImage(rate: "1x", isLarge: false, color: self.theme.rootController.navigationBar.controlColor))) + case .x1_5: + self.rateButton.setContent(.image(optionsRateImage(rate: "1.5x", isLarge: false, color: self.theme.rootController.navigationBar.controlColor))) case .x2: - self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: []) + self.rateButton.setContent(.image(optionsRateImage(rate: "2x", isLarge: false, color: self.theme.rootController.navigationBar.controlColor))) default: break } @@ -475,8 +485,8 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi let bounds = CGRect(origin: CGPoint(), size: size) let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0)) transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 44.0 - rightInset, y: 0.0), size: CGSize(width: 44.0, height: minHeight))) - let rateButtonSize = CGSize(width: 24.0, height: minHeight) - transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 18.0 - closeButtonSize.width - 17.0 - rateButtonSize.width - rightInset, y: 0.0), size: rateButtonSize)) + let rateButtonSize = CGSize(width: 30.0, height: minHeight) + transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 20.0 - closeButtonSize.width - rateButtonSize.width - rightInset, y: -4.0), size: rateButtonSize)) transition.updateFrame(node: self.playPauseIconNode, frame: CGRect(origin: CGPoint(x: 6.0, y: 4.0 + UIScreenPixel), size: CGSize(width: 28.0, height: 28.0))) transition.updateFrame(node: self.actionButton, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 40.0, height: 37.0))) transition.updateFrame(node: self.scrubbingNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 37.0 - 2.0), size: CGSize(width: size.width, height: 2.0))) @@ -491,7 +501,48 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi } @objc public func rateButtonPressed() { - self.toggleRate?() + self.rateButton.contextAction?(self.rateButton.containerNode, nil) + } + + private func speedList(strings: PresentationStrings) -> [(String, String, AudioPlaybackRate)] { + let speedList: [(String, String, AudioPlaybackRate)] = [ + ("0.5x", "0.5x", .x0_5), + (strings.PlaybackSpeed_Normal, "1x", .x1), + ("1.5x", "1.5x", .x1_5), + ("2x", "2x", .x2) + ] + return speedList + } + + private func contextMenuSpeedItems() -> Signal<[ContextMenuItem], NoError> { + var items: [ContextMenuItem] = [] + + for (text, _, rate) in self.speedList(strings: self.strings) { + let isSelected = self.playbackBaseRate == rate + items.append(.action(ContextMenuActionItem(text: text, icon: { theme in + if isSelected { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + } else { + return nil + } + }, action: { [weak self] _, f in + f(.default) + + self?.setRate?(rate) + }))) + } + + return .single(items) + } + + private func openRateMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) { + guard let controller = self.getController?() else { + return + } + let items: Signal<[ContextMenuItem], NoError> = self.contextMenuSpeedItems() + let contextController = ContextController(account: self.context.account, presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.rateButton.referenceNode)), items: items, reactionItems: [], gesture: gesture) + self.presentInGlobalOverlay?(contextController) +// controller.presentInGlobalOverlay(contextController) } @objc public func actionButtonPressed() { @@ -554,3 +605,159 @@ private final class PlayPauseIconNode: ManagedAnimationNode { } } } + +private func optionsRateImage(rate: String, isLarge: Bool, color: UIColor = .white) -> UIImage? { + return generateImage(isLarge ? CGSize(width: 30.0, height: 30.0) : CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in + UIGraphicsPushContext(context) + + context.clear(CGRect(origin: CGPoint(), size: size)) + + if let image = generateTintedImage(image: UIImage(bundleImageName: isLarge ? "Chat/Context Menu/Playspeed30" : "Chat/Context Menu/Playspeed24"), color: color) { + image.draw(at: CGPoint(x: 0.0, y: 0.0)) + } + + let string = NSMutableAttributedString(string: rate, font: Font.with(size: 11.0, design: .round, weight: .semibold), textColor: color) + + var offset = CGPoint(x: 1.0, y: 0.0) + if rate.count >= 3 { + if rate == "0.5x" { + string.addAttribute(.kern, value: -0.8 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string)) + offset.x += -0.5 + } else { + string.addAttribute(.kern, value: -0.5 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string)) + offset.x += -0.3 + } + } else { + offset.x += -0.3 + } + + if !isLarge { + offset.x *= 0.5 + offset.y *= 0.5 + } + + let boundingRect = string.boundingRect(with: size, options: [], context: nil) + string.draw(at: CGPoint(x: offset.x + floor((size.width - boundingRect.width) / 2.0), y: offset.y + floor((size.height - boundingRect.height) / 2.0))) + + UIGraphicsPopContext() + }) +} + +private final class RateButton: HighlightableButtonNode { + enum Content { + case image(UIImage?) + } + + let referenceNode: ContextReferenceContentNode + let containerNode: ContextControllerSourceNode + private let iconNode: ASImageNode + + var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? + + private let wide: Bool + + init(wide: Bool = false) { + self.wide = wide + + self.referenceNode = ContextReferenceContentNode() + self.containerNode = ContextControllerSourceNode() + self.containerNode.animateScale = false + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + self.iconNode.displayWithoutProcessing = true + self.iconNode.contentMode = .scaleToFill + + super.init() + + self.containerNode.addSubnode(self.referenceNode) + self.referenceNode.addSubnode(self.iconNode) + self.addSubnode(self.containerNode) + + self.containerNode.shouldBegin = { [weak self] location in + guard let strongSelf = self, let _ = strongSelf.contextAction else { + return false + } + return true + } + self.containerNode.activated = { [weak self] gesture, _ in + guard let strongSelf = self else { + return + } + strongSelf.contextAction?(strongSelf.containerNode, gesture) + } + + self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 26.0, height: 44.0)) + self.referenceNode.frame = self.containerNode.bounds + + if let image = self.iconNode.image { + self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) + } + + self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -4.0, bottom: 0.0, right: -4.0) + } + + private var content: Content? + func setContent(_ content: Content, animated: Bool = false) { + if animated { + if let snapshotView = self.referenceNode.view.snapshotContentTree() { + snapshotView.frame = self.referenceNode.frame + self.view.addSubview(snapshotView) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + snapshotView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false) + + self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.iconNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3) + } + + switch content { + case let .image(image): + if let image = image { + self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) + } + + self.iconNode.image = image + self.iconNode.isHidden = false + } + } else { + self.content = content + switch content { + case let .image(image): + if let image = image { + self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) + } + + self.iconNode.image = image + self.iconNode.isHidden = false + } + } + } + + override func didLoad() { + super.didLoad() + self.view.isOpaque = false + } + + override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + return CGSize(width: wide ? 32.0 : 22.0, height: 44.0) + } + + func onLayout() { + } +} + +private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { + private let controller: ViewController + private let sourceNode: ContextReferenceContentNode + + init(controller: ViewController, sourceNode: ContextReferenceContentNode) { + self.controller = controller + self.sourceNode = sourceNode + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} diff --git a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryPanel.swift b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryPanel.swift index 9e1e41a976..a7ab775591 100644 --- a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryPanel.swift +++ b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryPanel.swift @@ -4,17 +4,21 @@ import Display import AsyncDisplayKit import TelegramCore import AccountContext +import TelegramUIPreferences public final class MediaNavigationAccessoryPanel: ASDisplayNode { public let containerNode: MediaNavigationAccessoryContainerNode public var close: (() -> Void)? - public var toggleRate: (() -> Void)? + public var setRate: ((AudioPlaybackRate) -> Void)? public var togglePlayPause: (() -> Void)? public var tapAction: (() -> Void)? public var playPrevious: (() -> Void)? public var playNext: (() -> Void)? + public var getController: (() -> ViewController?)? + public var presentInGlobalOverlay: ((ViewController) -> Void)? + public init(context: AccountContext, displayBackground: Bool = false) { self.containerNode = MediaNavigationAccessoryContainerNode(context: context, displayBackground: displayBackground) @@ -27,8 +31,8 @@ public final class MediaNavigationAccessoryPanel: ASDisplayNode { close() } } - self.containerNode.headerNode.toggleRate = { [weak self] in - self?.toggleRate?() + self.containerNode.headerNode.setRate = { [weak self] rate in + self?.setRate?(rate) } self.containerNode.headerNode.togglePlayPause = { [weak self] in if let strongSelf = self, let togglePlayPause = strongSelf.togglePlayPause { @@ -50,6 +54,20 @@ public final class MediaNavigationAccessoryPanel: ASDisplayNode { playNext() } } + + self.containerNode.headerNode.getController = { [weak self] in + if let strongSelf = self, let getController = strongSelf.getController { + return getController() + } else { + return nil + } + } + + self.containerNode.headerNode.presentInGlobalOverlay = { [weak self] c in + if let strongSelf = self, let presentInGlobalOverlay = strongSelf.presentInGlobalOverlay { + presentInGlobalOverlay(c) + } + } } public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index c781285b0b..ba568b1fc3 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -645,32 +645,28 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context) mediaAccessoryPanel.containerNode.headerNode.displayScrubber = item.playbackData?.type != .instantVideo + mediaAccessoryPanel.getController = { [weak self] in + return self + } + mediaAccessoryPanel.presentInGlobalOverlay = { [weak self] c in + self?.presentInGlobalOverlay(c) + } mediaAccessoryPanel.close = { [weak self] in if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause)) } } - mediaAccessoryPanel.toggleRate = { - [weak self] in + mediaAccessoryPanel.setRate = { [weak self] rate in guard let strongSelf = self else { return } let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> AudioPlaybackRate in let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings) as? MusicPlaybackSettings ?? MusicPlaybackSettings.defaultSettings - let nextRate: AudioPlaybackRate - switch settings.voicePlaybackRate { - case .x1: - nextRate = .x2 - case .x2: - nextRate = .x1 - default: - nextRate = .x1 - } transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in - return settings.withUpdatedVoicePlaybackRate(nextRate) + return settings.withUpdatedVoicePlaybackRate(rate) }) - return nextRate + return rate } |> deliverOnMainQueue).start(next: { baseRate in guard let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType else { @@ -688,22 +684,31 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { }) let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let slowdown = baseRate == .x1 - strongSelf.present( - UndoOverlayController( - presentationData: presentationData, - content: .audioRate( - slowdown: slowdown, - text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp + let slowdown: Bool? + if baseRate == .x1 { + slowdown = true + } else if baseRate == .x2 { + slowdown = false + } else { + slowdown = nil + } + if let slowdown = slowdown { + strongSelf.present( + UndoOverlayController( + presentationData: presentationData, + content: .audioRate( + slowdown: slowdown, + text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp + ), + elevatedLayout: false, + animateInAsReplacement: hasTooltip, + action: { action in + return true + } ), - elevatedLayout: false, - animateInAsReplacement: hasTooltip, - action: { action in - return true - } - ), - in: .current - ) + in: .current + ) + } }) } mediaAccessoryPanel.togglePlayPause = { [weak self] in diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index a999734b3a..e20dd6681e 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -4776,7 +4776,6 @@ public final class VoiceChatController: ViewController { let speakingPeersUpdated = self.currentSpeakingPeers != speakingPeers self.currentCallMembers = callMembers - self.currentSpeakingPeers = speakingPeers self.currentInvitedPeers = invitedPeers var entries: [ListEntry] = [] @@ -5081,9 +5080,10 @@ public final class VoiceChatController: ViewController { self.updateMainVideo(waitForFullSize: true, entries: fullscreenEntries, force: true) return } - + self.updateRequestedVideoChannels() + self.currentSpeakingPeers = speakingPeers self.peerIdToEndpointId = peerIdToEndpointId var updateLayout = false diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 396c93fe20..8f3a5b822c 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -10978,7 +10978,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var attemptSelectionImpl: ((Peer) -> Void)? let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: filter, attemptSelection: { peer in attemptSelectionImpl?(peer) - }, multipleSelection: true)) + }, multipleSelection: true, forwardedMessagesCount: messages.count)) let context = self.context attemptSelectionImpl = { [weak controller] peer in guard let controller = controller else { diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index 6cb1ee9204..0354afcac3 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -1080,7 +1080,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let viaBotNode = viaBotApply() if strongSelf.viaBotNode == nil { strongSelf.viaBotNode = viaBotNode - strongSelf.addSubnode(viaBotNode) + strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode) } let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 15.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 14.0)), y: 8.0), size: viaBotLayout.size) viaBotNode.frame = viaBotFrame @@ -1161,7 +1161,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } else { let forwardBackgroundNode = NavigationBackgroundNode(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)) strongSelf.forwardBackgroundNode = forwardBackgroundNode - strongSelf.addSubnode(forwardBackgroundNode) + strongSelf.contextSourceNode.contentNode.addSubnode(forwardBackgroundNode) } } @@ -1169,7 +1169,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let forwardInfoNode = forwardInfoApply(forwardInfoSize.width) if strongSelf.forwardInfoNode == nil { strongSelf.forwardInfoNode = forwardInfoNode - strongSelf.addSubnode(forwardInfoNode) + strongSelf.contextSourceNode.contentNode.addSubnode(forwardInfoNode) } let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - forwardInfoSize.width - layoutConstants.bubble.edgeInset - 12.0)), y: 8.0), size: forwardInfoSize) forwardInfoNode.frame = forwardInfoFrame diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index a76da960e4..69be62c976 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -43,11 +43,11 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } private var forwardInfoNode: ChatMessageForwardInfoNode? - private var forwardBackgroundNode: ASImageNode? + private var forwardBackgroundNode: NavigationBackgroundNode? private var viaBotNode: TextNode? private var replyInfoNode: ChatMessageReplyInfoNode? - private var replyBackgroundNode: ASImageNode? + private var replyBackgroundNode: NavigationBackgroundNode? private var actionButtonsNode: ChatMessageActionButtonsNode? @@ -389,7 +389,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD var viaBotApply: (TextNodeLayout, () -> TextNode)? var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)? - var updatedReplyBackgroundNode: ASImageNode? + var updatedReplyBackgroundNode: NavigationBackgroundNode? var replyBackgroundImage: UIImage? var replyMarkup: ReplyMarkupMessageAttribute? @@ -432,6 +432,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD let botString = addAttributesToStringWithRanges(item.presentationData.strings.Conversation_MessageViaUser("@\(inlineBotNameString)")._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) viaBotApply = viaBotLayout(TextNodeLayoutArguments(attributedString: botString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0, availableWidth), height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + ignoreForward = true } } @@ -464,11 +466,10 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD if let currentReplyBackgroundNode = currentReplyBackgroundNode { updatedReplyBackgroundNode = currentReplyBackgroundNode } else { - updatedReplyBackgroundNode = ASImageNode() + updatedReplyBackgroundNode = NavigationBackgroundNode(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)) } - let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) - replyBackgroundImage = graphics.chatFreeformContentAdditionalInfoBackgroundImage + updatedReplyBackgroundNode?.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate) } var updatedShareButtonNode: ChatMessageShareButton? @@ -487,7 +488,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD var forwardAuthorSignature: String? var forwardInfoSizeApply: (CGSize, (CGFloat) -> ChatMessageForwardInfoNode)? - var updatedForwardBackgroundNode: ASImageNode? + var updatedForwardBackgroundNode: NavigationBackgroundNode? var forwardBackgroundImage: UIImage? if !ignoreForward, let forwardInfo = item.message.forwardInfo { @@ -517,11 +518,10 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD if let currentForwardBackgroundNode = currentForwardBackgroundNode { updatedForwardBackgroundNode = currentForwardBackgroundNode } else { - updatedForwardBackgroundNode = ASImageNode() + updatedForwardBackgroundNode = NavigationBackgroundNode(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)) } - let graphics = PresentationResourcesChat.additionalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) - forwardBackgroundImage = graphics.chatServiceBubbleFillImage + updatedForwardBackgroundNode?.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate) } var maxContentWidth = normalDisplaySize.width @@ -613,10 +613,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD if let updatedReplyBackgroundNode = updatedReplyBackgroundNode { if strongSelf.replyBackgroundNode == nil { strongSelf.replyBackgroundNode = updatedReplyBackgroundNode - strongSelf.addSubnode(updatedReplyBackgroundNode) - updatedReplyBackgroundNode.image = replyBackgroundImage - } else { - strongSelf.replyBackgroundNode?.image = replyBackgroundImage + strongSelf.contextSourceNode.contentNode.addSubnode(updatedReplyBackgroundNode) } } else if let replyBackgroundNode = strongSelf.replyBackgroundNode { replyBackgroundNode.removeFromSupernode() @@ -627,11 +624,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD let viaBotNode = viaBotApply() if strongSelf.viaBotNode == nil { strongSelf.viaBotNode = viaBotNode - strongSelf.addSubnode(viaBotNode) + strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode) } let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 10.0)), y: 8.0), size: viaBotLayout.size) viaBotNode.frame = viaBotFrame - strongSelf.replyBackgroundNode?.frame = CGRect(origin: CGPoint(x: viaBotFrame.minX - 4.0, y: viaBotFrame.minY - 2.0), size: CGSize(width: viaBotFrame.size.width + 8.0, height: viaBotFrame.size.height + 5.0)) + let replyBackgroundFrame = CGRect(origin: CGPoint(x: viaBotFrame.minX - 4.0, y: viaBotFrame.minY - 2.0), size: CGSize(width: viaBotFrame.size.width + 8.0, height: viaBotFrame.size.height + 5.0)) + strongSelf.replyBackgroundNode?.frame = replyBackgroundFrame + strongSelf.replyBackgroundNode?.update(size: replyBackgroundFrame.size, cornerRadius: 8.0, transition: .immediate) } else if let viaBotNode = strongSelf.viaBotNode { viaBotNode.removeFromSupernode() strongSelf.viaBotNode = nil @@ -641,7 +640,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD let replyInfoNode = replyInfoApply() if strongSelf.replyInfoNode == nil { strongSelf.replyInfoNode = replyInfoNode - strongSelf.addSubnode(replyInfoNode) + strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode) } var viaBotSize = CGSize() if let viaBotNode = strongSelf.viaBotNode { @@ -654,7 +653,9 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } } replyInfoNode.frame = replyInfoFrame - strongSelf.replyBackgroundNode?.frame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0)) + let replyBackgroundFrame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0)) + strongSelf.replyBackgroundNode?.frame = replyBackgroundFrame + strongSelf.replyBackgroundNode?.update(size: replyBackgroundFrame.size, cornerRadius: 8.0, transition: .immediate) } else if let replyInfoNode = strongSelf.replyInfoNode { replyInfoNode.removeFromSupernode() strongSelf.replyInfoNode = nil @@ -694,8 +695,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD if let updatedForwardBackgroundNode = updatedForwardBackgroundNode { if strongSelf.forwardBackgroundNode == nil { strongSelf.forwardBackgroundNode = updatedForwardBackgroundNode - strongSelf.addSubnode(updatedForwardBackgroundNode) - updatedForwardBackgroundNode.image = forwardBackgroundImage + strongSelf.contextSourceNode.contentNode.addSubnode(updatedForwardBackgroundNode) } } else if let forwardBackgroundNode = strongSelf.forwardBackgroundNode { forwardBackgroundNode.removeFromSupernode() @@ -706,7 +706,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD let forwardInfoNode = forwardInfoApply(forwardInfoSize.width) if strongSelf.forwardInfoNode == nil { strongSelf.forwardInfoNode = forwardInfoNode - strongSelf.addSubnode(forwardInfoNode) + strongSelf.contextSourceNode.contentNode.addSubnode(forwardInfoNode) forwardInfoNode.openPsa = { [weak strongSelf] type, sourceNode in guard let strongSelf = strongSelf, let item = strongSelf.item else { return @@ -716,7 +716,9 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - forwardInfoSize.width - layoutConstants.bubble.edgeInset - 12.0)), y: 8.0), size: forwardInfoSize) forwardInfoNode.frame = forwardInfoFrame - strongSelf.forwardBackgroundNode?.frame = CGRect(origin: CGPoint(x: forwardInfoFrame.minX - 6.0, y: forwardInfoFrame.minY - 2.0), size: CGSize(width: forwardInfoFrame.size.width + 10.0, height: forwardInfoFrame.size.height + 4.0)) + let forwardBackgroundFrame = CGRect(origin: CGPoint(x: forwardInfoFrame.minX - 6.0, y: forwardInfoFrame.minY - 2.0), size: CGSize(width: forwardInfoFrame.size.width + 10.0, height: forwardInfoFrame.size.height + 4.0)) + strongSelf.forwardBackgroundNode?.frame = forwardBackgroundFrame + strongSelf.forwardBackgroundNode?.update(size: forwardBackgroundFrame.size, cornerRadius: 8.0, transition: .immediate) } else if let forwardInfoNode = strongSelf.forwardInfoNode { forwardInfoNode.removeFromSupernode() strongSelf.forwardInfoNode = nil diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index 35aa60b1d2..40b90c78cc 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -719,7 +719,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { let viaBotNode = viaBotApply() if strongSelf.viaBotNode == nil { strongSelf.viaBotNode = viaBotNode - strongSelf.addSubnode(viaBotNode) + strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode) } viaBotNode.frame = viaBotFrame if let replyBackgroundNode = strongSelf.replyBackgroundNode { diff --git a/submodules/TelegramUI/Sources/InstantVideoRadialStatusNode.swift b/submodules/TelegramUI/Sources/InstantVideoRadialStatusNode.swift index 91a1f10153..2827f864c0 100644 --- a/submodules/TelegramUI/Sources/InstantVideoRadialStatusNode.swift +++ b/submodules/TelegramUI/Sources/InstantVideoRadialStatusNode.swift @@ -11,13 +11,15 @@ private final class InstantVideoRadialStatusNodeParameters: NSObject { let progress: CGFloat let dimProgress: CGFloat let playProgress: CGFloat + let blinkProgress: CGFloat let hasSeek: Bool - init(color: UIColor, progress: CGFloat, dimProgress: CGFloat, playProgress: CGFloat, hasSeek: Bool) { + init(color: UIColor, progress: CGFloat, dimProgress: CGFloat, playProgress: CGFloat, blinkProgress: CGFloat, hasSeek: Bool) { self.color = color self.progress = progress self.dimProgress = dimProgress self.playProgress = playProgress + self.blinkProgress = blinkProgress self.hasSeek = hasSeek } } @@ -64,6 +66,12 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele } } + private var effectiveBlinkProgress: CGFloat = 0.0 { + didSet { + self.setNeedsDisplay() + } + } + private var _statusValue: MediaPlayerStatus? private var statusValue: MediaPlayerStatus? { get { @@ -196,8 +204,38 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele if let seekingProgress = self.seekingProgress { if seekingProgress > 0.98 && fraction > 0.0 && fraction < 0.05 { self.hapticFeedback.impact(.light) + + let blinkAnimation = POPBasicAnimation() + blinkAnimation.property = POPAnimatableProperty.property(withName: "blinkProgress", initializer: { property in + property?.readBlock = { node, values in + values?.pointee = (node as! InstantVideoRadialStatusNode).effectiveBlinkProgress + } + property?.writeBlock = { node, values in + (node as! InstantVideoRadialStatusNode).effectiveBlinkProgress = values!.pointee + } + property?.threshold = 0.01 + }) as? POPAnimatableProperty + blinkAnimation.fromValue = 1.0 as NSNumber + blinkAnimation.toValue = 0.0 as NSNumber + blinkAnimation.duration = 0.5 + self.pop_add(blinkAnimation, forKey: "blinkProgress") } else if seekingProgress > 0.0 && seekingProgress < 0.05 && fraction > 0.98 { self.hapticFeedback.impact(.light) + + let blinkAnimation = POPBasicAnimation() + blinkAnimation.property = POPAnimatableProperty.property(withName: "blinkProgress", initializer: { property in + property?.readBlock = { node, values in + values?.pointee = (node as! InstantVideoRadialStatusNode).effectiveBlinkProgress + } + property?.writeBlock = { node, values in + (node as! InstantVideoRadialStatusNode).effectiveBlinkProgress = values!.pointee + } + property?.threshold = 0.01 + }) as? POPAnimatableProperty + blinkAnimation.fromValue = -1.0 as NSNumber + blinkAnimation.toValue = 0.0 as NSNumber + blinkAnimation.duration = 0.5 + self.pop_add(blinkAnimation, forKey: "blinkProgress") } } let newProgress = min(0.99, fraction) @@ -216,7 +254,7 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele } override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { - return InstantVideoRadialStatusNodeParameters(color: self.color, progress: self.effectiveProgress, dimProgress: self.effectiveDimProgress, playProgress: self.effectivePlayProgress, hasSeek: self.hasSeek) + return InstantVideoRadialStatusNodeParameters(color: self.color, progress: self.effectiveProgress, dimProgress: self.effectiveDimProgress, playProgress: self.effectivePlayProgress, blinkProgress: self.effectiveBlinkProgress, hasSeek: self.hasSeek) } @objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { @@ -231,9 +269,23 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele if let parameters = parameters as? InstantVideoRadialStatusNodeParameters { context.setStrokeColor(parameters.color.cgColor) + context.addEllipse(in: bounds) + context.clip() + if !parameters.dimProgress.isZero { - context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.35 * min(1.0, parameters.dimProgress)).cgColor) - context.fillEllipse(in: bounds) + if parameters.playProgress == 1.0 { + context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.35 * min(1.0, parameters.dimProgress)).cgColor) + context.fillEllipse(in: bounds) + } else { + var locations: [CGFloat] = [0.0, 0.8, 1.0] + let alpha: CGFloat = 0.2 + 0.15 * parameters.playProgress + let colors: [CGColor] = [UIColor(rgb: 0x000000, alpha: alpha * min(1.0, parameters.dimProgress * parameters.playProgress)).cgColor, UIColor(rgb: 0x000000, alpha: alpha * min(1.0, parameters.dimProgress * parameters.playProgress)).cgColor, UIColor(rgb: 0x000000, alpha: alpha * min(1.0, parameters.dimProgress)).cgColor] + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! + + let center = bounds.center + context.drawRadialGradient(gradient, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: bounds.width / 2.0, options: .drawsAfterEndLocation) + } } context.setBlendMode(.normal) @@ -255,9 +307,19 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele } if !parameters.dimProgress.isZero { + context.setLineWidth(lineWidth) + + if parameters.blinkProgress > 0.0 { + context.setStrokeColor(parameters.color.withAlphaComponent(0.2 * parameters.blinkProgress).cgColor) + context.strokeEllipse(in: CGRect(x: (bounds.size.width - pathDiameter) / 2.0 , y: (bounds.size.height - pathDiameter) / 2.0, width: pathDiameter, height: pathDiameter)) + } + if parameters.hasSeek { - context.setStrokeColor(parameters.color.withAlphaComponent(0.2 * parameters.dimProgress).cgColor) - context.setLineWidth(lineWidth) + var progress = parameters.dimProgress + if parameters.blinkProgress < 0.0 { + progress = parameters.dimProgress + parameters.blinkProgress + } + context.setStrokeColor(parameters.color.withAlphaComponent(0.2 * progress).cgColor) context.strokeEllipse(in: CGRect(x: (bounds.size.width - pathDiameter) / 2.0 , y: (bounds.size.height - pathDiameter) / 2.0, width: pathDiameter, height: pathDiameter)) } diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift index 6545a7e36d..c5bd2ad74a 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -206,32 +206,28 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context, displayBackground: true) mediaAccessoryPanel.containerNode.headerNode.displayScrubber = item.playbackData?.type != .instantVideo + mediaAccessoryPanel.getController = { [weak self] in + return self?.parentController + } + mediaAccessoryPanel.presentInGlobalOverlay = { [weak self] c in + self?.parentController?.presentInGlobalOverlay(c) + } mediaAccessoryPanel.close = { [weak self] in if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause)) } } - mediaAccessoryPanel.toggleRate = { - [weak self] in + mediaAccessoryPanel.setRate = { [weak self] rate in guard let strongSelf = self else { return } let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> AudioPlaybackRate in let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings) as? MusicPlaybackSettings ?? MusicPlaybackSettings.defaultSettings - let nextRate: AudioPlaybackRate - switch settings.voicePlaybackRate { - case .x1: - nextRate = .x2 - case .x2: - nextRate = .x1 - default: - nextRate = .x1 - } transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in - return settings.withUpdatedVoicePlaybackRate(nextRate) + return settings.withUpdatedVoicePlaybackRate(rate) }) - return nextRate + return rate } |> deliverOnMainQueue).start(next: { baseRate in guard let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType else { @@ -250,22 +246,31 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { }) let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let slowdown = baseRate == .x1 - controller.present( - UndoOverlayController( - presentationData: presentationData, - content: .audioRate( - slowdown: slowdown, - text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp + let slowdown: Bool? + if baseRate == .x1 { + slowdown = true + } else if baseRate == .x2 { + slowdown = false + } else { + slowdown = nil + } + if let slowdown = slowdown { + controller.present( + UndoOverlayController( + presentationData: presentationData, + content: .audioRate( + slowdown: slowdown, + text: slowdown ? presentationData.strings.Conversation_AudioRateTooltipNormal : presentationData.strings.Conversation_AudioRateTooltipSpeedUp + ), + elevatedLayout: false, + animateInAsReplacement: hasTooltip, + action: { action in + return true + } ), - elevatedLayout: false, - animateInAsReplacement: hasTooltip, - action: { action in - return true - } - ), - in: .current - ) + in: .current + ) + } } }) } diff --git a/submodules/TelegramUI/Sources/PeerSelectionController.swift b/submodules/TelegramUI/Sources/PeerSelectionController.swift index 04b95dfe60..2873c2ae74 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionController.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionController.swift @@ -58,6 +58,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon private let hasContactSelector: Bool private let hasGlobalSearch: Bool private let pretendPresentedInModal: Bool + private let forwardedMessagesCount: Int override public var _presentedInModal: Bool { get { @@ -85,6 +86,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon self.attemptSelection = params.attemptSelection self.createNewGroup = params.createNewGroup self.pretendPresentedInModal = params.pretendPresentedInModal + self.forwardedMessagesCount = params.forwardedMessagesCount super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) @@ -148,7 +150,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon } override public func loadDisplayNode() { - self.displayNode = PeerSelectionControllerNode(context: self.context, filter: self.filter, hasChatListSelector: self.hasChatListSelector, hasContactSelector: self.hasContactSelector, hasGlobalSearch: self.hasGlobalSearch, createNewGroup: self.createNewGroup, present: { [weak self] c, a in + self.displayNode = PeerSelectionControllerNode(context: self.context, filter: self.filter, hasChatListSelector: self.hasChatListSelector, hasContactSelector: self.hasContactSelector, hasGlobalSearch: self.hasGlobalSearch, forwardedMessagesCount: self.forwardedMessagesCount, createNewGroup: self.createNewGroup, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) }, presentInGlobalOverlay: { [weak self] c, a in self?.presentInGlobalOverlay(c, with: a) diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index 9d6eb2af3d..c599ae7588 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -66,7 +66,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { return self.readyValue.get() } - init(context: AccountContext, filter: ChatListNodePeersFilter, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, createNewGroup: (() -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) { + init(context: AccountContext, filter: ChatListNodePeersFilter, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, forwardedMessagesCount: Int, createNewGroup: (() -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) { self.context = context self.present = present self.presentInGlobalOverlay = presentInGlobalOverlay @@ -79,6 +79,16 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: .peer(PeerId(0)), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil) + var mockMessageIds: [MessageId]? + if forwardedMessagesCount > 0 { + var messageIds: [MessageId] = [] + for _ in 0 ..< forwardedMessagesCount { + messageIds.append(MessageId(peerId: PeerId(0), namespace: Namespaces.Message.Local, id: Int32.random(in: 0 ..< Int32.max))) + } + mockMessageIds = messageIds + } + self.presentationInterfaceState = self.presentationInterfaceState.updatedInterfaceState { $0.withUpdatedForwardMessageIds(mockMessageIds) } + if hasChatListSelector && hasContactSelector { self.toolbarBackgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor) @@ -276,13 +286,6 @@ final class PeerSelectionControllerNode: ASDisplayNode { guard let textInputNode = textInputPanelNode.textInputNode else { return } -// let previousSupportedOrientations = strongSelf.supportedOrientations -// if layout.size.width > layout.size.height { -// strongSelf.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .landscape) -// } else { -// strongSelf.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) -// } - let controller = ChatSendMessageActionSheetController(context: strongSelf.context, interfaceState: strongSelf.presentationInterfaceState, gesture: gesture, sourceSendButton: node, textInputNode: textInputNode, completion: { [weak self] in if let strongSelf = self { // strongSelf.supportedOrientations = previousSupportedOrientations @@ -292,7 +295,6 @@ final class PeerSelectionControllerNode: ASDisplayNode { }, schedule: { [weak textInputPanelNode] in textInputPanelNode?.sendMessage(.schedule) }) -// strongSelf.sendMessageActionsController = controller strongSelf.presentInGlobalOverlay(controller, nil) }, openScheduledMessages: { }, openPeersNearby: {