mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-08 08:31:13 +00:00
Merge commit '7b9836682d9426621cd0a022b6aef935fddeb6d3'
This commit is contained in:
commit
3150e296a3
@ -9,7 +9,7 @@ final class CameraSession {
|
|||||||
private let multiSession: Any?
|
private let multiSession: Any?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
if #available(iOS 13.0, *) {
|
if #available(iOS 13.0, *), AVCaptureMultiCamSession.isMultiCamSupported {
|
||||||
self.multiSession = AVCaptureMultiCamSession()
|
self.multiSession = AVCaptureMultiCamSession()
|
||||||
self.singleSession = nil
|
self.singleSession = nil
|
||||||
} else {
|
} else {
|
||||||
@ -473,6 +473,8 @@ private final class CameraContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func startRecording() -> Signal<Double, NoError> {
|
public func startRecording() -> Signal<Double, NoError> {
|
||||||
|
self.mainDeviceContext.device.setTorchMode(self._flashMode)
|
||||||
|
|
||||||
if let additionalDeviceContext = self.additionalDeviceContext {
|
if let additionalDeviceContext = self.additionalDeviceContext {
|
||||||
return combineLatest(
|
return combineLatest(
|
||||||
self.mainDeviceContext.output.startRecording(isDualCamera: true, position: self.positionValue),
|
self.mainDeviceContext.output.startRecording(isDualCamera: true, position: self.positionValue),
|
||||||
|
@ -40,20 +40,23 @@ final class CameraDevice {
|
|||||||
selectedDevice = device
|
selectedDevice = device
|
||||||
} else if let device = AVCaptureDevice.default(.builtInDualWideCamera, for: .video, position: position) {
|
} else if let device = AVCaptureDevice.default(.builtInDualWideCamera, for: .video, position: position) {
|
||||||
selectedDevice = device
|
selectedDevice = device
|
||||||
} else {
|
} else if let device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera, .builtInTelephotoCamera], mediaType: .video, position: position).devices.first {
|
||||||
selectedDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera, .builtInWideAngleCamera, .builtInTelephotoCamera], mediaType: .video, position: position).devices.first
|
selectedDevice = device
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if #available(iOS 11.1, *), dual, case .front = position {
|
if #available(iOS 11.1, *), dual, case .front = position, let trueDepthDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInTrueDepthCamera], mediaType: .video, position: position).devices.first {
|
||||||
if let trueDepthDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInTrueDepthCamera], mediaType: .video, position: position).devices.first {
|
selectedDevice = trueDepthDevice
|
||||||
selectedDevice = trueDepthDevice
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if selectedDevice == nil {
|
if selectedDevice == nil {
|
||||||
selectedDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera, .builtInWideAngleCamera, .builtInTelephotoCamera], mediaType: .video, position: position).devices.first
|
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.videoDevice = selectedDevice
|
||||||
self.videoDevicePromise.set(.single(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) {
|
func setZoomLevel(_ zoomLevel: CGFloat) {
|
||||||
guard let device = self.videoDevice else {
|
guard let device = self.videoDevice else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.transaction(device) { device in
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,35 @@ import CoreMedia
|
|||||||
import Vision
|
import Vision
|
||||||
import ImageBlur
|
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 {
|
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 {
|
static func lastBackImage() -> UIImage {
|
||||||
let imagePath = NSTemporaryDirectory() + "backCameraImage.jpg"
|
let imagePath = NSTemporaryDirectory() + "backCameraImage.jpg"
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: imagePath)), let image = UIImage(data: data) {
|
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() {
|
public override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
self.updateOrientation()
|
||||||
self.placeholderView.frame = self.bounds.insetBy(dx: -1.0, dy: -1.0)
|
self.placeholderView.frame = self.bounds.insetBy(dx: -1.0, dy: -1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1141,13 +1141,18 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
controlsAreVisible = false
|
controlsAreVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var controlsBottomInset: CGFloat = 0.0
|
||||||
let previewSize: CGSize
|
let previewSize: CGSize
|
||||||
let previewTopInset: CGFloat = environment.statusBarHeight + 5.0
|
var previewTopInset: CGFloat = environment.statusBarHeight + 5.0
|
||||||
if case .regular = environment.metrics.widthClass {
|
if case .regular = environment.metrics.widthClass {
|
||||||
let previewHeight = context.availableSize.height - previewTopInset - 75.0
|
let previewHeight = context.availableSize.height - previewTopInset - 75.0
|
||||||
previewSize = CGSize(width: floorToScreenPixels(previewHeight / 1.77778), height: previewHeight)
|
previewSize = CGSize(width: floorToScreenPixels(previewHeight / 1.77778), height: previewHeight)
|
||||||
} else {
|
} else {
|
||||||
previewSize = CGSize(width: context.availableSize.width, height: floorToScreenPixels(context.availableSize.width * 1.77778))
|
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
|
let previewBottomInset = context.availableSize.height - previewSize.height - previewTopInset
|
||||||
|
|
||||||
@ -1155,6 +1160,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
if component.sourceHint == .storyEditor {
|
if component.sourceHint == .storyEditor {
|
||||||
topInset = previewTopInset + 31.0
|
topInset = previewTopInset + 31.0
|
||||||
}
|
}
|
||||||
|
|
||||||
let bottomInset: CGFloat = environment.inputHeight > 0.0 ? environment.inputHeight : 145.0
|
let bottomInset: CGFloat = environment.inputHeight > 0.0 ? environment.inputHeight : 145.0
|
||||||
|
|
||||||
var leftEdge: CGFloat = environment.safeInsets.left
|
var leftEdge: CGFloat = environment.safeInsets.left
|
||||||
@ -1985,7 +1991,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
if case .regular = environment.metrics.widthClass {
|
if case .regular = environment.metrics.widthClass {
|
||||||
doneButtonPosition.x -= 20.0
|
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
|
context.add(doneButton
|
||||||
.position(doneButtonPosition)
|
.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)
|
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 {
|
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
|
context.add(modeAndSize
|
||||||
.position(modeAndSizePosition)
|
.position(modeAndSizePosition)
|
||||||
@ -2108,7 +2114,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
if case .regular = environment.metrics.widthClass {
|
if case .regular = environment.metrics.widthClass {
|
||||||
backButtonPosition.x += 20.0
|
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
|
context.add(backButton
|
||||||
.position(backButtonPosition)
|
.position(backButtonPosition)
|
||||||
|
@ -6,6 +6,7 @@ import TelegramApi
|
|||||||
public enum EngineOutgoingMessageContent {
|
public enum EngineOutgoingMessageContent {
|
||||||
case text(String, [MessageTextEntity])
|
case text(String, [MessageTextEntity])
|
||||||
case file(FileMediaReference)
|
case file(FileMediaReference)
|
||||||
|
case contextResult(ChatContextResultCollection, ChatContextResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class StoryPreloadInfo {
|
public final class StoryPreloadInfo {
|
||||||
@ -225,30 +226,41 @@ public extension TelegramEngine {
|
|||||||
storyId: StoryId? = nil,
|
storyId: StoryId? = nil,
|
||||||
content: EngineOutgoingMessageContent
|
content: EngineOutgoingMessageContent
|
||||||
) -> Signal<[MessageId?], NoError> {
|
) -> Signal<[MessageId?], NoError> {
|
||||||
var attributes: [MessageAttribute] = []
|
let message: EnqueueMessage?
|
||||||
var text: String = ""
|
if case let .contextResult(results, result) = content {
|
||||||
var mediaReference: AnyMediaReference?
|
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 {
|
||||||
switch content {
|
var attributes: [MessageAttribute] = []
|
||||||
case let .text(textValue, entities):
|
var text: String = ""
|
||||||
if !entities.isEmpty {
|
var mediaReference: AnyMediaReference?
|
||||||
attributes.append(TextEntitiesMessageAttribute(entities: entities))
|
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
|
message = .message(
|
||||||
case let .file(fileReference):
|
text: text,
|
||||||
mediaReference = fileReference.abstract
|
attributes: attributes,
|
||||||
|
inlineStickers: [:],
|
||||||
|
mediaReference: mediaReference,
|
||||||
|
replyToMessageId: replyToMessageId,
|
||||||
|
replyToStoryId: storyId,
|
||||||
|
localGroupingKey: nil,
|
||||||
|
correlationId: nil,
|
||||||
|
bubbleUpEmojiOrStickersets: []
|
||||||
|
)
|
||||||
}
|
}
|
||||||
let message: EnqueueMessage = .message(
|
|
||||||
text: text,
|
guard let message else {
|
||||||
attributes: attributes,
|
return .complete()
|
||||||
inlineStickers: [:],
|
}
|
||||||
mediaReference: mediaReference,
|
|
||||||
replyToMessageId: replyToMessageId,
|
|
||||||
replyToStoryId: storyId,
|
|
||||||
localGroupingKey: nil,
|
|
||||||
correlationId: nil,
|
|
||||||
bubbleUpEmojiOrStickersets: []
|
|
||||||
)
|
|
||||||
return enqueueMessages(
|
return enqueueMessages(
|
||||||
account: self.account,
|
account: self.account,
|
||||||
peerId: peerId,
|
peerId: peerId,
|
||||||
|
@ -101,6 +101,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
let panelWidth: CGFloat
|
let panelWidth: CGFloat
|
||||||
let animateFlipAction: ActionSlot<Void>
|
let animateFlipAction: ActionSlot<Void>
|
||||||
let animateShutter: () -> Void
|
let animateShutter: () -> Void
|
||||||
|
let toggleCameraPositionAction: ActionSlot<Void>
|
||||||
let dismissAllTooltips: () -> Void
|
let dismissAllTooltips: () -> Void
|
||||||
let present: (ViewController) -> Void
|
let present: (ViewController) -> Void
|
||||||
let push: (ViewController) -> Void
|
let push: (ViewController) -> Void
|
||||||
@ -116,6 +117,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
panelWidth: CGFloat,
|
panelWidth: CGFloat,
|
||||||
animateFlipAction: ActionSlot<Void>,
|
animateFlipAction: ActionSlot<Void>,
|
||||||
animateShutter: @escaping () -> Void,
|
animateShutter: @escaping () -> Void,
|
||||||
|
toggleCameraPositionAction: ActionSlot<Void>,
|
||||||
dismissAllTooltips: @escaping () -> Void,
|
dismissAllTooltips: @escaping () -> Void,
|
||||||
present: @escaping (ViewController) -> Void,
|
present: @escaping (ViewController) -> Void,
|
||||||
push: @escaping (ViewController) -> Void,
|
push: @escaping (ViewController) -> Void,
|
||||||
@ -130,6 +132,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
self.panelWidth = panelWidth
|
self.panelWidth = panelWidth
|
||||||
self.animateFlipAction = animateFlipAction
|
self.animateFlipAction = animateFlipAction
|
||||||
self.animateShutter = animateShutter
|
self.animateShutter = animateShutter
|
||||||
|
self.toggleCameraPositionAction = toggleCameraPositionAction
|
||||||
self.dismissAllTooltips = dismissAllTooltips
|
self.dismissAllTooltips = dismissAllTooltips
|
||||||
self.present = present
|
self.present = present
|
||||||
self.push = push
|
self.push = push
|
||||||
@ -183,8 +186,10 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
private let present: (ViewController) -> Void
|
private let present: (ViewController) -> Void
|
||||||
private let completion: ActionSlot<Signal<CameraScreen.Result, NoError>>
|
private let completion: ActionSlot<Signal<CameraScreen.Result, NoError>>
|
||||||
private let updateState: ActionSlot<CameraState>
|
private let updateState: ActionSlot<CameraState>
|
||||||
|
private let toggleCameraPositionAction: ActionSlot<Void>
|
||||||
|
|
||||||
private let animateShutter: () -> Void
|
private let animateShutter: () -> Void
|
||||||
|
private let animateFlipAction: ActionSlot<Void>
|
||||||
private let dismissAllTooltips: () -> Void
|
private let dismissAllTooltips: () -> Void
|
||||||
|
|
||||||
private var cameraStateDisposable: Disposable?
|
private var cameraStateDisposable: Disposable?
|
||||||
@ -214,6 +219,8 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
completion: ActionSlot<Signal<CameraScreen.Result, NoError>>,
|
completion: ActionSlot<Signal<CameraScreen.Result, NoError>>,
|
||||||
updateState: ActionSlot<CameraState>,
|
updateState: ActionSlot<CameraState>,
|
||||||
animateShutter: @escaping () -> Void = {},
|
animateShutter: @escaping () -> Void = {},
|
||||||
|
animateFlipAction: ActionSlot<Void>,
|
||||||
|
toggleCameraPositionAction: ActionSlot<Void>,
|
||||||
dismissAllTooltips: @escaping () -> Void = {}
|
dismissAllTooltips: @escaping () -> Void = {}
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -222,6 +229,8 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
self.completion = completion
|
self.completion = completion
|
||||||
self.updateState = updateState
|
self.updateState = updateState
|
||||||
self.animateShutter = animateShutter
|
self.animateShutter = animateShutter
|
||||||
|
self.animateFlipAction = animateFlipAction
|
||||||
|
self.toggleCameraPositionAction = toggleCameraPositionAction
|
||||||
self.dismissAllTooltips = dismissAllTooltips
|
self.dismissAllTooltips = dismissAllTooltips
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
@ -245,6 +254,12 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.setupVolumeButtonsHandler()
|
self.setupVolumeButtonsHandler()
|
||||||
|
|
||||||
|
self.toggleCameraPositionAction.connect({ [weak self] in
|
||||||
|
if let self {
|
||||||
|
self.togglePosition(self.animateFlipAction)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -472,7 +487,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeState() -> State {
|
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 {
|
static var body: Body {
|
||||||
@ -1026,6 +1041,7 @@ public class CameraScreen: ViewController {
|
|||||||
private weak var controller: CameraScreen?
|
private weak var controller: CameraScreen?
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let updateState: ActionSlot<CameraState>
|
private let updateState: ActionSlot<CameraState>
|
||||||
|
private let toggleCameraPositionAction: ActionSlot<Void>
|
||||||
|
|
||||||
fileprivate let backgroundView: UIView
|
fileprivate let backgroundView: UIView
|
||||||
fileprivate let containerView: UIView
|
fileprivate let containerView: UIView
|
||||||
@ -1070,6 +1086,7 @@ public class CameraScreen: ViewController {
|
|||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.context = controller.context
|
self.context = controller.context
|
||||||
self.updateState = ActionSlot<CameraState>()
|
self.updateState = ActionSlot<CameraState>()
|
||||||
|
self.toggleCameraPositionAction = ActionSlot<Void>()
|
||||||
|
|
||||||
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
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(_:)))
|
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
|
||||||
self.mainPreviewContainerView.addGestureRecognizer(tapGestureRecognizer)
|
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(_:)))
|
let pipPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePipPan(_:)))
|
||||||
self.additionalPreviewContainerView.addGestureRecognizer(pipPanGestureRecognizer)
|
self.additionalPreviewContainerView.addGestureRecognizer(pipPanGestureRecognizer)
|
||||||
|
|
||||||
@ -1354,6 +1375,10 @@ public class CameraScreen: ViewController {
|
|||||||
self.camera.focus(at: point, autoFocus: false)
|
self.camera.focus(at: point, autoFocus: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func handleDoubleTap(_ gestureRecognizer: UITapGestureRecognizer) {
|
||||||
|
self.toggleCameraPositionAction.invoke(Void())
|
||||||
|
}
|
||||||
|
|
||||||
private var pipTranslation: CGPoint?
|
private var pipTranslation: CGPoint?
|
||||||
@objc private func handlePipPan(_ gestureRecognizer: UIPanGestureRecognizer) {
|
@objc private func handlePipPan(_ gestureRecognizer: UIPanGestureRecognizer) {
|
||||||
guard let layout = self.validLayout else {
|
guard let layout = self.validLayout else {
|
||||||
@ -1829,6 +1854,7 @@ public class CameraScreen: ViewController {
|
|||||||
animateShutter: { [weak self] in
|
animateShutter: { [weak self] in
|
||||||
self?.mainPreviewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
self?.mainPreviewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
},
|
},
|
||||||
|
toggleCameraPositionAction: self.toggleCameraPositionAction,
|
||||||
dismissAllTooltips: { [weak self] in
|
dismissAllTooltips: { [weak self] in
|
||||||
self?.dismissAllTooltips()
|
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.transitionDimView, frame: CGRect(origin: .zero, size: layout.size))
|
||||||
|
|
||||||
transition.setFrame(view: self.previewContainerView, frame: previewFrame)
|
let previewContainerFrame: CGRect
|
||||||
transition.setFrame(view: self.mainPreviewContainerView, frame: CGRect(origin: .zero, size: previewFrame.size))
|
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
|
let dualCamUpdated = self.appliedDualCam != self.isDualCamEnabled
|
||||||
self.appliedDualCam = self.isDualCamEnabled
|
self.appliedDualCam = self.isDualCamEnabled
|
||||||
|
@ -749,10 +749,10 @@ final class CaptureControlsComponent: Component {
|
|||||||
blobOffset -= self.frame.width / 2.0
|
blobOffset -= self.frame.width / 2.0
|
||||||
var isBanding = false
|
var isBanding = false
|
||||||
if location.y < -10.0 {
|
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)
|
component.zoomUpdated(fraction)
|
||||||
} else {
|
} else {
|
||||||
component.zoomUpdated(1.0)
|
component.zoomUpdated(0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if location.x < self.frame.width / 2.0 - 30.0 {
|
if location.x < self.frame.width / 2.0 - 30.0 {
|
||||||
|
@ -77,7 +77,7 @@ func verticesDataForRotation(_ rotation: TextureRotation, rect: CGRect = CGRect(
|
|||||||
|
|
||||||
func textureDimensionsForRotation(texture: MTLTexture, rotation: TextureRotation) -> (width: Int, height: Int) {
|
func textureDimensionsForRotation(texture: MTLTexture, rotation: TextureRotation) -> (width: Int, height: Int) {
|
||||||
switch rotation {
|
switch rotation {
|
||||||
case .rotate90Degrees, .rotate270Degrees:
|
case .rotate90Degrees, .rotate90DegreesMirrored, .rotate270Degrees:
|
||||||
return (texture.height, texture.width)
|
return (texture.height, texture.width)
|
||||||
default:
|
default:
|
||||||
return (texture.width, texture.height)
|
return (texture.width, texture.height)
|
||||||
|
@ -253,7 +253,7 @@ final class VideoInputPass: DefaultRenderPass {
|
|||||||
|
|
||||||
func textureDimensionsForRotation(width: Int, height: Int, rotation: TextureRotation) -> (width: Int, height: Int) {
|
func textureDimensionsForRotation(width: Int, height: Int, rotation: TextureRotation) -> (width: Int, height: Int) {
|
||||||
switch rotation {
|
switch rotation {
|
||||||
case .rotate90Degrees, .rotate270Degrees:
|
case .rotate90Degrees, .rotate270Degrees, .rotate90DegreesMirrored:
|
||||||
return (height, width)
|
return (height, width)
|
||||||
default:
|
default:
|
||||||
return (width, height)
|
return (width, height)
|
||||||
|
@ -1897,12 +1897,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
if let self, let controller = self.controller, values.gradientColors != nil, controller.previousSavedValues != values {
|
if let self, let controller = self.controller, values.gradientColors != nil, controller.previousSavedValues != values {
|
||||||
if !isSavingAvailable && controller.previousSavedValues == nil {
|
if !isSavingAvailable && controller.previousSavedValues == nil {
|
||||||
controller.previousSavedValues = values
|
controller.previousSavedValues = values
|
||||||
|
controller.isSavingAvailable = false
|
||||||
} else {
|
} else {
|
||||||
self.hasAnyChanges = true
|
self.hasAnyChanges = true
|
||||||
|
|
||||||
controller.isSavingAvailable = 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) {
|
@objc func handleDismissPan(_ gestureRecognizer: UIPanGestureRecognizer) {
|
||||||
guard let controller = self.controller, let layout = self.validLayout, (layout.inputHeight ?? 0.0).isZero else {
|
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()
|
self.isDismissBySwipeSuppressed = controller.isEligibleForDraft()
|
||||||
controller.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut))
|
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
|
self.isEnhancing = true
|
||||||
controller.requestLayout(transition: .animated(duration: 0.3, curve: .easeInOut))
|
controller.requestLayout(transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||||
}
|
}
|
||||||
@ -2197,14 +2197,27 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
} else if self.isEnhancing {
|
} else if self.isEnhancing {
|
||||||
if let mediaEditor = self.mediaEditor {
|
if let mediaEditor = self.mediaEditor {
|
||||||
let value = mediaEditor.getToolValue(.enhance) as? Float ?? 0.0
|
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 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)
|
mediaEditor.setToolValue(.enhance, value: updatedValue)
|
||||||
}
|
}
|
||||||
self.requestUpdate()
|
self.requestUpdate()
|
||||||
gestureRecognizer.setTranslation(.zero, in: self.view)
|
gestureRecognizer.setTranslation(.zero, in: self.view)
|
||||||
}
|
}
|
||||||
case .ended, .cancelled:
|
case .ended, .cancelled:
|
||||||
|
self.enhanceInitialTranslation = nil
|
||||||
if self.isDismissing {
|
if self.isDismissing {
|
||||||
if abs(translation.y) > self.view.frame.height * 0.33 || abs(velocity.y) > 1000.0, !controller.isEligibleForDraft() {
|
if abs(translation.y) > self.view.frame.height * 0.33 || abs(velocity.y) > 1000.0, !controller.isEligibleForDraft() {
|
||||||
controller.requestDismiss(saveDraft: false, animated: true)
|
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 {
|
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 {
|
} else {
|
||||||
if let image = mediaEditor.resultImage {
|
if let image = mediaEditor.resultImage {
|
||||||
@ -4329,7 +4342,11 @@ private final class ToolValueComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let previousValue, component.value != previousValue, self.alpha > 0.0 {
|
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
|
return availableSize
|
||||||
|
@ -214,6 +214,7 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
|
|
||||||
public final class View: UIView {
|
public final class View: UIView {
|
||||||
private let buttonsContainerView = UIView()
|
private let buttonsContainerView = UIView()
|
||||||
|
private let buttonsBackgroundView = UIView()
|
||||||
private let cancelButton = ComponentView<Empty>()
|
private let cancelButton = ComponentView<Empty>()
|
||||||
private let adjustmentsButton = ComponentView<Empty>()
|
private let adjustmentsButton = ComponentView<Empty>()
|
||||||
private let tintButton = ComponentView<Empty>()
|
private let tintButton = ComponentView<Empty>()
|
||||||
@ -244,10 +245,11 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
|
|
||||||
self.backgroundColor = .clear
|
self.backgroundColor = .clear
|
||||||
|
|
||||||
self.addSubview(self.buttonsContainerView)
|
|
||||||
self.addSubview(self.previewContainerView)
|
self.addSubview(self.previewContainerView)
|
||||||
|
self.addSubview(self.buttonsContainerView)
|
||||||
self.previewContainerView.addSubview(self.optionsContainerView)
|
self.previewContainerView.addSubview(self.optionsContainerView)
|
||||||
self.optionsContainerView.addSubview(self.optionsBackgroundView)
|
self.optionsContainerView.addSubview(self.optionsBackgroundView)
|
||||||
|
self.buttonsContainerView.addSubview(self.buttonsBackgroundView)
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
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)
|
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)
|
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)
|
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.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()
|
self.state?.updated()
|
||||||
@ -338,8 +344,9 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
|
|
||||||
let buttonSideInset: CGFloat
|
let buttonSideInset: CGFloat
|
||||||
let buttonBottomInset: CGFloat = 8.0
|
let buttonBottomInset: CGFloat = 8.0
|
||||||
|
var controlsBottomInset: CGFloat = 0.0
|
||||||
let previewSize: CGSize
|
let previewSize: CGSize
|
||||||
let topInset: CGFloat = environment.statusBarHeight + 5.0
|
var topInset: CGFloat = environment.statusBarHeight + 5.0
|
||||||
if isTablet {
|
if isTablet {
|
||||||
let previewHeight = availableSize.height - topInset - 75.0
|
let previewHeight = availableSize.height - topInset - 75.0
|
||||||
previewSize = CGSize(width: floorToScreenPixels(previewHeight / 1.77778), height: previewHeight)
|
previewSize = CGSize(width: floorToScreenPixels(previewHeight / 1.77778), height: previewHeight)
|
||||||
@ -347,10 +354,17 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
previewSize = CGSize(width: availableSize.width, height: floorToScreenPixels(availableSize.width * 1.77778))
|
previewSize = CGSize(width: availableSize.width, height: floorToScreenPixels(availableSize.width * 1.77778))
|
||||||
buttonSideInset = 10.0
|
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))
|
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), size: CGSize(width: availableSize.width, height: environment.safeInsets.bottom))
|
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(
|
let cancelButtonSize = self.cancelButton.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
@ -873,6 +887,7 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
let optionsFrame = CGRect(origin: .zero, size: optionsSize)
|
let optionsFrame = CGRect(origin: .zero, size: optionsSize)
|
||||||
if let optionsView = self.toolOptions.view {
|
if let optionsView = self.toolOptions.view {
|
||||||
if optionsView.superview == nil {
|
if optionsView.superview == nil {
|
||||||
|
optionsView.clipsToBounds = true
|
||||||
self.optionsContainerView.addSubview(optionsView)
|
self.optionsContainerView.addSubview(optionsView)
|
||||||
}
|
}
|
||||||
optionsTransition.setFrame(view: optionsView, frame: optionsFrame)
|
optionsTransition.setFrame(view: optionsView, frame: optionsFrame)
|
||||||
@ -885,9 +900,11 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
previewContainerFrame.size.height -= controlsBottomInset
|
||||||
|
|
||||||
let optionsBackgroundFrame = CGRect(
|
let optionsBackgroundFrame = CGRect(
|
||||||
origin: CGPoint(x: 0.0, y: previewContainerFrame.height - optionsSize.height),
|
origin: CGPoint(x: 0.0, y: previewContainerFrame.height - optionsSize.height + controlsBottomInset),
|
||||||
size: optionsSize
|
size: CGSize(width: optionsSize.width, height: optionsSize.height - controlsBottomInset)
|
||||||
)
|
)
|
||||||
transition.setFrame(view: self.optionsContainerView, frame: optionsBackgroundFrame)
|
transition.setFrame(view: self.optionsContainerView, frame: optionsBackgroundFrame)
|
||||||
transition.setFrame(view: self.optionsBackgroundView, frame: CGRect(origin: .zero, size: optionsBackgroundFrame.size))
|
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.previewContainerView, frame: previewContainerFrame)
|
||||||
transition.setFrame(view: self.buttonsContainerView, frame: buttonsContainerFrame)
|
transition.setFrame(view: self.buttonsContainerView, frame: buttonsContainerFrame)
|
||||||
|
transition.setFrame(view: self.buttonsBackgroundView, frame: CGRect(origin: .zero, size: buttonsContainerFrame.size))
|
||||||
|
|
||||||
return availableSize
|
return availableSize
|
||||||
}
|
}
|
||||||
|
@ -109,23 +109,26 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
|
|
||||||
self.inputMediaInteraction = ChatEntityKeyboardInputNode.Interaction(
|
self.inputMediaInteraction = ChatEntityKeyboardInputNode.Interaction(
|
||||||
sendSticker: { [weak self] fileReference, _, _, _, _, _, _, _, _ in
|
sendSticker: { [weak self] fileReference, _, _, _, _, _, _, _, _ in
|
||||||
if let view = self?.view {
|
if let self, let view = self.view {
|
||||||
self?.performSendStickerAction(view: view, fileReference: fileReference)
|
self.performSendStickerAction(view: view, fileReference: fileReference)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
sendEmoji: { [weak self] text, attribute, bool1 in
|
sendEmoji: { [weak self] text, attribute, _ in
|
||||||
if let self {
|
if let self {
|
||||||
let _ = self
|
let _ = self
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sendGif: { [weak self] fileReference, _, _, _, _ in
|
sendGif: { [weak self] fileReference, _, _, _, _ in
|
||||||
if let view = self?.view {
|
if let self, let view = self.view {
|
||||||
self?.performSendGifAction(view: view, fileReference: fileReference)
|
self.performSendStickerAction(view: view, fileReference: fileReference)
|
||||||
}
|
}
|
||||||
return false
|
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
|
return false
|
||||||
},
|
},
|
||||||
updateChoosingSticker: { _ in },
|
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(
|
func performSendMessageAction(
|
||||||
view: StoryItemSetContainerComponent.View
|
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(
|
func setMediaRecordingActive(
|
||||||
view: StoryItemSetContainerComponent.View,
|
view: StoryItemSetContainerComponent.View,
|
||||||
isActive: Bool,
|
isActive: Bool,
|
||||||
@ -1449,7 +1475,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
guard let self, let view, let controller else {
|
guard let self, let view, let controller else {
|
||||||
return
|
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)
|
controller?.present(c, in: .current)
|
||||||
if let webSearchController = c as? WebSearchController {
|
if let webSearchController = c as? WebSearchController {
|
||||||
webSearchController.searchingUpdated = { [weak mediaGroups] searching in
|
webSearchController.searchingUpdated = { [weak mediaGroups] searching in
|
||||||
@ -1767,118 +1793,41 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
sendMessage(nil)
|
sendMessage(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentWebSearch(view: StoryItemSetContainerComponent.View, editingMessage: Bool, attachment: Bool, activateOnDisplay: Bool = true, present: @escaping (ViewController, Any?) -> Void) {
|
private func presentWebSearch(view: StoryItemSetContainerComponent.View, activateOnDisplay: Bool = true, present: @escaping (ViewController, Any?) -> Void) {
|
||||||
/*guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
guard let component = view.component else {
|
||||||
return
|
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())
|
let theme = component.theme
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] configuration in
|
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) })
|
||||||
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
|
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.SearchBots())
|
||||||
self?.attachmentController?.dismiss(animated: true, completion: nil)
|
|> deliverOnMainQueue).start(next: { [weak self, weak view] configuration in
|
||||||
legacyEnqueueWebSearchMessages(selectionState, editingState, enqueueChatContextResult: { [weak self] result in
|
if let self {
|
||||||
if let strongSelf = 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
|
||||||
strongSelf.enqueueChatContextResult(results, result, hideVia: true)
|
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
|
}, enqueueMediaMessages: { [weak self, weak view] signals in
|
||||||
if let strongSelf = self, !signals.isEmpty {
|
if let self, let view, !signals.isEmpty {
|
||||||
if editingMessage {
|
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: StoryId(peerId: peer.id, id: storyId), signals: signals, silentPosting: false)
|
||||||
strongSelf.editMessageMediaWithLegacySignals(signals)
|
|
||||||
} else {
|
|
||||||
strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}), activateOnDisplay: activateOnDisplay)
|
}), activateOnDisplay: activateOnDisplay)
|
||||||
controller.attemptItemSelection = { [weak strongSelf] item in
|
controller.getCaptionPanelView = { [weak self, weak view] in
|
||||||
guard let strongSelf, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
|
if let view {
|
||||||
return false
|
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))
|
present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
}
|
}
|
||||||
})*/
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getCaptionPanelView(view: StoryItemSetContainerComponent.View, peer: EnginePeer) -> TGCaptionPanelView? {
|
private func getCaptionPanelView(view: StoryItemSetContainerComponent.View, peer: EnginePeer) -> TGCaptionPanelView? {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user