Camera and editor improvements

This commit is contained in:
Ilya Laktyushin 2023-06-10 02:23:53 +04:00
parent f0efd5e219
commit 62f92ee5af
7 changed files with 135 additions and 56 deletions

View File

@ -43,7 +43,7 @@ private struct CameraState {
return CameraState(mode: mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, recording: self.recording, duration: self.duration)
}
func updatedPosition(_ mode: Camera.Position) -> CameraState {
func updatedPosition(_ position: Camera.Position) -> CameraState {
return CameraState(mode: self.mode, position: position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, recording: self.recording, duration: self.duration)
}
@ -216,13 +216,7 @@ private final class CameraScreenComponent: CombinedComponent {
self.hapticFeedback.impact(.light)
}
private var lastFlipTimestamp: Double?
func togglePosition() {
let currentTimestamp = CACurrentMediaTime()
if let lastFlipTimestamp = self.lastFlipTimestamp, currentTimestamp - lastFlipTimestamp < 2.0 {
return
}
self.lastFlipTimestamp = currentTimestamp
self.camera.togglePosition()
self.hapticFeedback.impact(.light)
}
@ -1334,7 +1328,7 @@ public class CameraScreen: ViewController {
private var galleryController: ViewController?
public func returnFromEditor() {
self.node.animateInFromEditor(toGallery: self.galleryController != nil)
self.node.animateInFromEditor(toGallery: self.galleryController?.displayNode.supernode != nil)
}
func presentGallery(fromGesture: Bool = false) {
@ -1377,17 +1371,17 @@ public class CameraScreen: ViewController {
self.node.resumeCameraCapture()
}
})
controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in
if let self, let controller {
let transitionFactor = controller.modalStyleOverlayTransitionFactor
if transitionFactor > 0.1 {
stopCameraCapture()
}
self.node.updateModalTransitionFactor(transitionFactor, transition: transition)
}
}
self.galleryController = controller
}
controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in
if let self, let controller {
let transitionFactor = controller.modalStyleOverlayTransitionFactor
if transitionFactor > 0.1 {
stopCameraCapture()
}
self.node.updateModalTransitionFactor(transitionFactor, transition: transition)
}
}
self.push(controller)
}

View File

