Various fixes

This commit is contained in:
Ilya Laktyushin 2023-06-30 18:25:53 +02:00
parent 7b741d20a6
commit 7551bdc55e
15 changed files with 245 additions and 56 deletions

View File

@ -11,7 +11,6 @@ import AddressBook
import UserNotifications import UserNotifications
import CoreTelephony import CoreTelephony
import TelegramPresentationData import TelegramPresentationData
import LegacyComponents
import AccountContext import AccountContext
public enum DeviceAccessCameraSubject { public enum DeviceAccessCameraSubject {
@ -88,7 +87,7 @@ public final class DeviceAccess {
} }
public static func isCameraAccessAuthorized() -> Bool { public static func isCameraAccessAuthorized() -> Bool {
return PGCamera.cameraAuthorizationStatus() == PGCameraAuthorizationStatusAuthorized return AVCaptureDevice.authorizationStatus(for: .video) == .authorized
} }
public static func authorizationStatus(applicationInForeground: Signal<Bool, NoError>? = nil, siriAuthorization: (() -> AccessType)? = nil, subject: DeviceAccessSubject) -> Signal<AccessType, NoError> { public static func authorizationStatus(applicationInForeground: Signal<Bool, NoError>? = nil, siriAuthorization: (() -> AccessType)? = nil, subject: DeviceAccessSubject) -> Signal<AccessType, NoError> {
@ -257,8 +256,8 @@ public final class DeviceAccess {
public static func authorizeAccess(to subject: DeviceAccessSubject, onlyCheck: Bool = false, registerForNotifications: ((@escaping (Bool) -> Void) -> Void)? = nil, requestSiriAuthorization: ((@escaping (Bool) -> Void) -> Void)? = nil, locationManager: LocationManager? = nil, presentationData: PresentationData? = nil, present: @escaping (ViewController, Any?) -> Void = { _, _ in }, openSettings: @escaping () -> Void = { }, displayNotificationFromBackground: @escaping (String) -> Void = { _ in }, _ completion: @escaping (Bool) -> Void = { _ in }) { public static func authorizeAccess(to subject: DeviceAccessSubject, onlyCheck: Bool = false, registerForNotifications: ((@escaping (Bool) -> Void) -> Void)? = nil, requestSiriAuthorization: ((@escaping (Bool) -> Void) -> Void)? = nil, locationManager: LocationManager? = nil, presentationData: PresentationData? = nil, present: @escaping (ViewController, Any?) -> Void = { _, _ in }, openSettings: @escaping () -> Void = { }, displayNotificationFromBackground: @escaping (String) -> Void = { _ in }, _ completion: @escaping (Bool) -> Void = { _ in }) {
switch subject { switch subject {
case let .camera(cameraSubject): case let .camera(cameraSubject):
let status = PGCamera.cameraAuthorizationStatus() let status = AVCaptureDevice.authorizationStatus(for: .video)
if status == PGCameraAuthorizationStatusNotDetermined { if case .notDetermined = status {
if !onlyCheck { if !onlyCheck {
AVCaptureDevice.requestAccess(for: AVMediaType.video) { response in AVCaptureDevice.requestAccess(for: AVMediaType.video) { response in
Queue.mainQueue().async { Queue.mainQueue().async {
@ -282,9 +281,9 @@ public final class DeviceAccess {
} else { } else {
completion(true) completion(true)
} }
} else if status == PGCameraAuthorizationStatusRestricted || status == PGCameraAuthorizationStatusDenied, let presentationData = presentationData { } else if [.restricted, .denied].contains(status), let presentationData = presentationData {
let text: String let text: String
if status == PGCameraAuthorizationStatusRestricted { if case .restricted = status {
text = presentationData.strings.AccessDenied_CameraRestricted text = presentationData.strings.AccessDenied_CameraRestricted
} else { } else {
switch cameraSubject { switch cameraSubject {
@ -300,7 +299,7 @@ public final class DeviceAccess {
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
openSettings() openSettings()
})]), nil) })]), nil)
} else if status == PGCameraAuthorizationStatusAuthorized { } else if case .authorized = status {
completion(true) completion(true)
} else { } else {
assertionFailure() assertionFailure()

View File

@ -611,3 +611,26 @@ extension Api.EncryptedMessage {
} }
} }
} }
extension Api.InputMedia {
func withUpdatedStickers(_ stickers: [Api.InputDocument]?) -> Api.InputMedia {
switch self {
case let .inputMediaUploadedDocument(flags, file, thumb, mimeType, attributes, _, ttlSeconds):
var flags = flags
var attributes = attributes
if let _ = stickers {
flags |= (1 << 0)
attributes.append(.documentAttributeHasStickers)
}
return .inputMediaUploadedDocument(flags: flags, file: file, thumb: thumb, mimeType: mimeType, attributes: attributes, stickers: stickers, ttlSeconds: ttlSeconds)
case let .inputMediaUploadedPhoto(flags, file, _, ttlSeconds):
var flags = flags
if let _ = stickers {
flags |= (1 << 0)
}
return .inputMediaUploadedPhoto(flags: flags, file: file, stickers: stickers, ttlSeconds: ttlSeconds)
default:
return self
}
}
}

View File

@ -11,6 +11,7 @@ public extension Stories {
case media case media
case text case text
case entities case entities
case embeddedStickers
case pin case pin
case privacy case privacy
case isForwardingDisabled case isForwardingDisabled
@ -23,6 +24,7 @@ public extension Stories {
public let media: Media public let media: Media
public let text: String public let text: String
public let entities: [MessageTextEntity] public let entities: [MessageTextEntity]
public let embeddedStickers: [TelegramMediaFile]
public let pin: Bool public let pin: Bool
public let privacy: EngineStoryPrivacy public let privacy: EngineStoryPrivacy
public let isForwardingDisabled: Bool public let isForwardingDisabled: Bool
@ -35,6 +37,7 @@ public extension Stories {
media: Media, media: Media,
text: String, text: String,
entities: [MessageTextEntity], entities: [MessageTextEntity],
embeddedStickers: [TelegramMediaFile],
pin: Bool, pin: Bool,
privacy: EngineStoryPrivacy, privacy: EngineStoryPrivacy,
isForwardingDisabled: Bool, isForwardingDisabled: Bool,
@ -46,6 +49,7 @@ public extension Stories {
self.media = media self.media = media
self.text = text self.text = text
self.entities = entities self.entities = entities
self.embeddedStickers = embeddedStickers
self.pin = pin self.pin = pin
self.privacy = privacy self.privacy = privacy
self.isForwardingDisabled = isForwardingDisabled self.isForwardingDisabled = isForwardingDisabled
@ -64,6 +68,11 @@ public extension Stories {
self.text = try container.decode(String.self, forKey: .text) self.text = try container.decode(String.self, forKey: .text)
self.entities = try container.decode([MessageTextEntity].self, forKey: .entities) self.entities = try container.decode([MessageTextEntity].self, forKey: .entities)
let stickersData = try container.decode(Data.self, forKey: .embeddedStickers)
let stickersDecoder = PostboxDecoder(buffer: MemoryBuffer(data: stickersData))
self.embeddedStickers = (try? stickersDecoder.decodeObjectArrayWithCustomDecoderForKey("stickers", decoder: { TelegramMediaFile(decoder: $0) })) ?? []
self.pin = try container.decode(Bool.self, forKey: .pin) self.pin = try container.decode(Bool.self, forKey: .pin)
self.privacy = try container.decode(EngineStoryPrivacy.self, forKey: .privacy) self.privacy = try container.decode(EngineStoryPrivacy.self, forKey: .privacy)
self.isForwardingDisabled = try container.decodeIfPresent(Bool.self, forKey: .isForwardingDisabled) ?? false self.isForwardingDisabled = try container.decodeIfPresent(Bool.self, forKey: .isForwardingDisabled) ?? false
@ -83,6 +92,11 @@ public extension Stories {
try container.encode(self.text, forKey: .text) try container.encode(self.text, forKey: .text)
try container.encode(self.entities, forKey: .entities) try container.encode(self.entities, forKey: .entities)
let stickersEncoder = PostboxEncoder()
stickersEncoder.encodeObjectArray(self.embeddedStickers, forKey: "stickers")
try container.encode(stickersEncoder.makeData(), forKey: .embeddedStickers)
try container.encode(self.pin, forKey: .pin) try container.encode(self.pin, forKey: .pin)
try container.encode(self.privacy, forKey: .privacy) try container.encode(self.privacy, forKey: .privacy)
try container.encode(self.isForwardingDisabled, forKey: .isForwardingDisabled) try container.encode(self.isForwardingDisabled, forKey: .isForwardingDisabled)
@ -270,7 +284,7 @@ final class PendingStoryManager {
self.currentPendingItemContext = pendingItemContext self.currentPendingItemContext = pendingItemContext
let stableId = firstItem.stableId let stableId = firstItem.stableId
pendingItemContext.disposable = (_internal_uploadStoryImpl(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.revalidationContext, auxiliaryMethods: self.auxiliaryMethods, stableId: stableId, media: firstItem.media, text: firstItem.text, entities: firstItem.entities, pin: firstItem.pin, privacy: firstItem.privacy, isForwardingDisabled: firstItem.isForwardingDisabled, period: Int(firstItem.period), randomId: firstItem.randomId) pendingItemContext.disposable = (_internal_uploadStoryImpl(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.revalidationContext, auxiliaryMethods: self.auxiliaryMethods, stableId: stableId, media: firstItem.media, text: firstItem.text, entities: firstItem.entities, embeddedStickers: firstItem.embeddedStickers, pin: firstItem.pin, privacy: firstItem.privacy, isForwardingDisabled: firstItem.isForwardingDisabled, period: Int(firstItem.period), randomId: firstItem.randomId)
|> deliverOn(self.queue)).start(next: { [weak self] event in |> deliverOn(self.queue)).start(next: { [weak self] event in
guard let `self` = self else { guard let `self` = self else {
return return

View File

@ -4,8 +4,15 @@ import Postbox
import TelegramApi import TelegramApi
public enum EngineStoryInputMedia { public enum EngineStoryInputMedia {
case image(dimensions: PixelDimensions, data: Data) case image(dimensions: PixelDimensions, data: Data, stickers: [TelegramMediaFile])
case video(dimensions: PixelDimensions, duration: Double, resource: TelegramMediaResource, firstFrameImageData: Data?) case video(dimensions: PixelDimensions, duration: Double, resource: TelegramMediaResource, firstFrameImageData: Data?, stickers: [TelegramMediaFile])
var embeddedStickers: [TelegramMediaFile] {
switch self {
case let .image(_, _, stickers), let .video(_, _, _, _, stickers):
return stickers
}
}
} }
public struct EngineStoryPrivacy: Codable, Equatable { public struct EngineStoryPrivacy: Codable, Equatable {
@ -567,7 +574,7 @@ public enum StoryUploadResult {
private func prepareUploadStoryContent(account: Account, media: EngineStoryInputMedia) -> Media { private func prepareUploadStoryContent(account: Account, media: EngineStoryInputMedia) -> Media {
switch media { switch media {
case let .image(dimensions, data): case let .image(dimensions, data, _):
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
account.postbox.mediaBox.storeResourceData(resource.id, data: data) account.postbox.mediaBox.storeResourceData(resource.id, data: data)
@ -580,7 +587,7 @@ private func prepareUploadStoryContent(account: Account, media: EngineStoryInput
flags: [] flags: []
) )
return imageMedia return imageMedia
case let .video(dimensions, duration, resource, firstFrameImageData): case let .video(dimensions, duration, resource, firstFrameImageData, _):
var previewRepresentations: [TelegramMediaImageRepresentation] = [] var previewRepresentations: [TelegramMediaImageRepresentation] = []
if let firstFrameImageData = firstFrameImageData { if let firstFrameImageData = firstFrameImageData {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
@ -709,6 +716,7 @@ func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, text:
media: inputMedia, media: inputMedia,
text: text, text: text,
entities: entities, entities: entities,
embeddedStickers: media.embeddedStickers,
pin: pin, pin: pin,
privacy: privacy, privacy: privacy,
isForwardingDisabled: isForwardingDisabled, isForwardingDisabled: isForwardingDisabled,
@ -757,7 +765,7 @@ private func _internal_putPendingStoryIdMapping(accountPeerId: PeerId, stableId:
} }
} }
func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, auxiliaryMethods: AccountAuxiliaryMethods, stableId: Int32, media: Media, text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) -> Signal<StoryUploadResult, NoError> { func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, auxiliaryMethods: AccountAuxiliaryMethods, stableId: Int32, media: Media, text: String, entities: [MessageTextEntity], embeddedStickers: [TelegramMediaFile], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) -> Signal<StoryUploadResult, NoError> {
let passFetchProgress = media is TelegramMediaFile let passFetchProgress = media is TelegramMediaFile
let (contentSignal, originalMedia) = uploadedStoryContent(postbox: postbox, network: network, media: media, accountPeerId: accountPeerId, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, auxiliaryMethods: auxiliaryMethods, passFetchProgress: passFetchProgress) let (contentSignal, originalMedia) = uploadedStoryContent(postbox: postbox, network: network, media: media, accountPeerId: accountPeerId, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, auxiliaryMethods: auxiliaryMethods, passFetchProgress: passFetchProgress)
return contentSignal return contentSignal
@ -802,6 +810,17 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId
flags |= 1 << 4 flags |= 1 << 4
} }
var inputMedia = inputMedia
if !embeddedStickers.isEmpty {
var stickersValue: [Api.InputDocument] = []
for file in embeddedStickers {
if let resource = file.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference {
stickersValue.append(Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)))
}
}
inputMedia = inputMedia.withUpdatedStickers(stickersValue)
}
return network.request(Api.functions.stories.sendStory( return network.request(Api.functions.stories.sendStory(
flags: flags, flags: flags,
media: inputMedia, media: inputMedia,

View File

@ -98,7 +98,7 @@ private final class CameraScreenComponent: CombinedComponent {
let hasAppeared: Bool let hasAppeared: Bool
let isVisible: Bool let isVisible: Bool
let panelWidth: CGFloat let panelWidth: CGFloat
let flipAnimationAction: ActionSlot<Void> let animateFlipAction: ActionSlot<Void>
let animateShutter: () -> Void let animateShutter: () -> Void
let present: (ViewController) -> Void let present: (ViewController) -> Void
let push: (ViewController) -> Void let push: (ViewController) -> Void
@ -112,7 +112,7 @@ private final class CameraScreenComponent: CombinedComponent {
hasAppeared: Bool, hasAppeared: Bool,
isVisible: Bool, isVisible: Bool,
panelWidth: CGFloat, panelWidth: CGFloat,
flipAnimationAction: ActionSlot<Void>, animateFlipAction: ActionSlot<Void>,
animateShutter: @escaping () -> Void, animateShutter: @escaping () -> Void,
present: @escaping (ViewController) -> Void, present: @escaping (ViewController) -> Void,
push: @escaping (ViewController) -> Void, push: @escaping (ViewController) -> Void,
@ -125,7 +125,7 @@ private final class CameraScreenComponent: CombinedComponent {
self.hasAppeared = hasAppeared self.hasAppeared = hasAppeared
self.isVisible = isVisible self.isVisible = isVisible
self.panelWidth = panelWidth self.panelWidth = panelWidth
self.flipAnimationAction = flipAnimationAction self.animateFlipAction = animateFlipAction
self.animateShutter = animateShutter self.animateShutter = animateShutter
self.present = present self.present = present
self.push = push self.push = push
@ -170,6 +170,10 @@ private final class CameraScreenComponent: CombinedComponent {
} }
} }
private var cameraAuthorizationStatus: AVAuthorizationStatus = .notDetermined
private var microphoneAuthorizationStatus: AVAuthorizationStatus = .notDetermined
private var galleryAuthorizationStatus: PHAuthorizationStatus = .notDetermined
private let context: AccountContext private let context: AccountContext
fileprivate let camera: Camera fileprivate let camera: Camera
private let present: (ViewController) -> Void private let present: (ViewController) -> Void
@ -652,7 +656,7 @@ private final class CameraScreenComponent: CombinedComponent {
} }
} }
let flipAnimationAction = component.flipAnimationAction let animateFlipAction = component.animateFlipAction
let captureControlsAvailableSize: CGSize let captureControlsAvailableSize: CGSize
if isTablet { if isTablet {
captureControlsAvailableSize = CGSize(width: panelWidth, height: availableSize.height) captureControlsAvailableSize = CGSize(width: panelWidth, height: availableSize.height)
@ -706,7 +710,7 @@ private final class CameraScreenComponent: CombinedComponent {
guard let state else { guard let state else {
return return
} }
state.togglePosition(flipAnimationAction) state.togglePosition(animateFlipAction)
}, },
galleryTapped: { galleryTapped: {
guard let controller = environment.controller() as? CameraScreen else { guard let controller = environment.controller() as? CameraScreen else {
@ -720,7 +724,7 @@ private final class CameraScreenComponent: CombinedComponent {
zoomUpdated: { fraction in zoomUpdated: { fraction in
state.updateZoom(fraction: fraction) state.updateZoom(fraction: fraction)
}, },
flipAnimationAction: flipAnimationAction flipAnimationAction: animateFlipAction
), ),
availableSize: captureControlsAvailableSize, availableSize: captureControlsAvailableSize,
transition: context.transition transition: context.transition
@ -743,14 +747,14 @@ private final class CameraScreenComponent: CombinedComponent {
id: "flip", id: "flip",
component: AnyComponent( component: AnyComponent(
FlipButtonContentComponent( FlipButtonContentComponent(
action: flipAnimationAction, action: animateFlipAction,
maskFrame: .zero maskFrame: .zero
) )
) )
), ),
minSize: CGSize(width: 44.0, height: 44.0), minSize: CGSize(width: 44.0, height: 44.0),
action: { action: {
state.togglePosition(flipAnimationAction) state.togglePosition(animateFlipAction)
} }
), ),
availableSize: availableSize, availableSize: availableSize,
@ -1036,7 +1040,7 @@ public class CameraScreen: ViewController {
private var pipPosition: PIPPosition = .bottomRight private var pipPosition: PIPPosition = .bottomRight
fileprivate var previewBlurPromise = ValuePromise<Bool>(false) fileprivate var previewBlurPromise = ValuePromise<Bool>(false)
private let flipAnimationAction = ActionSlot<Void>() private let animateFlipAction = ActionSlot<Void>()
fileprivate var cameraIsActive = true fileprivate var cameraIsActive = true
fileprivate var hasGallery = false fileprivate var hasGallery = false
@ -1738,6 +1742,7 @@ public class CameraScreen: ViewController {
self.hasAppeared = hasAppeared self.hasAppeared = hasAppeared
transition = transition.withUserData(CameraScreenTransition.finishedAnimateIn) transition = transition.withUserData(CameraScreenTransition.finishedAnimateIn)
// self.presentCameraTooltip() // self.presentCameraTooltip()
// self.presentDualCameraTooltip() // self.presentDualCameraTooltip()
} }
@ -1753,7 +1758,7 @@ public class CameraScreen: ViewController {
hasAppeared: self.hasAppeared, hasAppeared: self.hasAppeared,
isVisible: self.cameraIsActive && !self.hasGallery, isVisible: self.cameraIsActive && !self.hasGallery,
panelWidth: panelWidth, panelWidth: panelWidth,
flipAnimationAction: self.flipAnimationAction, animateFlipAction: self.animateFlipAction,
animateShutter: { [weak self] in animateShutter: { [weak self] in
self?.mainPreviewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) self?.mainPreviewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}, },

View File

@ -8,6 +8,7 @@ import LocalMediaResources
import CameraButtonComponent import CameraButtonComponent
enum ShutterButtonState: Equatable { enum ShutterButtonState: Equatable {
case disabled
case generic case generic
case video case video
case stopRecording case stopRecording
@ -162,7 +163,7 @@ private final class ShutterButtonContentComponent: Component {
let ringWidth: CGFloat = 3.0 let ringWidth: CGFloat = 3.0
var recordingProgress: Float? var recordingProgress: Float?
switch component.shutterState { switch component.shutterState {
case .generic: case .generic, .disabled:
innerColor = .white innerColor = .white
innerSize = CGSize(width: 60.0, height: 60.0) innerSize = CGSize(width: 60.0, height: 60.0)
ringSize = CGSize(width: 68.0, height: 68.0) ringSize = CGSize(width: 68.0, height: 68.0)
@ -986,7 +987,7 @@ final class CaptureControlsComponent: Component {
var blobState: ShutterBlobView.BlobState var blobState: ShutterBlobView.BlobState
switch component.shutterState { switch component.shutterState {
case .generic: case .generic, .disabled:
blobState = .generic blobState = .generic
case .video, .transition: case .video, .transition:
blobState = .video blobState = .video

View File

@ -134,9 +134,9 @@ final class MediaEditorComposer {
} }
private var filteredImage: CIImage? private var filteredImage: CIImage?
func processImage(inputImage: UIImage, pool: CVPixelBufferPool?, time: CMTime, completion: @escaping (CVPixelBuffer?, CMTime) -> Void) { func processImage(inputImage: UIImage, pool: CVPixelBufferPool?, time: CMTime, completion: @escaping (CVPixelBuffer?) -> Void) {
guard let pool else { guard let pool else {
completion(nil, time) completion(nil)
return return
} }
if self.filteredImage == nil, let device = self.device { if self.filteredImage == nil, let device = self.device {
@ -161,15 +161,15 @@ final class MediaEditorComposer {
compositedImage = compositedImage.samplingLinear().transformed(by: CGAffineTransform(scaleX: scale, y: scale)) compositedImage = compositedImage.samplingLinear().transformed(by: CGAffineTransform(scaleX: scale, y: scale))
self.ciContext?.render(compositedImage, to: pixelBuffer) self.ciContext?.render(compositedImage, to: pixelBuffer)
completion(pixelBuffer, time) completion(pixelBuffer)
} else { } else {
completion(nil, time) completion(nil)
} }
}) })
return return
} }
} }
completion(nil, time) completion(nil)
} }
func processImage(inputImage: CIImage, time: CMTime, completion: @escaping (CIImage?) -> Void) { func processImage(inputImage: CIImage, time: CMTime, completion: @escaping (CIImage?) -> Void) {

View File

@ -569,18 +569,30 @@ public final class MediaEditorVideoExport {
let progress = (position - .zero).seconds / duration let progress = (position - .zero).seconds / duration
self.statusValue = .progress(Float(progress)) self.statusValue = .progress(Float(progress))
composer.processImage(inputImage: image, pool: writer.pixelBufferPool, time: position, completion: { pixelBuffer, timestamp in composer.processImage(inputImage: image, pool: writer.pixelBufferPool, time: position, completion: { pixelBuffer in
if let pixelBuffer { if let pixelBuffer {
if !writer.appendPixelBuffer(pixelBuffer, at: timestamp) { if !writer.appendPixelBuffer(pixelBuffer, at: position) {
Logger.shared.log("VideoExport", "Failed to append pixelbuffer") Logger.shared.log("VideoExport", "Failed to append pixelbuffer at \(position.seconds), trying to wait")
Queue.concurrentDefaultQueue().after(1.0, {
if !writer.appendPixelBuffer(pixelBuffer, at: position) {
Logger.shared.log("VideoExport", "Failed to append pixelbuffer at \(position.seconds), complete failure")
writer.markVideoAsFinished() writer.markVideoAsFinished()
appendFailed = true appendFailed = true
self.semaphore.signal()
}
})
} else {
Logger.shared.log("VideoExport", "Appended pixelbuffer at \(position.seconds)")
Thread.sleep(forTimeInterval: 0.01)
self.semaphore.signal()
} }
} else { } else {
Logger.shared.log("VideoExport", "No pixelbuffer from composer") Logger.shared.log("VideoExport", "No pixelbuffer from composer")
}
Thread.sleep(forTimeInterval: 0.001) Thread.sleep(forTimeInterval: 0.01)
self.semaphore.signal() self.semaphore.signal()
}
}) })
self.semaphore.wait() self.semaphore.wait()

View File

@ -641,13 +641,13 @@ final class VideoInputScalePass: RenderPass {
} }
func process(input: MTLTexture, secondInput: MTLTexture?, timestamp: CMTime, device: MTLDevice, commandBuffer: MTLCommandBuffer) -> MTLTexture? { func process(input: MTLTexture, secondInput: MTLTexture?, timestamp: CMTime, device: MTLDevice, commandBuffer: MTLCommandBuffer) -> MTLTexture? {
#if targetEnvironment(simulator) //#if targetEnvironment(simulator)
//
#else //#else
guard max(input.width, input.height) > 1920 || secondInput != nil else { guard max(input.width, input.height) > 1920 || secondInput != nil else {
return input return input
} }
#endif //#endif
let scaledSize = CGSize(width: input.width, height: input.height).fitted(CGSize(width: 1920.0, height: 1920.0)) let scaledSize = CGSize(width: input.width, height: input.height).fitted(CGSize(width: 1920.0, height: 1920.0))
let width: Int let width: Int
@ -695,9 +695,9 @@ final class VideoInputScalePass: RenderPass {
renderCommandEncoder.setRenderPipelineState(self.mainPipelineState!) renderCommandEncoder.setRenderPipelineState(self.mainPipelineState!)
#if targetEnvironment(simulator) //#if targetEnvironment(simulator)
let secondInput = input // let secondInput = input
#endif //#endif
let (mainVideoState, additionalVideoState, transitionVideoState) = self.transitionState(for: timestamp, mainInput: input, additionalInput: secondInput) let (mainVideoState, additionalVideoState, transitionVideoState) = self.transitionState(for: timestamp, mainInput: input, additionalInput: secondInput)

View File

@ -3016,7 +3016,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
fileprivate let transitionOut: (Bool, Bool?) -> TransitionOut? fileprivate let transitionOut: (Bool, Bool?) -> TransitionOut?
public var cancelled: (Bool) -> Void = { _ in } public var cancelled: (Bool) -> Void = { _ in }
public var completion: (Int64, MediaEditorScreen.Result?, NSAttributedString, MediaEditorResultPrivacy , @escaping (@escaping () -> Void) -> Void) -> Void = { _, _, _, _, _ in } public var completion: (Int64, MediaEditorScreen.Result?, NSAttributedString, MediaEditorResultPrivacy, [TelegramMediaFile], @escaping (@escaping () -> Void) -> Void) -> Void = { _, _, _, _, _, _ in }
public var dismissed: () -> Void = { } public var dismissed: () -> Void = { }
public var willDismiss: () -> Void = { } public var willDismiss: () -> Void = { }
@ -3031,7 +3031,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
initialVideoPosition: Double? = nil, initialVideoPosition: Double? = nil,
transitionIn: TransitionIn?, transitionIn: TransitionIn?,
transitionOut: @escaping (Bool, Bool?) -> TransitionOut?, transitionOut: @escaping (Bool, Bool?) -> TransitionOut?,
completion: @escaping (Int64, MediaEditorScreen.Result?, NSAttributedString, MediaEditorResultPrivacy, @escaping (@escaping () -> Void) -> Void) -> Void completion: @escaping (Int64, MediaEditorScreen.Result?, NSAttributedString, MediaEditorResultPrivacy, [TelegramMediaFile], @escaping (@escaping () -> Void) -> Void) -> Void
) { ) {
self.context = context self.context = context
self.subject = subject self.subject = subject
@ -3484,8 +3484,22 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
randomId = Int64.random(in: .min ... .max) randomId = Int64.random(in: .min ... .max)
} }
var stickers: [TelegramMediaFile] = []
for entity in codableEntities {
if case let .sticker(stickerEntity) = entity, case let .file(file) = stickerEntity.content {
stickers.append(file)
if let subEntities = stickerEntity.renderSubEntities {
for entity in subEntities {
if let stickerEntity = entity as? DrawingStickerEntity, case let .file(file) = stickerEntity.content {
stickers.append(file)
}
}
}
}
}
if self.isEditingStory && !self.node.hasAnyChanges { if self.isEditingStory && !self.node.hasAnyChanges {
self.completion(randomId, nil, caption, self.state.privacy, { [weak self] finished in self.completion(randomId, nil, caption, self.state.privacy, stickers, { [weak self] finished in
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
self?.dismiss() self?.dismiss()
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {
@ -3616,7 +3630,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
if let self { if let self {
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image ?? UIImage(), dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image ?? UIImage(), dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in
if let self { if let self {
self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), caption, self.state.privacy, { [weak self] finished in self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), caption, self.state.privacy, stickers, { [weak self] finished in
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
self?.dismiss() self?.dismiss()
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {
@ -3638,7 +3652,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] resultImage in makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] resultImage in
if let self, let resultImage { if let self, let resultImage {
self.completion(randomId, .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), caption, self.state.privacy, { [weak self] finished in self.completion(randomId, .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), caption, self.state.privacy, stickers, { [weak self] finished in
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
self?.dismiss() self?.dismiss()
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {

View File

@ -2809,7 +2809,7 @@ public final class StoryItemSetContainerComponent: Component {
initialVideoPosition: videoPlaybackPosition, initialVideoPosition: videoPlaybackPosition,
transitionIn: nil, transitionIn: nil,
transitionOut: { _, _ in return nil }, transitionOut: { _, _ in return nil },
completion: { [weak self] _, mediaResult, caption, privacy, commit in completion: { [weak self] _, mediaResult, caption, privacy, stickers, commit in
guard let self else { guard let self else {
return return
} }
@ -2831,7 +2831,7 @@ public final class StoryItemSetContainerComponent: Component {
updateProgressImpl?(0.0) updateProgressImpl?(0.0)
if let imageData = compressImageToJPEG(image, quality: 0.7) { if let imageData = compressImageToJPEG(image, quality: 0.7) {
let _ = (context.engine.messages.editStory(media: .image(dimensions: dimensions, data: imageData), id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy) let _ = (context.engine.messages.editStory(media: .image(dimensions: dimensions, data: imageData, stickers: stickers), id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy)
|> deliverOnMainQueue).start(next: { [weak self] result in |> deliverOnMainQueue).start(next: { [weak self] result in
guard let self else { guard let self else {
return return
@ -2870,7 +2870,7 @@ public final class StoryItemSetContainerComponent: Component {
} }
let firstFrameImageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) } let firstFrameImageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) }
let _ = (context.engine.messages.editStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: firstFrameImageData), id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy) let _ = (context.engine.messages.editStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: firstFrameImageData, stickers: stickers), id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy)
|> deliverOnMainQueue).start(next: { [weak self] result in |> deliverOnMainQueue).start(next: { [weak self] result in
guard let self else { guard let self else {
return return
@ -2990,6 +2990,14 @@ public final class StoryItemSetContainerComponent: Component {
let additionalCount = component.slice.item.storyItem.privacy?.additionallyIncludePeers.count ?? 0 let additionalCount = component.slice.item.storyItem.privacy?.additionallyIncludePeers.count ?? 0
var hasLinkedStickers = false
let media = component.slice.item.storyItem.media._asMedia()
if let image = media as? TelegramMediaImage {
hasLinkedStickers = image.flags.contains(.hasStickers)
} else if let file = media as? TelegramMediaFile {
hasLinkedStickers = file.hasLinkedStickers
}
let privacyText: String let privacyText: String
switch component.slice.item.storyItem.privacy?.base { switch component.slice.item.storyItem.privacy?.base {
case .closeFriends: case .closeFriends:
@ -3127,6 +3135,8 @@ public final class StoryItemSetContainerComponent: Component {
}))) })))
} }
let _ = hasLinkedStickers
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
let contextController = ContextController(account: component.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) let contextController = ContextController(account: component.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
contextController.dismissed = { [weak self] in contextController.dismissed = { [weak self] in

View File

@ -1,7 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "arrow_left.pdf", "filename" : "ic_next.pdf",
"idiom" : "universal" "idiom" : "universal"
} }
], ],

View File

@ -0,0 +1,92 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
0.000000 -1.000000 1.000000 0.000000 -6.195312 14.000000 cm
1.000000 1.000000 1.000000 scn
-0.707107 8.902419 m
-1.097631 8.511895 -1.097631 7.878730 -0.707107 7.488206 c
-0.316583 7.097682 0.316583 7.097682 0.707107 7.488206 c
-0.707107 8.902419 l
h
6.000000 14.195312 m
6.707107 14.902419 l
6.316583 15.292944 5.683417 15.292944 5.292893 14.902419 c
6.000000 14.195312 l
h
11.292893 7.488206 m
11.683417 7.097682 12.316583 7.097682 12.707107 7.488206 c
13.097631 7.878730 13.097631 8.511895 12.707107 8.902419 c
11.292893 7.488206 l
h
0.707107 7.488206 m
6.707107 13.488206 l
5.292893 14.902419 l
-0.707107 8.902419 l
0.707107 7.488206 l
h
5.292893 13.488206 m
11.292893 7.488206 l
12.707107 8.902419 l
6.707107 14.902419 l
5.292893 13.488206 l
h
f
n
Q
endstream
endobj
3 0 obj
785
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 10.000000 16.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000000875 00000 n
0000000897 00000 n
0000001070 00000 n
0000001144 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1203
%%EOF

View File

@ -354,7 +354,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
} else { } else {
return nil return nil
} }
}, completion: { [weak self] randomId, mediaResult, caption, privacy, commit in }, completion: { [weak self] randomId, mediaResult, caption, privacy, stickers, commit in
guard let self, let mediaResult else { guard let self, let mediaResult else {
dismissCameraImpl?() dismissCameraImpl?()
commit({}) commit({})
@ -373,7 +373,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
case let .image(image, dimensions): case let .image(image, dimensions):
if let imageData = compressImageToJPEG(image, quality: 0.7) { if let imageData = compressImageToJPEG(image, quality: 0.7) {
let entities = generateChatInputTextEntities(caption) let entities = generateChatInputTextEntities(caption)
self.context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData), text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId) self.context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData, stickers: stickers), text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId)
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {
commit({}) commit({})
} }
@ -396,7 +396,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
} }
let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) } let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) }
let entities = generateChatInputTextEntities(caption) let entities = generateChatInputTextEntities(caption)
self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: imageData), text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId) self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: imageData, stickers: stickers), text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId)
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {
commit({}) commit({})
} }