diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index f180045b7c..311ea9721a 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -9,7 +9,7 @@ final class CameraSession { private let multiSession: Any? init() { - if #available(iOS 13.0, *) { + if #available(iOS 13.0, *), AVCaptureMultiCamSession.isMultiCamSupported { self.multiSession = AVCaptureMultiCamSession() self.singleSession = nil } else { @@ -473,6 +473,8 @@ private final class CameraContext { } public func startRecording() -> Signal { + self.mainDeviceContext.device.setTorchMode(self._flashMode) + if let additionalDeviceContext = self.additionalDeviceContext { return combineLatest( self.mainDeviceContext.output.startRecording(isDualCamera: true, position: self.positionValue), diff --git a/submodules/Camera/Sources/CameraDevice.swift b/submodules/Camera/Sources/CameraDevice.swift index 02fcbd06ec..13ea9b216d 100644 --- a/submodules/Camera/Sources/CameraDevice.swift +++ b/submodules/Camera/Sources/CameraDevice.swift @@ -40,20 +40,23 @@ final class CameraDevice { selectedDevice = device } else if let device = AVCaptureDevice.default(.builtInDualWideCamera, for: .video, position: position) { selectedDevice = device - } else { - selectedDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera, .builtInWideAngleCamera, .builtInTelephotoCamera], mediaType: .video, position: position).devices.first + } else if let device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera, .builtInTelephotoCamera], mediaType: .video, position: position).devices.first { + selectedDevice = device } } else { - if #available(iOS 11.1, *), dual, case .front = position { - if let trueDepthDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInTrueDepthCamera], mediaType: .video, position: position).devices.first { - selectedDevice = trueDepthDevice - } + if #available(iOS 11.1, *), dual, case .front = position, let trueDepthDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInTrueDepthCamera], mediaType: .video, position: position).devices.first { + selectedDevice = trueDepthDevice } if selectedDevice == nil { selectedDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera, .builtInWideAngleCamera, .builtInTelephotoCamera], mediaType: .video, position: position).devices.first } } + if selectedDevice == nil, #available(iOS 13.0, *) { + let allDevices = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera, .builtInTripleCamera, .builtInTelephotoCamera, .builtInDualWideCamera, .builtInTrueDepthCamera, .builtInWideAngleCamera, .builtInUltraWideCamera], mediaType: .video, position: position).devices + Logger.shared.log("Camera", "No device selected, availabled devices: \(allDevices)") + } + self.videoDevice = selectedDevice self.videoDevicePromise.set(.single(selectedDevice)) @@ -234,12 +237,34 @@ final class CameraDevice { } } + func setTorchMode(_ flashMode: AVCaptureDevice.FlashMode) { + guard let device = self.videoDevice else { + return + } + self.transaction(device) { device in + let torchMode: AVCaptureDevice.TorchMode + switch flashMode { + case .on: + torchMode = .on + case .off: + torchMode = .off + case .auto: + torchMode = .auto + @unknown default: + torchMode = .off + } + if device.isTorchModeSupported(torchMode) { + device.torchMode = torchMode + } + } + } + func setZoomLevel(_ zoomLevel: CGFloat) { guard let device = self.videoDevice else { return } self.transaction(device) { device in - device.videoZoomFactor = max(1.0, min(10.0, zoomLevel)) + device.videoZoomFactor = max(device.neutralZoomFactor, min(10.0, device.neutralZoomFactor + zoomLevel)) } } diff --git a/submodules/Camera/Sources/CameraPreviewView.swift b/submodules/Camera/Sources/CameraPreviewView.swift index e08915b971..e313d58dce 100644 --- a/submodules/Camera/Sources/CameraPreviewView.swift +++ b/submodules/Camera/Sources/CameraPreviewView.swift @@ -9,7 +9,35 @@ import CoreMedia import Vision import ImageBlur +private extension UIInterfaceOrientation { + var videoOrientation: AVCaptureVideoOrientation { + switch self { + case .portraitUpsideDown: return .portraitUpsideDown + case .landscapeRight: return .landscapeRight + case .landscapeLeft: return .landscapeLeft + case .portrait: return .portrait + default: return .portrait + } + } +} + public class CameraSimplePreviewView: UIView { + func updateOrientation() { + guard self.videoPreviewLayer.connection?.isVideoOrientationSupported == true else { + return + } + let statusBarOrientation: UIInterfaceOrientation + if #available(iOS 13.0, *) { + statusBarOrientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation ?? .portrait + } else { + statusBarOrientation = UIApplication.shared.statusBarOrientation + } + let videoOrientation: AVCaptureVideoOrientation = statusBarOrientation.videoOrientation +// videoPreviewLayer.frame = view.layer.bounds + self.videoPreviewLayer.connection?.videoOrientation = videoOrientation + self.videoPreviewLayer.removeAllAnimations() + } + static func lastBackImage() -> UIImage { let imagePath = NSTemporaryDirectory() + "backCameraImage.jpg" if let data = try? Data(contentsOf: URL(fileURLWithPath: imagePath)), let image = UIImage(data: data) { @@ -80,6 +108,7 @@ public class CameraSimplePreviewView: UIView { public override func layoutSubviews() { super.layoutSubviews() + self.updateOrientation() self.placeholderView.frame = self.bounds.insetBy(dx: -1.0, dy: -1.0) } diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index 3cc9d73740..08d842c082 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -1140,14 +1140,19 @@ private final class DrawingScreenComponent: CombinedComponent { if state.drawingViewState.isDrawing || component.isInteractingWithEntities { controlsAreVisible = false } - + + var controlsBottomInset: CGFloat = 0.0 let previewSize: CGSize - let previewTopInset: CGFloat = environment.statusBarHeight + 5.0 + var previewTopInset: CGFloat = environment.statusBarHeight + 5.0 if case .regular = environment.metrics.widthClass { let previewHeight = context.availableSize.height - previewTopInset - 75.0 previewSize = CGSize(width: floorToScreenPixels(previewHeight / 1.77778), height: previewHeight) } else { previewSize = CGSize(width: context.availableSize.width, height: floorToScreenPixels(context.availableSize.width * 1.77778)) + if context.availableSize.height < previewSize.height + 30.0 { + previewTopInset = 0.0 + controlsBottomInset = -50.0 + } } let previewBottomInset = context.availableSize.height - previewSize.height - previewTopInset @@ -1155,6 +1160,7 @@ private final class DrawingScreenComponent: CombinedComponent { if component.sourceHint == .storyEditor { topInset = previewTopInset + 31.0 } + let bottomInset: CGFloat = environment.inputHeight > 0.0 ? environment.inputHeight : 145.0 var leftEdge: CGFloat = environment.safeInsets.left @@ -1985,7 +1991,7 @@ private final class DrawingScreenComponent: CombinedComponent { if case .regular = environment.metrics.widthClass { doneButtonPosition.x -= 20.0 } - doneButtonPosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 3.0 + doneButton.size.height / 2.0) + doneButtonPosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 3.0 + doneButton.size.height / 2.0) + controlsBottomInset } context.add(doneButton .position(doneButtonPosition) @@ -2063,7 +2069,7 @@ private final class DrawingScreenComponent: CombinedComponent { ) var modeAndSizePosition = CGPoint(x: context.availableSize.width / 2.0 - (modeRightInset - 57.0) / 2.0, y: context.availableSize.height - environment.safeInsets.bottom - modeAndSize.size.height / 2.0 - 9.0) if component.sourceHint == .storyEditor { - modeAndSizePosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 8.0 + modeAndSize.size.height / 2.0) + modeAndSizePosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 8.0 + modeAndSize.size.height / 2.0) + controlsBottomInset } context.add(modeAndSize .position(modeAndSizePosition) @@ -2108,7 +2114,7 @@ private final class DrawingScreenComponent: CombinedComponent { if case .regular = environment.metrics.widthClass { backButtonPosition.x += 20.0 } - backButtonPosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 3.0 + backButton.size.height / 2.0) + backButtonPosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 3.0 + backButton.size.height / 2.0) + controlsBottomInset } context.add(backButton .position(backButtonPosition) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index e6325abd42..a22ee2710f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -6,6 +6,7 @@ import TelegramApi public enum EngineOutgoingMessageContent { case text(String, [MessageTextEntity]) case file(FileMediaReference) + case contextResult(ChatContextResultCollection, ChatContextResult) } public final class StoryPreloadInfo { @@ -225,30 +226,41 @@ public extension TelegramEngine { storyId: StoryId? = nil, content: EngineOutgoingMessageContent ) -> Signal<[MessageId?], NoError> { - var attributes: [MessageAttribute] = [] - var text: String = "" - var mediaReference: AnyMediaReference? - - switch content { - case let .text(textValue, entities): - if !entities.isEmpty { - attributes.append(TextEntitiesMessageAttribute(entities: entities)) + let message: EnqueueMessage? + if case let .contextResult(results, result) = content { + message = self.outgoingMessageWithChatContextResult(to: peerId, threadId: nil, botId: results.botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: storyId, hideVia: true, silentPosting: false, scheduleTime: nil, correlationId: nil) + } else { + var attributes: [MessageAttribute] = [] + var text: String = "" + var mediaReference: AnyMediaReference? + switch content { + case let .text(textValue, entities): + if !entities.isEmpty { + attributes.append(TextEntitiesMessageAttribute(entities: entities)) + } + text = textValue + case let .file(fileReference): + mediaReference = fileReference.abstract + default: + fatalError() } - text = textValue - case let .file(fileReference): - mediaReference = fileReference.abstract + message = .message( + text: text, + attributes: attributes, + inlineStickers: [:], + mediaReference: mediaReference, + replyToMessageId: replyToMessageId, + replyToStoryId: storyId, + localGroupingKey: nil, + correlationId: nil, + bubbleUpEmojiOrStickersets: [] + ) } - let message: EnqueueMessage = .message( - text: text, - attributes: attributes, - inlineStickers: [:], - mediaReference: mediaReference, - replyToMessageId: replyToMessageId, - replyToStoryId: storyId, - localGroupingKey: nil, - correlationId: nil, - bubbleUpEmojiOrStickersets: [] - ) + + guard let message else { + return .complete() + } + return enqueueMessages( account: self.account, peerId: peerId, diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index 20c96b41d4..fb8cf2f55f 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -101,6 +101,7 @@ private final class CameraScreenComponent: CombinedComponent { let panelWidth: CGFloat let animateFlipAction: ActionSlot let animateShutter: () -> Void + let toggleCameraPositionAction: ActionSlot let dismissAllTooltips: () -> Void let present: (ViewController) -> Void let push: (ViewController) -> Void @@ -116,6 +117,7 @@ private final class CameraScreenComponent: CombinedComponent { panelWidth: CGFloat, animateFlipAction: ActionSlot, animateShutter: @escaping () -> Void, + toggleCameraPositionAction: ActionSlot, dismissAllTooltips: @escaping () -> Void, present: @escaping (ViewController) -> Void, push: @escaping (ViewController) -> Void, @@ -130,6 +132,7 @@ private final class CameraScreenComponent: CombinedComponent { self.panelWidth = panelWidth self.animateFlipAction = animateFlipAction self.animateShutter = animateShutter + self.toggleCameraPositionAction = toggleCameraPositionAction self.dismissAllTooltips = dismissAllTooltips self.present = present self.push = push @@ -183,8 +186,10 @@ private final class CameraScreenComponent: CombinedComponent { private let present: (ViewController) -> Void private let completion: ActionSlot> private let updateState: ActionSlot + private let toggleCameraPositionAction: ActionSlot private let animateShutter: () -> Void + private let animateFlipAction: ActionSlot private let dismissAllTooltips: () -> Void private var cameraStateDisposable: Disposable? @@ -214,6 +219,8 @@ private final class CameraScreenComponent: CombinedComponent { completion: ActionSlot>, updateState: ActionSlot, animateShutter: @escaping () -> Void = {}, + animateFlipAction: ActionSlot, + toggleCameraPositionAction: ActionSlot, dismissAllTooltips: @escaping () -> Void = {} ) { self.context = context @@ -222,6 +229,8 @@ private final class CameraScreenComponent: CombinedComponent { self.completion = completion self.updateState = updateState self.animateShutter = animateShutter + self.animateFlipAction = animateFlipAction + self.toggleCameraPositionAction = toggleCameraPositionAction self.dismissAllTooltips = dismissAllTooltips super.init() @@ -245,6 +254,12 @@ private final class CameraScreenComponent: CombinedComponent { } self.setupVolumeButtonsHandler() + + self.toggleCameraPositionAction.connect({ [weak self] in + if let self { + self.togglePosition(self.animateFlipAction) + } + }) } deinit { @@ -472,7 +487,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, animateShutter: self.animateShutter, dismissAllTooltips: self.dismissAllTooltips) + return State(context: self.context, camera: self.camera, present: self.present, completion: self.completion, updateState: self.updateState, animateShutter: self.animateShutter, animateFlipAction: self.animateFlipAction, toggleCameraPositionAction: self.toggleCameraPositionAction, dismissAllTooltips: self.dismissAllTooltips) } static var body: Body { @@ -1026,7 +1041,8 @@ public class CameraScreen: ViewController { private weak var controller: CameraScreen? private let context: AccountContext private let updateState: ActionSlot - + private let toggleCameraPositionAction: ActionSlot + fileprivate let backgroundView: UIView fileprivate let containerView: UIView private let componentExternalState = CameraScreenComponent.ExternalState() @@ -1070,6 +1086,7 @@ public class CameraScreen: ViewController { self.controller = controller self.context = controller.context self.updateState = ActionSlot() + self.toggleCameraPositionAction = ActionSlot() self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 } @@ -1296,6 +1313,10 @@ public class CameraScreen: ViewController { let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:))) self.mainPreviewContainerView.addGestureRecognizer(tapGestureRecognizer) + let doubleGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleDoubleTap(_:))) + doubleGestureRecognizer.numberOfTapsRequired = 2 + self.mainPreviewContainerView.addGestureRecognizer(doubleGestureRecognizer) + let pipPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePipPan(_:))) self.additionalPreviewContainerView.addGestureRecognizer(pipPanGestureRecognizer) @@ -1354,6 +1375,10 @@ public class CameraScreen: ViewController { self.camera.focus(at: point, autoFocus: false) } + @objc private func handleDoubleTap(_ gestureRecognizer: UITapGestureRecognizer) { + self.toggleCameraPositionAction.invoke(Void()) + } + private var pipTranslation: CGPoint? @objc private func handlePipPan(_ gestureRecognizer: UIPanGestureRecognizer) { guard let layout = self.validLayout else { @@ -1829,6 +1854,7 @@ public class CameraScreen: ViewController { animateShutter: { [weak self] in self?.mainPreviewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) }, + toggleCameraPositionAction: self.toggleCameraPositionAction, dismissAllTooltips: { [weak self] in self?.dismissAllTooltips() }, @@ -1872,10 +1898,17 @@ public class CameraScreen: ViewController { transition.setFrame(view: self.transitionDimView, frame: CGRect(origin: .zero, size: layout.size)) - transition.setFrame(view: self.previewContainerView, frame: previewFrame) - transition.setFrame(view: self.mainPreviewContainerView, frame: CGRect(origin: .zero, size: previewFrame.size)) + let previewContainerFrame: CGRect + if isTablet { + previewContainerFrame = CGRect(origin: .zero, size: layout.size) + } else { + previewContainerFrame = previewFrame + } - transition.setFrame(view: self.previewBlurView, frame: CGRect(origin: .zero, size: previewFrame.size)) + transition.setFrame(view: self.previewContainerView, frame: previewContainerFrame) + transition.setFrame(view: self.mainPreviewContainerView, frame: CGRect(origin: .zero, size: previewContainerFrame.size)) + + transition.setFrame(view: self.previewBlurView, frame: CGRect(origin: .zero, size: previewContainerFrame.size)) let dualCamUpdated = self.appliedDualCam != self.isDualCamEnabled self.appliedDualCam = self.isDualCamEnabled diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift index e2ab97ef27..dd8a08a649 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift @@ -749,10 +749,10 @@ final class CaptureControlsComponent: Component { blobOffset -= self.frame.width / 2.0 var isBanding = false if location.y < -10.0 { - let fraction = 1.0 + min(8.0, ((abs(location.y) - 10.0) / 60.0)) + let fraction = min(8.0, ((abs(location.y) - 10.0) / 60.0)) component.zoomUpdated(fraction) } else { - component.zoomUpdated(1.0) + component.zoomUpdated(0.0) } if location.x < self.frame.width / 2.0 - 30.0 { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/RenderPass.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/RenderPass.swift index 2e2c04ebeb..0a5eecf235 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/RenderPass.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/RenderPass.swift @@ -77,7 +77,7 @@ func verticesDataForRotation(_ rotation: TextureRotation, rect: CGRect = CGRect( func textureDimensionsForRotation(texture: MTLTexture, rotation: TextureRotation) -> (width: Int, height: Int) { switch rotation { - case .rotate90Degrees, .rotate270Degrees: + case .rotate90Degrees, .rotate90DegreesMirrored, .rotate270Degrees: return (texture.height, texture.width) default: return (texture.width, texture.height) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/VideoTextureSource.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/VideoTextureSource.swift index 1d30ce287c..19090b7673 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/VideoTextureSource.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/VideoTextureSource.swift @@ -253,7 +253,7 @@ final class VideoInputPass: DefaultRenderPass { func textureDimensionsForRotation(width: Int, height: Int, rotation: TextureRotation) -> (width: Int, height: Int) { switch rotation { - case .rotate90Degrees, .rotate270Degrees: + case .rotate90Degrees, .rotate270Degrees, .rotate90DegreesMirrored: return (height, width) default: return (width, height) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index cf7f9399cf..89092625a6 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -1897,12 +1897,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate if let self, let controller = self.controller, values.gradientColors != nil, controller.previousSavedValues != values { if !isSavingAvailable && controller.previousSavedValues == nil { controller.previousSavedValues = values + controller.isSavingAvailable = false } else { self.hasAnyChanges = true - controller.isSavingAvailable = true - controller.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut)) } + controller.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut)) } } @@ -2154,7 +2154,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } } - private var enhanceGestureOffset: CGFloat? + private var enhanceInitialTranslation: Float? @objc func handleDismissPan(_ gestureRecognizer: UIPanGestureRecognizer) { guard let controller = self.controller, let layout = self.validLayout, (layout.inputHeight ?? 0.0).isZero else { @@ -2180,7 +2180,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate self.isDismissBySwipeSuppressed = controller.isEligibleForDraft() controller.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut)) } - } else if abs(translation.x) > 10.0 && !self.isDismissing { + } else if abs(translation.x) > 10.0 && !self.isDismissing && !self.isEnhancing { self.isEnhancing = true controller.requestLayout(transition: .animated(duration: 0.3, curve: .easeInOut)) } @@ -2197,14 +2197,27 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } else if self.isEnhancing { if let mediaEditor = self.mediaEditor { let value = mediaEditor.getToolValue(.enhance) as? Float ?? 0.0 + + if self.enhanceInitialTranslation == nil && value != 0.0 { + self.enhanceInitialTranslation = value + } + let delta = Float((translation.x / self.frame.width) * 1.5) - let updatedValue = max(-1.0, min(1.0, value + delta)) + var updatedValue = max(-1.0, min(1.0, value + delta)) + if let enhanceInitialTranslation = self.enhanceInitialTranslation { + if enhanceInitialTranslation > 0.0 { + updatedValue = max(0.0, updatedValue) + } else { + updatedValue = min(0.0, updatedValue) + } + } mediaEditor.setToolValue(.enhance, value: updatedValue) } self.requestUpdate() gestureRecognizer.setTranslation(.zero, in: self.view) } case .ended, .cancelled: + self.enhanceInitialTranslation = nil if self.isDismissing { if abs(translation.y) > self.view.frame.height * 0.33 || abs(velocity.y) > 1000.0, !controller.isEligibleForDraft() { controller.requestDismiss(saveDraft: false, animated: true) @@ -3799,7 +3812,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate }) if case let .draft(draft, id) = subject, id == nil { - removeStoryDraft(engine: self.context.engine, path: draft.path, delete: true) + removeStoryDraft(engine: self.context.engine, path: draft.path, delete: !draft.isVideo) } } else { if let image = mediaEditor.resultImage { @@ -4329,7 +4342,11 @@ private final class ToolValueComponent: Component { } if let previousValue, component.value != previousValue, self.alpha > 0.0 { - self.hapticFeedback.impact(.click05) + if component.value == "100" || component.value == "0" { + self.hapticFeedback.impact(.medium) + } else { + self.hapticFeedback.impact(.click05) + } } return availableSize diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift index 747d1d76d9..6f0eb6306d 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift @@ -214,6 +214,7 @@ private final class MediaToolsScreenComponent: Component { public final class View: UIView { private let buttonsContainerView = UIView() + private let buttonsBackgroundView = UIView() private let cancelButton = ComponentView() private let adjustmentsButton = ComponentView() private let tintButton = ComponentView() @@ -244,10 +245,11 @@ private final class MediaToolsScreenComponent: Component { self.backgroundColor = .clear - self.addSubview(self.buttonsContainerView) self.addSubview(self.previewContainerView) + self.addSubview(self.buttonsContainerView) self.previewContainerView.addSubview(self.optionsContainerView) self.optionsContainerView.addSubview(self.optionsBackgroundView) + self.buttonsContainerView.addSubview(self.buttonsBackgroundView) } required init?(coder: NSCoder) { @@ -277,6 +279,8 @@ private final class MediaToolsScreenComponent: Component { view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) } + self.buttonsBackgroundView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.optionsContainerView.layer.animatePosition(from: CGPoint(x: 0.0, y: self.optionsContainerView.frame.height), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) } @@ -312,6 +316,8 @@ private final class MediaToolsScreenComponent: Component { view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } + self.buttonsBackgroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.optionsContainerView.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: self.optionsContainerView.frame.height), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) self.state?.updated() @@ -335,11 +341,12 @@ private final class MediaToolsScreenComponent: Component { let mediaEditor = (environment.controller() as? MediaToolsScreen)?.mediaEditor let sectionUpdated = component.sectionUpdated - + let buttonSideInset: CGFloat let buttonBottomInset: CGFloat = 8.0 + var controlsBottomInset: CGFloat = 0.0 let previewSize: CGSize - let topInset: CGFloat = environment.statusBarHeight + 5.0 + var topInset: CGFloat = environment.statusBarHeight + 5.0 if isTablet { let previewHeight = availableSize.height - topInset - 75.0 previewSize = CGSize(width: floorToScreenPixels(previewHeight / 1.77778), height: previewHeight) @@ -347,10 +354,17 @@ private final class MediaToolsScreenComponent: Component { } else { previewSize = CGSize(width: availableSize.width, height: floorToScreenPixels(availableSize.width * 1.77778)) buttonSideInset = 10.0 + if availableSize.height < previewSize.height + 30.0 { + topInset = 0.0 + controlsBottomInset = -75.0 +// self.buttonsBackgroundView.backgroundColor = .black + } else { + self.buttonsBackgroundView.backgroundColor = .clear + } } - let previewContainerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - previewSize.width) / 2.0), y: environment.safeInsets.top), size: CGSize(width: previewSize.width, height: availableSize.height - environment.safeInsets.top - environment.safeInsets.bottom)) - let buttonsContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - environment.safeInsets.bottom), size: CGSize(width: availableSize.width, height: environment.safeInsets.bottom)) + var previewContainerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - previewSize.width) / 2.0), y: environment.safeInsets.top), size: CGSize(width: previewSize.width, height: availableSize.height - environment.safeInsets.top - environment.safeInsets.bottom + controlsBottomInset)) + let buttonsContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - environment.safeInsets.bottom + controlsBottomInset), size: CGSize(width: availableSize.width, height: environment.safeInsets.bottom - controlsBottomInset)) let cancelButtonSize = self.cancelButton.update( transition: transition, @@ -873,6 +887,7 @@ private final class MediaToolsScreenComponent: Component { let optionsFrame = CGRect(origin: .zero, size: optionsSize) if let optionsView = self.toolOptions.view { if optionsView.superview == nil { + optionsView.clipsToBounds = true self.optionsContainerView.addSubview(optionsView) } optionsTransition.setFrame(view: optionsView, frame: optionsFrame) @@ -885,9 +900,11 @@ private final class MediaToolsScreenComponent: Component { } } + previewContainerFrame.size.height -= controlsBottomInset + let optionsBackgroundFrame = CGRect( - origin: CGPoint(x: 0.0, y: previewContainerFrame.height - optionsSize.height), - size: optionsSize + origin: CGPoint(x: 0.0, y: previewContainerFrame.height - optionsSize.height + controlsBottomInset), + size: CGSize(width: optionsSize.width, height: optionsSize.height - controlsBottomInset) ) transition.setFrame(view: self.optionsContainerView, frame: optionsBackgroundFrame) transition.setFrame(view: self.optionsBackgroundView, frame: CGRect(origin: .zero, size: optionsBackgroundFrame.size)) @@ -906,6 +923,7 @@ private final class MediaToolsScreenComponent: Component { transition.setFrame(view: self.previewContainerView, frame: previewContainerFrame) transition.setFrame(view: self.buttonsContainerView, frame: buttonsContainerFrame) + transition.setFrame(view: self.buttonsBackgroundView, frame: CGRect(origin: .zero, size: buttonsContainerFrame.size)) return availableSize } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index ef5be115d0..8de36f6d42 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -109,23 +109,26 @@ final class StoryItemSetContainerSendMessage { self.inputMediaInteraction = ChatEntityKeyboardInputNode.Interaction( sendSticker: { [weak self] fileReference, _, _, _, _, _, _, _, _ in - if let view = self?.view { - self?.performSendStickerAction(view: view, fileReference: fileReference) + if let self, let view = self.view { + self.performSendStickerAction(view: view, fileReference: fileReference) } return false }, - sendEmoji: { [weak self] text, attribute, bool1 in + sendEmoji: { [weak self] text, attribute, _ in if let self { let _ = self } }, sendGif: { [weak self] fileReference, _, _, _, _ in - if let view = self?.view { - self?.performSendGifAction(view: view, fileReference: fileReference) + if let self, let view = self.view { + self.performSendStickerAction(view: view, fileReference: fileReference) } return false }, - sendBotContextResultAsGif: { _, _, _, _, _, _ in + sendBotContextResultAsGif: { [weak self] results, result, _, _, _, _ in + if let self, let view = self.view { + self.performSendContextResultAction(view: view, results: results, result: result) + } return false }, updateChoosingSticker: { _ in }, @@ -323,113 +326,6 @@ final class StoryItemSetContainerSendMessage { } } - func performSendStickerAction(view: StoryItemSetContainerComponent.View, fileReference: FileMediaReference) { - guard let component = view.component else { - return - } - let focusedItem = component.slice.item - guard let peerId = focusedItem.peerId else { - return - } - let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id) - let peer = component.slice.peer - - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - let controller = component.controller() as? StoryContainerScreen - - if let navigationController = controller?.navigationController as? NavigationController { - var controllers = navigationController.viewControllers - for controller in controllers.reversed() { - if !(controller is StoryContainerScreen) { - controllers.removeLast() - } else { - break - } - } - navigationController.setViewControllers(controllers, animated: true) - - controller?.window?.forEachController({ controller in - if let controller = controller as? StickerPackScreenImpl { - controller.dismiss() - } - }) - } - - let _ = (component.context.engine.messages.enqueueOutgoingMessage( - to: peerId, - replyTo: nil, - storyId: focusedStoryId, - content: .file(fileReference) - ) |> deliverOnMainQueue).start(next: { [weak controller, weak view] messageIds in - if let controller { - Queue.mainQueue().after(0.3) { - controller.present(UndoOverlayController( - presentationData: presentationData, - content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false), - elevatedLayout: false, - animateInAsReplacement: false, - action: { [weak view] action in - if case .undo = action, let messageId = messageIds.first { - view?.navigateToPeer(peer: peer, chat: true, messageId: messageId) - } - return false - } - ), in: .current) - } - } - }) - - self.currentInputMode = .text - if hasFirstResponder(view) { - view.endEditing(true) - } else { - view.state?.updated(transition: .spring(duration: 0.3)) - controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) - } - } - - func performSendGifAction(view: StoryItemSetContainerComponent.View, fileReference: FileMediaReference) { - guard let component = view.component else { - return - } - let focusedItem = component.slice.item - guard let peerId = focusedItem.peerId else { - return - } - let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id) - let peer = component.slice.peer - - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - let controller = component.controller() - - let _ = (component.context.engine.messages.enqueueOutgoingMessage( - to: peerId, - replyTo: nil, - storyId: focusedStoryId, - content: .file(fileReference) - ) |> deliverOnMainQueue).start(next: { [weak controller, weak view] messageIds in - if let controller { - Queue.mainQueue().after(0.3) { - controller.present(UndoOverlayController( - presentationData: presentationData, - content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false), - elevatedLayout: false, - animateInAsReplacement: false, - action: { [weak view] action in - if case .undo = action, let messageId = messageIds.first { - view?.navigateToPeer(peer: peer, chat: true, messageId: messageId) - } - return false - } - ), in: .current) - } - } - }) - - self.currentInputMode = .text - view.endEditing(true) - } - func performSendMessageAction( view: StoryItemSetContainerComponent.View ) { @@ -500,6 +396,136 @@ final class StoryItemSetContainerSendMessage { } } + func performSendStickerAction(view: StoryItemSetContainerComponent.View, fileReference: FileMediaReference) { + guard let component = view.component else { + return + } + let focusedItem = component.slice.item + guard let peerId = focusedItem.peerId else { + return + } + let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id) + let peer = component.slice.peer + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let controller = component.controller() as? StoryContainerScreen + + if let navigationController = controller?.navigationController as? NavigationController { + var controllers = navigationController.viewControllers + for controller in controllers.reversed() { + if !(controller is StoryContainerScreen) { + controllers.removeLast() + } else { + break + } + } + navigationController.setViewControllers(controllers, animated: true) + + controller?.window?.forEachController({ controller in + if let controller = controller as? StickerPackScreenImpl { + controller.dismiss() + } + }) + } + + let _ = (component.context.engine.messages.enqueueOutgoingMessage( + to: peerId, + replyTo: nil, + storyId: focusedStoryId, + content: .file(fileReference) + ) |> deliverOnMainQueue).start(next: { [weak controller, weak view] messageIds in + if let controller { + Queue.mainQueue().after(0.3) { + controller.present(UndoOverlayController( + presentationData: presentationData, + content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false), + elevatedLayout: false, + animateInAsReplacement: false, + action: { [weak view] action in + if case .undo = action, let messageId = messageIds.first { + view?.navigateToPeer(peer: peer, chat: true, messageId: messageId) + } + return false + } + ), in: .current) + } + } + }) + + self.currentInputMode = .text + if hasFirstResponder(view) { + view.endEditing(true) + } else { + view.state?.updated(transition: .spring(duration: 0.3)) + controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) + } + } + + func performSendContextResultAction(view: StoryItemSetContainerComponent.View, results: ChatContextResultCollection, result: ChatContextResult) { + guard let component = view.component else { + return + } + let focusedItem = component.slice.item + guard let peerId = focusedItem.peerId else { + return + } + let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id) + let peer = component.slice.peer + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let controller = component.controller() as? StoryContainerScreen + + if let navigationController = controller?.navigationController as? NavigationController { + var controllers = navigationController.viewControllers + for controller in controllers.reversed() { + if !(controller is StoryContainerScreen) { + controllers.removeLast() + } else { + break + } + } + navigationController.setViewControllers(controllers, animated: true) + + controller?.window?.forEachController({ controller in + if let controller = controller as? StickerPackScreenImpl { + controller.dismiss() + } + }) + } + + let _ = (component.context.engine.messages.enqueueOutgoingMessage( + to: peerId, + replyTo: nil, + storyId: focusedStoryId, + content: .contextResult(results, result) + ) |> deliverOnMainQueue).start(next: { [weak controller, weak view] messageIds in + if let controller { + Queue.mainQueue().after(0.3) { + controller.present(UndoOverlayController( + presentationData: presentationData, + content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false), + elevatedLayout: false, + animateInAsReplacement: false, + action: { [weak view] action in + if case .undo = action, let messageId = messageIds.first { + view?.navigateToPeer(peer: peer, chat: true, messageId: messageId) + } + return false + } + ), in: .current) + } + } + }) + + self.currentInputMode = .text + if hasFirstResponder(view) { + view.endEditing(true) + } else { + view.state?.updated(transition: .spring(duration: 0.3)) + controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) + } + } + func setMediaRecordingActive( view: StoryItemSetContainerComponent.View, isActive: Bool, @@ -1449,7 +1475,7 @@ final class StoryItemSetContainerSendMessage { guard let self, let view, let controller else { return } - self.presentWebSearch(view: view, editingMessage: false, attachment: true, activateOnDisplay: activateOnDisplay, present: { [weak controller] c, a in + self.presentWebSearch(view: view, activateOnDisplay: activateOnDisplay, present: { [weak controller] c, a in controller?.present(c, in: .current) if let webSearchController = c as? WebSearchController { webSearchController.searchingUpdated = { [weak mediaGroups] searching in @@ -1767,118 +1793,41 @@ final class StoryItemSetContainerSendMessage { sendMessage(nil) } - private func presentWebSearch(view: StoryItemSetContainerComponent.View, editingMessage: Bool, attachment: Bool, activateOnDisplay: Bool = true, present: @escaping (ViewController, Any?) -> Void) { - /*guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { + private func presentWebSearch(view: StoryItemSetContainerComponent.View, activateOnDisplay: Bool = true, present: @escaping (ViewController, Any?) -> Void) { + guard let component = view.component else { return } + let context = component.context + let peer = component.slice.peer + let storyId = component.slice.item.storyItem.id - let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.SearchBots()) - |> deliverOnMainQueue).start(next: { [weak self] configuration in - if let strongSelf = self { - let controller = WebSearchController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: EnginePeer(peer), chatLocation: strongSelf.chatLocation, configuration: configuration, mode: .media(attachment: attachment, completion: { [weak self] results, selectionState, editingState, silentPosting in - self?.attachmentController?.dismiss(animated: true, completion: nil) - legacyEnqueueWebSearchMessages(selectionState, editingState, enqueueChatContextResult: { [weak self] result in - if let strongSelf = self { - strongSelf.enqueueChatContextResult(results, result, hideVia: true) + let theme = component.theme + let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) + + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.SearchBots()) + |> deliverOnMainQueue).start(next: { [weak self, weak view] configuration in + if let self { + let controller = WebSearchController(context: context, updatedPresentationData: updatedPresentationData, peer: peer, chatLocation: .peer(id: peer.id), configuration: configuration, mode: .media(attachment: true, completion: { [weak self] results, selectionState, editingState, silentPosting in + legacyEnqueueWebSearchMessages(selectionState, editingState, enqueueChatContextResult: { [weak self, weak view] result in + if let self, let view { + self.performSendContextResultAction(view: view, results: results, result: result) } - }, enqueueMediaMessages: { [weak self] signals in - if let strongSelf = self, !signals.isEmpty { - if editingMessage { - strongSelf.editMessageMediaWithLegacySignals(signals) - } else { - strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting) - } + }, enqueueMediaMessages: { [weak self, weak view] signals in + if let self, let view, !signals.isEmpty { + self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: StoryId(peerId: peer.id, id: storyId), signals: signals, silentPosting: false) } }) }), activateOnDisplay: activateOnDisplay) - controller.attemptItemSelection = { [weak strongSelf] item in - guard let strongSelf, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { - return false + controller.getCaptionPanelView = { [weak self, weak view] in + if let view { + return self?.getCaptionPanelView(view: view, peer: peer) + } else { + return nil } - - enum ItemType { - case gif - case image - case video - } - - var itemType: ItemType? - switch item { - case let .internalReference(reference): - if reference.type == "gif" { - itemType = .gif - } else if reference.type == "photo" { - itemType = .image - } else if reference.type == "video" { - itemType = .video - } - case let .externalReference(reference): - if reference.type == "gif" { - itemType = .gif - } else if reference.type == "photo" { - itemType = .image - } else if reference.type == "video" { - itemType = .video - } - } - - var bannedSendPhotos: (Int32, Bool)? - var bannedSendVideos: (Int32, Bool)? - var bannedSendGifs: (Int32, Bool)? - - if let channel = peer as? TelegramChannel { - if let value = channel.hasBannedPermission(.banSendPhotos) { - bannedSendPhotos = value - } - if let value = channel.hasBannedPermission(.banSendVideos) { - bannedSendVideos = value - } - if let value = channel.hasBannedPermission(.banSendGifs) { - bannedSendGifs = value - } - } else if let group = peer as? TelegramGroup { - if group.hasBannedPermission(.banSendPhotos) { - bannedSendPhotos = (Int32.max, false) - } - if group.hasBannedPermission(.banSendVideos) { - bannedSendVideos = (Int32.max, false) - } - if group.hasBannedPermission(.banSendGifs) { - bannedSendGifs = (Int32.max, false) - } - } - - if let itemType { - switch itemType { - case .image: - if bannedSendPhotos != nil { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - - return false - } - case .video: - if bannedSendVideos != nil { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - - return false - } - case .gif: - if bannedSendGifs != nil { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - - return false - } - } - } - - return true - } - controller.getCaptionPanelView = { [weak strongSelf] in - return strongSelf?.getCaptionPanelView() } present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } - })*/ + }) } private func getCaptionPanelView(view: StoryItemSetContainerComponent.View, peer: EnginePeer) -> TGCaptionPanelView? {