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:
posting = .disabled
}
posting = .enabled
return StoriesConfiguration(posting: posting)
} else {
return .defaultValue

View File

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

View File

@ -76,24 +76,9 @@ public class CameraSimplePreviewView: UIView {
super.init(frame: frame)
self.videoPreviewLayer.videoGravity = main ? .resizeAspectFill : .resizeAspect
self.placeholderView.contentMode = main ? .scaleAspectFill : .scaleAspectFit
self.addSubview(self.placeholderView)
if main {
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)
}
}
}
self.addSubview(self.placeholderView)
}
required init?(coder: NSCoder) {

View File

@ -2586,7 +2586,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
context: context,
account: context.account,
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),
icon: .none,
location: .point(location, .top),

View File

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

View File

@ -642,7 +642,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
#endif
if case .notDetermined = cameraAccess, !self.requestedCameraAccess {
if !stories, case .notDetermined = cameraAccess, !self.requestedCameraAccess {
self.requestedCameraAccess = true
self.mediaAssetsContext.requestCameraAccess()
}
@ -1301,8 +1301,15 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
if let bannedSendPhotos = self.controller?.bannedSendPhotos, let bannedSendVideos = self.controller?.bannedSendVideos {
bannedSendMedia = (max(bannedSendPhotos.0, bannedSendVideos.0), bannedSendPhotos.1 || bannedSendVideos.1)
}
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
let placeholderNode: MediaPickerPlaceholderNode
if let current = self.placeholderNode {
@ -1326,7 +1333,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
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)
} else if let placeholderNode = self.placeholderNode, bannedSendMedia == nil {
self.placeholderNode = nil

View File

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

View File

@ -391,7 +391,7 @@ private final class CameraScreenComponent: CombinedComponent {
self.lastDualCameraTimestamp = currentTimestamp
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
camera.setDualCameraEnabled(isEnabled)
@ -772,7 +772,7 @@ private final class CameraScreenComponent: CombinedComponent {
transition: .immediate
)
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))
.disappear(.default(scale: true))
)
@ -1133,13 +1133,7 @@ public class CameraScreen: ViewController {
} else if dualCamWasEnabled != isDualCameraEnabled {
self.requestUpdateLayout(hasAppeared: self.hasAppeared, transition: .spring(duration: 0.4))
updateCameraStoredStateInteractively(engine: self.context.engine) { current in
if let current {
return current.withIsDualCameraEnabled(isDualCameraEnabled)
} else {
return CameraStoredState(isDualCameraEnabled: isDualCameraEnabled, dualCameraPosition: self.pipPosition)
}
}
UserDefaults.standard.set(isDualCameraEnabled as NSNumber, forKey: "TelegramStoryCameraIsDualEnabled")
}
}
}
@ -1149,9 +1143,9 @@ public class CameraScreen: ViewController {
self.context = controller.context
self.updateState = ActionSlot<CameraState>()
self.toggleCameraPositionAction = ActionSlot<Void>()
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.backgroundView = UIView()
self.backgroundView.backgroundColor = UIColor(rgb: 0x000000)
@ -1170,6 +1164,22 @@ public class CameraScreen: ViewController {
self.previewBlurView = BlurView()
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.clipsToBounds = true
self.mainPreviewView = CameraSimplePreviewView(frame: .zero, main: true)
@ -1177,12 +1187,13 @@ public class CameraScreen: ViewController {
self.additionalPreviewContainerView = UIView()
self.additionalPreviewContainerView.clipsToBounds = true
self.additionalPreviewView = CameraSimplePreviewView(frame: .zero, main: false)
var cameraFrontPosition = false
if let useCameraFrontPosition = UserDefaults.standard.object(forKey: "TelegramStoryCameraUseFrontPosition") as? NSNumber, useCameraFrontPosition.boolValue {
cameraFrontPosition = true
if isDualCameraEnabled {
self.mainPreviewView.resetPlaceholder(front: false)
self.additionalPreviewView.resetPlaceholder(front: true)
} else {
self.mainPreviewView.resetPlaceholder(front: cameraFrontPosition)
}
self.mainPreviewView.resetPlaceholder(front: cameraFrontPosition)
self.cameraState = CameraState(
mode: .photo,
@ -1191,7 +1202,7 @@ public class CameraScreen: ViewController {
flashModeDidChange: false,
recording: .none,
duration: 0.0,
isDualCameraEnabled: false
isDualCameraEnabled: isDualCameraEnabled
)
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())
}
@ -1313,6 +1353,7 @@ public class CameraScreen: ViewController {
configuration: Camera.Configuration(
preset: .hd1920x1080,
position: self.cameraState.position,
isDualEnabled: self.cameraState.isDualCameraEnabled,
audio: true,
photo: true,
metadata: false,
@ -1341,6 +1382,7 @@ public class CameraScreen: ViewController {
}
})
var isFirstTime = true
self.changingPositionDisposable = combineLatest(
queue: Queue.mainQueue(),
camera.modeChange,
@ -1397,9 +1439,13 @@ public class CameraScreen: ViewController {
})
}
if self.cameraState.isDualCameraEnabled {
self.mainPreviewView.removePlaceholder()
self.additionalPreviewView.removePlaceholder()
if isFirstTime {
isFirstTime = false
} 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.containerLayoutUpdated(layout: layout, transition: .spring(duration: 0.4))
updateCameraStoredStateInteractively(engine: self.context.engine) { current in
if let current {
return current.withDualCameraPosition(self.pipPosition)
} else {
return CameraStoredState(isDualCameraEnabled: true, dualCameraPosition: self.pipPosition)
}
}
UserDefaults.standard.set(self.pipPosition.rawValue as NSNumber, forKey: "TelegramStoryCameraDualPosition")
default:
break
}
@ -1797,13 +1837,12 @@ public class CameraScreen: ViewController {
}
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 = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.maxY + 3.0), size: CGSize())
let location = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
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) {
let _ = ApplicationSpecificNotice.incrementStoriesDualCameraTip(accountManager: accountManager).start()
let _ = ApplicationSpecificNotice.incrementStoriesDualCameraTip(accountManager: accountManager, count: 2).start()
return .dismiss(consume: true)
}
return .ignore
@ -1846,7 +1885,7 @@ public class CameraScreen: ViewController {
guard let self else {
return
}
if count < 1 {
if count < 2 {
self.presentDualCameraTooltip()
}
})
@ -2040,7 +2079,7 @@ public class CameraScreen: ViewController {
self.appliedDualCamera = isDualCameraEnabled
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
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 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) {
self.context = context
self.peerId = peerId
@ -105,6 +107,7 @@ public final class ChatScheduleTimeController: ViewController {
}
override public func dismiss(completion: (() -> Void)? = nil) {
self.dismissed()
self.controllerNode.animateOut(completion: completion)
}

View File

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

View File

@ -1117,7 +1117,7 @@ final class MediaEditorScreenComponent: Component {
}
self.deactivateInput()
},
sendMessageOptionsAction: { },
sendMessageOptionsAction: nil,
sendStickerAction: { _ in },
setMediaRecordingActive: nil,
lockMediaRecording: nil,
@ -2797,6 +2797,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
private var drawingScreen: DrawingScreen?
private var stickerScreen: StickerPickerScreen?
private var defaultToEmoji = false
private var previousDrawingData: Data?
private var previousDrawingEntities: [DrawingEntity]?
@ -2895,7 +2896,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
switch mode {
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
if let self {
if let content {
@ -2905,8 +2907,17 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.hasAnyChanges = true
self.controller?.isSavingAvailable = true
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.mediaEditor?.play()
}
}
controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in
@ -3140,7 +3151,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
case bottomRight
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 {
case .topLeft:
return CGPoint(x: offset.x, y: offset.y)

View File

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

View File

@ -46,7 +46,7 @@ public final class MessageInputActionButtonComponent: Component {
public let mode: Mode
public let action: (Mode, Action, Bool) -> Void
public let longPressAction: () -> Void
public let longPressAction: ((UIView, ContextGesture?) -> Void)?
public let switchMediaInputMode: () -> Void
public let updateMediaCancelFraction: (CGFloat) -> Void
public let lockMediaRecording: () -> Void
@ -62,7 +62,7 @@ public final class MessageInputActionButtonComponent: Component {
public init(
mode: Mode,
action: @escaping (Mode, Action, Bool) -> Void,
longPressAction: @escaping () -> Void,
longPressAction: ((UIView, ContextGesture?) -> Void)?,
switchMediaInputMode: @escaping () -> Void,
updateMediaCancelFraction: @escaping (CGFloat) -> Void,
lockMediaRecording: @escaping () -> Void,
@ -113,9 +113,14 @@ public final class MessageInputActionButtonComponent: Component {
return true
}
public final class View: HighlightTrackingButton {
public final class View: UIView {
private var micButton: ChatTextInputMediaRecordingButton?
public let button: HighlightTrackingButtonNode
public let referenceNode: ContextReferenceContentNode
public let containerNode: ContextControllerSourceNode
private let sendIconView: UIImageView
private var moreButton: MoreHeaderButton?
private var component: MessageInputActionButtonComponent?
@ -124,13 +129,31 @@ public final class MessageInputActionButtonComponent: Component {
override init(frame: CGRect) {
self.sendIconView = UIImageView()
self.button = HighlightTrackingButtonNode()
self.referenceNode = ContextReferenceContentNode()
self.containerNode = ContextControllerSourceNode()
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.addSubview(self.sendIconView)
self.highligthedChanged = { [weak self] highlighted in
self.button.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
}
@ -141,8 +164,10 @@ public final class MessageInputActionButtonComponent: Component {
transition.setSublayerTransform(view: self, transform: CATransform3DMakeScale(scale, scale, 1.0))
}
self.addTarget(self, action: #selector(self.touchDown), for: .touchDown)
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
self.button.addTarget(self, action: #selector(self.touchDown), forControlEvents: .touchDown)
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) {
@ -162,10 +187,11 @@ public final class MessageInputActionButtonComponent: Component {
}
component.action(component.mode, .up, false)
}
override public func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
return super.continueTracking(touch, with: event)
}
// public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// let result = super.hitTest(point, with: event)
// return result
// }
func update(component: MessageInputActionButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let previousComponent = self.component
@ -174,6 +200,8 @@ public final class MessageInputActionButtonComponent: Component {
let themeUpdated = previousComponent?.theme !== component.theme
self.containerNode.isUserInteractionEnabled = component.longPressAction != nil
if self.micButton == nil {
let micButton = ChatTextInputMediaRecordingButton(
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)
self.moreButton = 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.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 presentInGlobalOverlay: (ViewController) -> Void
public let sendMessageAction: () -> Void
public let sendMessageOptionsAction: () -> Void
public let sendMessageOptionsAction: ((UIView, ContextGesture?) -> Void)?
public let sendStickerAction: (TelegramMediaFile) -> Void
public let setMediaRecordingActive: ((Bool, Bool, Bool) -> Void)?
public let lockMediaRecording: (() -> Void)?
@ -114,7 +114,7 @@ public final class MessageInputPanelComponent: Component {
presentController: @escaping (ViewController) -> Void,
presentInGlobalOverlay: @escaping (ViewController) -> Void,
sendMessageAction: @escaping () -> Void,
sendMessageOptionsAction: @escaping () -> Void,
sendMessageOptionsAction: ((UIView, ContextGesture?) -> Void)?,
sendStickerAction: @escaping (TelegramMediaFile) -> Void,
setMediaRecordingActive: ((Bool, Bool, Bool) -> Void)?,
lockMediaRecording: (() -> Void)?,
@ -747,7 +747,7 @@ public final class MessageInputPanelComponent: Component {
break
}
},
longPressAction: {},
longPressAction: nil,
switchMediaInputMode: {
},
updateMediaCancelFraction: { _ in
@ -926,12 +926,7 @@ public final class MessageInputPanelComponent: Component {
break
}
},
longPressAction: { [weak self] in
guard let self, let component = self.component else {
return
}
component.sendMessageOptionsAction()
},
longPressAction: component.sendMessageOptionsAction,
switchMediaInputMode: { [weak self] in
guard let self else {
return

View File

@ -52,6 +52,7 @@ public final class StoryContentContextImpl: StoryContentContext {
PostboxViewKey.cachedPeerData(peerId: peerId),
PostboxViewKey.storiesState(key: .peer(peerId)),
PostboxViewKey.storyItems(peerId: peerId),
PostboxViewKey.peerPresences(peerIds: Set([peerId]))
]
if peerId == context.account.peerId {
inputKeys.append(PostboxViewKey.storiesState(key: .local))
@ -112,6 +113,11 @@ public final class StoryContentContextImpl: StoryContentContext {
return
}
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 {
var isMuted = false
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
@ -119,9 +125,17 @@ public final class StoryContentContextImpl: StoryContentContext {
} else {
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 {
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)
@ -923,6 +937,7 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
self.storyDisposable = (combineLatest(queue: .mainQueue(),
context.engine.data.subscribe(
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.NotificationSettings(id: storyId.peerId),
TelegramEngine.EngineData.Item.NotificationSettings.Global()
@ -960,18 +975,19 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
return
}
let (peer, areVoiceMessagesAvailable, notificationSettings, globalNotificationSettings) = data
let (peer, presence, areVoiceMessagesAvailable, notificationSettings, globalNotificationSettings) = data
let (item, peers, allEntityFiles) = itemAndPeers
guard let peer else {
return
}
let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings())
let additionalPeerData = StoryContentContextState.AdditionalPeerData(
isMuted: isMuted,
areVoiceMessagesAvailable: areVoiceMessagesAvailable
areVoiceMessagesAvailable: areVoiceMessagesAvailable,
presence: presence
)
if item == nil {
@ -1098,6 +1114,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
self.storyDisposable = (combineLatest(queue: .mainQueue(),
context.engine.data.subscribe(
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.NotificationSettings(id: peerId),
TelegramEngine.EngineData.Item.NotificationSettings.Global()
@ -1111,7 +1128,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
return
}
let (peer, areVoiceMessagesAvailable, notificationSettings, globalNotificationSettings) = data
let (peer, presence, areVoiceMessagesAvailable, notificationSettings, globalNotificationSettings) = data
guard let peer else {
return
@ -1121,7 +1138,8 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
let additionalPeerData = StoryContentContextState.AdditionalPeerData(
isMuted: isMuted,
areVoiceMessagesAvailable: areVoiceMessagesAvailable
areVoiceMessagesAvailable: areVoiceMessagesAvailable,
presence: presence
)
self.listState = state

View File

@ -113,19 +113,31 @@ public final class StoryContentItem: Equatable {
public final class StoryContentContextState {
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 areVoiceMessagesAvailable: Bool
public let presence: EnginePeer.Presence?
public init(
isMuted: Bool,
areVoiceMessagesAvailable: Bool
areVoiceMessagesAvailable: Bool,
presence: EnginePeer.Presence?
) {
self.isMuted = isMuted
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)
},
sendMessageOptionsAction: { [weak self] in
sendMessageOptionsAction: { [weak self] sourceView, gesture in
guard let self else {
return
}
self.sendMessageContext.presentSendMessageOptions(view: self)
self.sendMessageContext.presentSendMessageOptions(view: self, sourceView: sourceView, gesture: gesture)
},
sendStickerAction: { [weak self] sticker in
guard let self else {
@ -2231,7 +2231,7 @@ public final class StoryItemSetContainerComponent: Component {
mode: .more,
action: { _, _, _ in
},
longPressAction: {},
longPressAction: nil,
switchMediaInputMode: {
},
updateMediaCancelFraction: { _ in
@ -2838,7 +2838,7 @@ public final class StoryItemSetContainerComponent: Component {
presentationData: presentationData,
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 {
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,
@ -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 {
return
}
@ -3189,11 +3189,7 @@ public final class StoryItemSetContainerComponent: Component {
guard let navigationController = controller.navigationController as? NavigationController else {
return
}
if messageId != nil || chat {
var subject: ChatControllerSubject?
if let messageId {
subject = .message(id: .id(messageId), highlight: false, timecode: nil)
}
if subject != nil || chat {
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 {
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 {
return
}
component.controller()?.forEachController { c in
controller.forEachController { c in
if let c = c as? UndoOverlayController {
c.dismiss()
} else if let c = c as? TooltipScreen {
@ -3550,6 +3546,14 @@ public final class StoryItemSetContainerComponent: Component {
}
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)
var items: [ContextMenuItem] = []
@ -3753,7 +3757,7 @@ public final class StoryItemSetContainerComponent: Component {
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
guard let self else {
return
@ -3779,12 +3783,7 @@ public final class StoryItemSetContainerComponent: Component {
return
}
component.controller()?.forEachController { c in
if let c = c as? UndoOverlayController {
c.dismiss()
}
return true
}
self.dismissAllTooltips()
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
var items: [ContextMenuItem] = []
@ -3976,7 +3975,7 @@ public final class StoryItemSetContainerComponent: Component {
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
guard let self else {
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 sourceView: UIView
private let position: ContextControllerReferenceViewInfo.ActionsPosition
var keepInPlace: Bool {
return true
}
init(controller: ViewController, sourceView: UIView) {
init(controller: ViewController, sourceView: UIView, position: ContextControllerReferenceViewInfo.ActionsPosition) {
self.controller = controller
self.sourceView = sourceView
self.position = position
}
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 MediaPasteboardUI
import WebPBinding
import ContextUI
import ChatScheduleTimeController
final class StoryItemSetContainerSendMessage {
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 {
return
}
@ -341,14 +343,17 @@ final class StoryItemSetContainerSendMessage {
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let text = isScheduled ? "Message Scheduled" : "Message Sent"
let tooltipScreen = UndoOverlayController(
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,
animateInAsReplacement: false,
action: { [weak view, weak self] action in
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
view?.updateIsProgressPaused()
@ -360,12 +365,110 @@ final class StoryItemSetContainerSendMessage {
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(
view: StoryItemSetContainerComponent.View
view: StoryItemSetContainerComponent.View,
silentPosting: Bool = false,
scheduleTime: Int32? = nil
) {
guard let component = view.component else {
return
@ -406,11 +509,13 @@ final class StoryItemSetContainerSendMessage {
to: peerId,
replyTo: nil,
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
Queue.mainQueue().after(0.3) {
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
Queue.mainQueue().after(0.3) {
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 storySource = 11
case mediaEditorState = 12
case cameraState = 13
}
public struct ApplicationSpecificItemCacheCollectionId {
@ -93,7 +92,6 @@ public struct ApplicationSpecificItemCacheCollectionId {
public static let translationState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.translationState.rawValue)
public static let storySource = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.storySource.rawValue)
public static let mediaEditorState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.mediaEditorState.rawValue)
public static let cameraState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cameraState.rawValue)
}
private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {

View File

@ -542,7 +542,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
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
@ -652,11 +652,12 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
self.arrowNode.frame = arrowBounds
self.arrowGradientNode?.frame = CGRect(origin: CGPoint(x: -arrowFrame.minX + backgroundFrame.minX, y: 0.0), size: backgroundFrame.size)
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)
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)
self.arrowNode.frame = arrowBounds