mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Camera and editor improvements
This commit is contained in:
parent
ef4d6c51c2
commit
6dbd76bf7a
@ -75,6 +75,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/MediaEditor",
|
||||
"//submodules/Components/MetalImageView",
|
||||
"//submodules/TelegramUI/Components/CameraButtonComponent",
|
||||
"//submodules/Utils/VolumeButtons"
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user