mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
[WIP] Stickers editor
This commit is contained in:
@@ -751,6 +751,13 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
state.cameraState = component.cameraState
|
||||
state.volumeButtonsListenerActive = component.hasAppeared && component.isVisible
|
||||
|
||||
let isSticker: Bool
|
||||
if let controller = controller() as? CameraScreen, case .sticker = controller.mode {
|
||||
isSticker = true
|
||||
} else {
|
||||
isSticker = false
|
||||
}
|
||||
|
||||
let isTablet: Bool
|
||||
if case .regular = environment.metrics.widthClass {
|
||||
isTablet = true
|
||||
@@ -879,6 +886,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
let captureControls = captureControls.update(
|
||||
component: CaptureControlsComponent(
|
||||
isTablet: isTablet,
|
||||
isSticker: isSticker,
|
||||
hasAppeared: component.hasAppeared && hasAllRequiredAccess,
|
||||
hasAccess: hasAllRequiredAccess,
|
||||
tintColor: controlsTintColor,
|
||||
@@ -1063,7 +1071,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
.disappear(.default(scale: true))
|
||||
)
|
||||
|
||||
if !isTablet && Camera.isDualCameraSupported(forRoundVideo: false) {
|
||||
if !isSticker && !isTablet && Camera.isDualCameraSupported(forRoundVideo: false) {
|
||||
let dualButton = dualButton.update(
|
||||
component: CameraButton(
|
||||
content: AnyComponentWithIdentity(
|
||||
@@ -1201,7 +1209,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
}
|
||||
}
|
||||
|
||||
if case .none = component.cameraState.recording, !state.isTransitioning && hasAllRequiredAccess {
|
||||
if !isSticker, case .none = component.cameraState.recording, !state.isTransitioning && hasAllRequiredAccess {
|
||||
let availableModeControlSize: CGSize
|
||||
if isTablet {
|
||||
availableModeControlSize = CGSize(width: panelWidth, height: 120.0)
|
||||
@@ -1314,6 +1322,11 @@ private class BlurView: UIVisualEffectView {
|
||||
}
|
||||
|
||||
public class CameraScreen: ViewController {
|
||||
public enum Mode {
|
||||
case story
|
||||
case sticker
|
||||
}
|
||||
|
||||
public enum PIPPosition: Int32 {
|
||||
case topLeft
|
||||
case topRight
|
||||
@@ -1443,7 +1456,7 @@ public class CameraScreen: ViewController {
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
fileprivate var didAppear: () -> Void = {}
|
||||
|
||||
|
||||
private let completion = ActionSlot<Signal<CameraScreen.Result, NoError>>()
|
||||
|
||||
var cameraState: CameraState {
|
||||
@@ -1735,22 +1748,31 @@ public class CameraScreen: ViewController {
|
||||
|
||||
fileprivate var captureStartTimestamp: Double?
|
||||
private func setupCamera() {
|
||||
guard self.camera == nil else {
|
||||
guard self.camera == nil, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
||||
let camera = Camera(
|
||||
configuration: Camera.Configuration(
|
||||
preset: .hd1920x1080,
|
||||
position: self.cameraState.position,
|
||||
isDualEnabled: self.cameraState.isDualCameraEnabled,
|
||||
audio: true,
|
||||
photo: true,
|
||||
metadata: false
|
||||
),
|
||||
previewView: self.mainPreviewView,
|
||||
secondaryPreviewView: self.additionalPreviewView
|
||||
)
|
||||
var isNew = false
|
||||
let camera: Camera
|
||||
if let cameraHolder = controller.holder {
|
||||
camera = cameraHolder.camera
|
||||
self.mainPreviewView = cameraHolder.previewView
|
||||
self.mainPreviewContainerView.addSubview(self.mainPreviewView)
|
||||
} else {
|
||||
camera = Camera(
|
||||
configuration: Camera.Configuration(
|
||||
preset: .hd1920x1080,
|
||||
position: self.cameraState.position,
|
||||
isDualEnabled: self.cameraState.isDualCameraEnabled,
|
||||
audio: true,
|
||||
photo: true,
|
||||
metadata: false
|
||||
),
|
||||
previewView: self.mainPreviewView,
|
||||
secondaryPreviewView: self.additionalPreviewView
|
||||
)
|
||||
isNew = true
|
||||
}
|
||||
|
||||
self.cameraStateDisposable = combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
@@ -1841,12 +1863,14 @@ public class CameraScreen: ViewController {
|
||||
})
|
||||
|
||||
camera.focus(at: CGPoint(x: 0.5, y: 0.5), autoFocus: true)
|
||||
camera.startCapture()
|
||||
if isNew {
|
||||
camera.startCapture()
|
||||
}
|
||||
self.captureStartTimestamp = CACurrentMediaTime()
|
||||
|
||||
self.camera = camera
|
||||
|
||||
if self.hasAppeared {
|
||||
if isNew && self.hasAppeared {
|
||||
self.maybePresentTooltips()
|
||||
}
|
||||
}
|
||||
@@ -2025,45 +2049,75 @@ public class CameraScreen: ViewController {
|
||||
)
|
||||
}
|
||||
|
||||
var animatedIn = false
|
||||
func animateIn() {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
self.transitionDimView.alpha = 0.0
|
||||
self.backgroundView.alpha = 0.0
|
||||
UIView.animate(withDuration: 0.4, animations: {
|
||||
self.backgroundView.alpha = 1.0
|
||||
})
|
||||
|
||||
if let layout = self.validLayout, layout.metrics.isTablet {
|
||||
self.controller?.statusBar.updateStatusBarStyle(.Hide, animated: true)
|
||||
if let layout = self.validLayout {
|
||||
if layout.metrics.isTablet {
|
||||
controller.statusBar.updateStatusBarStyle(.Hide, animated: true)
|
||||
} else {
|
||||
controller.statusBar.updateStatusBarStyle(.White, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
if let transitionIn = self.controller?.transitionIn, let sourceView = transitionIn.sourceView {
|
||||
let sourceLocalFrame = sourceView.convert(transitionIn.sourceRect, to: self.view)
|
||||
|
||||
let sourceScale = sourceLocalFrame.width / self.previewContainerView.frame.width
|
||||
self.previewContainerView.layer.animatePosition(from: sourceLocalFrame.center, to: self.previewContainerView.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
|
||||
self.requestUpdateLayout(hasAppeared: true, transition: .immediate)
|
||||
})
|
||||
self.previewContainerView.layer.animateScale(from: sourceScale, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
let minSide = min(self.previewContainerView.bounds.width, self.previewContainerView.bounds.height)
|
||||
self.previewContainerView.layer.animateBounds(from: CGRect(origin: CGPoint(x: (self.previewContainerView.bounds.width - minSide) / 2.0, y: (self.previewContainerView.bounds.height - minSide) / 2.0), size: CGSize(width: minSide, height: minSide)), to: self.previewContainerView.bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.previewContainerView.layer.animate(
|
||||
from: minSide / 2.0 as NSNumber,
|
||||
to: self.previewContainerView.layer.cornerRadius as NSNumber,
|
||||
keyPath: "cornerRadius",
|
||||
timingFunction: kCAMediaTimingFunctionSpring,
|
||||
duration: 0.3
|
||||
)
|
||||
if case .story = controller.mode {
|
||||
let sourceScale = sourceLocalFrame.width / self.previewContainerView.frame.width
|
||||
|
||||
self.previewContainerView.layer.animatePosition(from: sourceLocalFrame.center, to: self.previewContainerView.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
|
||||
self.requestUpdateLayout(hasAppeared: true, transition: .immediate)
|
||||
})
|
||||
self.previewContainerView.layer.animateScale(from: sourceScale, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
let minSide = min(self.previewContainerView.bounds.width, self.previewContainerView.bounds.height)
|
||||
self.previewContainerView.layer.animateBounds(from: CGRect(origin: CGPoint(x: (self.previewContainerView.bounds.width - minSide) / 2.0, y: (self.previewContainerView.bounds.height - minSide) / 2.0), size: CGSize(width: minSide, height: minSide)), to: self.previewContainerView.bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.previewContainerView.layer.animate(
|
||||
from: minSide / 2.0 as NSNumber,
|
||||
to: self.previewContainerView.layer.cornerRadius as NSNumber,
|
||||
keyPath: "cornerRadius",
|
||||
timingFunction: kCAMediaTimingFunctionSpring,
|
||||
duration: 0.3
|
||||
)
|
||||
} else {
|
||||
let sourceInnerFrame = sourceView.convert(transitionIn.sourceRect, to: self.previewContainerView)
|
||||
let sourceCenter = sourceInnerFrame.center
|
||||
self.mainPreviewView.layer.position = CGPoint(x: self.previewContainerView.frame.width / 2.0, y: self.previewContainerView.frame.height / 2.0)
|
||||
self.mainPreviewView.layer.animatePosition(from: sourceCenter, to: self.mainPreviewView.layer.position, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
|
||||
self.requestUpdateLayout(hasAppeared: true, transition: .immediate)
|
||||
})
|
||||
|
||||
self.mainPreviewView.layer.animateBounds(from: self.mainPreviewView.bounds, to: CGRect(origin: .zero, size: self.previewContainerView.frame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
|
||||
let sourceScale = self.mainPreviewView.layer.value(forKeyPath: "transform.scale.x") as? CGFloat ?? 1.0
|
||||
self.mainPreviewView.transform = CGAffineTransform.identity
|
||||
self.mainPreviewView.layer.animateScale(from: sourceScale, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
|
||||
Queue.mainQueue().justDispatch {
|
||||
self.animatedIn = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if let view = self.componentHost.view {
|
||||
view.layer.animatePosition(from: sourceLocalFrame.center, to: view.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void) {
|
||||
self.camera?.stopCapture(invalidate: true)
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
||||
UIView.animate(withDuration: 0.25, animations: {
|
||||
self.backgroundView.alpha = 0.0
|
||||
@@ -2071,34 +2125,52 @@ public class CameraScreen: ViewController {
|
||||
|
||||
if let transitionOut = self.controller?.transitionOut(false), let destinationView = transitionOut.destinationView {
|
||||
let destinationLocalFrame = destinationView.convert(transitionOut.destinationRect, to: self.view)
|
||||
|
||||
let targetScale = destinationLocalFrame.width / self.previewContainerView.frame.width
|
||||
self.previewContainerView.layer.animatePosition(from: self.previewContainerView.center, to: destinationLocalFrame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
self.previewContainerView.layer.animateScale(from: 1.0, to: targetScale, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
|
||||
let minSide = min(self.previewContainerView.bounds.width, self.previewContainerView.bounds.height)
|
||||
self.previewContainerView.layer.animateBounds(from: self.previewContainerView.bounds, to: CGRect(origin: CGPoint(x: (self.previewContainerView.bounds.width - minSide) / 2.0, y: (self.previewContainerView.bounds.height - minSide) / 2.0), size: CGSize(width: minSide, height: minSide)), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
self.previewContainerView.layer.animate(
|
||||
from: self.previewContainerView.layer.cornerRadius as NSNumber,
|
||||
to: minSide / 2.0 as NSNumber,
|
||||
keyPath: "cornerRadius",
|
||||
timingFunction: kCAMediaTimingFunctionSpring,
|
||||
duration: 0.3,
|
||||
removeOnCompletion: false
|
||||
)
|
||||
if case .story = controller.mode {
|
||||
self.previewContainerView.layer.animatePosition(from: self.previewContainerView.center, to: destinationLocalFrame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
self.previewContainerView.layer.animateScale(from: 1.0, to: targetScale, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
|
||||
let minSide = min(self.previewContainerView.bounds.width, self.previewContainerView.bounds.height)
|
||||
self.previewContainerView.layer.animateBounds(from: self.previewContainerView.bounds, to: CGRect(origin: CGPoint(x: (self.previewContainerView.bounds.width - minSide) / 2.0, y: (self.previewContainerView.bounds.height - minSide) / 2.0), size: CGSize(width: minSide, height: minSide)), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
self.previewContainerView.layer.animate(
|
||||
from: self.previewContainerView.layer.cornerRadius as NSNumber,
|
||||
to: minSide / 2.0 as NSNumber,
|
||||
keyPath: "cornerRadius",
|
||||
timingFunction: kCAMediaTimingFunctionSpring,
|
||||
duration: 0.3,
|
||||
removeOnCompletion: false
|
||||
)
|
||||
} else {
|
||||
let destinationInnerFrame = destinationView.convert(transitionOut.destinationRect, to: self.previewContainerView)
|
||||
let initialCenter = self.mainPreviewView.layer.position
|
||||
self.mainPreviewView.layer.position = destinationInnerFrame.center
|
||||
self.mainPreviewView.layer.animatePosition(from: initialCenter, to: self.mainPreviewView.layer.position, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
|
||||
self.mainPreviewView.layer.animateBounds(from: self.mainPreviewView.bounds, to: CGRect(origin: .zero, size: self.previewContainerView.frame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
|
||||
let targetScale = destinationInnerFrame.width / self.previewContainerView.frame.width
|
||||
// self.mainPreviewView.transform = CGAffineTransform.identity
|
||||
self.mainPreviewView.layer.animateScale(from: 1.0, to: targetScale, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
if let view = self.componentHost.view {
|
||||
view.layer.animatePosition(from: view.center, to: destinationLocalFrame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
view.layer.animateScale(from: 1.0, to: targetScale, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
view.layer.animateScale(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
} else {
|
||||
completion()
|
||||
}
|
||||
|
||||
self.componentHost.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
self.previewContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false)
|
||||
if case .story = controller.mode {
|
||||
self.previewContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
|
||||
func animateOutToEditor() {
|
||||
@@ -2344,7 +2416,7 @@ public class CameraScreen: ViewController {
|
||||
|
||||
fileprivate var hasAppeared = false
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, hasAppeared: Bool = false, transition: Transition) {
|
||||
guard let _ = self.controller else {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
let isFirstTime = self.validLayout == nil
|
||||
@@ -2578,7 +2650,14 @@ public class CameraScreen: ViewController {
|
||||
self.additionalPreviewContainerView.insertSubview(additionalPreviewView, at: 0)
|
||||
}
|
||||
|
||||
mainPreviewView.frame = mainPreviewInnerFrame
|
||||
if case .sticker = controller.mode {
|
||||
if self.animatedIn {
|
||||
mainPreviewView.frame = mainPreviewInnerFrame
|
||||
}
|
||||
} else {
|
||||
mainPreviewView.frame = mainPreviewInnerFrame
|
||||
}
|
||||
|
||||
additionalPreviewView.frame = additionalPreviewInnerFrame
|
||||
|
||||
self.previewFrameLeftDimView.isHidden = !isTablet
|
||||
@@ -2604,14 +2683,14 @@ public class CameraScreen: ViewController {
|
||||
transition.setPosition(view: self.transitionCornersView, position: CGPoint(x: layout.size.width + screenCornerRadius / 2.0, y: layout.size.height / 2.0))
|
||||
transition.setBounds(view: self.transitionCornersView, bounds: CGRect(origin: .zero, size: CGSize(width: screenCornerRadius, height: layout.size.height)))
|
||||
|
||||
if isTablet && isFirstTime {
|
||||
if (controller.mode == .sticker || isTablet) && isFirstTime {
|
||||
self.animateIn()
|
||||
}
|
||||
|
||||
if self.cameraState.flashMode == .on && (self.cameraState.recording != .none || self.cameraState.mode == .video) {
|
||||
self.controller?.statusBarStyle = .Black
|
||||
controller.statusBarStyle = .Black
|
||||
} else {
|
||||
self.controller?.statusBarStyle = .White
|
||||
controller.statusBarStyle = .White
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2621,6 +2700,7 @@ public class CameraScreen: ViewController {
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
fileprivate let mode: Mode
|
||||
fileprivate let holder: CameraHolder?
|
||||
fileprivate let transitionIn: TransitionIn?
|
||||
fileprivate let transitionOut: (Bool) -> TransitionOut?
|
||||
@@ -2645,6 +2725,7 @@ public class CameraScreen: ViewController {
|
||||
}
|
||||
fileprivate let completion: (Signal<CameraScreen.Result, NoError>, ResultTransition?, @escaping () -> Void) -> Void
|
||||
public var transitionedIn: () -> Void = {}
|
||||
public var transitionedOut: () -> Void = {}
|
||||
|
||||
private var audioSessionDisposable: Disposable?
|
||||
|
||||
@@ -2663,6 +2744,8 @@ public class CameraScreen: ViewController {
|
||||
return self.node.cameraState
|
||||
}
|
||||
|
||||
public var isEmbedded = false
|
||||
|
||||
fileprivate func updateCameraState(_ f: (CameraState) -> CameraState, transition: Transition) {
|
||||
self.node.cameraState = f(self.node.cameraState)
|
||||
self.node.requestUpdateLayout(hasAppeared: self.node.hasAppeared, transition: transition)
|
||||
@@ -2670,12 +2753,14 @@ public class CameraScreen: ViewController {
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
mode: Mode,
|
||||
holder: CameraHolder? = nil,
|
||||
transitionIn: TransitionIn?,
|
||||
transitionOut: @escaping (Bool) -> TransitionOut?,
|
||||
completion: @escaping (Signal<CameraScreen.Result, NoError>, ResultTransition?, @escaping () -> Void) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.mode = mode
|
||||
self.holder = holder
|
||||
self.transitionIn = transitionIn
|
||||
self.transitionOut = transitionOut
|
||||
@@ -2907,13 +2992,17 @@ public class CameraScreen: ViewController {
|
||||
self.hapticFeedback.impact(.light)
|
||||
}
|
||||
|
||||
self.node.camera?.stopCapture(invalidate: true)
|
||||
if case .story = self.mode {
|
||||
self.node.camera?.stopCapture(invalidate: true)
|
||||
}
|
||||
|
||||
self.isDismissed = true
|
||||
if animated {
|
||||
self.ignoreStatusBar = true
|
||||
if let layout = self.validLayout, layout.metrics.isTablet {
|
||||
if let layout = self.validLayout, layout.metrics.isTablet || self.mode == .sticker {
|
||||
self.node.animateOut(completion: {
|
||||
self.dismiss(animated: false)
|
||||
self.transitionedOut()
|
||||
})
|
||||
} else {
|
||||
if !interactive {
|
||||
@@ -2923,6 +3012,7 @@ public class CameraScreen: ViewController {
|
||||
}
|
||||
self.updateTransitionProgress(0.0, transition: .animated(duration: 0.4, curve: .spring), completion: { [weak self] in
|
||||
self?.dismiss(animated: false)
|
||||
self?.transitionedOut()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user