mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-31 23:47:01 +00:00
Merge commit '3f6ac92df77e0f26115998191176f58ab38b060c'
This commit is contained in:
commit
ea35164d1f
@ -1022,6 +1022,16 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
return rects
|
||||
}
|
||||
|
||||
public func animateInstantVideoFromSnapshot(snapshotView: UIView, transition: CombinedTransition) {
|
||||
for contentNode in self.contentNodes {
|
||||
if let contentNode = contentNode as? ChatMessageInstantVideoBubbleContentNode {
|
||||
snapshotView.frame = contentNode.interactiveVideoNode.view.convert(snapshotView.frame, from: self.view)
|
||||
contentNode.interactiveVideoNode.animateFromSnapshot(snapshotView: snapshotView, transition: transition)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
|
@ -1778,9 +1778,16 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
if !self.animatedFadeIn {
|
||||
self.animatedFadeIn = true
|
||||
self.dateAndStatusNode.layer.animateAlpha(from: 0.0, to: self.dateAndStatusNode.alpha, duration: 0.15, delay: 0.18)
|
||||
|
||||
if let durationNode = self.durationNode {
|
||||
durationNode.layer.animateAlpha(from: 0.0, to: durationNode.alpha, duration: 0.15, delay: 0.18)
|
||||
}
|
||||
if let durationBackgroundNode = self.durationBackgroundNode {
|
||||
durationBackgroundNode.layer.animateAlpha(from: 0.0, to: durationBackgroundNode.alpha, duration: 0.15, delay: 0.18)
|
||||
}
|
||||
if let audioTranscriptionButton = self.audioTranscriptionButton {
|
||||
audioTranscriptionButton.layer.animateAlpha(from: 0.0, to: audioTranscriptionButton.alpha, duration: 0.15, delay: 0.18)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,115 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import HierarchyTrackingLayer
|
||||
import ComponentFlow
|
||||
import Display
|
||||
|
||||
private let shadowImage: UIImage? = {
|
||||
UIImage(named: "Stories/PanelGradient")
|
||||
}()
|
||||
|
||||
final class LoadingEffectView: UIView {
|
||||
private let duration: Double
|
||||
|
||||
private let hierarchyTrackingLayer: HierarchyTrackingLayer
|
||||
|
||||
private let gradientWidth: CGFloat
|
||||
private let backgroundView: UIImageView
|
||||
|
||||
private let borderGradientView: UIImageView
|
||||
private let borderContainerView: UIView
|
||||
let borderMaskLayer: SimpleShapeLayer
|
||||
|
||||
init(effectAlpha: CGFloat, borderAlpha: CGFloat, gradientWidth: CGFloat = 200.0, duration: Double) {
|
||||
self.hierarchyTrackingLayer = HierarchyTrackingLayer()
|
||||
|
||||
self.duration = duration
|
||||
|
||||
self.gradientWidth = gradientWidth
|
||||
self.backgroundView = UIImageView()
|
||||
|
||||
self.borderGradientView = UIImageView()
|
||||
self.borderContainerView = UIView()
|
||||
self.borderMaskLayer = SimpleShapeLayer()
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
self.layer.addSublayer(self.hierarchyTrackingLayer)
|
||||
self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
|
||||
guard let self, self.bounds.width != 0.0 else {
|
||||
return
|
||||
}
|
||||
self.updateAnimations(size: self.bounds.size)
|
||||
}
|
||||
|
||||
let generateGradient: (CGFloat) -> UIImage? = { baseAlpha in
|
||||
return generateImage(CGSize(width: self.gradientWidth, height: 16.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let foregroundColor = UIColor(white: 1.0, alpha: min(1.0, baseAlpha * 4.0))
|
||||
|
||||
if let shadowImage {
|
||||
UIGraphicsPushContext(context)
|
||||
|
||||
for i in 0 ..< 2 {
|
||||
let shadowFrame = CGRect(origin: CGPoint(x: CGFloat(i) * (size.width * 0.5), y: 0.0), size: CGSize(width: size.width * 0.5, height: size.height))
|
||||
|
||||
context.saveGState()
|
||||
context.translateBy(x: shadowFrame.midX, y: shadowFrame.midY)
|
||||
context.rotate(by: CGFloat(i == 0 ? 1.0 : -1.0) * CGFloat.pi * 0.5)
|
||||
let adjustedRect = CGRect(origin: CGPoint(x: -shadowFrame.height * 0.5, y: -shadowFrame.width * 0.5), size: CGSize(width: shadowFrame.height, height: shadowFrame.width))
|
||||
|
||||
context.clip(to: adjustedRect, mask: shadowImage.cgImage!)
|
||||
context.setFillColor(foregroundColor.cgColor)
|
||||
context.fill(adjustedRect)
|
||||
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
})
|
||||
}
|
||||
self.backgroundView.image = generateGradient(effectAlpha)
|
||||
self.addSubview(self.backgroundView)
|
||||
|
||||
self.borderGradientView.image = generateGradient(borderAlpha)
|
||||
self.borderContainerView.addSubview(self.borderGradientView)
|
||||
self.addSubview(self.borderContainerView)
|
||||
self.borderContainerView.layer.mask = self.borderMaskLayer
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func updateAnimations(size: CGSize) {
|
||||
if self.backgroundView.layer.animation(forKey: "shimmer") != nil {
|
||||
return
|
||||
}
|
||||
|
||||
let animation = self.backgroundView.layer.makeAnimation(from: 0.0 as NSNumber, to: (size.width + self.gradientWidth + size.width * 0.2) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: self.duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
||||
animation.repeatCount = Float.infinity
|
||||
self.backgroundView.layer.add(animation, forKey: "shimmer")
|
||||
self.borderGradientView.layer.add(animation, forKey: "shimmer")
|
||||
}
|
||||
|
||||
func update(size: CGSize, transition: Transition) {
|
||||
if self.backgroundView.bounds.size != size {
|
||||
self.backgroundView.layer.removeAllAnimations()
|
||||
|
||||
self.borderMaskLayer.fillColor = nil
|
||||
self.borderMaskLayer.strokeColor = UIColor.white.cgColor
|
||||
let lineWidth: CGFloat = 3.0
|
||||
self.borderMaskLayer.lineWidth = lineWidth
|
||||
self.borderMaskLayer.path = UIBezierPath(ovalIn: CGRect(origin: .zero, size: size)).cgPath
|
||||
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: size.height)))
|
||||
|
||||
transition.setFrame(view: self.borderContainerView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
transition.setFrame(view: self.borderGradientView, frame: CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: size.height)))
|
||||
}
|
||||
|
||||
self.updateAnimations(size: size)
|
||||
}
|
||||
}
|
@ -76,8 +76,10 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent {
|
||||
|
||||
let context: AccountContext
|
||||
let cameraState: CameraState
|
||||
let previewFrame: CGRect
|
||||
let isPreviewing: Bool
|
||||
let isMuted: Bool
|
||||
let totalDuration: Double
|
||||
let getController: () -> VideoMessageCameraScreen?
|
||||
let present: (ViewController) -> Void
|
||||
let push: (ViewController) -> Void
|
||||
@ -88,8 +90,10 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent {
|
||||
init(
|
||||
context: AccountContext,
|
||||
cameraState: CameraState,
|
||||
previewFrame: CGRect,
|
||||
isPreviewing: Bool,
|
||||
isMuted: Bool,
|
||||
totalDuration: Double,
|
||||
getController: @escaping () -> VideoMessageCameraScreen?,
|
||||
present: @escaping (ViewController) -> Void,
|
||||
push: @escaping (ViewController) -> Void,
|
||||
@ -99,8 +103,10 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent {
|
||||
) {
|
||||
self.context = context
|
||||
self.cameraState = cameraState
|
||||
self.previewFrame = previewFrame
|
||||
self.isPreviewing = isPreviewing
|
||||
self.isMuted = isMuted
|
||||
self.totalDuration = totalDuration
|
||||
self.getController = getController
|
||||
self.present = present
|
||||
self.push = push
|
||||
@ -113,6 +119,9 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.previewFrame != rhs.previewFrame {
|
||||
return false
|
||||
}
|
||||
if lhs.cameraState != rhs.cameraState {
|
||||
return false
|
||||
}
|
||||
@ -122,6 +131,9 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent {
|
||||
if lhs.isMuted != rhs.isMuted {
|
||||
return false
|
||||
}
|
||||
if lhs.totalDuration != rhs.totalDuration {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -244,12 +256,12 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent {
|
||||
let duration = initialDuration + recordingData.duration
|
||||
if let self, let controller = self.getController() {
|
||||
controller.updateCameraState({ $0.updatedDuration(duration) }, transition: .easeInOut(duration: 0.1))
|
||||
if duration > 59.0 {
|
||||
self.stopVideoRecording()
|
||||
}
|
||||
if isFirstRecording {
|
||||
controller.node.setupLiveUpload(filePath: recordingData.filePath)
|
||||
}
|
||||
if duration > 59.5 {
|
||||
controller.onStop()
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
@ -304,6 +316,8 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent {
|
||||
|
||||
let viewOnceButton = Child(PlainButtonComponent.self)
|
||||
let recordMoreButton = Child(PlainButtonComponent.self)
|
||||
|
||||
let muteIcon = Child(ZStack<Empty>.self)
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||
@ -319,15 +333,23 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent {
|
||||
var showRecordMore = false
|
||||
if component.isPreviewing {
|
||||
showViewOnce = true
|
||||
showRecordMore = true
|
||||
|
||||
viewOnceOffset = 67.0
|
||||
if component.totalDuration < 59.0 {
|
||||
showRecordMore = true
|
||||
viewOnceOffset = 67.0
|
||||
} else {
|
||||
viewOnceOffset = 14.0
|
||||
}
|
||||
} else if case .handsFree = component.cameraState.recording {
|
||||
showViewOnce = true
|
||||
}
|
||||
|
||||
if let controller = component.getController(), !controller.viewOnceAvailable {
|
||||
showViewOnce = false
|
||||
if let controller = component.getController() {
|
||||
if controller.isSendingImmediately || controller.scheduledLock {
|
||||
showViewOnce = true
|
||||
}
|
||||
if !controller.viewOnceAvailable {
|
||||
showViewOnce = false
|
||||
}
|
||||
}
|
||||
|
||||
if !component.isPreviewing {
|
||||
@ -444,14 +466,35 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent {
|
||||
)
|
||||
}
|
||||
|
||||
// var isVideoRecording = false
|
||||
// if case .video = component.cameraState.mode {
|
||||
// isVideoRecording = true
|
||||
// } else if component.cameraState.recording != .none {
|
||||
// isVideoRecording = true
|
||||
// }
|
||||
if component.isPreviewing && component.isMuted {
|
||||
let muteIcon = muteIcon.update(
|
||||
component: ZStack([
|
||||
AnyComponentWithIdentity(
|
||||
id: "background",
|
||||
component: AnyComponent(
|
||||
RoundedRectangle(color: UIColor(rgb: 0x000000, alpha: 0.3), cornerRadius: 24.0)
|
||||
)
|
||||
),
|
||||
AnyComponentWithIdentity(
|
||||
id: "icon",
|
||||
component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Chat/Message/InstantVideoMute",
|
||||
tintColor: .white
|
||||
)
|
||||
)
|
||||
)
|
||||
]),
|
||||
availableSize: CGSize(width: 24.0, height: 24.0),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(muteIcon
|
||||
.position(CGPoint(x: component.previewFrame.midX, y: component.previewFrame.maxY - 24.0))
|
||||
.appear(.default(scale: true, alpha: true))
|
||||
.disappear(.default(scale: true, alpha: true))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
@ -483,13 +526,16 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
fileprivate let containerView: UIView
|
||||
fileprivate let componentHost: ComponentView<ViewControllerComponentContainer.Environment>
|
||||
fileprivate let previewContainerView: UIView
|
||||
private var previewSnapshotView: UIView?
|
||||
private var previewBlurView: BlurView
|
||||
|
||||
fileprivate var mainPreviewView: CameraSimplePreviewView
|
||||
fileprivate var additionalPreviewView: CameraSimplePreviewView
|
||||
private var progressView: RecordingProgressView
|
||||
private let loadingView: LoadingEffectView
|
||||
|
||||
private var resultPreviewView: ResultPreviewView?
|
||||
|
||||
|
||||
private var cameraStateDisposable: Disposable?
|
||||
|
||||
private let idleTimerExtensionDisposable = MetaDisposable()
|
||||
@ -560,6 +606,11 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
|
||||
self.progressView = RecordingProgressView(frame: .zero)
|
||||
|
||||
self.loadingView = LoadingEffectView(effectAlpha: 0.1, borderAlpha: 0.25, duration: 1.0)
|
||||
|
||||
self.previewBlurView = BlurView()
|
||||
self.previewBlurView.isUserInteractionEnabled = false
|
||||
|
||||
if isDualCameraEnabled {
|
||||
self.mainPreviewView.resetPlaceholder(front: false)
|
||||
self.additionalPreviewView.resetPlaceholder(front: true)
|
||||
@ -589,17 +640,27 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
self.previewContainerView.addSubview(self.mainPreviewView)
|
||||
self.previewContainerView.addSubview(self.additionalPreviewView)
|
||||
self.previewContainerView.addSubview(self.progressView)
|
||||
|
||||
self.previewContainerView.addSubview(self.previewBlurView)
|
||||
self.previewContainerView.addSubview(self.loadingView)
|
||||
|
||||
self.completion.connect { [weak self] result in
|
||||
if let self {
|
||||
self.addCaptureResult(result)
|
||||
}
|
||||
}
|
||||
|
||||
self.mainPreviewView.removePlaceholder(delay: 0.0)
|
||||
if isDualCameraEnabled {
|
||||
self.mainPreviewView.removePlaceholder(delay: 0.0)
|
||||
}
|
||||
self.withReadyCamera(isFirstTime: true, {
|
||||
self.additionalPreviewView.removePlaceholder(delay: 0.35)
|
||||
self.startRecording.invoke(Void())
|
||||
if isDualCameraEnabled {
|
||||
self.mainPreviewView.removePlaceholder(delay: 0.0)
|
||||
}
|
||||
self.loadingView.alpha = 0.0
|
||||
self.additionalPreviewView.removePlaceholder(delay: 0.0)
|
||||
|
||||
Queue.mainQueue().after(0.15) {
|
||||
self.startRecording.invoke(Void())
|
||||
}
|
||||
})
|
||||
|
||||
self.idleTimerExtensionDisposable.set(self.context.sharedContext.applicationBindings.pushIdleTimerExtension())
|
||||
@ -744,10 +805,43 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
|
||||
func resumeCameraCapture() {
|
||||
if !self.mainPreviewView.isEnabled {
|
||||
if let snapshotView = self.previewContainerView.snapshotView(afterScreenUpdates: false) {
|
||||
self.previewContainerView.insertSubview(snapshotView, belowSubview: self.previewBlurView)
|
||||
self.previewSnapshotView = snapshotView
|
||||
}
|
||||
self.mainPreviewView.isEnabled = true
|
||||
self.additionalPreviewView.isEnabled = true
|
||||
self.camera?.startCapture()
|
||||
|
||||
UIView.animate(withDuration: 0.25, animations: {
|
||||
self.loadingView.alpha = 1.0
|
||||
self.previewBlurView.effect = UIBlurEffect(style: .dark)
|
||||
})
|
||||
|
||||
let action = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
UIView.animate(withDuration: 0.4, animations: {
|
||||
self.previewBlurView.effect = nil
|
||||
self.previewSnapshotView?.alpha = 0.0
|
||||
}, completion: { _ in
|
||||
self.previewSnapshotView?.removeFromSuperview()
|
||||
self.previewSnapshotView = nil
|
||||
})
|
||||
}
|
||||
if #available(iOS 13.0, *) {
|
||||
let _ = (self.mainPreviewView.isPreviewing
|
||||
|> filter { $0 }
|
||||
|> take(1)).startStandalone(next: { _ in
|
||||
action()
|
||||
})
|
||||
} else {
|
||||
Queue.mainQueue().after(1.0) {
|
||||
action()
|
||||
}
|
||||
}
|
||||
|
||||
self.cameraIsActive = true
|
||||
self.requestUpdateLayout(transition: .immediate)
|
||||
}
|
||||
@ -776,10 +870,12 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
|
||||
self.transitioningToPreview = false
|
||||
|
||||
let composition = composition(with: self.results)
|
||||
controller.updatePreviewState({ _ in
|
||||
return PreviewState(composition: composition, trimRange: nil, isMuted: true)
|
||||
}, transition: .spring(duration: 0.4))
|
||||
if !controller.isSendingImmediately {
|
||||
let composition = composition(with: self.results)
|
||||
controller.updatePreviewState({ _ in
|
||||
return PreviewState(composition: composition, trimRange: nil, isMuted: true)
|
||||
}, transition: .spring(duration: 0.4))
|
||||
}
|
||||
}
|
||||
|
||||
private func debugSaveResult(path: String) {
|
||||
@ -797,8 +893,16 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
let result = super.hitTest(point, with: event)
|
||||
|
||||
if let resultPreviewView = self.resultPreviewView {
|
||||
if resultPreviewView.bounds.contains(self.view.convert(point, to: resultPreviewView)) {
|
||||
return resultPreviewView
|
||||
}
|
||||
}
|
||||
|
||||
if let controller = self.controller, let layout = self.validLayout {
|
||||
if point.y > layout.size.height - controller.inputPanelFrame.height - 34.0 {
|
||||
let insets = layout.insets(options: .input)
|
||||
if point.y > layout.size.height - insets.bottom - controller.inputPanelFrame.height {
|
||||
if layout.metrics.isTablet {
|
||||
if point.x < layout.size.width * 0.33 {
|
||||
return result
|
||||
@ -807,6 +911,7 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@ -916,8 +1021,6 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
let isFirstTime = self.validLayout == nil
|
||||
self.validLayout = layout
|
||||
|
||||
// let isTablet = layout.metrics.isTablet
|
||||
|
||||
let environment = ViewControllerComponentContainer.Environment(
|
||||
statusBarHeight: layout.statusBarHeight ?? 0.0,
|
||||
navigationHeight: 0.0,
|
||||
@ -944,7 +1047,45 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
self.didAppear()
|
||||
}
|
||||
|
||||
let backgroundFrame = CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: controller.inputPanelFrame.minY))
|
||||
var backgroundFrame = CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: controller.inputPanelFrame.minY))
|
||||
if backgroundFrame.maxY < layout.size.height - 100.0 && (layout.inputHeight ?? 0.0).isZero {
|
||||
backgroundFrame = CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: layout.size.height - layout.intrinsicInsets.bottom - controller.inputPanelFrame.height))
|
||||
}
|
||||
|
||||
transition.setPosition(view: self.backgroundView, position: backgroundFrame.center)
|
||||
transition.setBounds(view: self.backgroundView, bounds: CGRect(origin: .zero, size: backgroundFrame.size))
|
||||
|
||||
transition.setPosition(view: self.containerView, position: backgroundFrame.center)
|
||||
transition.setBounds(view: self.containerView, bounds: CGRect(origin: .zero, size: backgroundFrame.size))
|
||||
|
||||
let availableHeight = layout.size.height - (layout.inputHeight ?? 0.0)
|
||||
let previewSide = min(369.0, layout.size.width - 24.0)
|
||||
let previewFrame: CGRect
|
||||
if layout.metrics.isTablet {
|
||||
previewFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - previewSide) / 2.0), y: max(layout.statusBarHeight ?? 0.0 + 24.0, availableHeight * 0.2 - previewSide / 2.0)), size: CGSize(width: previewSide, height: previewSide))
|
||||
} else {
|
||||
previewFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - previewSide) / 2.0), y: max(layout.statusBarHeight ?? 0.0 + 16.0, availableHeight * 0.4 - previewSide / 2.0)), size: CGSize(width: previewSide, height: previewSide))
|
||||
}
|
||||
if !self.animatingIn {
|
||||
transition.setFrame(view: self.previewContainerView, frame: previewFrame)
|
||||
}
|
||||
transition.setCornerRadius(layer: self.previewContainerView.layer, cornerRadius: previewSide / 2.0)
|
||||
|
||||
let previewInnerFrame = CGRect(origin: .zero, size: previewFrame.size)
|
||||
|
||||
let additionalPreviewSize = CGSize(width: previewFrame.size.width, height: previewFrame.size.width / 3.0 * 4.0)
|
||||
let additionalPreviewInnerFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((previewFrame.height - additionalPreviewSize.height) / 2.0)), size: additionalPreviewSize)
|
||||
self.mainPreviewView.frame = previewInnerFrame
|
||||
self.additionalPreviewView.frame = additionalPreviewInnerFrame
|
||||
|
||||
self.progressView.frame = previewInnerFrame
|
||||
self.progressView.value = CGFloat(self.cameraState.duration / 60.0)
|
||||
|
||||
transition.setAlpha(view: self.additionalPreviewView, alpha: self.cameraState.position == .front ? 1.0 : 0.0)
|
||||
|
||||
self.previewBlurView.frame = previewInnerFrame
|
||||
self.previewSnapshotView?.frame = previewInnerFrame
|
||||
self.loadingView.update(size: previewInnerFrame.size, transition: .immediate)
|
||||
|
||||
let componentSize = self.componentHost.update(
|
||||
transition: transition,
|
||||
@ -952,8 +1093,10 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
VideoMessageCameraScreenComponent(
|
||||
context: self.context,
|
||||
cameraState: self.cameraState,
|
||||
previewFrame: previewFrame,
|
||||
isPreviewing: self.previewState != nil || self.transitioningToPreview,
|
||||
isMuted: self.previewState?.isMuted ?? true,
|
||||
totalDuration: self.previewState?.composition.duration.seconds ?? 0.0,
|
||||
getController: { [weak self] in
|
||||
return self?.controller
|
||||
},
|
||||
@ -984,37 +1127,6 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
transition.setFrame(view: componentView, frame: componentFrame)
|
||||
}
|
||||
|
||||
transition.setPosition(view: self.backgroundView, position: backgroundFrame.center)
|
||||
transition.setBounds(view: self.backgroundView, bounds: CGRect(origin: .zero, size: backgroundFrame.size))
|
||||
|
||||
transition.setPosition(view: self.containerView, position: backgroundFrame.center)
|
||||
transition.setBounds(view: self.containerView, bounds: CGRect(origin: .zero, size: backgroundFrame.size))
|
||||
|
||||
let availableHeight = layout.size.height - (layout.inputHeight ?? 0.0)
|
||||
let previewSide = min(369.0, layout.size.width - 24.0)
|
||||
let previewFrame: CGRect
|
||||
if layout.metrics.isTablet {
|
||||
previewFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - previewSide) / 2.0), y: max(layout.statusBarHeight ?? 0.0 + 24.0, availableHeight * 0.2 - previewSide / 2.0)), size: CGSize(width: previewSide, height: previewSide))
|
||||
} else {
|
||||
previewFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - previewSide) / 2.0), y: max(layout.statusBarHeight ?? 0.0 + 16.0, availableHeight * 0.4 - previewSide / 2.0)), size: CGSize(width: previewSide, height: previewSide))
|
||||
}
|
||||
if !self.animatingIn {
|
||||
transition.setFrame(view: self.previewContainerView, frame: previewFrame)
|
||||
}
|
||||
transition.setCornerRadius(layer: self.previewContainerView.layer, cornerRadius: previewSide / 2.0)
|
||||
|
||||
let previewInnerFrame = CGRect(origin: .zero, size: previewFrame.size)
|
||||
|
||||
let additionalPreviewSize = CGSize(width: previewFrame.size.width, height: previewFrame.size.width / 3.0 * 4.0)
|
||||
let additionalPreviewInnerFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((previewFrame.height - additionalPreviewSize.height) / 2.0)), size: additionalPreviewSize)
|
||||
self.mainPreviewView.frame = previewInnerFrame
|
||||
self.additionalPreviewView.frame = additionalPreviewInnerFrame
|
||||
|
||||
self.progressView.frame = previewInnerFrame
|
||||
self.progressView.value = CGFloat(self.cameraState.duration / 60.0)
|
||||
|
||||
transition.setAlpha(view: self.additionalPreviewView, alpha: self.cameraState.position == .front ? 1.0 : 0.0)
|
||||
|
||||
if let previewState = self.previewState {
|
||||
if previewState.composition !== self.resultPreviewView?.composition {
|
||||
self.resultPreviewView?.removeFromSuperview()
|
||||
@ -1105,10 +1217,7 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
private let micLevelValue = ValuePromise<Float>(0.0)
|
||||
private let durationValue = ValuePromise<TimeInterval>(0.0)
|
||||
public let recordingStatus: RecordingStatus
|
||||
|
||||
public var onDismiss: (Bool) -> Void = { _ in
|
||||
}
|
||||
|
||||
|
||||
public var onStop: () -> Void = {
|
||||
}
|
||||
|
||||
@ -1251,6 +1360,7 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
super.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
fileprivate var isSendingImmediately = false
|
||||
public func sendVideoRecording() {
|
||||
if case .none = self.cameraState.recording, self.node.results.isEmpty {
|
||||
self.completion(nil)
|
||||
@ -1259,6 +1369,7 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
|
||||
if case .none = self.cameraState.recording {
|
||||
} else {
|
||||
self.isSendingImmediately = true
|
||||
self.waitingForNextResult = true
|
||||
self.node.stopRecording.invoke(Void())
|
||||
}
|
||||
@ -1362,6 +1473,8 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
|
||||
private var waitingForNextResult = false
|
||||
public func stopVideoRecording() -> Bool {
|
||||
self.node.dismissAllTooltips()
|
||||
|
||||
self.waitingForNextResult = true
|
||||
self.node.transitioningToPreview = true
|
||||
self.node.requestUpdateLayout(transition: .spring(duration: 0.4))
|
||||
@ -1376,6 +1489,7 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
public func lockVideoRecording() {
|
||||
if case .none = self.cameraState.recording {
|
||||
self.scheduledLock = true
|
||||
self.node.requestUpdateLayout(transition: .spring(duration: 0.4))
|
||||
} else {
|
||||
self.updateCameraState({ $0.updatedRecording(.handsFree) }, transition: .spring(duration: 0.4))
|
||||
}
|
||||
@ -1396,7 +1510,7 @@ public class VideoMessageCameraScreen: ViewController {
|
||||
}
|
||||
|
||||
public func hideVideoSnapshot() {
|
||||
self.node.previewContainerView.alpha = 0.02
|
||||
self.node.previewContainerView.isHidden = true
|
||||
}
|
||||
|
||||
public func updateTrimRange(start: Double, end: Double, updatedEnd: Bool, apply: Bool) {
|
||||
@ -1465,3 +1579,46 @@ private func composition(with results: [VideoMessageCameraScreen.CaptureResult])
|
||||
}
|
||||
return composition
|
||||
}
|
||||
|
||||
private class BlurView: UIVisualEffectView {
|
||||
private func setup() {
|
||||
for subview in self.subviews {
|
||||
if subview.description.contains("VisualEffectSubview") {
|
||||
subview.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
if let sublayer = self.layer.sublayers?[0], let filters = sublayer.filters {
|
||||
sublayer.backgroundColor = nil
|
||||
sublayer.isOpaque = false
|
||||
let allowedKeys: [String] = [
|
||||
"gaussianBlur"
|
||||
]
|
||||
sublayer.filters = filters.filter { filter in
|
||||
guard let filter = filter as? NSObject else {
|
||||
return true
|
||||
}
|
||||
let filterName = String(describing: filter)
|
||||
if !allowedKeys.contains(filterName) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override var effect: UIVisualEffect? {
|
||||
get {
|
||||
return super.effect
|
||||
}
|
||||
set {
|
||||
super.effect = newValue
|
||||
self.setup()
|
||||
}
|
||||
}
|
||||
|
||||
override func didAddSubview(_ subview: UIView) {
|
||||
super.didAddSubview(subview)
|
||||
self.setup()
|
||||
}
|
||||
}
|
||||
|
@ -6195,19 +6195,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let videoRecorder = videoRecorder {
|
||||
strongSelf.recorderFeedback?.impact(.light)
|
||||
|
||||
videoRecorder.onDismiss = { [weak self] isCancelled in
|
||||
self?.chatDisplayNode.updateRecordedMediaDeleted(isCancelled)
|
||||
self?.beginMediaRecordingRequestId += 1
|
||||
self?.lockMediaRecordingRequestId = nil
|
||||
self?.videoRecorder.set(.single(nil))
|
||||
}
|
||||
videoRecorder.onStop = {
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
$0.updatedInputTextPanelState { panelState in
|
||||
return panelState.withUpdatedMediaRecordingState(.video(status: .editing, isLocked: false))
|
||||
}
|
||||
})
|
||||
strongSelf.dismissMediaRecorder(.pause)
|
||||
}
|
||||
}
|
||||
strongSelf.present(videoRecorder, in: .window(.root))
|
||||
@ -15352,7 +15342,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
func deactivateRaiseGesture() {
|
||||
self.raiseToListenActivateRecordingTimer?.invalidate()
|
||||
self.raiseToListenActivateRecordingTimer = nil
|
||||
self.dismissMediaRecorder(.preview)
|
||||
self.dismissMediaRecorder(.pause)
|
||||
}
|
||||
|
||||
func requestAudioRecorder(beginWithTone: Bool) {
|
||||
@ -15617,12 +15607,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
// self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
// $0.updatedInputTextPanelState { panelState in
|
||||
// return panelState.withUpdatedMediaRecordingState(.video(status: .editing, isLocked: false))
|
||||
// }
|
||||
// })
|
||||
}
|
||||
default:
|
||||
self.recorderDataDisposable.set(nil)
|
||||
@ -15687,6 +15671,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.videoRecorder.set(.single(nil))
|
||||
}
|
||||
|
||||
self.recorderDataDisposable.set(nil)
|
||||
self.chatDisplayNode.updateRecordedMediaDeleted(true)
|
||||
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
$0.updatedRecordedMediaPreview(nil)
|
||||
|
@ -635,7 +635,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
||||
case let .videoMessage(videoMessage):
|
||||
let combinedTransition = CombinedTransition(horizontal: .animated(duration: horizontalDuration, curve: ChatMessageTransitionNodeImpl.horizontalAnimationCurve), vertical: .animated(duration: verticalDuration, curve: ChatMessageTransitionNodeImpl.verticalAnimationCurve))
|
||||
|
||||
if let itemNode = self.itemNode as? ChatMessageInstantVideoItemNode {
|
||||
if let itemNode = self.itemNode as? ChatMessageBubbleItemNode {
|
||||
itemNode.cancelInsertionAnimations()
|
||||
|
||||
self.contextSourceNode.isExtractedToContextPreview = true
|
||||
@ -659,7 +659,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran
|
||||
strongSelf.endAnimation()
|
||||
})
|
||||
|
||||
itemNode.animateFromSnapshot(snapshotView: videoMessage.view, transition: combinedTransition)
|
||||
itemNode.animateInstantVideoFromSnapshot(snapshotView: videoMessage.view, transition: combinedTransition)
|
||||
}
|
||||
case let .mediaInput(mediaInput):
|
||||
if let snapshotView = mediaInput.extractSnapshot() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user