mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Camera and editor improvements
This commit is contained in:
parent
f0efd5e219
commit
62f92ee5af
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -21,22 +21,22 @@ public enum EditorToolKey: Int32, CaseIterable {
|
||||
case highlightsTint
|
||||
case blur
|
||||
case curves
|
||||
|
||||
static let adjustmentToolsKeys: [EditorToolKey] = [
|
||||
.enhance,
|
||||
.brightness,
|
||||
.contrast,
|
||||
.saturation,
|
||||
.warmth,
|
||||
.fade,
|
||||
.highlights,
|
||||
.shadows,
|
||||
.vignette,
|
||||
.grain,
|
||||
.sharpen
|
||||
]
|
||||
}
|
||||
|
||||
private 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 {
|
||||
if lhs.originalDimensions != rhs.originalDimensions {
|
||||
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
@ -749,16 +761,25 @@ final class MediaEditorScreenComponent: Component {
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 200.0)
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
@ -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 {
|
||||
|
@ -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))
|
||||
|
Loading…
x
Reference in New Issue
Block a user