diff --git a/submodules/TelegramUI/Components/CameraScreen/BUILD b/submodules/TelegramUI/Components/CameraScreen/BUILD index 8e9359b72a..04fe9cd525 100644 --- a/submodules/TelegramUI/Components/CameraScreen/BUILD +++ b/submodules/TelegramUI/Components/CameraScreen/BUILD @@ -75,6 +75,7 @@ swift_library( "//submodules/TelegramUI/Components/MediaEditor", "//submodules/Components/MetalImageView", "//submodules/TelegramUI/Components/CameraButtonComponent", + "//submodules/Utils/VolumeButtons" ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index 17e9bdacaa..17767554d5 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -19,6 +19,7 @@ import TooltipUI import MediaEditor import BundleIconComponent import CameraButtonComponent +import VolumeButtons let videoRedColor = UIColor(rgb: 0xff3b30) @@ -87,6 +88,7 @@ private final class CameraScreenComponent: CombinedComponent { let camera: Camera let updateState: ActionSlot let hasAppeared: Bool + let isVisible: Bool let panelWidth: CGFloat let flipAnimationAction: ActionSlot let animateShutter: () -> Void @@ -99,6 +101,7 @@ private final class CameraScreenComponent: CombinedComponent { camera: Camera, updateState: ActionSlot, hasAppeared: Bool, + isVisible: Bool, panelWidth: CGFloat, flipAnimationAction: ActionSlot, animateShutter: @escaping () -> Void, @@ -110,6 +113,7 @@ private final class CameraScreenComponent: CombinedComponent { self.camera = camera self.updateState = updateState self.hasAppeared = hasAppeared + self.isVisible = isVisible self.panelWidth = panelWidth self.flipAnimationAction = flipAnimationAction self.animateShutter = animateShutter @@ -125,6 +129,9 @@ private final class CameraScreenComponent: CombinedComponent { if lhs.hasAppeared != rhs.hasAppeared { return false } + if lhs.isVisible != rhs.isVisible { + return false + } if lhs.panelWidth != rhs.panelWidth { return false } @@ -159,6 +166,8 @@ private final class CameraScreenComponent: CombinedComponent { private let completion: ActionSlot> private let updateState: ActionSlot + private let animateShutter: () -> Void + private var cameraStateDisposable: Disposable? private var resultDisposable = MetaDisposable() @@ -166,6 +175,9 @@ private final class CameraScreenComponent: CombinedComponent { fileprivate var lastGalleryAsset: PHAsset? private var lastGalleryAssetsDisposable: Disposable? + private var volumeButtonsListener: VolumeButtonsListener? + private let volumeButtonsListenerShouldBeActive = ValuePromise(false, ignoreRepeated: true) + var cameraState = CameraState(mode: .photo, position: .unspecified, flashMode: .off, flashModeDidChange: false, recording: .none, duration: 0.0, isDualCamEnabled: false) { didSet { self.updateState.invoke(self.cameraState) @@ -176,12 +188,20 @@ private final class CameraScreenComponent: CombinedComponent { private let hapticFeedback = HapticFeedback() - init(context: AccountContext, camera: Camera, present: @escaping (ViewController) -> Void, completion: ActionSlot>, updateState: ActionSlot) { + init( + context: AccountContext, + camera: Camera, + present: @escaping (ViewController) -> Void, + completion: ActionSlot>, + updateState: ActionSlot, + animateShutter: @escaping () -> Void = {} + ) { self.context = context self.camera = camera self.present = present self.completion = completion self.updateState = updateState + self.animateShutter = animateShutter super.init() @@ -202,6 +222,8 @@ private final class CameraScreenComponent: CombinedComponent { Queue.concurrentDefaultQueue().async { self.setupRecentAssetSubscription() } + + self.setupVolumeButtonsHandler() } deinit { @@ -226,6 +248,77 @@ private final class CameraScreenComponent: CombinedComponent { }) } + func setupVolumeButtonsHandler() { + guard self.volumeButtonsListener == nil else { + return + } + + self.volumeButtonsListener = VolumeButtonsListener( + shouldBeActive: self.volumeButtonsListenerShouldBeActive.get(), + upPressed: { [weak self] in + if let self { + self.handleVolumePressed() + } + }, + upReleased: { [weak self] in + if let self { + self.handleVolumeReleased() + } + }, + downPressed: { [weak self] in + if let self { + self.handleVolumePressed() + } + }, + downReleased: { [weak self] in + if let self { + self.handleVolumeReleased() + } + } + ) + } + + var volumeButtonsListenerActive = false { + didSet { + self.volumeButtonsListenerShouldBeActive.set(self.volumeButtonsListenerActive) + } + } + + private var buttonPressTimestamp: Double? + private var buttonPressTimer: SwiftSignalKit.Timer? + + private func handleVolumePressed() { + self.buttonPressTimestamp = CACurrentMediaTime() + + self.buttonPressTimer = SwiftSignalKit.Timer(timeout: 0.3, repeat: false, completion: { [weak self] in + if let self, let _ = self.buttonPressTimestamp { + if case .none = self.cameraState.recording { + self.startVideoRecording(pressing: true) + } + self.buttonPressTimestamp = nil + } + }, queue: Queue.mainQueue()) + self.buttonPressTimer?.start() + } + + private func handleVolumeReleased() { + if case .none = self.cameraState.recording { + switch self.cameraState.mode { + case .photo: + self.animateShutter() + self.takePhoto() + case .video: + self.startVideoRecording(pressing: false) + } + } else { + self.stopVideoRecording() + } + + self.buttonPressTimer?.invalidate() + self.buttonPressTimer = nil + self.buttonPressTimestamp = nil + } + func updateCameraMode(_ mode: CameraMode) { self.cameraState = self.cameraState.updatedMode(mode) self.updated(transition: .spring(duration: 0.3)) @@ -329,7 +422,7 @@ private final class CameraScreenComponent: CombinedComponent { } func makeState() -> State { - return State(context: self.context, camera: self.camera, present: self.present, completion: self.completion, updateState: self.updateState) + return State(context: self.context, camera: self.camera, present: self.present, completion: self.completion, updateState: self.updateState, animateShutter: self.animateShutter) } static var body: Body { @@ -351,6 +444,8 @@ private final class CameraScreenComponent: CombinedComponent { let state = context.state let controller = environment.controller let availableSize = context.availableSize + + state.volumeButtonsListenerActive = component.hasAppeared && component.isVisible let isTablet: Bool if case .regular = environment.metrics.widthClass { @@ -873,22 +968,24 @@ public class CameraScreen: ViewController { fileprivate let transitionDimView: UIView fileprivate let transitionCornersView: UIImageView fileprivate let camera: Camera - - private var presentationData: PresentationData - private var validLayout: ContainerViewLayout? private var changingPositionDisposable: Disposable? private var isDualCamEnabled = false private var appliedDualCam = false private var cameraPosition: Camera.Position = .back - private let completion = ActionSlot>() - - fileprivate var previewBlurPromise = ValuePromise(false) + private var pipPosition: PIPPosition = .bottomRight + fileprivate var previewBlurPromise = ValuePromise(false) private let flipAnimationAction = ActionSlot() - private var pipPosition: PIPPosition = .bottomRight + fileprivate var cameraIsActive = true + fileprivate var hasGallery = false + + private var presentationData: PresentationData + private var validLayout: ContainerViewLayout? + + private let completion = ActionSlot>() init(controller: CameraScreen) { self.controller = controller @@ -972,21 +1069,6 @@ public class CameraScreen: ViewController { ).start(next: { [weak self] modeChange, forceBlur in if let self { if modeChange != .none { -// if case .dualCamera = modeChange, self.cameraPosition == .front { -// if self.isDualCamEnabled { -// if let snapshot = self.mainPreviewView.snapshotView(afterScreenUpdates: false) { -// snapshot.frame = CGRect(origin: .zero, size: self.mainPreviewContainerView.bounds.size) -// self.mainPreviewView.addSubview(snapshot) -// self.previewSnapshotView = snapshot -// } -// } else { -// if let snapshot = self.mainPreviewView.snapshotView(afterScreenUpdates: false) { -// snapshot.frame = CGRect(origin: .zero, size: self.mainPreviewContainerView.bounds.size) -// self.additionalPreviewView.addSubview(snapshot) -// self.additionalPreviewSnapshotView = snapshot -// } -// } -// } else { if case .dualCamera = modeChange, self.cameraPosition == .front { } else { @@ -1255,7 +1337,7 @@ public class CameraScreen: ViewController { func animateOut(completion: @escaping () -> Void) { self.camera.stopCapture(invalidate: true) - + UIView.animate(withDuration: 0.25, animations: { self.backgroundView.alpha = 0.0 }) @@ -1293,6 +1375,9 @@ public class CameraScreen: ViewController { } func animateOutToEditor() { + self.cameraIsActive = false + self.requestUpdateLayout(hasAppeared: self.hasAppeared, transition: .immediate) + let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut)) if let view = self.componentHost.findTaggedView(tag: cancelButtonTag) { view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2) @@ -1324,6 +1409,9 @@ public class CameraScreen: ViewController { self.previewBlurPromise.set(true) } self.camera.stopCapture() + + self.cameraIsActive = false + self.requestUpdateLayout(hasAppeared: self.hasAppeared, transition: .immediate) } func resumeCameraCapture() { @@ -1355,6 +1443,9 @@ public class CameraScreen: ViewController { self.previewBlurPromise.set(false) } } + + self.cameraIsActive = true + self.requestUpdateLayout(hasAppeared: self.hasAppeared, transition: .immediate) } } @@ -1362,6 +1453,9 @@ public class CameraScreen: ViewController { if !toGallery { self.resumeCameraCapture() + self.cameraIsActive = true + self.requestUpdateLayout(hasAppeared: self.hasAppeared, transition: .immediate) + let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut)) if let view = self.componentHost.findTaggedView(tag: cancelButtonTag) { view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) @@ -1537,6 +1631,7 @@ public class CameraScreen: ViewController { camera: self.camera, updateState: self.updateState, hasAppeared: self.hasAppeared, + isVisible: self.cameraIsActive && !self.hasGallery, panelWidth: panelWidth, flipAnimationAction: self.flipAnimationAction, animateShutter: { [weak self] in @@ -1778,6 +1873,8 @@ public class CameraScreen: ViewController { self.dismissAllTooltips() + self.node.hasGallery = true + self.didStopCameraCapture = false let stopCameraCapture = { [weak self] in guard let self, !self.didStopCameraCapture else { @@ -1825,7 +1922,8 @@ public class CameraScreen: ViewController { self.completion(.single(.draft(draft)), resultTransition, dismissed) } } - }, dismissed: { + }, dismissed: { [weak self] in + self?.node.hasGallery = false resumeCameraCapture() }) self.galleryController = controller diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index 0f25d9b04e..f4b7401447 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -175,7 +175,7 @@ private final class StoryContainerScreenComponent: Component { private var transitionCloneMasterView: UIView private var volumeButtonsListener: VolumeButtonsListener? - private let volumeButtonsListenerShouldBeActvie = ValuePromise(false, ignoreRepeated: true) + private let volumeButtonsListenerShouldBeActive = ValuePromise(false, ignoreRepeated: true) private var isAnimatingOut: Bool = false private var didAnimateOut: Bool = false @@ -580,19 +580,24 @@ private final class StoryContainerScreenComponent: Component { private func updateVolumeButtonMonitoring() { if self.volumeButtonsListener == nil { - self.volumeButtonsListener = VolumeButtonsListener(shouldBeActive: self.volumeButtonsListenerShouldBeActvie.get(), valueChanged: { [weak self] in + let buttonAction = { [weak self] in guard let self, self.storyItemSharedState.useAmbientMode else { return } self.storyItemSharedState.useAmbientMode = false - self.volumeButtonsListenerShouldBeActvie.set(false) + self.volumeButtonsListenerShouldBeActive.set(false) for (_, itemSetView) in self.visibleItemSetViews { if let componentView = itemSetView.view.view as? StoryItemSetContainerComponent.View { componentView.leaveAmbientMode() } } - }) + } + self.volumeButtonsListener = VolumeButtonsListener( + shouldBeActive: self.volumeButtonsListenerShouldBeActive.get(), + upPressed: buttonAction, + downPressed: buttonAction + ) } } @@ -624,7 +629,7 @@ private final class StoryContainerScreenComponent: Component { self.focusedItem.set(focusedItemId) if self.storyItemSharedState.useAmbientMode { - self.volumeButtonsListenerShouldBeActvie.set(isVideo) + self.volumeButtonsListenerShouldBeActive.set(isVideo) if isVideo { self.updateVolumeButtonMonitoring() } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index fb00452f13..a379357c49 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -10967,15 +10967,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - self.volumeButtonsListener = VolumeButtonsListener(shouldBeActive: shouldBeActive, valueChanged: { [weak self] in - guard let strongSelf = self, strongSelf.traceVisibility() && isTopmostChatController(strongSelf) else { + let buttonAction = { [weak self] in + guard let self, self.traceVisibility() && isTopmostChatController(self) else { return } - strongSelf.videoUnmuteTooltipController?.dismiss() + self.videoUnmuteTooltipController?.dismiss() var actions: [(Bool, (Double?) -> Void)] = [] var hasUnconsumed = false - strongSelf.chatDisplayNode.historyNode.forEachVisibleItemNode { itemNode in + self.chatDisplayNode.historyNode.forEachVisibleItemNode { itemNode in if let itemNode = itemNode as? ChatMessageItemView, let (action, _, _, isUnconsumed, _) = itemNode.playMediaWithSound() { if case let .visible(fraction, _) = itemNode.visibility, fraction > 0.7 { actions.insert((isUnconsumed, action), at: 0) @@ -10991,7 +10991,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G break } } - }) + } + self.volumeButtonsListener = VolumeButtonsListener( + shouldBeActive: shouldBeActive, + upPressed: buttonAction, + downPressed: buttonAction + ) self.chatDisplayNode.historyNode.openNextChannelToRead = { [weak self] peer, location in guard let strongSelf = self else { diff --git a/submodules/Utils/VolumeButtons/Sources/VolumeButtons.swift b/submodules/Utils/VolumeButtons/Sources/VolumeButtons.swift index 693235f365..58e5d866a8 100644 --- a/submodules/Utils/VolumeButtons/Sources/VolumeButtons.swift +++ b/submodules/Utils/VolumeButtons/Sources/VolumeButtons.swift @@ -10,21 +10,25 @@ public class VolumeButtonsListener: NSObject { private var disposable: Disposable? - public init(shouldBeActive: Signal, valueChanged: @escaping () -> Void) { - var impl: (() -> Void)? - + public init( + shouldBeActive: Signal, + upPressed: @escaping () -> Void, + upReleased: @escaping () -> Void = {}, + downPressed: @escaping () -> Void, + downReleased: @escaping () -> Void = {} + ) { self.handler = PGCameraVolumeButtonHandler(upButtonPressedBlock: { - impl?() - }, upButtonReleasedBlock: {}, downButtonPressedBlock: { - impl?() - }, downButtonReleasedBlock: {}) + upPressed() + }, upButtonReleasedBlock: { + upReleased() + }, downButtonPressedBlock: { + downPressed() + }, downButtonReleasedBlock: { + downReleased() + }) super.init() - - impl = { - valueChanged() - } - + self.disposable = (shouldBeActive |> deliverOnMainQueue).start(next: { [weak self] value in guard let strongSelf = self else {