diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index f87a98e5b4..52bda6c267 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -136,11 +136,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll private var currentMessageText: NSAttributedString? private var currentAuthorNameText: String? private var currentDateText: String? - + private var currentMessage: Message? - private var currentWebPageAndMedia: (TelegramMediaWebpage, Media)? - private let messageContextDisposable = MetaDisposable() private var videoFramePreviewNode: (ASImageNode, ImmediateTextNode)? @@ -148,11 +146,15 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll private var validLayout: (CGSize, LayoutMetrics, CGFloat, CGFloat, CGFloat, CGFloat)? var playbackControl: (() -> Void)? - var seekBackward: (() -> Void)? - var seekForward: (() -> Void)? - + var seekBackward: ((Double) -> Void)? + var seekForward: ((Double) -> Void)? + var setPlayRate: ((Double) -> Void)? var fetchControl: (() -> Void)? + private var seekTimer: SwiftSignalKit.Timer? + private var currentIsPaused: Bool = true + private var seekRate: Double = 1.0 + var performAction: ((GalleryControllerInteractionTapAction) -> Void)? var openActionOptions: ((GalleryControllerInteractionTapAction) -> Void)? @@ -169,6 +171,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll self.statusButtonNode.isHidden = true self.statusNode.isHidden = true case let .fetch(status, seekable): + self.currentIsPaused = true self.authorNameNode.isHidden = true self.dateNode.isHidden = true self.backwardButton.isHidden = !seekable @@ -197,6 +200,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll self.statusNode.transitionToState(statusState, completion: {}) self.statusButtonNode.isUserInteractionEnabled = statusState != .none case let .playback(paused, seekable): + self.currentIsPaused = paused self.authorNameNode.isHidden = true self.dateNode.isHidden = true self.backwardButton.isHidden = !seekable @@ -390,6 +394,79 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll super.didLoad() self.scrollNode.view.delegate = self self.scrollNode.view.showsVerticalScrollIndicator = false + + let backwardLongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.seekBackwardLongPress(_:))) + backwardLongPressGestureRecognizer.minimumPressDuration = 0.3 + self.backwardButton.view.addGestureRecognizer(backwardLongPressGestureRecognizer) + + let forwardLongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.seekForwardLongPress(_:))) + forwardLongPressGestureRecognizer.minimumPressDuration = 0.3 + self.forwardButton.view.addGestureRecognizer(forwardLongPressGestureRecognizer) + } + + private var wasPlaying = false + @objc private func seekBackwardLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { + switch gestureRecognizer.state { + case .began: + self.wasPlaying = !(self.currentIsPaused ?? true) + if self.wasPlaying { + self.playbackControl?() + } + + var time: Double = 0.0 + let seekTimer = SwiftSignalKit.Timer(timeout: 0.1, repeat: true, completion: { [weak self] in + if let strongSelf = self { + var delta: Double = 0.8 + if time >= 4.0 { + delta = 3.2 + } else if time >= 2.0 { + delta = 1.6 + } + time += 0.1 + + strongSelf.seekBackward?(delta) + } + }, queue: Queue.mainQueue()) + self.seekTimer = seekTimer + seekTimer.start() + case .ended, .cancelled: + self.seekTimer?.invalidate() + self.seekTimer = nil + if self.wasPlaying { + self.playbackControl?() + self.wasPlaying = false + } + default: + break + } + } + + @objc private func seekForwardLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { + switch gestureRecognizer.state { + case .began: + self.seekRate = 4.0 + self.setPlayRate?(self.seekRate) + let seekTimer = SwiftSignalKit.Timer(timeout: 2.0, repeat: true, completion: { [weak self] in + if let strongSelf = self { + if strongSelf.seekRate == 4.0 { + strongSelf.seekRate = 8.0 + } + strongSelf.setPlayRate?(strongSelf.seekRate) + if strongSelf.seekRate == 8.0 { + strongSelf.seekTimer?.invalidate() + strongSelf.seekTimer = nil + } + } + }, queue: Queue.mainQueue()) + self.seekTimer = seekTimer + seekTimer.start() + case .ended, .cancelled: + self.setPlayRate?(1.0) + self.seekTimer?.invalidate() + self.seekTimer = nil + default: + break + } } private func actionForAttributes(_ attributes: [NSAttributedString.Key: Any], _ index: Int) -> GalleryControllerInteractionTapAction? { @@ -1106,11 +1183,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll } @objc func backwardButtonPressed() { - self.seekBackward?() + self.seekBackward?(15.0) } @objc func forwardButtonPressed() { - self.seekForward?() + self.seekForward?(15.0) } @objc private func statusPressed() { diff --git a/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift b/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift index baa26a72cf..97e49dded0 100644 --- a/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift +++ b/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift @@ -152,6 +152,10 @@ final class ChatVideoGalleryItemScrubberView: UIView { transition.updateAlpha(node: self.scrubberNode, alpha: alpha) } + func animateTo(_ timestamp: Double) { + self.scrubberNode.animateTo(timestamp) + } + func setStatusSignal(_ status: Signal?) { let mappedStatus: Signal? if let status = status { diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index f84163b142..fe7bd8b2cf 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -360,31 +360,37 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { strongSelf.videoNode?.togglePlayPause() } } - self.footerContentNode.seekBackward = { [weak self] in + self.footerContentNode.seekBackward = { [weak self] delta in if let strongSelf = self, let videoNode = strongSelf.videoNode { let _ = (videoNode.status |> take(1)).start(next: { [weak videoNode] status in if let strongVideoNode = videoNode, let timestamp = status?.timestamp { - strongVideoNode.seek(max(0.0, timestamp - 15.0)) + strongVideoNode.seek(max(0.0, timestamp - delta)) } }) } } - self.footerContentNode.seekForward = { [weak self] in + self.footerContentNode.seekForward = { [weak self] delta in if let strongSelf = self, let videoNode = strongSelf.videoNode { let _ = (videoNode.status |> take(1)).start(next: { [weak videoNode] status in if let strongVideoNode = videoNode, let timestamp = status?.timestamp, let duration = status?.duration { - let nextTimestamp = timestamp + 15.0 + let nextTimestamp = timestamp + delta if nextTimestamp > duration { strongVideoNode.seek(0.0) strongVideoNode.pause() } else { - strongVideoNode.seek(min(duration, timestamp + 15.0)) + strongVideoNode.seek(min(duration, timestamp + delta)) } } }) } } + self.footerContentNode.setPlayRate = { [weak self] rate in + if let strongSelf = self, let videoNode = strongSelf.videoNode { + videoNode.setBaseRate(rate) + } + } + self.footerContentNode.fetchControl = { [weak self] in guard let strongSelf = self, let fetchStatus = strongSelf.fetchStatus, let fetchControls = strongSelf.fetchControls else { return @@ -952,6 +958,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { switch action { case let .timecode(timecode): + self.scrubberView.animateTo(timecode) videoNode.seek(timecode) } } diff --git a/submodules/LegacyComponents/Sources/TGCameraController.m b/submodules/LegacyComponents/Sources/TGCameraController.m index 829760984f..e056f37685 100644 --- a/submodules/LegacyComponents/Sources/TGCameraController.m +++ b/submodules/LegacyComponents/Sources/TGCameraController.m @@ -955,12 +955,14 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus _camera.disabled = true; _shutterIsBusy = true; + + TGDispatchAfter(0.25, dispatch_get_main_queue(), ^ + { + [_previewView blink]; + }); + if (![self willPresentResultController]) { - TGDispatchAfter(0.35, dispatch_get_main_queue(), ^ - { - [_previewView blink]; - }); } else { diff --git a/submodules/LegacyComponents/Sources/TGCameraPreviewView.m b/submodules/LegacyComponents/Sources/TGCameraPreviewView.m index 19d27fc065..b49624e7a9 100644 --- a/submodules/LegacyComponents/Sources/TGCameraPreviewView.m +++ b/submodules/LegacyComponents/Sources/TGCameraPreviewView.m @@ -206,7 +206,7 @@ _fadeView.alpha = 1.0f; } completion:^(BOOL finished) { - [UIView animateWithDuration:0.07f delay:0.0f options:UIViewAnimationOptionCurveLinear animations:^ + [UIView animateWithDuration:0.15f delay:0.0f options:UIViewAnimationOptionCurveLinear animations:^ { _fadeView.alpha = 0.0f; } completion:^(BOOL finished) diff --git a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift index 0e7941cf0a..b7ae5509dc 100644 --- a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift +++ b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift @@ -769,6 +769,18 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { } } + private var animateToValue: Double? + private var animating = false + public func animateTo(_ timestamp: Double) { + self.animateToValue = timestamp + ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut).animateView({ + self.updateProgress() + }, completion: { finished in + self.animateToValue = nil + self.animating = false + }) + } + private func updateProgress() { let bounds = self.bounds @@ -792,6 +804,15 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { } } + if let animateToValue = animateToValue { + if animating { + return + } else if let (_, duration) = timestampAndDuration { + animating = true + timestampAndDuration = (animateToValue, duration) + } + } + switch self.contentNodes { case let .standard(node): let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((bounds.size.height - node.lineHeight) / 2.0)), size: CGSize(width: bounds.size.width, height: node.lineHeight)) diff --git a/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift index b851a49e79..66d808929d 100644 --- a/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift @@ -133,6 +133,8 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { super.init() + self.clipsToBounds = true + self.addSubnode(self.button) self.addSubnode(self.discussButton) self.view.addSubview(self.activityIndicator)