mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
148947a0e9
commit
7b15dbe31c
@ -366,17 +366,17 @@ private final class CameraContext {
|
||||
|> 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
|
||||
|> 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
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] _ in
|
||||
self?.modeChange = .none
|
||||
})
|
||||
}
|
||||
|
@ -75,7 +75,6 @@ final class CameraOutput: NSObject {
|
||||
let videoOutput = AVCaptureVideoDataOutput()
|
||||
let audioOutput = AVCaptureAudioDataOutput()
|
||||
let metadataOutput = AVCaptureMetadataOutput()
|
||||
private let faceLandmarksOutput = FaceLandmarksDataOutput()
|
||||
|
||||
let exclusive: Bool
|
||||
|
||||
@ -85,17 +84,12 @@ final class CameraOutput: NSObject {
|
||||
|
||||
private let queue = DispatchQueue(label: "")
|
||||
private let metadataQueue = DispatchQueue(label: "")
|
||||
private let faceLandmarksQueue = DispatchQueue(label: "")
|
||||
|
||||
private var photoCaptureRequests: [Int64: PhotoCaptureContext] = [:]
|
||||
private var videoRecorder: VideoRecorder?
|
||||
|
||||
var activeFilter: CameraFilter?
|
||||
var faceLandmarks: Bool = false
|
||||
|
||||
|
||||
var processSampleBuffer: ((CMSampleBuffer, CVImageBuffer, AVCaptureConnection) -> Void)?
|
||||
var processCodes: (([CameraCode]) -> Void)?
|
||||
var processFaceLandmarks: (([VNFaceObservation]) -> Void)?
|
||||
|
||||
init(exclusive: Bool) {
|
||||
self.exclusive = exclusive
|
||||
@ -104,12 +98,6 @@ final class CameraOutput: NSObject {
|
||||
|
||||
self.videoOutput.alwaysDiscardsLateVideoFrames = false
|
||||
self.videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] as [String : Any]
|
||||
|
||||
self.faceLandmarksOutput.outputFaceObservations = { [weak self] observations in
|
||||
if let self {
|
||||
self.processFaceLandmarks?(observations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -273,7 +261,7 @@ final class CameraOutput: NSObject {
|
||||
}
|
||||
|
||||
let uniqueId = settings.uniqueID
|
||||
let photoCapture = PhotoCaptureContext(settings: settings, filter: self.activeFilter, mirror: mirror)
|
||||
let photoCapture = PhotoCaptureContext(settings: settings, filter: nil, mirror: mirror)
|
||||
self.photoCaptureRequests[uniqueId] = photoCapture
|
||||
self.photoOutput.capturePhoto(with: settings, delegate: photoCapture)
|
||||
|
||||
@ -361,32 +349,10 @@ extension CameraOutput: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureA
|
||||
return
|
||||
}
|
||||
|
||||
if self.faceLandmarks {
|
||||
self.faceLandmarksQueue.async {
|
||||
self.faceLandmarksOutput.process(sampleBuffer: sampleBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
if let videoPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
|
||||
self.processSampleBuffer?(sampleBuffer, videoPixelBuffer, connection)
|
||||
}
|
||||
|
||||
// let finalSampleBuffer: CMSampleBuffer = sampleBuffer
|
||||
// if let videoPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer), let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer) {
|
||||
// var finalVideoPixelBuffer = videoPixelBuffer
|
||||
// if let filter = self.activeFilter {
|
||||
// if !filter.isPrepared {
|
||||
// filter.prepare(with: formatDescription, outputRetainedBufferCountHint: 3)
|
||||
// }
|
||||
//
|
||||
// guard let filteredBuffer = filter.render(pixelBuffer: finalVideoPixelBuffer) else {
|
||||
// return
|
||||
// }
|
||||
// finalVideoPixelBuffer = filteredBuffer
|
||||
// }
|
||||
// self.processSampleBuffer?(finalVideoPixelBuffer, connection)
|
||||
// }
|
||||
|
||||
if let videoRecorder = self.videoRecorder, videoRecorder.isRecording {
|
||||
videoRecorder.appendSampleBuffer(sampleBuffer)
|
||||
}
|
||||
|
@ -2975,6 +2975,8 @@ public final class DrawingToolsInteraction {
|
||||
private var isActive = false
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
private let startTimestamp = CACurrentMediaTime()
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
drawingView: DrawingView,
|
||||
@ -3126,6 +3128,10 @@ public final class DrawingToolsInteraction {
|
||||
if let textEntityView = entityView as? DrawingTextEntityView {
|
||||
textEntityView.beginEditing(accessoryView: self.textEditAccessoryView)
|
||||
} else {
|
||||
if self.isVideo {
|
||||
entityView.seek(to: 0.0)
|
||||
}
|
||||
|
||||
entityView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
entityView.layer.animateScale(from: 0.1, to: entity.scale, duration: 0.2)
|
||||
|
||||
|
@ -43,7 +43,8 @@ swift_library(
|
||||
"//submodules/MoreButtonNode:MoreButtonNode",
|
||||
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
|
||||
"//submodules/TelegramUI/Components/CameraScreen",
|
||||
"//submodules/TelegramUI/Components/MediaEditor",
|
||||
"//submodules/TelegramUI/Components/MediaEditor",
|
||||
"//submodules/RadialStatusNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -17,7 +17,7 @@ final class AssetDownloadManager {
|
||||
let identifier: String
|
||||
let updated: () -> Void
|
||||
|
||||
var status: AssetDownloadStatus = .progress(0.0)
|
||||
var status: AssetDownloadStatus = .none
|
||||
var disposable: Disposable?
|
||||
|
||||
init(identifier: String, updated: @escaping () -> Void) {
|
||||
@ -40,9 +40,7 @@ final class AssetDownloadManager {
|
||||
}
|
||||
|
||||
func download(asset: PHAsset) {
|
||||
if let currentAssetContext = self.currentAssetContext {
|
||||
currentAssetContext.disposable?.dispose()
|
||||
}
|
||||
self.cancelAllDownloads()
|
||||
|
||||
let queue = self.queue
|
||||
let identifier = asset.localIdentifier
|
||||
@ -59,6 +57,7 @@ final class AssetDownloadManager {
|
||||
}
|
||||
}
|
||||
})
|
||||
self.currentAssetContext = assetContext
|
||||
assetContext.disposable = (downloadAssetMediaData(asset)
|
||||
|> deliverOn(queue)).start(next: { [weak self] status in
|
||||
guard let self else {
|
||||
@ -69,13 +68,31 @@ final class AssetDownloadManager {
|
||||
currentAssetContext.updated()
|
||||
}
|
||||
})
|
||||
self.currentAssetContext = assetContext
|
||||
}
|
||||
|
||||
func cancelAllDownloads() {
|
||||
if let currentAssetContext = self.currentAssetContext {
|
||||
currentAssetContext.status = .none
|
||||
currentAssetContext.updated()
|
||||
currentAssetContext.disposable?.dispose()
|
||||
self.queue.justDispatch {
|
||||
if self.currentAssetContext === currentAssetContext {
|
||||
self.currentAssetContext = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cancel(identifier: String) {
|
||||
if let currentAssetContext = self.currentAssetContext, currentAssetContext.identifier == identifier {
|
||||
currentAssetContext.status = .none
|
||||
currentAssetContext.updated()
|
||||
currentAssetContext.disposable?.dispose()
|
||||
self.currentAssetContext = nil
|
||||
self.queue.justDispatch {
|
||||
if self.currentAssetContext === currentAssetContext {
|
||||
self.currentAssetContext = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,7 +110,7 @@ final class AssetDownloadManager {
|
||||
if let currentAssetContext = self.currentAssetContext, currentAssetContext.identifier == identifier {
|
||||
next(currentAssetContext.status)
|
||||
} else {
|
||||
next(.progress(0.0))
|
||||
next(.none)
|
||||
}
|
||||
|
||||
let queue = self.queue
|
||||
@ -129,27 +146,35 @@ final class AssetDownloadManager {
|
||||
}
|
||||
|
||||
func checkIfAssetIsLocal(_ asset: PHAsset) -> Signal<Bool, NoError> {
|
||||
if asset.isLocallyAvailable == true {
|
||||
return .single(true)
|
||||
}
|
||||
return Signal { subscriber in
|
||||
let options = PHImageRequestOptions()
|
||||
options.isNetworkAccessAllowed = false
|
||||
|
||||
let requestId: PHImageRequestID
|
||||
if #available(iOS 13, *) {
|
||||
requestId = imageManager.requestImageDataAndOrientation(for: asset, options: options) { data, _, _, _ in
|
||||
if data != nil {
|
||||
subscriber.putNext(data != nil)
|
||||
}
|
||||
if case .video = asset.mediaType {
|
||||
let options = PHVideoRequestOptions()
|
||||
options.isNetworkAccessAllowed = false
|
||||
|
||||
requestId = imageManager.requestAVAsset(forVideo: asset, options: options) { asset, _, _ in
|
||||
subscriber.putNext(asset != nil)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
} else {
|
||||
requestId = imageManager.requestImageData(for: asset, options: options) { data, _, _, _ in
|
||||
if data != nil {
|
||||
let options = PHImageRequestOptions()
|
||||
options.isNetworkAccessAllowed = false
|
||||
|
||||
if #available(iOS 13, *) {
|
||||
requestId = imageManager.requestImageDataAndOrientation(for: asset, options: options) { data, _, _, _ in
|
||||
subscriber.putNext(data != nil)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
} else {
|
||||
requestId = imageManager.requestImageData(for: asset, options: options) { data, _, _, _ in
|
||||
subscriber.putNext(data != nil)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}
|
||||
|
||||
return ActionDisposable {
|
||||
imageManager.cancelImageRequest(requestId)
|
||||
}
|
||||
@ -157,32 +182,57 @@ func checkIfAssetIsLocal(_ asset: PHAsset) -> Signal<Bool, NoError> {
|
||||
}
|
||||
|
||||
enum AssetDownloadStatus {
|
||||
case none
|
||||
case progress(Float)
|
||||
case completed
|
||||
}
|
||||
|
||||
private func downloadAssetMediaData(_ asset: PHAsset) -> Signal<AssetDownloadStatus, NoError> {
|
||||
return Signal { subscriber in
|
||||
let options = PHImageRequestOptions()
|
||||
options.isNetworkAccessAllowed = true
|
||||
options.progressHandler = { progress, _, _, _ in
|
||||
subscriber.putNext(.progress(Float(progress)))
|
||||
}
|
||||
|
||||
let requestId: PHImageRequestID
|
||||
if #available(iOS 13, *) {
|
||||
requestId = imageManager.requestImageDataAndOrientation(for: asset, options: options) { data, _, _, _ in
|
||||
if data != nil {
|
||||
if case .video = asset.mediaType {
|
||||
let options = PHVideoRequestOptions()
|
||||
options.isNetworkAccessAllowed = true
|
||||
options.progressHandler = { progress, _, _, _ in
|
||||
subscriber.putNext(.progress(Float(progress)))
|
||||
}
|
||||
|
||||
subscriber.putNext(.progress(0.0))
|
||||
|
||||
requestId = imageManager.requestAVAsset(forVideo: asset, options: options) { asset, _, _ in
|
||||
if asset != nil {
|
||||
subscriber.putNext(.completed)
|
||||
} else {
|
||||
subscriber.putNext(.none)
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
} else {
|
||||
requestId = imageManager.requestImageData(for: asset, options: options) { data, _, _, _ in
|
||||
if data != nil {
|
||||
subscriber.putNext(.completed)
|
||||
let options = PHImageRequestOptions()
|
||||
options.isNetworkAccessAllowed = true
|
||||
options.progressHandler = { progress, _, _, _ in
|
||||
subscriber.putNext(.progress(Float(progress)))
|
||||
}
|
||||
|
||||
subscriber.putNext(.progress(0.0))
|
||||
if #available(iOS 13, *) {
|
||||
requestId = imageManager.requestImageDataAndOrientation(for: asset, options: options) { data, _, _, _ in
|
||||
if data != nil {
|
||||
subscriber.putNext(.completed)
|
||||
} else {
|
||||
subscriber.putNext(.none)
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
} else {
|
||||
requestId = imageManager.requestImageData(for: asset, options: options) { data, _, _, _ in
|
||||
if data != nil {
|
||||
subscriber.putNext(.completed)
|
||||
} else {
|
||||
subscriber.putNext(.none)
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import InvisibleInkDustNode
|
||||
import ImageBlur
|
||||
import FastBlur
|
||||
import MediaEditor
|
||||
import RadialStatusNode
|
||||
|
||||
enum MediaPickerGridItemContent: Equatable {
|
||||
case asset(PHFetchResult<PHAsset>, Int)
|
||||
@ -90,7 +91,9 @@ private let maskImage = generateImage(CGSize(width: 1.0, height: 36.0), opaque:
|
||||
|
||||
final class MediaPickerGridItemNode: GridItemNode {
|
||||
var currentMediaState: (TGMediaSelectableItem, Int)?
|
||||
var currentState: (PHFetchResult<PHAsset>, Int)?
|
||||
var currentAssetState: (PHFetchResult<PHAsset>, Int)?
|
||||
var currentAsset: PHAsset?
|
||||
|
||||
var currentDraftState: (MediaEditorDraft, Int)?
|
||||
var enableAnimations: Bool = true
|
||||
var stories: Bool = false
|
||||
@ -103,6 +106,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
private let typeIconNode: ASImageNode
|
||||
private let durationNode: ImmediateTextNode
|
||||
private let draftNode: ImmediateTextNode
|
||||
private var statusNode: RadialStatusNode?
|
||||
|
||||
private let activateAreaNode: AccessibilityAreaNode
|
||||
|
||||
@ -112,6 +116,8 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
private let spoilerDisposable = MetaDisposable()
|
||||
var spoilerNode: SpoilerOverlayNode?
|
||||
|
||||
private let progressDisposable = MetaDisposable()
|
||||
|
||||
private var currentIsPreviewing = false
|
||||
|
||||
var selected: (() -> Void)?
|
||||
@ -140,7 +146,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
|
||||
self.activateAreaNode = AccessibilityAreaNode()
|
||||
self.activateAreaNode.accessibilityTraits = [.image]
|
||||
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
@ -168,7 +174,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
var selectableItem: TGMediaSelectableItem? {
|
||||
if let (media, _) = self.currentMediaState {
|
||||
return media
|
||||
} else if let (fetchResult, index) = self.currentState {
|
||||
} else if let (fetchResult, index) = self.currentAssetState {
|
||||
return TGMediaAsset(phAsset: fetchResult[index])
|
||||
} else {
|
||||
return nil
|
||||
@ -179,7 +185,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
var tag: Int32? {
|
||||
if let tag = self._cachedTag {
|
||||
return tag
|
||||
} else if let (fetchResult, index) = self.currentState {
|
||||
} else if let (fetchResult, index) = self.currentAssetState {
|
||||
let asset = fetchResult.object(at: index)
|
||||
if let localTimestamp = asset.creationDate?.timeIntervalSince1970 {
|
||||
let tag = Month(localTimestamp: Int32(localTimestamp)).packedValue
|
||||
@ -251,6 +257,32 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
|
||||
}
|
||||
|
||||
func updateProgress(_ value: Float?, animated: Bool) {
|
||||
if let value {
|
||||
let statusNode: RadialStatusNode
|
||||
if let current = self.statusNode {
|
||||
statusNode = current
|
||||
} else {
|
||||
statusNode = RadialStatusNode(backgroundNodeColor: UIColor(rgb: 0x000000, alpha: 0.6))
|
||||
statusNode.isUserInteractionEnabled = false
|
||||
self.addSubnode(statusNode)
|
||||
self.statusNode = statusNode
|
||||
}
|
||||
let adjustedProgress = max(0.027, CGFloat(value))
|
||||
let state: RadialStatusNodeState = .progress(color: .white, lineWidth: nil, value: adjustedProgress, cancelEnabled: true, animateRotation: true)
|
||||
statusNode.transitionToState(state)
|
||||
} else if let statusNode = self.statusNode {
|
||||
self.statusNode = nil
|
||||
if animated {
|
||||
statusNode.transitionToState(.none, animated: true, completion: { [weak statusNode] in
|
||||
statusNode?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
statusNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setup(interaction: MediaPickerInteraction, draft: MediaEditorDraft, index: Int, theme: PresentationTheme, selectable: Bool, enableAnimations: Bool, stories: Bool) {
|
||||
self.interaction = interaction
|
||||
self.theme = theme
|
||||
@ -259,14 +291,18 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
|
||||
self.backgroundColor = theme.list.mediaPlaceholderColor
|
||||
|
||||
if self.currentDraftState == nil || self.currentDraftState?.0.path != draft.path || self.currentDraftState!.1 != index || self.currentState != nil {
|
||||
if self.currentDraftState == nil || self.currentDraftState?.0.path != draft.path || self.currentDraftState!.1 != index || self.currentAssetState != nil {
|
||||
let imageSignal: Signal<UIImage?, NoError> = .single(draft.thumbnail)
|
||||
self.imageNode.setSignal(imageSignal)
|
||||
|
||||
self.currentDraftState = (draft, index)
|
||||
if self.currentState != nil {
|
||||
self.currentState = nil
|
||||
if self.currentAssetState != nil {
|
||||
self.currentAsset = nil
|
||||
self.currentAssetState = nil
|
||||
self.typeIconNode.removeFromSupernode()
|
||||
|
||||
self.progressDisposable.set(nil)
|
||||
self.updateProgress(nil, animated: false)
|
||||
}
|
||||
|
||||
if self.draftNode.supernode == nil {
|
||||
@ -354,11 +390,30 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
self.draftNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if self.currentState == nil || self.currentState!.0 !== fetchResult || self.currentState!.1 != index || self.currentDraftState != nil {
|
||||
self.backgroundNode.image = nil
|
||||
if self.currentAssetState == nil || self.currentAssetState!.0 !== fetchResult || self.currentAssetState!.1 != index || self.currentDraftState != nil {
|
||||
let editingContext = interaction.editingState
|
||||
let asset = fetchResult.object(at: index)
|
||||
|
||||
if asset.localIdentifier == self.currentAsset?.localIdentifier {
|
||||
return
|
||||
}
|
||||
|
||||
self.progressDisposable.set(
|
||||
(interaction.downloadManager.downloadProgress(identifier: asset.localIdentifier)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let self {
|
||||
switch status {
|
||||
case .none, .completed:
|
||||
self.updateProgress(nil, animated: true)
|
||||
case let .progress(progress):
|
||||
self.updateProgress(progress, animated: true)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
self.backgroundNode.image = nil
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
self.activateAreaNode.accessibilityLabel = "Photo \(asset.creationDate?.formatted(date: .abbreviated, time: .standard) ?? "")"
|
||||
}
|
||||
@ -489,7 +544,8 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
self.currentState = (fetchResult, index)
|
||||
self.currentAssetState = (fetchResult, index)
|
||||
self.currentAsset = asset
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
@ -554,6 +610,11 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
spoilerNode.frame = self.bounds
|
||||
spoilerNode.update(size: self.bounds.size, transition: .immediate)
|
||||
}
|
||||
|
||||
let statusSize = CGSize(width: 40.0, height: 40.0)
|
||||
if let statusNode = self.statusNode {
|
||||
statusNode.view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.bounds.width - statusSize.width) / 2.0), y: floorToScreenPixels((self.bounds.height - statusSize.height) / 2.0)), size: statusSize)
|
||||
}
|
||||
}
|
||||
|
||||
func transitionView(snapshot: Bool) -> UIView {
|
||||
@ -589,10 +650,16 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
self.interaction?.openDraft(draft, self.imageNode.image)
|
||||
return
|
||||
}
|
||||
guard let (fetchResult, index) = self.currentState else {
|
||||
guard let (fetchResult, index) = self.currentAssetState else {
|
||||
return
|
||||
}
|
||||
self.interaction?.openMedia(fetchResult, index, self.imageNode.image)
|
||||
if self.statusNode != nil {
|
||||
if let asset = self.currentAsset {
|
||||
self.interaction?.downloadManager.cancel(identifier: asset.localIdentifier)
|
||||
}
|
||||
} else {
|
||||
self.interaction?.openMedia(fetchResult, index, self.imageNode.image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ import CameraScreen
|
||||
import MediaEditor
|
||||
|
||||
final class MediaPickerInteraction {
|
||||
let downloadManager: AssetDownloadManager
|
||||
let openMedia: (PHFetchResult<PHAsset>, Int, UIImage?) -> Void
|
||||
let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void
|
||||
let openDraft: (MediaEditorDraft, UIImage?) -> Void
|
||||
@ -36,7 +37,8 @@ final class MediaPickerInteraction {
|
||||
let editingState: TGMediaEditingContext
|
||||
var hiddenMediaId: String?
|
||||
|
||||
init(openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void, schedule: @escaping () -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
||||
init(downloadManager: AssetDownloadManager, openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void, schedule: @escaping () -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
||||
self.downloadManager = downloadManager
|
||||
self.openMedia = openMedia
|
||||
self.openSelectedMedia = openSelectedMedia
|
||||
self.openDraft = openDraft
|
||||
@ -393,6 +395,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.selectionChangedDisposable?.dispose()
|
||||
self.itemsDimensionsUpdatedDisposable?.dispose()
|
||||
self.fastScrollDisposable?.dispose()
|
||||
self.currentAssetDownloadDisposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -777,8 +780,29 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
|
||||
private weak var currentGalleryController: TGModernGalleryController?
|
||||
|
||||
private func requestAssetDownload(_ asset: PHAsset) {
|
||||
|
||||
fileprivate var currentAssetDownloadDisposable = MetaDisposable()
|
||||
|
||||
fileprivate func cancelAssetDownloads() {
|
||||
guard let downloadManager = self.controller?.downloadManager else {
|
||||
return
|
||||
}
|
||||
self.currentAssetDownloadDisposable.set(nil)
|
||||
downloadManager.cancelAllDownloads()
|
||||
}
|
||||
|
||||
fileprivate func requestAssetDownload(asset: PHAsset) {
|
||||
guard let downloadManager = self.controller?.downloadManager else {
|
||||
return
|
||||
}
|
||||
downloadManager.download(asset: asset)
|
||||
self.currentAssetDownloadDisposable.set(
|
||||
(downloadManager.downloadProgress(identifier: asset.localIdentifier)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let self, case .completed = status, let controller = self.controller, let customSelection = self.controller?.customSelection {
|
||||
customSelection(controller, asset)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
private var openingMedia = false
|
||||
@ -794,26 +818,19 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.openingMedia = true
|
||||
|
||||
let asset = fetchResult[index]
|
||||
customSelection(controller, asset)
|
||||
|
||||
// let isLocallyAvailable = asset.isLocallyAvailable
|
||||
//
|
||||
// if let isLocallyAvailable {
|
||||
// if isLocallyAvailable {
|
||||
// customSelection(controller, asset)
|
||||
// } else {
|
||||
// self.requestAssetDownload(asset)
|
||||
// }
|
||||
// } else {
|
||||
// let _ = (checkIfAssetIsLocal(asset)
|
||||
// |> deliverOnMainQueue).start(next: { [weak self] isLocallyAvailable in
|
||||
// if isLocallyAvailable {
|
||||
// customSelection(controller, asset)
|
||||
// } else {
|
||||
// self?.requestAssetDownload(asset)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
let _ = (checkIfAssetIsLocal(asset)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] isLocallyAvailable in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if isLocallyAvailable {
|
||||
self.cancelAssetDownloads()
|
||||
customSelection(controller, asset)
|
||||
} else {
|
||||
self.requestAssetDownload(asset: asset)
|
||||
}
|
||||
})
|
||||
|
||||
Queue.mainQueue().after(0.3) {
|
||||
self.openingMedia = false
|
||||
@ -1365,6 +1382,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
private let groupedPromise = ValuePromise<Bool>(true)
|
||||
|
||||
private let downloadManager = AssetDownloadManager()
|
||||
|
||||
private var isDismissing = false
|
||||
|
||||
fileprivate let mainButtonState: AttachmentMainButtonState?
|
||||
@ -1538,13 +1557,17 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
}
|
||||
|
||||
self.interaction = MediaPickerInteraction(openMedia: { [weak self] fetchResult, index, immediateThumbnail in
|
||||
self.interaction = MediaPickerInteraction(downloadManager: self.downloadManager,
|
||||
openMedia: { [weak self] fetchResult, index, immediateThumbnail in
|
||||
self?.controllerNode.openMedia(fetchResult: fetchResult, index: index, immediateThumbnail: immediateThumbnail)
|
||||
}, openSelectedMedia: { [weak self] item, immediateThumbnail in
|
||||
},
|
||||
openSelectedMedia: { [weak self] item, immediateThumbnail in
|
||||
self?.controllerNode.openSelectedMedia(item: item, immediateThumbnail: immediateThumbnail)
|
||||
}, openDraft: { [weak self] draft, immediateThumbnail in
|
||||
},
|
||||
openDraft: { [weak self] draft, immediateThumbnail in
|
||||
self?.controllerNode.openDraft(draft: draft, immediateThumbnail: immediateThumbnail)
|
||||
}, toggleSelection: { [weak self] item, value, suggestUndo in
|
||||
},
|
||||
toggleSelection: { [weak self] item, value, suggestUndo in
|
||||
if let self = self, let selectionState = self.interaction?.selectionState {
|
||||
if let _ = item as? TGMediaPickerGalleryPhotoItem {
|
||||
if self.bannedSendPhotos != nil {
|
||||
@ -1798,7 +1821,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.undoOverlayController?.dismissWithCommitAction()
|
||||
}
|
||||
|
||||
public func requestDismiss(completion: @escaping () -> Void) {
|
||||
public func requestDismiss(completion: @escaping () -> Void) {
|
||||
if let selectionState = self.interaction?.selectionState, selectionState.count() > 0 {
|
||||
self.isDismissing = true
|
||||
|
||||
@ -1837,6 +1860,12 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
self.controllerNode.cancelAssetDownloads()
|
||||
|
||||
super.dismiss(animated: flag, completion: completion)
|
||||
}
|
||||
|
||||
@objc private func rightButtonPressed() {
|
||||
self.moreButtonNode.buttonPressed()
|
||||
}
|
||||
|
@ -284,6 +284,7 @@ final class PendingStoryManager {
|
||||
self.currentPendingItemContext = pendingItemContext
|
||||
|
||||
let stableId = firstItem.stableId
|
||||
Logger.shared.log("PendingStoryManager", "setting up item context for: \(firstItem.stableId) 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
|
||||
guard let `self` = self else {
|
||||
|
@ -746,6 +746,7 @@ func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, text:
|
||||
period: Int32(period),
|
||||
randomId: randomId
|
||||
))
|
||||
Logger.shared.log("UploadStory", "Appended new pending item stableId: \(stableId) randomId: \(randomId)")
|
||||
transaction.setLocalStoryState(state: CodableEntry(currentState))
|
||||
}).start()
|
||||
}
|
||||
@ -789,6 +790,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], embeddedStickers: [TelegramMediaFile], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) -> Signal<StoryUploadResult, NoError> {
|
||||
Logger.shared.log("UploadStory", "uploadStoryImpl for stableId: \(stableId) randomId: \(randomId)")
|
||||
let passFetchProgress = media is TelegramMediaFile
|
||||
let (contentSignal, originalMedia) = uploadedStoryContent(postbox: postbox, network: network, media: media, embeddedStickers: embeddedStickers, accountPeerId: accountPeerId, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, auxiliaryMethods: auxiliaryMethods, passFetchProgress: passFetchProgress)
|
||||
return contentSignal
|
||||
|
@ -303,6 +303,8 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
self.isPressingButton = true
|
||||
}
|
||||
self.buttonPressTimestamp = nil
|
||||
self.buttonPressTimer?.invalidate()
|
||||
self.buttonPressTimer = nil
|
||||
}
|
||||
}, queue: Queue.mainQueue())
|
||||
self.buttonPressTimer?.start()
|
||||
@ -641,11 +643,15 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
}
|
||||
controller.presentGallery()
|
||||
},
|
||||
swipeHintUpdated: { hint in
|
||||
state.updateSwipeHint(hint)
|
||||
swipeHintUpdated: { [weak state] hint in
|
||||
if let state {
|
||||
state.updateSwipeHint(hint)
|
||||
}
|
||||
},
|
||||
zoomUpdated: { fraction in
|
||||
state.updateZoom(fraction: fraction)
|
||||
zoomUpdated: { [weak state] fraction in
|
||||
if let state {
|
||||
state.updateZoom(fraction: fraction)
|
||||
}
|
||||
},
|
||||
flipAnimationAction: animateFlipAction
|
||||
),
|
||||
@ -737,10 +743,9 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
component: CameraButton(
|
||||
content: flashContentComponent,
|
||||
action: { [weak state] in
|
||||
guard let state else {
|
||||
return
|
||||
if let state {
|
||||
state.toggleFlashMode()
|
||||
}
|
||||
state.toggleFlashMode()
|
||||
}
|
||||
).tagged(flashButtonTag),
|
||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
||||
@ -762,10 +767,9 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
)
|
||||
),
|
||||
action: { [weak state] in
|
||||
guard let state else {
|
||||
return
|
||||
if let state {
|
||||
state.toggleDualCamera()
|
||||
}
|
||||
state.toggleDualCamera()
|
||||
}
|
||||
).tagged(dualButtonTag),
|
||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
||||
|
@ -507,7 +507,9 @@ public final class MediaEditor {
|
||||
self.onPlaybackAction(.seek(start))
|
||||
self.player?.play()
|
||||
self.additionalPlayer?.play()
|
||||
self.onPlaybackAction(.play)
|
||||
Queue.mainQueue().justDispatch {
|
||||
self.onPlaybackAction(.play)
|
||||
}
|
||||
}
|
||||
})
|
||||
Queue.mainQueue().justDispatch {
|
||||
|
@ -3615,6 +3615,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
}
|
||||
let fittedSize = resultImage.size.aspectFitted(CGSize(width: 128.0, height: 128.0))
|
||||
|
||||
let context = self.context
|
||||
let saveImageDraft: (UIImage, PixelDimensions) -> Void = { image, dimensions in
|
||||
if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) {
|
||||
let path = "\(Int64.random(in: .min ... .max)).jpg"
|
||||
@ -3622,9 +3623,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
let draft = MediaEditorDraft(path: path, isVideo: false, thumbnail: thumbnailImage, dimensions: dimensions, duration: nil, values: values, caption: caption, privacy: privacy, timestamp: timestamp)
|
||||
try? data.write(to: URL(fileURLWithPath: draft.fullPath()))
|
||||
if let id {
|
||||
saveStorySource(engine: self.context.engine, item: draft, id: id)
|
||||
saveStorySource(engine: context.engine, item: draft, id: id)
|
||||
} else {
|
||||
addStoryDraft(engine: self.context.engine, item: draft)
|
||||
addStoryDraft(engine: context.engine, item: draft)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3636,9 +3637,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
let draft = MediaEditorDraft(path: path, isVideo: true, thumbnail: thumbnailImage, dimensions: dimensions, duration: duration, values: values, caption: caption, privacy: privacy, timestamp: timestamp)
|
||||
try? FileManager.default.moveItem(atPath: videoPath, toPath: draft.fullPath())
|
||||
if let id {
|
||||
saveStorySource(engine: self.context.engine, item: draft, id: id)
|
||||
saveStorySource(engine: context.engine, item: draft, id: id)
|
||||
} else {
|
||||
addStoryDraft(engine: self.context.engine, item: draft)
|
||||
addStoryDraft(engine: context.engine, item: draft)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3861,6 +3862,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
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
|
||||
if let self {
|
||||
Logger.shared.log("Media Editor", "completed with video \(videoResult)")
|
||||
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?.dismiss()
|
||||
@ -3883,6 +3885,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
|
||||
if let self, let resultImage {
|
||||
Logger.shared.log("Media Editor", "completed with image \(resultImage)")
|
||||
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?.dismiss()
|
||||
|
@ -374,6 +374,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
case let .image(image, dimensions):
|
||||
if let imageData = compressImageToJPEG(image, quality: 0.7) {
|
||||
let entities = generateChatInputTextEntities(caption)
|
||||
Logger.shared.log("MediaEditor", "Calling uploadStory for image, 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 {
|
||||
commit({})
|
||||
@ -404,7 +405,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
Logger.shared.log("MediaEditor", "Calling uploadStory for video, randomId \(randomId)")
|
||||
let entities = generateChatInputTextEntities(caption)
|
||||
self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: stickers), text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId)
|
||||
Queue.mainQueue().justDispatch {
|
||||
|
Loading…
x
Reference in New Issue
Block a user