@ -370,6 +370,12 @@ final class CaptureControlsComponent: Component {
private let lockImage = UIImage(bundleImageName: "Camera/LockIcon")
private var lastFlipTimestamp: Double?
private var didFlip = false
private var wasBanding: Bool?
private var panBlobState: ShutterBlobView.BlobState?
private let hapticFeedback = HapticFeedback()
public func matches(tag: Any) -> Bool {
@ -425,9 +431,6 @@ final class CaptureControlsComponent: Component {
}
}
private var didFlip = false
private var wasBanding: Bool?
private var panBlobState: ShutterBlobView.BlobState?
@objc private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
guard let component = self.component else {
return
@ -651,7 +654,15 @@ final class CaptureControlsComponent: Component {
)
),
minSize: CGSize(width: 44.0, height: 44.0),
action: {
action: { [weak self] in
guard let self else {
return
}
let currentTimestamp = CACurrentMediaTime()
if let lastFlipTimestamp = self.lastFlipTimestamp, currentTimestamp - lastFlipTimestamp < 1.3 {
return
}
self.lastFlipTimestamp = currentTimestamp
component.flipTapped()
flipAnimationAction.invoke(Void())
}

View File

@ -494,6 +494,10 @@ public final class MediaEditor {
}
}
public var isPlaying: Bool {
return (self.player?.rate ?? 0.0) > 0.0
}
public func play() {
self.player?.play()
}

View File

@ -21,21 +21,21 @@ public enum EditorToolKey: Int32, CaseIterable {
case highlightsTint
case blur
case curves
}
private let adjustmentToolsKeys: [EditorToolKey] = [
.enhance,
.brightness,
.contrast,
.saturation,
.warmth,
.fade,
.highlights,
.shadows,
.vignette,
.grain,
.sharpen
]
static let adjustmentToolsKeys: [EditorToolKey] = [
.enhance,
.brightness,
.contrast,
.saturation,
.warmth,
.fade,
.highlights,
.shadows,
.vignette,
.grain,
.sharpen
]
}
public final class MediaEditorValues: Codable, Equatable {
public static func == (lhs: MediaEditorValues, rhs: MediaEditorValues) -> Bool {
@ -75,9 +75,28 @@ public final class MediaEditorValues: Codable, Equatable {
if lhs.entities != rhs.entities {
return false
}
// if lhs.toolValues != rhs.toolValues {
// return false
// }
for key in EditorToolKey.allCases {
let lhsToolValue = lhs.toolValues[key]
let rhsToolValue = rhs.toolValues[key]
if (lhsToolValue == nil) != (rhsToolValue == nil) {
return false
}
if let lhsToolValue = lhsToolValue as? Float, let rhsToolValue = rhsToolValue as? Float {
return lhsToolValue != rhsToolValue
}
if let lhsToolValue = lhsToolValue as? BlurValue, let rhsToolValue = rhsToolValue as? BlurValue {
return lhsToolValue != rhsToolValue
}
if let lhsToolValue = lhsToolValue as? TintValue, let rhsToolValue = rhsToolValue as? TintValue {
return lhsToolValue != rhsToolValue
}
if let lhsToolValue = lhsToolValue as? CurvesValue, let rhsToolValue = rhsToolValue as? CurvesValue {
return lhsToolValue != rhsToolValue
}
}
return true
}
@ -662,7 +681,7 @@ public struct CurvesValue: Equatable, Codable {
private let toolEpsilon: Float = 0.005
public extension MediaEditorValues {
var hasAdjustments: Bool {
for key in adjustmentToolsKeys {
for key in EditorToolKey.adjustmentToolsKeys {
if let value = self.toolValues[key] as? Float, abs(value) > toolEpsilon {
return true
}

View File

@ -71,7 +71,7 @@ final class VideoTextureSource: NSObject, TextureSource, AVPlayerItemOutputPullD
}
func invalidate() {
self.playerItemOutput?.setDelegate(nil, queue: self.queue)
self.playerItemOutput?.setDelegate(nil, queue: nil)
self.playerItemOutput = nil
self.playerItemObservation?.invalidate()
self.playerItemStatusObservation?.invalidate()

View File

@ -287,10 +287,14 @@ final class MediaEditorScreenComponent: Component {
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2)
}
}
if let view = self.inputPanel.view {
if case .camera = source {
if let view = self.inputPanel.view {
view.layer.animatePosition(from: CGPoint(x: 0.0, y: 44.0), to: .zero, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
view.layer.animateScale(from: 0.6, to: 1.0, duration: 0.2)
}
if let view = self.scrubber.view {
view.layer.animatePosition(from: CGPoint(x: 0.0, y: 44.0), to: .zero, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
view.layer.animateScale(from: 0.6, to: 1.0, duration: 0.2)
@ -327,8 +331,15 @@ final class MediaEditorScreenComponent: Component {
transition.setScale(view: view, scale: 0.1)
}
if let view = self.inputPanel.view {
if case .camera = source {
if case .camera = source {
if let view = self.inputPanel.view {
view.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 44.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
view.layer.animateAlpha(from: view.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
}
if let view = self.scrubber.view {
view.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 44.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
view.layer.animateAlpha(from: view.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2)
@ -426,6 +437,7 @@ final class MediaEditorScreenComponent: Component {
}
}
private var isEditingCaption = false
func update(component: MediaEditorScreenComponent, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
guard !self.isDismissed else {
return availableSize
@ -752,14 +764,23 @@ final class MediaEditorScreenComponent: Component {
let fadeTransition = Transition(animation: .curve(duration: 0.3, curve: .easeInOut))
if self.inputPanelExternalState.isEditing {
component.mediaEditor?.stop()
fadeTransition.setAlpha(view: self.fadeView, alpha: 1.0)
} else {
component.mediaEditor?.play()
fadeTransition.setAlpha(view: self.fadeView, alpha: 0.0)
}
transition.setFrame(view: self.fadeView, frame: CGRect(origin: .zero, size: availableSize))
let isEditingCaption = self.inputPanelExternalState.isEditing
if self.isEditingCaption != isEditingCaption {
self.isEditingCaption = isEditingCaption
if isEditingCaption {
mediaEditor?.stop()
} else {
mediaEditor?.play()
}
}
var isEditingTextEntity = false
var sizeSliderVisible = false
var sizeValue: CGFloat?
@ -1130,6 +1151,8 @@ public final class MediaEditorScreen: ViewController {
fileprivate var subject: MediaEditorScreen.Subject?
private var subjectDisposable: Disposable?
private var appInForegroundDisposable: Disposable?
private var wasPlaying = false
private let backgroundDimView: UIView
fileprivate let componentHost: ComponentView<ViewControllerComponentContainer.Environment>
@ -1289,11 +1312,24 @@ public final class MediaEditorScreen: ViewController {
}
}
}
self.appInForegroundDisposable = (controller.context.sharedContext.applicationBindings.applicationInForeground
|> deliverOnMainQueue).start(next: { [weak self] inForeground in
if let self, let mediaEditor = self.mediaEditor {
if inForeground && self.wasPlaying {
mediaEditor.play()
} else if !inForeground {
self.wasPlaying = mediaEditor.isPlaying
mediaEditor.stop()
}
}
})
}
deinit {
self.subjectDisposable?.dispose()
self.gradientColorsDisposable?.dispose()
self.appInForegroundDisposable?.dispose()
}
private func setup(with subject: MediaEditorScreen.Subject) {
@ -2254,6 +2290,10 @@ public final class MediaEditorScreen: ViewController {
self.transitionOut = transitionOut
self.completion = completion
if let transitionIn, case .camera = transitionIn {
self.isSavingAvailable = true
}
super.init(navigationBarPresentationData: nil)
self.navigationPresentation = .flatModal
@ -3094,7 +3134,7 @@ private final class ToolValueComponent: Component {
self.state = state
let titleSize = self.title.update(
transition: transition,
transition: .immediate,
component: AnyComponent(Text(
text: component.title,
font: Font.light(34.0),
@ -3116,11 +3156,11 @@ private final class ToolValueComponent: Component {
self.addSubview(titleView)
}
transition.setPosition(view: titleView, position: titleFrame.center)
transition.setBounds(view: titleView, bounds: CGRect(origin: .zero, size: titleFrame.size))
titleView.bounds = CGRect(origin: .zero, size: titleFrame.size)
}
let valueSize = self.value.update(
transition: transition,
transition: .immediate,
component: AnyComponent(Text(
text: component.value,
font: Font.with(size: 90.0, weight: .thin, traits: .monospacedNumbers),
@ -3142,7 +3182,7 @@ private final class ToolValueComponent: Component {
self.addSubview(valueView)
}
transition.setPosition(view: valueView, position: valueFrame.center)
transition.setBounds(view: valueView, bounds: CGRect(origin: .zero, size: valueFrame.size))
valueView.bounds = CGRect(origin: .zero, size: valueFrame.size)
}
if let previousValue, component.value != previousValue, self.alpha > 0.0 {

View File

@ -29,6 +29,14 @@ private class VideoFrameLayer: SimpleShapeLayer {
}
}
private final class HandleView: UIImageView {
var hitTestSlop = UIEdgeInsets()
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return self.bounds.inset(by: self.hitTestSlop).contains(point)
}
}
final class VideoScrubberComponent: Component {
typealias EnvironmentType = Empty
@ -93,10 +101,10 @@ final class VideoScrubberComponent: Component {
}
final class View: UIView, UITextFieldDelegate {
private let leftHandleView = UIImageView()
private let rightHandleView = UIImageView()
private let leftHandleView = HandleView()
private let rightHandleView = HandleView()
private let borderView = UIImageView()
private let cursorView = UIImageView()
private let cursorView = HandleView()
private let transparentFramesContainer = UIView()
private let opaqueFramesContainer = UIView()
@ -144,14 +152,17 @@ final class VideoScrubberComponent: Component {
self.leftHandleView.image = handleImage
self.leftHandleView.isUserInteractionEnabled = true
self.leftHandleView.tintColor = .white
self.leftHandleView.hitTestSlop = UIEdgeInsets(top: -8.0, left: -9.0, bottom: -8.0, right: -9.0)
self.rightHandleView.image = handleImage
self.rightHandleView.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
self.rightHandleView.isUserInteractionEnabled = true
self.rightHandleView.tintColor = .white
self.rightHandleView.hitTestSlop = UIEdgeInsets(top: -8.0, left: -9.0, bottom: -8.0, right: -9.0)
self.cursorView.image = positionImage
self.cursorView.isUserInteractionEnabled = true
self.cursorView.hitTestSlop = UIEdgeInsets(top: -8.0, left: -9.0, bottom: -8.0, right: -9.0)
self.borderView.image = generateImage(CGSize(width: 1.0, height: scrubberHeight), rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size))