Camera and editor improvements

This commit is contained in:
Ilya Laktyushin 2023-06-25 23:17:58 +02:00
parent ef4d6c51c2
commit 6dbd76bf7a
5 changed files with 161 additions and 48 deletions

View File

@ -75,6 +75,7 @@ swift_library(
"//submodules/TelegramUI/Components/MediaEditor",
"//submodules/Components/MetalImageView",
"//submodules/TelegramUI/Components/CameraButtonComponent",
"//submodules/Utils/VolumeButtons"
],
visibility = [
"//visibility:public",

View File

@ -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<CameraState>
let hasAppeared: Bool
let isVisible: Bool
let panelWidth: CGFloat
let flipAnimationAction: ActionSlot<Void>
let animateShutter: () -> Void
@ -99,6 +101,7 @@ private final class CameraScreenComponent: CombinedComponent {
camera: Camera,
updateState: ActionSlot<CameraState>,
hasAppeared: Bool,
isVisible: Bool,
panelWidth: CGFloat,
flipAnimationAction: ActionSlot<Void>,
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<Signal<CameraScreen.Result, NoError>>
private let updateState: ActionSlot<CameraState>
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<Bool>(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<Signal<CameraScreen.Result, NoError>>, updateState: ActionSlot<CameraState>) {
init(
context: AccountContext,
camera: Camera,
present: @escaping (ViewController) -> Void,
completion: ActionSlot<Signal<CameraScreen.Result, NoError>>,
updateState: ActionSlot<CameraState>,
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<Signal<CameraScreen.Result, NoError>>()
fileprivate var previewBlurPromise = ValuePromise<Bool>(false)
private var pipPosition: PIPPosition = .bottomRight
fileprivate var previewBlurPromise = ValuePromise<Bool>(false)
private let flipAnimationAction = ActionSlot<Void>()
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<Signal<CameraScreen.Result, NoError>>()
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

View File

@ -175,7 +175,7 @@ private final class StoryContainerScreenComponent: Component {
private var transitionCloneMasterView: UIView
private var volumeButtonsListener: VolumeButtonsListener?
private let volumeButtonsListenerShouldBeActvie = ValuePromise<Bool>(false, ignoreRepeated: true)
private let volumeButtonsListenerShouldBeActive = ValuePromise<Bool>(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()
}

View File

@ -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 {

View File

@ -10,21 +10,25 @@ public class VolumeButtonsListener: NSObject {
private var disposable: Disposable?
public init(shouldBeActive: Signal<Bool, NoError>, valueChanged: @escaping () -> Void) {
var impl: (() -> Void)?
public init(
shouldBeActive: Signal<Bool, NoError>,
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 {