Various fixes

This commit is contained in:
Ilya Laktyushin 2023-07-11 18:35:22 +02:00
parent 5ad9671d70
commit 2c05606341
21 changed files with 465 additions and 326 deletions

View File

@ -1112,7 +1112,6 @@ public struct StoriesConfiguration {
default: default:
posting = .disabled posting = .disabled
} }
posting = .enabled
return StoriesConfiguration(posting: posting) return StoriesConfiguration(posting: posting)
} else { } else {
return .defaultValue return .defaultValue

View File

@ -108,7 +108,7 @@ private final class CameraContext {
private let session: CameraSession private let session: CameraSession
private var mainDeviceContext: CameraDeviceContext private var mainDeviceContext: CameraDeviceContext?
private var additionalDeviceContext: CameraDeviceContext? private var additionalDeviceContext: CameraDeviceContext?
private let cameraImageContext = CIContext() private let cameraImageContext = CIContext()
@ -132,11 +132,11 @@ private final class CameraContext {
private var lastSnapshotTimestamp: Double = CACurrentMediaTime() private var lastSnapshotTimestamp: Double = CACurrentMediaTime()
private var lastAdditionalSnapshotTimestamp: Double = CACurrentMediaTime() private var lastAdditionalSnapshotTimestamp: Double = CACurrentMediaTime()
private func savePreviewSnapshot(pixelBuffer: CVPixelBuffer, mirror: Bool) { private func savePreviewSnapshot(pixelBuffer: CVPixelBuffer, front: Bool) {
Queue.concurrentDefaultQueue().async { Queue.concurrentDefaultQueue().async {
var ciImage = CIImage(cvImageBuffer: pixelBuffer) var ciImage = CIImage(cvImageBuffer: pixelBuffer)
let size = ciImage.extent.size let size = ciImage.extent.size
if mirror { if front {
var transform = CGAffineTransformMakeScale(1.0, -1.0) var transform = CGAffineTransformMakeScale(1.0, -1.0)
transform = CGAffineTransformTranslate(transform, 0.0, -size.height) transform = CGAffineTransformTranslate(transform, 0.0, -size.height)
ciImage = ciImage.transformed(by: transform) ciImage = ciImage.transformed(by: transform)
@ -144,7 +144,7 @@ private final class CameraContext {
ciImage = ciImage.clampedToExtent().applyingGaussianBlur(sigma: 40.0).cropped(to: CGRect(origin: .zero, size: size)) ciImage = ciImage.clampedToExtent().applyingGaussianBlur(sigma: 40.0).cropped(to: CGRect(origin: .zero, size: size))
if let cgImage = self.cameraImageContext.createCGImage(ciImage, from: ciImage.extent) { if let cgImage = self.cameraImageContext.createCGImage(ciImage, from: ciImage.extent) {
let uiImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: .right) let uiImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: .right)
if mirror { if front {
CameraSimplePreviewView.saveLastFrontImage(uiImage) CameraSimplePreviewView.saveLastFrontImage(uiImage)
} else { } else {
CameraSimplePreviewView.saveLastBackImage(uiImage) CameraSimplePreviewView.saveLastBackImage(uiImage)
@ -163,41 +163,8 @@ private final class CameraContext {
self.positionValue = configuration.position self.positionValue = configuration.position
self._positionPromise = ValuePromise<Camera.Position>(configuration.position) self._positionPromise = ValuePromise<Camera.Position>(configuration.position)
self.mainDeviceContext = CameraDeviceContext(session: session, exclusive: true, additional: false) self.setDualCameraEnabled(configuration.isDualEnabled, change: false)
self.configure {
self.mainDeviceContext.configure(position: configuration.position, previewView: self.simplePreviewView, audio: configuration.audio, photo: configuration.photo, metadata: configuration.metadata)
}
self.mainDeviceContext.output.processSampleBuffer = { [weak self] sampleBuffer, pixelBuffer, connection in
guard let self else {
return
}
self.previewNode?.enqueue(sampleBuffer)
let timestamp = CACurrentMediaTime()
if timestamp > self.lastSnapshotTimestamp + 2.5 {
var mirror = false
if #available(iOS 13.0, *) {
mirror = connection.inputPorts.first?.sourceDevicePosition == .front
}
self.savePreviewSnapshot(pixelBuffer: pixelBuffer, mirror: mirror)
self.lastSnapshotTimestamp = timestamp
}
}
self.mainDeviceContext.output.processFaceLandmarks = { [weak self] observations in
guard let self else {
return
}
if let previewView = self.previewView {
previewView.drawFaceObservations(observations)
}
}
self.mainDeviceContext.output.processCodes = { [weak self] codes in
self?.detectedCodesPipe.putNext(codes)
}
NotificationCenter.default.addObserver( NotificationCenter.default.addObserver(
self, self,
selector: #selector(self.sessionRuntimeError), selector: #selector(self.sessionRuntimeError),
@ -217,10 +184,10 @@ private final class CameraContext {
func stopCapture(invalidate: Bool = false) { func stopCapture(invalidate: Bool = false) {
if invalidate { if invalidate {
self.mainDeviceContext.device.resetZoom() self.mainDeviceContext?.device.resetZoom()
self.configure { self.configure {
self.mainDeviceContext.invalidate() self.mainDeviceContext?.invalidate()
} }
} }
@ -237,11 +204,11 @@ private final class CameraContext {
focusMode = .autoFocus focusMode = .autoFocus
exposureMode = .autoExpose exposureMode = .autoExpose
} }
self.mainDeviceContext.device.setFocusPoint(point, focusMode: focusMode, exposureMode: exposureMode, monitorSubjectAreaChange: true) self.mainDeviceContext?.device.setFocusPoint(point, focusMode: focusMode, exposureMode: exposureMode, monitorSubjectAreaChange: true)
} }
func setFps(_ fps: Float64) { func setFps(_ fps: Float64) {
self.mainDeviceContext.device.fps = fps self.mainDeviceContext?.device.fps = fps
} }
private var modeChange: Camera.ModeChange = .none { private var modeChange: Camera.ModeChange = .none {
@ -259,7 +226,10 @@ private final class CameraContext {
private var positionValue: Camera.Position = .back private var positionValue: Camera.Position = .back
func togglePosition() { func togglePosition() {
if self.isDualCameraEnabled { guard let mainDeviceContext = self.mainDeviceContext else {
return
}
if self.isDualCameraEnabled == true {
let targetPosition: Camera.Position let targetPosition: Camera.Position
if case .back = self.positionValue { if case .back = self.positionValue {
targetPosition = .front targetPosition = .front
@ -269,13 +239,13 @@ private final class CameraContext {
self.positionValue = targetPosition self.positionValue = targetPosition
self._positionPromise.set(targetPosition) self._positionPromise.set(targetPosition)
self.mainDeviceContext.output.markPositionChange(position: targetPosition) mainDeviceContext.output.markPositionChange(position: targetPosition)
} else { } else {
self.configure { self.configure {
self.mainDeviceContext.invalidate() self.mainDeviceContext?.invalidate()
let targetPosition: Camera.Position let targetPosition: Camera.Position
if case .back = self.mainDeviceContext.device.position { if case .back = mainDeviceContext.device.position {
targetPosition = .front targetPosition = .front
} else { } else {
targetPosition = .back targetPosition = .back
@ -284,7 +254,7 @@ private final class CameraContext {
self._positionPromise.set(targetPosition) self._positionPromise.set(targetPosition)
self.modeChange = .position self.modeChange = .position
self.mainDeviceContext.configure(position: targetPosition, previewView: self.simplePreviewView, audio: self.initialConfiguration.audio, photo: self.initialConfiguration.photo, metadata: self.initialConfiguration.metadata) mainDeviceContext.configure(position: targetPosition, previewView: self.simplePreviewView, audio: self.initialConfiguration.audio, photo: self.initialConfiguration.photo, metadata: self.initialConfiguration.metadata)
self.queue.after(0.5) { self.queue.after(0.5) {
self.modeChange = .none self.modeChange = .none
@ -295,13 +265,13 @@ private final class CameraContext {
public func setPosition(_ position: Camera.Position) { public func setPosition(_ position: Camera.Position) {
self.configure { self.configure {
self.mainDeviceContext.invalidate() self.mainDeviceContext?.invalidate()
self._positionPromise.set(position) self._positionPromise.set(position)
self.positionValue = position self.positionValue = position
self.modeChange = .position self.modeChange = .position
self.mainDeviceContext.configure(position: position, previewView: self.simplePreviewView, audio: self.initialConfiguration.audio, photo: self.initialConfiguration.photo, metadata: self.initialConfiguration.metadata) self.mainDeviceContext?.configure(position: position, previewView: self.simplePreviewView, audio: self.initialConfiguration.audio, photo: self.initialConfiguration.photo, metadata: self.initialConfiguration.metadata)
self.queue.after(0.5) { self.queue.after(0.5) {
self.modeChange = .none self.modeChange = .none
@ -309,103 +279,111 @@ private final class CameraContext {
} }
} }
private var isDualCameraEnabled = false private var isDualCameraEnabled: Bool?
public func setDualCameraEnabled(_ enabled: Bool) { public func setDualCameraEnabled(_ enabled: Bool, change: Bool = true) {
guard enabled != self.isDualCameraEnabled else { guard enabled != self.isDualCameraEnabled else {
return return
} }
self.isDualCameraEnabled = enabled self.isDualCameraEnabled = enabled
self.modeChange = .dualCamera if change {
self.modeChange = .dualCamera
}
if enabled { if enabled {
self.configure { self.configure {
self.mainDeviceContext.invalidate() self.mainDeviceContext?.invalidate()
self.mainDeviceContext = CameraDeviceContext(session: self.session, exclusive: false, additional: false) self.mainDeviceContext = CameraDeviceContext(session: self.session, exclusive: false, additional: false)
self.mainDeviceContext.configure(position: .back, previewView: self.simplePreviewView, audio: self.initialConfiguration.audio, photo: self.initialConfiguration.photo, metadata: self.initialConfiguration.metadata) self.mainDeviceContext?.configure(position: .back, previewView: self.simplePreviewView, audio: self.initialConfiguration.audio, photo: self.initialConfiguration.photo, metadata: self.initialConfiguration.metadata)
self.additionalDeviceContext = CameraDeviceContext(session: self.session, exclusive: false, additional: true) self.additionalDeviceContext = CameraDeviceContext(session: self.session, exclusive: false, additional: true)
self.additionalDeviceContext?.configure(position: .front, previewView: self.secondaryPreviewView, audio: false, photo: true, metadata: false) self.additionalDeviceContext?.configure(position: .front, previewView: self.secondaryPreviewView, audio: false, photo: true, metadata: false)
} }
self.mainDeviceContext.output.processSampleBuffer = { [weak self] sampleBuffer, pixelBuffer, connection in self.mainDeviceContext?.output.processSampleBuffer = { [weak self] sampleBuffer, pixelBuffer, connection in
guard let self else { guard let self, let mainDeviceContext = self.mainDeviceContext else {
return return
} }
self.previewNode?.enqueue(sampleBuffer) self.previewNode?.enqueue(sampleBuffer)
let timestamp = CACurrentMediaTime() let timestamp = CACurrentMediaTime()
if timestamp > self.lastSnapshotTimestamp + 2.5 { if timestamp > self.lastSnapshotTimestamp + 2.5, !mainDeviceContext.output.isRecording {
var mirror = false var front = false
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
mirror = connection.inputPorts.first?.sourceDevicePosition == .front front = connection.inputPorts.first?.sourceDevicePosition == .front
} }
self.savePreviewSnapshot(pixelBuffer: pixelBuffer, mirror: mirror) self.savePreviewSnapshot(pixelBuffer: pixelBuffer, front: front)
self.lastSnapshotTimestamp = timestamp self.lastSnapshotTimestamp = timestamp
} }
} }
self.additionalDeviceContext?.output.processSampleBuffer = { [weak self] sampleBuffer, pixelBuffer, connection in self.additionalDeviceContext?.output.processSampleBuffer = { [weak self] sampleBuffer, pixelBuffer, connection in
guard let self else { guard let self, let additionalDeviceContext = self.additionalDeviceContext else {
return return
} }
let timestamp = CACurrentMediaTime() let timestamp = CACurrentMediaTime()
if timestamp > self.lastAdditionalSnapshotTimestamp + 2.5 { if timestamp > self.lastAdditionalSnapshotTimestamp + 2.5, !additionalDeviceContext.output.isRecording {
var mirror = false var front = false
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
mirror = connection.inputPorts.first?.sourceDevicePosition == .front front = connection.inputPorts.first?.sourceDevicePosition == .front
} }
self.savePreviewSnapshot(pixelBuffer: pixelBuffer, mirror: mirror) self.savePreviewSnapshot(pixelBuffer: pixelBuffer, front: front)
self.lastAdditionalSnapshotTimestamp = timestamp self.lastAdditionalSnapshotTimestamp = timestamp
} }
} }
} else { } else {
self.configure { self.configure {
self.mainDeviceContext.invalidate() self.mainDeviceContext?.invalidate()
self.additionalDeviceContext?.invalidate() self.additionalDeviceContext?.invalidate()
self.additionalDeviceContext = nil self.additionalDeviceContext = nil
self.mainDeviceContext = CameraDeviceContext(session: self.session, exclusive: true, additional: false) self.mainDeviceContext = CameraDeviceContext(session: self.session, exclusive: true, additional: false)
self.mainDeviceContext.configure(position: self.positionValue, previewView: self.simplePreviewView, audio: self.initialConfiguration.audio, photo: self.initialConfiguration.photo, metadata: self.initialConfiguration.metadata) self.mainDeviceContext?.configure(position: self.positionValue, previewView: self.simplePreviewView, audio: self.initialConfiguration.audio, photo: self.initialConfiguration.photo, metadata: self.initialConfiguration.metadata)
} }
self.mainDeviceContext.output.processSampleBuffer = { [weak self] sampleBuffer, pixelBuffer, connection in self.mainDeviceContext?.output.processSampleBuffer = { [weak self] sampleBuffer, pixelBuffer, connection in
guard let self else { guard let self, let mainDeviceContext = self.mainDeviceContext else {
return return
} }
self.previewNode?.enqueue(sampleBuffer) self.previewNode?.enqueue(sampleBuffer)
let timestamp = CACurrentMediaTime() let timestamp = CACurrentMediaTime()
if timestamp > self.lastSnapshotTimestamp + 2.5, !self.mainDeviceContext.output.isRecording { if timestamp > self.lastSnapshotTimestamp + 2.5, !mainDeviceContext.output.isRecording {
var mirror = false var front = false
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
mirror = connection.inputPorts.first?.sourceDevicePosition == .front front = connection.inputPorts.first?.sourceDevicePosition == .front
} }
self.savePreviewSnapshot(pixelBuffer: pixelBuffer, mirror: mirror) self.savePreviewSnapshot(pixelBuffer: pixelBuffer, front: front)
self.lastSnapshotTimestamp = timestamp self.lastSnapshotTimestamp = timestamp
} }
} }
self.mainDeviceContext?.output.processCodes = { [weak self] codes in
self?.detectedCodesPipe.putNext(codes)
}
} }
if #available(iOS 13.0, *), let previewView = self.simplePreviewView { if change {
if enabled, let secondaryPreviewView = self.secondaryPreviewView { if #available(iOS 13.0, *), let previewView = self.simplePreviewView {
let _ = (combineLatest(previewView.isPreviewing, secondaryPreviewView.isPreviewing) if enabled, let secondaryPreviewView = self.secondaryPreviewView {
|> map { first, second in let _ = (combineLatest(previewView.isPreviewing, secondaryPreviewView.isPreviewing)
return first && second |> map { first, second in
return first && second
}
|> filter { $0 }
|> take(1)
|> delay(0.1, queue: self.queue)
|> deliverOn(self.queue)).start(next: { [weak self] _ in
self?.modeChange = .none
})
} else {
let _ = (previewView.isPreviewing
|> filter { $0 }
|> take(1)
|> deliverOn(self.queue)).start(next: { [weak self] _ in
self?.modeChange = .none
})
} }
|> filter { $0 }
|> take(1)
|> delay(0.1, queue: self.queue)
|> deliverOn(self.queue)).start(next: { [weak self] _ in
self?.modeChange = .none
})
} else { } else {
let _ = (previewView.isPreviewing self.queue.after(0.4) {
|> filter { $0 } self.modeChange = .none
|> take(1) }
|> deliverOn(self.queue)).start(next: { [weak self] _ in
self?.modeChange = .none
})
}
} else {
self.queue.after(0.4) {
self.modeChange = .none
} }
} }
} }
@ -417,15 +395,15 @@ private final class CameraContext {
} }
var hasTorch: Signal<Bool, NoError> { var hasTorch: Signal<Bool, NoError> {
return self.mainDeviceContext.device.isTorchAvailable return self.mainDeviceContext?.device.isTorchAvailable ?? .never()
} }
func setTorchActive(_ active: Bool) { func setTorchActive(_ active: Bool) {
self.mainDeviceContext.device.setTorchActive(active) self.mainDeviceContext?.device.setTorchActive(active)
} }
var isFlashActive: Signal<Bool, NoError> { var isFlashActive: Signal<Bool, NoError> {
return self.mainDeviceContext.output.isFlashActive return self.mainDeviceContext?.output.isFlashActive ?? .never()
} }
private var _flashMode: Camera.FlashMode = .off { private var _flashMode: Camera.FlashMode = .off {
@ -443,19 +421,22 @@ private final class CameraContext {
} }
func setZoomLevel(_ zoomLevel: CGFloat) { func setZoomLevel(_ zoomLevel: CGFloat) {
self.mainDeviceContext.device.setZoomLevel(zoomLevel) self.mainDeviceContext?.device.setZoomLevel(zoomLevel)
} }
func setZoomDelta(_ zoomDelta: CGFloat) { func setZoomDelta(_ zoomDelta: CGFloat) {
self.mainDeviceContext.device.setZoomDelta(zoomDelta) self.mainDeviceContext?.device.setZoomDelta(zoomDelta)
} }
func takePhoto() -> Signal<PhotoCaptureResult, NoError> { func takePhoto() -> Signal<PhotoCaptureResult, NoError> {
guard let mainDeviceContext = self.mainDeviceContext else {
return .complete()
}
let orientation = self.simplePreviewView?.videoPreviewLayer.connection?.videoOrientation ?? .portrait let orientation = self.simplePreviewView?.videoPreviewLayer.connection?.videoOrientation ?? .portrait
if let additionalDeviceContext = self.additionalDeviceContext { if let additionalDeviceContext = self.additionalDeviceContext {
let dualPosition = self.positionValue let dualPosition = self.positionValue
return combineLatest( return combineLatest(
self.mainDeviceContext.output.takePhoto(orientation: orientation, flashMode: self._flashMode), mainDeviceContext.output.takePhoto(orientation: orientation, flashMode: self._flashMode),
additionalDeviceContext.output.takePhoto(orientation: orientation, flashMode: self._flashMode) additionalDeviceContext.output.takePhoto(orientation: orientation, flashMode: self._flashMode)
) |> map { main, additional in ) |> map { main, additional in
if case let .finished(mainImage, _, _) = main, case let .finished(additionalImage, _, _) = additional { if case let .finished(mainImage, _, _) = main, case let .finished(additionalImage, _, _) = additional {
@ -469,29 +450,35 @@ private final class CameraContext {
} }
} |> distinctUntilChanged } |> distinctUntilChanged
} else { } else {
return self.mainDeviceContext.output.takePhoto(orientation: orientation, flashMode: self._flashMode) return mainDeviceContext.output.takePhoto(orientation: orientation, flashMode: self._flashMode)
} }
} }
public func startRecording() -> Signal<Double, NoError> { public func startRecording() -> Signal<Double, NoError> {
self.mainDeviceContext.device.setTorchMode(self._flashMode) guard let mainDeviceContext = self.mainDeviceContext else {
return .complete()
}
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), mainDeviceContext.output.startRecording(isDualCamera: true, position: self.positionValue),
additionalDeviceContext.output.startRecording(isDualCamera: true) additionalDeviceContext.output.startRecording(isDualCamera: true)
) |> map { value, _ in ) |> map { value, _ in
return value return value
} }
} else { } else {
return self.mainDeviceContext.output.startRecording(isDualCamera: false) return mainDeviceContext.output.startRecording(isDualCamera: false)
} }
} }
public func stopRecording() -> Signal<VideoCaptureResult, NoError> { public func stopRecording() -> Signal<VideoCaptureResult, NoError> {
guard let mainDeviceContext = self.mainDeviceContext else {
return .complete()
}
if let additionalDeviceContext = self.additionalDeviceContext { if let additionalDeviceContext = self.additionalDeviceContext {
return combineLatest( return combineLatest(
self.mainDeviceContext.output.stopRecording(), mainDeviceContext.output.stopRecording(),
additionalDeviceContext.output.stopRecording() additionalDeviceContext.output.stopRecording()
) |> mapToSignal { main, additional in ) |> mapToSignal { main, additional in
if case let .finished(mainResult, _, duration, positionChangeTimestamps, _) = main, case let .finished(additionalResult, _, _, _, _) = additional { if case let .finished(mainResult, _, duration, positionChangeTimestamps, _) = main, case let .finished(additionalResult, _, _, _, _) = additional {
@ -506,7 +493,7 @@ private final class CameraContext {
} }
} else { } else {
let mirror = self.positionValue == .front let mirror = self.positionValue == .front
return self.mainDeviceContext.output.stopRecording() return mainDeviceContext.output.stopRecording()
|> map { result -> VideoCaptureResult in |> map { result -> VideoCaptureResult in
if case let .finished(mainResult, _, duration, positionChangeTimestamps, time) = result { if case let .finished(mainResult, _, duration, positionChangeTimestamps, time) = result {
var transitionImage = mainResult.1 var transitionImage = mainResult.1
@ -557,14 +544,16 @@ public final class Camera {
public struct Configuration { public struct Configuration {
let preset: Preset let preset: Preset
let position: Position let position: Position
let isDualEnabled: Bool
let audio: Bool let audio: Bool
let photo: Bool let photo: Bool
let metadata: Bool let metadata: Bool
let preferredFps: Double let preferredFps: Double
public init(preset: Preset, position: Position, audio: Bool, photo: Bool, metadata: Bool, preferredFps: Double) { public init(preset: Preset, position: Position, isDualEnabled: Bool = false, audio: Bool, photo: Bool, metadata: Bool, preferredFps: Double) {
self.preset = preset self.preset = preset
self.position = position self.position = position
self.isDualEnabled = isDualEnabled
self.audio = audio self.audio = audio
self.photo = photo self.photo = photo
self.metadata = metadata self.metadata = metadata

View File

@ -76,24 +76,9 @@ public class CameraSimplePreviewView: UIView {
super.init(frame: frame) super.init(frame: frame)
self.videoPreviewLayer.videoGravity = main ? .resizeAspectFill : .resizeAspect self.videoPreviewLayer.videoGravity = main ? .resizeAspectFill : .resizeAspect
self.placeholderView.contentMode = main ? .scaleAspectFill : .scaleAspectFit self.placeholderView.contentMode = main ? .scaleAspectFill : .scaleAspectFit
self.addSubview(self.placeholderView)
if main { self.addSubview(self.placeholderView)
if #available(iOS 13.0, *) {
self.previewingDisposable = (self.isPreviewing
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in
self?.removePlaceholder(delay: 0.15)
})
} else {
Queue.mainQueue().after(0.35) {
self.removePlaceholder(delay: 0.15)
}
}
}
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {

View File

@ -2586,7 +2586,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
context: context, context: context,
account: context.account, account: context.account,
sharedContext: context.sharedContext, sharedContext: context.sharedContext,
text: .markdown(text: "Posting stories is currently available only to subscribers of [Telegram Premium]()."), text: .markdown(text: "Posting stories is currently available only\nto subscribers of [Telegram Premium]()."),
style: .customBlur(UIColor(rgb: 0x2a2a2a), 2.0), style: .customBlur(UIColor(rgb: 0x2a2a2a), 2.0),
icon: .none, icon: .none,
location: .point(location, .top), location: .point(location, .top),

View File

@ -219,6 +219,8 @@ private final class StickerSelectionComponent: Component {
let topPanelHeight: CGFloat = 42.0 let topPanelHeight: CGFloat = 42.0
let defaultToEmoji = component.getController()?.defaultToEmoji ?? false
let context = component.context let context = component.context
let stickerPeekBehavior = EmojiContentPeekBehaviorImpl( let stickerPeekBehavior = EmojiContentPeekBehaviorImpl(
context: context, context: context,
@ -247,7 +249,7 @@ private final class StickerSelectionComponent: Component {
gifContent: nil, gifContent: nil,
hasRecentGifs: false, hasRecentGifs: false,
availableGifSearchEmojies: [], availableGifSearchEmojies: [],
defaultToEmojiTab: false, defaultToEmojiTab: defaultToEmoji,
externalTopPanelContainer: self.panelHostView, externalTopPanelContainer: self.panelHostView,
externalBottomPanelContainer: nil, externalBottomPanelContainer: nil,
displayTopPanelBackground: .blur, displayTopPanelBackground: .blur,
@ -1629,6 +1631,7 @@ public class StickerPickerScreen: ViewController {
private let context: AccountContext private let context: AccountContext
private let theme: PresentationTheme private let theme: PresentationTheme
private let inputData: Signal<StickerPickerInputData, NoError> private let inputData: Signal<StickerPickerInputData, NoError>
fileprivate let defaultToEmoji: Bool
private var currentLayout: ContainerViewLayout? private var currentLayout: ContainerViewLayout?
@ -1639,10 +1642,11 @@ public class StickerPickerScreen: ViewController {
public var presentGallery: () -> Void = { } public var presentGallery: () -> Void = { }
public init(context: AccountContext, inputData: Signal<StickerPickerInputData, NoError>) { public init(context: AccountContext, inputData: Signal<StickerPickerInputData, NoError>, defaultToEmoji: Bool = false) {
self.context = context self.context = context
self.theme = defaultDarkColorPresentationTheme self.theme = defaultDarkColorPresentationTheme
self.inputData = inputData self.inputData = inputData
self.defaultToEmoji = defaultToEmoji
super.init(navigationBarPresentationData: nil) super.init(navigationBarPresentationData: nil)

View File

@ -642,7 +642,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
} }
#endif #endif
if case .notDetermined = cameraAccess, !self.requestedCameraAccess { if !stories, case .notDetermined = cameraAccess, !self.requestedCameraAccess {
self.requestedCameraAccess = true self.requestedCameraAccess = true
self.mediaAssetsContext.requestCameraAccess() self.mediaAssetsContext.requestCameraAccess()
} }
@ -1301,8 +1301,15 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
if let bannedSendPhotos = self.controller?.bannedSendPhotos, let bannedSendVideos = self.controller?.bannedSendVideos { if let bannedSendPhotos = self.controller?.bannedSendPhotos, let bannedSendVideos = self.controller?.bannedSendVideos {
bannedSendMedia = (max(bannedSendPhotos.0, bannedSendVideos.0), bannedSendPhotos.1 || bannedSendVideos.1) bannedSendMedia = (max(bannedSendPhotos.0, bannedSendVideos.0), bannedSendPhotos.1 || bannedSendVideos.1)
} }
if case let .noAccess(cameraAccess) = self.state { if case let .noAccess(cameraAccess) = self.state {
var hasCamera = cameraAccess == .authorized
if let subject = self.controller?.subject, case .assets(_, .story) = subject {
hasCamera = false
self.controller?.navigationItem.rightBarButtonItem = nil
}
var placeholderTransition = transition var placeholderTransition = transition
let placeholderNode: MediaPickerPlaceholderNode let placeholderNode: MediaPickerPlaceholderNode
if let current = self.placeholderNode { if let current = self.placeholderNode {
@ -1326,7 +1333,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.updateNavigation(transition: .immediate) self.updateNavigation(transition: .immediate)
} }
placeholderNode.update(layout: layout, theme: self.presentationData.theme, strings: self.presentationData.strings, hasCamera: cameraAccess == .authorized, transition: placeholderTransition) placeholderNode.update(layout: layout, theme: self.presentationData.theme, strings: self.presentationData.strings, hasCamera: hasCamera, transition: placeholderTransition)
placeholderTransition.updateFrame(node: placeholderNode, frame: innerBounds) placeholderTransition.updateFrame(node: placeholderNode, frame: innerBounds)
} else if let placeholderNode = self.placeholderNode, bannedSendMedia == nil { } else if let placeholderNode = self.placeholderNode, bannedSendMedia == nil {
self.placeholderNode = nil self.placeholderNode = nil

View File

@ -224,13 +224,22 @@ public extension TelegramEngine {
to peerId: EnginePeer.Id, to peerId: EnginePeer.Id,
replyTo replyToMessageId: EngineMessage.Id?, replyTo replyToMessageId: EngineMessage.Id?,
storyId: StoryId? = nil, storyId: StoryId? = nil,
content: EngineOutgoingMessageContent content: EngineOutgoingMessageContent,
silentPosting: Bool = false,
scheduleTime: Int32? = nil
) -> Signal<[MessageId?], NoError> { ) -> Signal<[MessageId?], NoError> {
let message: EnqueueMessage? var message: EnqueueMessage?
if case let .contextResult(results, result) = content { 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) message = self.outgoingMessageWithChatContextResult(to: peerId, threadId: nil, botId: results.botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: storyId, hideVia: true, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: nil)
} else { } else {
var attributes: [MessageAttribute] = [] var attributes: [MessageAttribute] = []
if silentPosting {
attributes.append(NotificationInfoMessageAttribute(flags: .muted))
}
if let scheduleTime = scheduleTime {
attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime))
}
var text: String = "" var text: String = ""
var mediaReference: AnyMediaReference? var mediaReference: AnyMediaReference?
switch content { switch content {
@ -257,6 +266,8 @@ public extension TelegramEngine {
) )
} }
guard let message = message else { guard let message = message else {
return .complete() return .complete()
} }

View File

@ -391,7 +391,7 @@ private final class CameraScreenComponent: CombinedComponent {
self.lastDualCameraTimestamp = currentTimestamp self.lastDualCameraTimestamp = currentTimestamp
controller.node.dismissAllTooltips() controller.node.dismissAllTooltips()
let _ = ApplicationSpecificNotice.incrementStoriesDualCameraTip(accountManager: self.context.sharedContext.accountManager).start() let _ = ApplicationSpecificNotice.incrementStoriesDualCameraTip(accountManager: self.context.sharedContext.accountManager, count: 2).start()
let isEnabled = !controller.cameraState.isDualCameraEnabled let isEnabled = !controller.cameraState.isDualCameraEnabled
camera.setDualCameraEnabled(isEnabled) camera.setDualCameraEnabled(isEnabled)
@ -772,7 +772,7 @@ private final class CameraScreenComponent: CombinedComponent {
transition: .immediate transition: .immediate
) )
context.add(dualButton context.add(dualButton
.position(CGPoint(x: availableSize.width - topControlInset - flashButton.size.width / 2.0 - 52.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + dualButton.size.height / 2.0 + 1.0)) .position(CGPoint(x: availableSize.width - topControlInset - flashButton.size.width / 2.0 - 58.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + dualButton.size.height / 2.0 + 2.0))
.appear(.default(scale: true)) .appear(.default(scale: true))
.disappear(.default(scale: true)) .disappear(.default(scale: true))
) )
@ -1133,13 +1133,7 @@ public class CameraScreen: ViewController {
} else if dualCamWasEnabled != isDualCameraEnabled { } else if dualCamWasEnabled != isDualCameraEnabled {
self.requestUpdateLayout(hasAppeared: self.hasAppeared, transition: .spring(duration: 0.4)) self.requestUpdateLayout(hasAppeared: self.hasAppeared, transition: .spring(duration: 0.4))
updateCameraStoredStateInteractively(engine: self.context.engine) { current in UserDefaults.standard.set(isDualCameraEnabled as NSNumber, forKey: "TelegramStoryCameraIsDualEnabled")
if let current {
return current.withIsDualCameraEnabled(isDualCameraEnabled)
} else {
return CameraStoredState(isDualCameraEnabled: isDualCameraEnabled, dualCameraPosition: self.pipPosition)
}
}
} }
} }
} }
@ -1149,9 +1143,9 @@ public class CameraScreen: ViewController {
self.context = controller.context self.context = controller.context
self.updateState = ActionSlot<CameraState>() self.updateState = ActionSlot<CameraState>()
self.toggleCameraPositionAction = ActionSlot<Void>() self.toggleCameraPositionAction = ActionSlot<Void>()
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 } self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.backgroundView = UIView() self.backgroundView = UIView()
self.backgroundView.backgroundColor = UIColor(rgb: 0x000000) self.backgroundView.backgroundColor = UIColor(rgb: 0x000000)
@ -1170,6 +1164,22 @@ public class CameraScreen: ViewController {
self.previewBlurView = BlurView() self.previewBlurView = BlurView()
self.previewBlurView.isUserInteractionEnabled = false self.previewBlurView.isUserInteractionEnabled = false
var isDualCameraEnabled = true
if let isDualCameraEnabledValue = UserDefaults.standard.object(forKey: "TelegramStoryCameraIsDualEnabled") as? NSNumber {
isDualCameraEnabled = isDualCameraEnabledValue.boolValue
}
var dualCameraPosition: PIPPosition = .topRight
if let dualCameraPositionValue = UserDefaults.standard.object(forKey: "TelegramStoryCameraDualPosition") as? NSNumber {
dualCameraPosition = PIPPosition(rawValue: dualCameraPositionValue.int32Value) ?? .topRight
}
self.pipPosition = dualCameraPosition
var cameraFrontPosition = false
if let cameraFrontPositionValue = UserDefaults.standard.object(forKey: "TelegramStoryCameraUseFrontPosition") as? NSNumber, cameraFrontPositionValue.boolValue {
cameraFrontPosition = true
}
self.mainPreviewContainerView = UIView() self.mainPreviewContainerView = UIView()
self.mainPreviewContainerView.clipsToBounds = true self.mainPreviewContainerView.clipsToBounds = true
self.mainPreviewView = CameraSimplePreviewView(frame: .zero, main: true) self.mainPreviewView = CameraSimplePreviewView(frame: .zero, main: true)
@ -1177,12 +1187,13 @@ public class CameraScreen: ViewController {
self.additionalPreviewContainerView = UIView() self.additionalPreviewContainerView = UIView()
self.additionalPreviewContainerView.clipsToBounds = true self.additionalPreviewContainerView.clipsToBounds = true
self.additionalPreviewView = CameraSimplePreviewView(frame: .zero, main: false) self.additionalPreviewView = CameraSimplePreviewView(frame: .zero, main: false)
var cameraFrontPosition = false if isDualCameraEnabled {
if let useCameraFrontPosition = UserDefaults.standard.object(forKey: "TelegramStoryCameraUseFrontPosition") as? NSNumber, useCameraFrontPosition.boolValue { self.mainPreviewView.resetPlaceholder(front: false)
cameraFrontPosition = true self.additionalPreviewView.resetPlaceholder(front: true)
} else {
self.mainPreviewView.resetPlaceholder(front: cameraFrontPosition)
} }
self.mainPreviewView.resetPlaceholder(front: cameraFrontPosition)
self.cameraState = CameraState( self.cameraState = CameraState(
mode: .photo, mode: .photo,
@ -1191,7 +1202,7 @@ public class CameraScreen: ViewController {
flashModeDidChange: false, flashModeDidChange: false,
recording: .none, recording: .none,
duration: 0.0, duration: 0.0,
isDualCameraEnabled: false isDualCameraEnabled: isDualCameraEnabled
) )
self.previewFrameLeftDimView = UIView() self.previewFrameLeftDimView = UIView()
@ -1265,6 +1276,35 @@ public class CameraScreen: ViewController {
} }
} }
if #available(iOS 13.0, *) {
if isDualCameraEnabled {
let _ = (combineLatest(
queue: Queue.mainQueue(),
self.mainPreviewView.isPreviewing,
self.additionalPreviewView.isPreviewing
)
|> filter { $0 && $1 }
|> take(1)).start(next: { [weak self] _, _ in
self?.mainPreviewView.removePlaceholder(delay: 0.15)
self?.additionalPreviewView.removePlaceholder(delay: 0.15)
})
} else {
let _ = (self.mainPreviewView.isPreviewing
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in
self?.mainPreviewView.removePlaceholder(delay: 0.15)
})
}
} else {
Queue.mainQueue().after(0.35) {
self.mainPreviewView.removePlaceholder(delay: 0.15)
if isDualCameraEnabled {
self.additionalPreviewView.removePlaceholder(delay: 0.15)
}
}
}
self.idleTimerExtensionDisposable.set(self.context.sharedContext.applicationBindings.pushIdleTimerExtension()) self.idleTimerExtensionDisposable.set(self.context.sharedContext.applicationBindings.pushIdleTimerExtension())
} }
@ -1313,6 +1353,7 @@ public class CameraScreen: ViewController {
configuration: Camera.Configuration( configuration: Camera.Configuration(
preset: .hd1920x1080, preset: .hd1920x1080,
position: self.cameraState.position, position: self.cameraState.position,
isDualEnabled: self.cameraState.isDualCameraEnabled,
audio: true, audio: true,
photo: true, photo: true,
metadata: false, metadata: false,
@ -1341,6 +1382,7 @@ public class CameraScreen: ViewController {
} }
}) })
var isFirstTime = true
self.changingPositionDisposable = combineLatest( self.changingPositionDisposable = combineLatest(
queue: Queue.mainQueue(), queue: Queue.mainQueue(),
camera.modeChange, camera.modeChange,
@ -1397,9 +1439,13 @@ public class CameraScreen: ViewController {
}) })
} }
if self.cameraState.isDualCameraEnabled { if isFirstTime {
self.mainPreviewView.removePlaceholder() isFirstTime = false
self.additionalPreviewView.removePlaceholder() } else {
if self.cameraState.isDualCameraEnabled {
self.mainPreviewView.removePlaceholder()
self.additionalPreviewView.removePlaceholder()
}
} }
} }
} }
@ -1508,13 +1554,7 @@ public class CameraScreen: ViewController {
self.pipPosition = pipPositionForLocation(layout: layout, position: location, velocity: velocity) self.pipPosition = pipPositionForLocation(layout: layout, position: location, velocity: velocity)
self.containerLayoutUpdated(layout: layout, transition: .spring(duration: 0.4)) self.containerLayoutUpdated(layout: layout, transition: .spring(duration: 0.4))
updateCameraStoredStateInteractively(engine: self.context.engine) { current in UserDefaults.standard.set(self.pipPosition.rawValue as NSNumber, forKey: "TelegramStoryCameraDualPosition")
if let current {
return current.withDualCameraPosition(self.pipPosition)
} else {
return CameraStoredState(isDualCameraEnabled: true, dualCameraPosition: self.pipPosition)
}
}
default: default:
break break
} }
@ -1797,13 +1837,12 @@ public class CameraScreen: ViewController {
} }
let parentFrame = self.view.convert(self.bounds, to: nil) let parentFrame = self.view.convert(self.bounds, to: nil)
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0) let location = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.maxY + 3.0), size: CGSize())
let accountManager = self.context.sharedContext.accountManager let accountManager = self.context.sharedContext.accountManager
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Enable Dual Camera Mode"), location: .point(location, .top), displayDuration: .manual, inset: 16.0, shouldDismissOnTouch: { point, containerFrame in let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Tap here to disable\nthe selfie camera"), textAlignment: .center, location: .point(location, .right), displayDuration: .custom(5.0), inset: 16.0, shouldDismissOnTouch: { point, containerFrame in
if containerFrame.contains(point) { if containerFrame.contains(point) {
let _ = ApplicationSpecificNotice.incrementStoriesDualCameraTip(accountManager: accountManager).start() let _ = ApplicationSpecificNotice.incrementStoriesDualCameraTip(accountManager: accountManager, count: 2).start()
return .dismiss(consume: true) return .dismiss(consume: true)
} }
return .ignore return .ignore
@ -1846,7 +1885,7 @@ public class CameraScreen: ViewController {
guard let self else { guard let self else {
return return
} }
if count < 1 { if count < 2 {
self.presentDualCameraTooltip() self.presentDualCameraTooltip()
} }
}) })
@ -2040,7 +2079,7 @@ public class CameraScreen: ViewController {
self.appliedDualCamera = isDualCameraEnabled self.appliedDualCamera = isDualCameraEnabled
let circleSide = floorToScreenPixels(previewSize.width * 160.0 / 430.0) let circleSide = floorToScreenPixels(previewSize.width * 160.0 / 430.0)
let circleOffset = CGPoint(x: previewSize.width * 224.0 / 1080.0, y: previewSize.width * 520.0 / 1080.0) let circleOffset = CGPoint(x: previewSize.width * 224.0 / 1080.0, y: previewSize.width * 480.0 / 1080.0)
var origin: CGPoint var origin: CGPoint
switch self.pipPosition { switch self.pipPosition {

View File

@ -1,73 +0,0 @@
import Foundation
import UIKit
import SwiftSignalKit
import TelegramCore
import TelegramUIPreferences
import MediaEditor
public final class CameraStoredState: Codable {
private enum CodingKeys: String, CodingKey {
case isDualCameraEnabled
case dualCameraPosition
}
public let isDualCameraEnabled: Bool
public let dualCameraPosition: CameraScreen.PIPPosition
public init(
isDualCameraEnabled: Bool,
dualCameraPosition: CameraScreen.PIPPosition
) {
self.isDualCameraEnabled = isDualCameraEnabled
self.dualCameraPosition = dualCameraPosition
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.isDualCameraEnabled = try container.decode(Bool.self, forKey: .isDualCameraEnabled)
self.dualCameraPosition = CameraScreen.PIPPosition(rawValue: try container.decode(Int32.self, forKey: .dualCameraPosition)) ?? .topRight
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.isDualCameraEnabled, forKey: .isDualCameraEnabled)
try container.encode(self.dualCameraPosition.rawValue, forKey: .dualCameraPosition)
}
public func withIsDualCameraEnabled(_ isDualCameraEnabled: Bool) -> CameraStoredState {
return CameraStoredState(isDualCameraEnabled: isDualCameraEnabled, dualCameraPosition: self.dualCameraPosition)
}
public func withDualCameraPosition(_ dualCameraPosition: CameraScreen.PIPPosition) -> CameraStoredState {
return CameraStoredState(isDualCameraEnabled: self.isDualCameraEnabled, dualCameraPosition: dualCameraPosition)
}
}
func cameraStoredState(engine: TelegramEngine) -> Signal<CameraStoredState?, NoError> {
let key = EngineDataBuffer(length: 4)
key.setInt32(0, value: 0)
return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.cameraState, id: key))
|> map { entry -> CameraStoredState? in
return entry?.get(CameraStoredState.self)
}
}
func updateCameraStoredStateInteractively(engine: TelegramEngine, _ f: @escaping (CameraStoredState?) -> CameraStoredState?) {
let key = EngineDataBuffer(length: 4)
key.setInt32(0, value: 0)
let _ = (engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.cameraState, id: key))
|> map { entry -> CameraStoredState? in
return entry?.get(CameraStoredState.self)
}
|> mapToSignal { state -> Signal<Never, NoError> in
if let updatedState = f(state) {
return engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.cameraState, id: key, item: updatedState)
} else {
return engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.cameraState, id: key)
}
}).start()
}

View File

@ -37,6 +37,8 @@ public final class ChatScheduleTimeController: ViewController {
private var presentationData: PresentationData private var presentationData: PresentationData
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
public var dismissed: () -> Void = {}
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, mode: ChatScheduleTimeControllerMode, style: ChatScheduleTimeControllerStyle, currentTime: Int32? = nil, minimalTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) { public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, mode: ChatScheduleTimeControllerMode, style: ChatScheduleTimeControllerStyle, currentTime: Int32? = nil, minimalTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) {
self.context = context self.context = context
self.peerId = peerId self.peerId = peerId
@ -105,6 +107,7 @@ public final class ChatScheduleTimeController: ViewController {
} }
override public func dismiss(completion: (() -> Void)? = nil) { override public func dismiss(completion: (() -> Void)? = nil) {
self.dismissed()
self.controllerNode.animateOut(completion: completion) self.controllerNode.animateOut(completion: completion)
} }

View File

@ -1136,17 +1136,19 @@ public func recommendedVideoExportConfiguration(values: MediaEditorValues, durat
let compressionProperties: [String: Any] let compressionProperties: [String: Any]
let codecType: AVVideoCodecType let codecType: AVVideoCodecType
if hasHEVCHardwareEncoder { var bitrate: Int = 3700
var bitrate: Int = 3700 if image {
if image { bitrate = 5000
} else {
if duration < 10 {
bitrate = 5800
} else if duration < 20 {
bitrate = 5500
} else if duration < 30 {
bitrate = 5000 bitrate = 5000
} else {
if duration < 10 {
bitrate = 5500
} else if duration < 25 {
bitrate = 4500
}
} }
}
if hasHEVCHardwareEncoder {
codecType = AVVideoCodecType.hevc codecType = AVVideoCodecType.hevc
compressionProperties = [ compressionProperties = [
AVVideoAverageBitRateKey: bitrate * 1000, AVVideoAverageBitRateKey: bitrate * 1000,
@ -1155,7 +1157,7 @@ public func recommendedVideoExportConfiguration(values: MediaEditorValues, durat
} else { } else {
codecType = AVVideoCodecType.h264 codecType = AVVideoCodecType.h264
compressionProperties = [ compressionProperties = [
AVVideoAverageBitRateKey: 3800000, AVVideoAverageBitRateKey: bitrate * 1000,
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel, AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC
] ]

View File

@ -1117,7 +1117,7 @@ final class MediaEditorScreenComponent: Component {
} }
self.deactivateInput() self.deactivateInput()
}, },
sendMessageOptionsAction: { }, sendMessageOptionsAction: nil,
sendStickerAction: { _ in }, sendStickerAction: { _ in },
setMediaRecordingActive: nil, setMediaRecordingActive: nil,
lockMediaRecording: nil, lockMediaRecording: nil,
@ -2797,6 +2797,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
private var drawingScreen: DrawingScreen? private var drawingScreen: DrawingScreen?
private var stickerScreen: StickerPickerScreen? private var stickerScreen: StickerPickerScreen?
private var defaultToEmoji = false
private var previousDrawingData: Data? private var previousDrawingData: Data?
private var previousDrawingEntities: [DrawingEntity]? private var previousDrawingEntities: [DrawingEntity]?
@ -2895,7 +2896,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
switch mode { switch mode {
case .sticker: case .sticker:
let controller = StickerPickerScreen(context: self.context, inputData: self.stickerPickerInputData.get()) self.mediaEditor?.stop()
let controller = StickerPickerScreen(context: self.context, inputData: self.stickerPickerInputData.get(), defaultToEmoji: self.defaultToEmoji)
controller.completion = { [weak self] content in controller.completion = { [weak self] content in
if let self { if let self {
if let content { if let content {
@ -2905,8 +2907,17 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.hasAnyChanges = true self.hasAnyChanges = true
self.controller?.isSavingAvailable = true self.controller?.isSavingAvailable = true
self.controller?.requestLayout(transition: .immediate) self.controller?.requestLayout(transition: .immediate)
if case let .file(file) = content {
if file.isCustomEmoji {
self.defaultToEmoji = true
} else {
self.defaultToEmoji = false
}
}
} }
self.stickerScreen = nil self.stickerScreen = nil
self.mediaEditor?.play()
} }
} }
controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in
@ -3140,7 +3151,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
case bottomRight case bottomRight
func getPosition(_ size: CGSize) -> CGPoint { func getPosition(_ size: CGSize) -> CGPoint {
let offset = CGPoint(x: 224.0, y: 520.0) let offset = CGPoint(x: 224.0, y: 480.0)
switch self { switch self {
case .topLeft: case .topLeft:
return CGPoint(x: offset.x, y: offset.y) return CGPoint(x: offset.x, y: offset.y)

View File

@ -258,7 +258,7 @@ final class StoryPreviewComponent: Component {
presentController: { _ in }, presentController: { _ in },
presentInGlobalOverlay: { _ in }, presentInGlobalOverlay: { _ in },
sendMessageAction: { }, sendMessageAction: { },
sendMessageOptionsAction: { }, sendMessageOptionsAction: nil,
sendStickerAction: { _ in }, sendStickerAction: { _ in },
setMediaRecordingActive: { _, _, _ in }, setMediaRecordingActive: { _, _, _ in },
lockMediaRecording: nil, lockMediaRecording: nil,

View File

@ -46,7 +46,7 @@ public final class MessageInputActionButtonComponent: Component {
public let mode: Mode public let mode: Mode
public let action: (Mode, Action, Bool) -> Void public let action: (Mode, Action, Bool) -> Void
public let longPressAction: () -> Void public let longPressAction: ((UIView, ContextGesture?) -> Void)?
public let switchMediaInputMode: () -> Void public let switchMediaInputMode: () -> Void
public let updateMediaCancelFraction: (CGFloat) -> Void public let updateMediaCancelFraction: (CGFloat) -> Void
public let lockMediaRecording: () -> Void public let lockMediaRecording: () -> Void
@ -62,7 +62,7 @@ public final class MessageInputActionButtonComponent: Component {
public init( public init(
mode: Mode, mode: Mode,
action: @escaping (Mode, Action, Bool) -> Void, action: @escaping (Mode, Action, Bool) -> Void,
longPressAction: @escaping () -> Void, longPressAction: ((UIView, ContextGesture?) -> Void)?,
switchMediaInputMode: @escaping () -> Void, switchMediaInputMode: @escaping () -> Void,
updateMediaCancelFraction: @escaping (CGFloat) -> Void, updateMediaCancelFraction: @escaping (CGFloat) -> Void,
lockMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void,
@ -113,9 +113,14 @@ public final class MessageInputActionButtonComponent: Component {
return true return true
} }
public final class View: HighlightTrackingButton { public final class View: UIView {
private var micButton: ChatTextInputMediaRecordingButton? private var micButton: ChatTextInputMediaRecordingButton?
public let button: HighlightTrackingButtonNode
public let referenceNode: ContextReferenceContentNode
public let containerNode: ContextControllerSourceNode
private let sendIconView: UIImageView private let sendIconView: UIImageView
private var moreButton: MoreHeaderButton? private var moreButton: MoreHeaderButton?
private var component: MessageInputActionButtonComponent? private var component: MessageInputActionButtonComponent?
@ -124,13 +129,31 @@ public final class MessageInputActionButtonComponent: Component {
override init(frame: CGRect) { override init(frame: CGRect) {
self.sendIconView = UIImageView() self.sendIconView = UIImageView()
self.button = HighlightTrackingButtonNode()
self.referenceNode = ContextReferenceContentNode()
self.containerNode = ContextControllerSourceNode()
super.init(frame: frame) super.init(frame: frame)
self.addSubview(self.button.view)
self.containerNode.addSubnode(self.referenceNode)
self.referenceNode.view.addSubview(self.sendIconView)
self.button.addSubnode(self.containerNode)
self.containerNode.shouldBegin = { [weak self] location in
guard let self, let component = self.component, let _ = component.longPressAction else {
return false
}
return true
}
self.containerNode.activated = { [weak self] gesture, _ in
guard let self, let component = self.component, let longPressAction = component.longPressAction else {
return
}
longPressAction(self, gesture)
}
self.isMultipleTouchEnabled = false self.button.highligthedChanged = { [weak self] highlighted in
self.addSubview(self.sendIconView)
self.highligthedChanged = { [weak self] highlighted in
guard let self else { guard let self else {
return return
} }
@ -141,8 +164,10 @@ public final class MessageInputActionButtonComponent: Component {
transition.setSublayerTransform(view: self, transform: CATransform3DMakeScale(scale, scale, 1.0)) transition.setSublayerTransform(view: self, transform: CATransform3DMakeScale(scale, scale, 1.0))
} }
self.addTarget(self, action: #selector(self.touchDown), for: .touchDown) self.button.addTarget(self, action: #selector(self.touchDown), forControlEvents: .touchDown)
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) self.button.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
// but.addTarget(self, action: #selector(self.touchDown), for: .touchDown)
// self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -162,10 +187,11 @@ public final class MessageInputActionButtonComponent: Component {
} }
component.action(component.mode, .up, false) component.action(component.mode, .up, false)
} }
override public func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { // public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return super.continueTracking(touch, with: event) // let result = super.hitTest(point, with: event)
} // return result
// }
func update(component: MessageInputActionButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize { func update(component: MessageInputActionButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let previousComponent = self.component let previousComponent = self.component
@ -174,6 +200,8 @@ public final class MessageInputActionButtonComponent: Component {
let themeUpdated = previousComponent?.theme !== component.theme let themeUpdated = previousComponent?.theme !== component.theme
self.containerNode.isUserInteractionEnabled = component.longPressAction != nil
if self.micButton == nil { if self.micButton == nil {
let micButton = ChatTextInputMediaRecordingButton( let micButton = ChatTextInputMediaRecordingButton(
context: component.context, context: component.context,
@ -240,7 +268,7 @@ public final class MessageInputActionButtonComponent: Component {
} }
} }
if self.moreButton == nil { if case .more = component.mode, self.moreButton == nil {
let moreButton = MoreHeaderButton(color: .white) let moreButton = MoreHeaderButton(color: .white)
self.moreButton = moreButton self.moreButton = moreButton
self.addSubnode(moreButton) self.addSubnode(moreButton)
@ -340,6 +368,10 @@ public final class MessageInputActionButtonComponent: Component {
} }
} }
transition.setFrame(view: self.button.view, frame: CGRect(origin: .zero, size: availableSize))
transition.setFrame(view: self.containerNode.view, frame: CGRect(origin: .zero, size: availableSize))
transition.setFrame(view: self.referenceNode.view, frame: CGRect(origin: .zero, size: availableSize))
transition.setAlpha(view: self.sendIconView, alpha: sendAlpha) transition.setAlpha(view: self.sendIconView, alpha: sendAlpha)
transition.setScale(view: self.sendIconView, scale: sendAlpha == 0.0 ? 0.01 : 1.0) transition.setScale(view: self.sendIconView, scale: sendAlpha == 0.0 ? 0.01 : 1.0)

View File

@ -72,7 +72,7 @@ public final class MessageInputPanelComponent: Component {
public let presentController: (ViewController) -> Void public let presentController: (ViewController) -> Void
public let presentInGlobalOverlay: (ViewController) -> Void public let presentInGlobalOverlay: (ViewController) -> Void
public let sendMessageAction: () -> Void public let sendMessageAction: () -> Void
public let sendMessageOptionsAction: () -> Void public let sendMessageOptionsAction: ((UIView, ContextGesture?) -> Void)?
public let sendStickerAction: (TelegramMediaFile) -> Void public let sendStickerAction: (TelegramMediaFile) -> Void
public let setMediaRecordingActive: ((Bool, Bool, Bool) -> Void)? public let setMediaRecordingActive: ((Bool, Bool, Bool) -> Void)?
public let lockMediaRecording: (() -> Void)? public let lockMediaRecording: (() -> Void)?
@ -114,7 +114,7 @@ public final class MessageInputPanelComponent: Component {
presentController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController) -> Void,
presentInGlobalOverlay: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void,
sendMessageAction: @escaping () -> Void, sendMessageAction: @escaping () -> Void,
sendMessageOptionsAction: @escaping () -> Void, sendMessageOptionsAction: ((UIView, ContextGesture?) -> Void)?,
sendStickerAction: @escaping (TelegramMediaFile) -> Void, sendStickerAction: @escaping (TelegramMediaFile) -> Void,
setMediaRecordingActive: ((Bool, Bool, Bool) -> Void)?, setMediaRecordingActive: ((Bool, Bool, Bool) -> Void)?,
lockMediaRecording: (() -> Void)?, lockMediaRecording: (() -> Void)?,
@ -747,7 +747,7 @@ public final class MessageInputPanelComponent: Component {
break break
} }
}, },
longPressAction: {}, longPressAction: nil,
switchMediaInputMode: { switchMediaInputMode: {
}, },
updateMediaCancelFraction: { _ in updateMediaCancelFraction: { _ in
@ -926,12 +926,7 @@ public final class MessageInputPanelComponent: Component {
break break
} }
}, },
longPressAction: { [weak self] in longPressAction: component.sendMessageOptionsAction,
guard let self, let component = self.component else {
return
}
component.sendMessageOptionsAction()
},
switchMediaInputMode: { [weak self] in switchMediaInputMode: { [weak self] in
guard let self else { guard let self else {
return return

View File

@ -52,6 +52,7 @@ public final class StoryContentContextImpl: StoryContentContext {
PostboxViewKey.cachedPeerData(peerId: peerId), PostboxViewKey.cachedPeerData(peerId: peerId),
PostboxViewKey.storiesState(key: .peer(peerId)), PostboxViewKey.storiesState(key: .peer(peerId)),
PostboxViewKey.storyItems(peerId: peerId), PostboxViewKey.storyItems(peerId: peerId),
PostboxViewKey.peerPresences(peerIds: Set([peerId]))
] ]
if peerId == context.account.peerId { if peerId == context.account.peerId {
inputKeys.append(PostboxViewKey.storiesState(key: .local)) inputKeys.append(PostboxViewKey.storiesState(key: .local))
@ -112,6 +113,11 @@ public final class StoryContentContextImpl: StoryContentContext {
return return
} }
let additionalPeerData: StoryContentContextState.AdditionalPeerData let additionalPeerData: StoryContentContextState.AdditionalPeerData
var peerPresence: PeerPresence?
if let presencesView = views.views[PostboxViewKey.peerPresences(peerIds: Set([peerId]))] as? PeerPresencesView {
peerPresence = presencesView.presences[peerId]
}
if let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView, let cachedUserData = cachedPeerDataView.cachedPeerData as? CachedUserData { if let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView, let cachedUserData = cachedPeerDataView.cachedPeerData as? CachedUserData {
var isMuted = false var isMuted = false
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
@ -119,9 +125,17 @@ public final class StoryContentContextImpl: StoryContentContext {
} else { } else {
isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: nil) isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: nil)
} }
additionalPeerData = StoryContentContextState.AdditionalPeerData(isMuted: isMuted, areVoiceMessagesAvailable: cachedUserData.voiceMessagesAvailable) additionalPeerData = StoryContentContextState.AdditionalPeerData(
isMuted: isMuted,
areVoiceMessagesAvailable: cachedUserData.voiceMessagesAvailable,
presence: peerPresence.flatMap { EnginePeer.Presence($0) }
)
} else { } else {
additionalPeerData = StoryContentContextState.AdditionalPeerData(isMuted: true, areVoiceMessagesAvailable: true) additionalPeerData = StoryContentContextState.AdditionalPeerData(
isMuted: true,
areVoiceMessagesAvailable: true,
presence: peerPresence.flatMap { EnginePeer.Presence($0) }
)
} }
let state = stateView.value?.get(Stories.PeerState.self) let state = stateView.value?.get(Stories.PeerState.self)
@ -923,6 +937,7 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
self.storyDisposable = (combineLatest(queue: .mainQueue(), self.storyDisposable = (combineLatest(queue: .mainQueue(),
context.engine.data.subscribe( context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: storyId.peerId), TelegramEngine.EngineData.Item.Peer.Peer(id: storyId.peerId),
TelegramEngine.EngineData.Item.Peer.Presence(id: storyId.peerId),
TelegramEngine.EngineData.Item.Peer.AreVoiceMessagesAvailable(id: storyId.peerId), TelegramEngine.EngineData.Item.Peer.AreVoiceMessagesAvailable(id: storyId.peerId),
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: storyId.peerId), TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: storyId.peerId),
TelegramEngine.EngineData.Item.NotificationSettings.Global() TelegramEngine.EngineData.Item.NotificationSettings.Global()
@ -960,18 +975,19 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
return return
} }
let (peer, areVoiceMessagesAvailable, notificationSettings, globalNotificationSettings) = data let (peer, presence, areVoiceMessagesAvailable, notificationSettings, globalNotificationSettings) = data
let (item, peers, allEntityFiles) = itemAndPeers let (item, peers, allEntityFiles) = itemAndPeers
guard let peer else { guard let peer else {
return return
} }
let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings()) let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings())
let additionalPeerData = StoryContentContextState.AdditionalPeerData( let additionalPeerData = StoryContentContextState.AdditionalPeerData(
isMuted: isMuted, isMuted: isMuted,
areVoiceMessagesAvailable: areVoiceMessagesAvailable areVoiceMessagesAvailable: areVoiceMessagesAvailable,
presence: presence
) )
if item == nil { if item == nil {
@ -1098,6 +1114,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
self.storyDisposable = (combineLatest(queue: .mainQueue(), self.storyDisposable = (combineLatest(queue: .mainQueue(),
context.engine.data.subscribe( context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
TelegramEngine.EngineData.Item.Peer.Presence(id: peerId),
TelegramEngine.EngineData.Item.Peer.AreVoiceMessagesAvailable(id: peerId), TelegramEngine.EngineData.Item.Peer.AreVoiceMessagesAvailable(id: peerId),
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId), TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId),
TelegramEngine.EngineData.Item.NotificationSettings.Global() TelegramEngine.EngineData.Item.NotificationSettings.Global()
@ -1111,7 +1128,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
return return
} }
let (peer, areVoiceMessagesAvailable, notificationSettings, globalNotificationSettings) = data let (peer, presence, areVoiceMessagesAvailable, notificationSettings, globalNotificationSettings) = data
guard let peer else { guard let peer else {
return return
@ -1121,7 +1138,8 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
let additionalPeerData = StoryContentContextState.AdditionalPeerData( let additionalPeerData = StoryContentContextState.AdditionalPeerData(
isMuted: isMuted, isMuted: isMuted,
areVoiceMessagesAvailable: areVoiceMessagesAvailable areVoiceMessagesAvailable: areVoiceMessagesAvailable,
presence: presence
) )
self.listState = state self.listState = state

View File

@ -113,19 +113,31 @@ public final class StoryContentItem: Equatable {
public final class StoryContentContextState { public final class StoryContentContextState {
public final class AdditionalPeerData: Equatable { public final class AdditionalPeerData: Equatable {
public static func == (lhs: StoryContentContextState.AdditionalPeerData, rhs: StoryContentContextState.AdditionalPeerData) -> Bool {
return lhs.isMuted == rhs.isMuted && lhs.areVoiceMessagesAvailable == rhs.areVoiceMessagesAvailable
}
public let isMuted: Bool public let isMuted: Bool
public let areVoiceMessagesAvailable: Bool public let areVoiceMessagesAvailable: Bool
public let presence: EnginePeer.Presence?
public init( public init(
isMuted: Bool, isMuted: Bool,
areVoiceMessagesAvailable: Bool areVoiceMessagesAvailable: Bool,
presence: EnginePeer.Presence?
) { ) {
self.isMuted = isMuted self.isMuted = isMuted
self.areVoiceMessagesAvailable = areVoiceMessagesAvailable self.areVoiceMessagesAvailable = areVoiceMessagesAvailable
self.presence = presence
}
public static func == (lhs: StoryContentContextState.AdditionalPeerData, rhs: StoryContentContextState.AdditionalPeerData) -> Bool {
if lhs.isMuted != rhs.isMuted {
return false
}
if lhs.areVoiceMessagesAvailable != rhs.areVoiceMessagesAvailable {
return false
}
if lhs.presence != rhs.presence {
return false
}
return true
} }
} }

View File

@ -1728,11 +1728,11 @@ public final class StoryItemSetContainerComponent: Component {
} }
self.sendMessageContext.performSendMessageAction(view: self) self.sendMessageContext.performSendMessageAction(view: self)
}, },
sendMessageOptionsAction: { [weak self] in sendMessageOptionsAction: { [weak self] sourceView, gesture in
guard let self else { guard let self else {
return return
} }
self.sendMessageContext.presentSendMessageOptions(view: self) self.sendMessageContext.presentSendMessageOptions(view: self, sourceView: sourceView, gesture: gesture)
}, },
sendStickerAction: { [weak self] sticker in sendStickerAction: { [weak self] sticker in
guard let self else { guard let self else {
@ -2231,7 +2231,7 @@ public final class StoryItemSetContainerComponent: Component {
mode: .more, mode: .more,
action: { _, _, _ in action: { _, _, _ in
}, },
longPressAction: {}, longPressAction: nil,
switchMediaInputMode: { switchMediaInputMode: {
}, },
updateMediaCancelFraction: { _ in updateMediaCancelFraction: { _ in
@ -2838,7 +2838,7 @@ public final class StoryItemSetContainerComponent: Component {
presentationData: presentationData, presentationData: presentationData,
content: .sticker(context: context, file: animation, loop: false, title: nil, text: "Reaction Sent.", undoText: "View in Chat", customAction: { [weak self] in content: .sticker(context: context, file: animation, loop: false, title: nil, text: "Reaction Sent.", undoText: "View in Chat", customAction: { [weak self] in
if let messageId = messageIds.first, let self { if let messageId = messageIds.first, let self {
self.navigateToPeer(peer: peer, chat: true, messageId: messageId) self.navigateToPeer(peer: peer, chat: true, subject: messageId.flatMap { .message(id: .id($0), highlight: false, timecode: nil) })
} }
}), }),
elevatedLayout: false, elevatedLayout: false,
@ -3179,7 +3179,7 @@ public final class StoryItemSetContainerComponent: Component {
} }
} }
func navigateToPeer(peer: EnginePeer, chat: Bool, messageId: EngineMessage.Id? = nil) { func navigateToPeer(peer: EnginePeer, chat: Bool, subject: ChatControllerSubject? = nil) {
guard let component = self.component else { guard let component = self.component else {
return return
} }
@ -3189,11 +3189,7 @@ public final class StoryItemSetContainerComponent: Component {
guard let navigationController = controller.navigationController as? NavigationController else { guard let navigationController = controller.navigationController as? NavigationController else {
return return
} }
if messageId != nil || chat { if subject != nil || chat {
var subject: ChatControllerSubject?
if let messageId {
subject = .message(id: .id(messageId), highlight: false, timecode: nil)
}
component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peer), subject: subject, keepStack: .always, animated: true, pushController: { [weak controller, weak navigationController] chatController, animated, completion in component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peer), subject: subject, keepStack: .always, animated: true, pushController: { [weak controller, weak navigationController] chatController, animated, completion in
guard let controller, let navigationController else { guard let controller, let navigationController else {
return return
@ -3537,12 +3533,12 @@ public final class StoryItemSetContainerComponent: Component {
} }
} }
private func performMyMoreAction(sourceView: UIView, gesture: ContextGesture?) { func dismissAllTooltips() {
guard let component = self.component, let controller = component.controller() else { guard let component = self.component, let controller = component.controller() else {
return return
} }
component.controller()?.forEachController { c in controller.forEachController { c in
if let c = c as? UndoOverlayController { if let c = c as? UndoOverlayController {
c.dismiss() c.dismiss()
} else if let c = c as? TooltipScreen { } else if let c = c as? TooltipScreen {
@ -3550,6 +3546,14 @@ public final class StoryItemSetContainerComponent: Component {
} }
return true return true
} }
}
private func performMyMoreAction(sourceView: UIView, gesture: ContextGesture?) {
guard let component = self.component, let controller = component.controller() else {
return
}
self.dismissAllTooltips()
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
@ -3753,7 +3757,7 @@ public final class StoryItemSetContainerComponent: Component {
let contextItems = ContextController.Items(content: .list(items), tip: tip, tipSignal: tipSignal) let contextItems = ContextController.Items(content: .list(items), tip: tip, tipSignal: tipSignal)
let contextController = ContextController(account: component.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(contextItems), gesture: gesture) let contextController = ContextController(account: component.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, position: .bottom)), items: .single(contextItems), gesture: gesture)
contextController.dismissed = { [weak self] in contextController.dismissed = { [weak self] in
guard let self else { guard let self else {
return return
@ -3779,12 +3783,7 @@ public final class StoryItemSetContainerComponent: Component {
return return
} }
component.controller()?.forEachController { c in self.dismissAllTooltips()
if let c = c as? UndoOverlayController {
c.dismiss()
}
return true
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
@ -3976,7 +3975,7 @@ public final class StoryItemSetContainerComponent: Component {
let contextItems = ContextController.Items(content: .list(items), tip: tip, tipSignal: tipSignal) let contextItems = ContextController.Items(content: .list(items), tip: tip, tipSignal: tipSignal)
let contextController = ContextController(account: component.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(contextItems), gesture: gesture) let contextController = ContextController(account: component.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, position: .bottom)), items: .single(contextItems), gesture: gesture)
contextController.dismissed = { [weak self] in contextController.dismissed = { [weak self] in
guard let self else { guard let self else {
return return
@ -4000,20 +3999,22 @@ public final class StoryItemSetContainerComponent: Component {
} }
} }
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController private let controller: ViewController
private let sourceView: UIView private let sourceView: UIView
private let position: ContextControllerReferenceViewInfo.ActionsPosition
var keepInPlace: Bool { var keepInPlace: Bool {
return true return true
} }
init(controller: ViewController, sourceView: UIView) { init(controller: ViewController, sourceView: UIView, position: ContextControllerReferenceViewInfo.ActionsPosition) {
self.controller = controller self.controller = controller
self.sourceView = sourceView self.sourceView = sourceView
self.position = position
} }
func transitionInfo() -> ContextControllerReferenceViewInfo? { func transitionInfo() -> ContextControllerReferenceViewInfo? {
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: .bottom) return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: self.position)
} }
} }

View File

@ -40,6 +40,8 @@ import OpenInExternalAppUI
import SafariServices import SafariServices
import MediaPasteboardUI import MediaPasteboardUI
import WebPBinding import WebPBinding
import ContextUI
import ChatScheduleTimeController
final class StoryItemSetContainerSendMessage { final class StoryItemSetContainerSendMessage {
enum InputMode { enum InputMode {
@ -331,7 +333,7 @@ final class StoryItemSetContainerSendMessage {
} }
} }
private func presentMessageSentTooltip(view: StoryItemSetContainerComponent.View, peer: EnginePeer, messageId: EngineMessage.Id?) { private func presentMessageSentTooltip(view: StoryItemSetContainerComponent.View, peer: EnginePeer, messageId: EngineMessage.Id?, isScheduled: Bool = false) {
guard let component = view.component, let controller = component.controller() as? StoryContainerScreen else { guard let component = view.component, let controller = component.controller() as? StoryContainerScreen else {
return return
} }
@ -341,14 +343,17 @@ final class StoryItemSetContainerSendMessage {
} }
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let text = isScheduled ? "Message Scheduled" : "Message Sent"
let tooltipScreen = UndoOverlayController( let tooltipScreen = UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .actionSucceeded(title: "", text: "Message Sent", cancel: messageId != nil ? "View in Chat" : "", destructive: false), content: .actionSucceeded(title: "", text: text, cancel: messageId != nil ? "View in Chat" : "", destructive: false),
elevatedLayout: false, elevatedLayout: false,
animateInAsReplacement: false, animateInAsReplacement: false,
action: { [weak view, weak self] action in action: { [weak view, weak self] action in
if case .undo = action, let messageId { if case .undo = action, let messageId {
view?.navigateToPeer(peer: peer, chat: true, messageId: messageId) view?.navigateToPeer(peer: peer, chat: true, subject: isScheduled ? .scheduledMessages : .message(id: .id(messageId), highlight: false, timecode: nil))
} }
self?.tooltipScreen = nil self?.tooltipScreen = nil
view?.updateIsProgressPaused() view?.updateIsProgressPaused()
@ -360,12 +365,110 @@ final class StoryItemSetContainerSendMessage {
view.updateIsProgressPaused() view.updateIsProgressPaused()
} }
func presentSendMessageOptions(view: StoryItemSetContainerComponent.View) { func presentSendMessageOptions(view: StoryItemSetContainerComponent.View, sourceView: UIView, gesture: ContextGesture?) {
guard let component = view.component, let controller = component.controller() as? StoryContainerScreen else {
return
}
view.dismissAllTooltips()
var sendWhenOnlineAvailable = false
if let presence = component.slice.additionalPeerData.presence, case .present = presence.status {
sendWhenOnlineAvailable = true
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendSilently, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self, weak view] _, a in
a(.default)
guard let self, let view else {
return
}
self.performSendMessageAction(view: view, silentPosting: true)
})))
if sendWhenOnlineAvailable {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendWhenOnline, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/WhenOnlineIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self, weak view] _, a in
a(.default)
guard let self, let view else {
return
}
self.performSendMessageAction(view: view, scheduleTime: scheduleWhenOnlineTimestamp)
})))
}
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self, weak view] _, a in
a(.default)
guard let self, let view else {
return
}
self.presentScheduleTimePicker(view: view)
})))
let contextItems = ContextController.Items(content: .list(items))
let contextController = ContextController(account: component.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, position: .top)), items: .single(contextItems), gesture: gesture)
contextController.dismissed = { [weak view] in
guard let view else {
return
}
view.contextController = nil
view.updateIsProgressPaused()
}
view.contextController = contextController
view.updateIsProgressPaused()
controller.present(contextController, in: .window(.root))
}
func presentScheduleTimePicker(
view: StoryItemSetContainerComponent.View
) {
guard let component = view.component else {
return
}
let focusedItem = component.slice.item
guard let peerId = focusedItem.peerId else {
return
}
let controller = component.controller() as? StoryContainerScreen
var sendWhenOnlineAvailable = false
if let presence = component.slice.additionalPeerData.presence, case .present = presence.status {
sendWhenOnlineAvailable = true
}
let timeController = ChatScheduleTimeController(context: component.context, updatedPresentationData: nil, peerId: peerId, mode: .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable), style: .media, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak self, weak view] time in
guard let self, let view else {
return
}
self.performSendMessageAction(view: view, scheduleTime: time)
})
timeController.dismissed = { [weak self, weak view] in
guard let self, let view else {
return
}
self.actionSheet = nil
view.updateIsProgressPaused()
}
view.endEditing(true)
controller?.present(timeController, in: .window(.root))
self.actionSheet = timeController
view.updateIsProgressPaused()
} }
func performSendMessageAction( func performSendMessageAction(
view: StoryItemSetContainerComponent.View view: StoryItemSetContainerComponent.View,
silentPosting: Bool = false,
scheduleTime: Int32? = nil
) { ) {
guard let component = view.component else { guard let component = view.component else {
return return
@ -406,11 +509,13 @@ final class StoryItemSetContainerSendMessage {
to: peerId, to: peerId,
replyTo: nil, replyTo: nil,
storyId: focusedStoryId, storyId: focusedStoryId,
content: .text(text.string, entities) content: .text(text.string, entities),
silentPosting: silentPosting,
scheduleTime: scheduleTime
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in ) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
Queue.mainQueue().after(0.3) { Queue.mainQueue().after(0.3) {
if let self, let view { if let self, let view {
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }) self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }, isScheduled: scheduleTime != nil)
} }
} }
}) })
@ -2181,7 +2286,7 @@ final class StoryItemSetContainerSendMessage {
|> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
Queue.mainQueue().after(0.3) { Queue.mainQueue().after(0.3) {
if let view { if let view {
self?.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }) self?.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }, isScheduled: scheduleTime != nil)
} }
} }
}) })

View File

@ -78,7 +78,6 @@ private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {
case translationState = 10 case translationState = 10
case storySource = 11 case storySource = 11
case mediaEditorState = 12 case mediaEditorState = 12
case cameraState = 13
} }
public struct ApplicationSpecificItemCacheCollectionId { public struct ApplicationSpecificItemCacheCollectionId {
@ -93,7 +92,6 @@ public struct ApplicationSpecificItemCacheCollectionId {
public static let translationState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.translationState.rawValue) public static let translationState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.translationState.rawValue)
public static let storySource = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.storySource.rawValue) public static let storySource = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.storySource.rawValue)
public static let mediaEditorState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.mediaEditorState.rawValue) public static let mediaEditorState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.mediaEditorState.rawValue)
public static let cameraState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cameraState.rawValue)
} }
private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 { private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {

View File

@ -542,7 +542,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
animationSpacing = 8.0 animationSpacing = 8.0
} }
let containerWidth = max(100.0, min(layout.size.width, 614.0) - (sideInset + layout.safeInsets.left) * 2.0) let containerWidth = max(100.0, min(layout.size.width, 614.0) - sideInset * 2.0)
var actionSize: CGSize = .zero var actionSize: CGSize = .zero
@ -652,11 +652,12 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
self.arrowNode.frame = arrowBounds self.arrowNode.frame = arrowBounds
self.arrowGradientNode?.frame = CGRect(origin: CGPoint(x: -arrowFrame.minX + backgroundFrame.minX, y: 0.0), size: backgroundFrame.size) self.arrowGradientNode?.frame = CGRect(origin: CGPoint(x: -arrowFrame.minX + backgroundFrame.minX, y: 0.0), size: backgroundFrame.size)
case .right: case .right:
arrowFrame = CGRect(origin: CGPoint(x: backgroundFrame.width + arrowSize.height, y: rect.midY), size: CGSize(width: arrowSize.height, height: arrowSize.width)) let arrowCenterY = floorToScreenPixels(rect.midY - arrowSize.height / 2.0)
arrowFrame = CGRect(origin: CGPoint(x: backgroundFrame.width + arrowSize.height, y: self.view.convert(CGPoint(x: 0.0, y: arrowCenterY), to: self.arrowContainer.supernode?.view).y), size: CGSize(width: arrowSize.height, height: arrowSize.width))
ContainedViewLayoutTransition.immediate.updateTransformRotation(node: self.arrowContainer, angle: -CGFloat.pi / 2.0) ContainedViewLayoutTransition.immediate.updateTransformRotation(node: self.arrowContainer, angle: -CGFloat.pi / 2.0)
transition.updateFrame(node: self.arrowContainer, frame: arrowFrame.offsetBy(dx: 8.0 - UIScreenPixel, dy: 16.0 + -backgroundFrame.minY - floorToScreenPixels((backgroundFrame.height + 20.0 - arrowSize.width) / 2.0))) transition.updateFrame(node: self.arrowContainer, frame: arrowFrame.offsetBy(dx: 8.0 - UIScreenPixel, dy: 0.0))
let arrowBounds = CGRect(origin: .zero, size: arrowSize) let arrowBounds = CGRect(origin: .zero, size: arrowSize)
self.arrowNode.frame = arrowBounds self.arrowNode.frame = arrowBounds