mirror of
https://github.com/ichmagmaus111/ghostgram.git
synced 2026-11-03 14:17:19 +01:00
Ghostgram for iOS, rebuilt from scratch on Telegram iOS 13.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
146 lines
6.6 KiB
Swift
146 lines
6.6 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Photos
|
|
import Display
|
|
import SwiftSignalKit
|
|
import TelegramCore
|
|
import TelegramPresentationData
|
|
import MediaEditor
|
|
import ComponentFlow
|
|
import AlertComponent
|
|
|
|
func mediaEditorCollageSubject(_ collage: MediaEditorCollage) -> MediaEditorScreenImpl.Subject? {
|
|
if !collage.isVideo {
|
|
guard let image = collage.image() else {
|
|
return nil
|
|
}
|
|
return .image(image: image, dimensions: PixelDimensions(collage.size), additionalImage: nil, additionalImagePosition: .topLeft, fromCamera: false)
|
|
}
|
|
var items: [MediaEditorScreenImpl.Subject.VideoCollageItem] = []
|
|
for item in collage.items {
|
|
let content: MediaEditorScreenImpl.Subject.VideoCollageItem.Content
|
|
switch item.source {
|
|
case let .image(image, _):
|
|
content = .image(image)
|
|
case let .imageFile(path):
|
|
guard let image = mediaEditorCollageImage(path: path) else {
|
|
return nil
|
|
}
|
|
content = .image(image)
|
|
case let .videoFile(path):
|
|
content = .video(path, item.duration)
|
|
case let .videoAsset(asset):
|
|
content = .asset(asset)
|
|
}
|
|
items.append(MediaEditorScreenImpl.Subject.VideoCollageItem(content: content, frame: item.frame, contentScale: item.contentScale, contentOffset: item.contentOffset, id: item.id, isMain: item.id == collage.mainItemId))
|
|
}
|
|
return .videoCollage(items: items)
|
|
}
|
|
|
|
extension MediaEditorScreenImpl {
|
|
func resolveCollageDraft(_ draft: MediaEditorDraft) {
|
|
guard let manifest = draft.collage else {
|
|
return
|
|
}
|
|
let resolutionId = UUID()
|
|
self.collageResolutionId = resolutionId
|
|
let engine = self.context.engine
|
|
Queue.concurrentDefaultQueue().async { [weak self] in
|
|
let result = Swift.Result { try manifest.resolve(engine: engine) }
|
|
Queue.mainQueue().async {
|
|
guard let self, self.collageResolutionId == resolutionId else {
|
|
return
|
|
}
|
|
switch result {
|
|
case let .success(resolved):
|
|
if let collage = resolved.collage {
|
|
self.openCollage(collage, draft: draft)
|
|
} else {
|
|
self.node.readyForCollage()
|
|
self.presentCollageAlert(text: self.context.sharedContext.currentPresentationData.with { $0 }.strings.Story_Editor_CollageDraftAllMediaMissing, completion: { [weak self] in
|
|
guard let self else {
|
|
return
|
|
}
|
|
if case let .draft(_, id) = self.node.actualSubject, id == nil {
|
|
removeStoryDraft(engine: self.context.engine, path: draft.path, delete: true)
|
|
}
|
|
self.requestDismiss(saveDraft: false, animated: true, discardDraft: false)
|
|
})
|
|
}
|
|
case .failure:
|
|
self.node.readyForCollage()
|
|
self.presentCollageAlert(text: self.context.sharedContext.currentPresentationData.with { $0 }.strings.Login_UnknownError, completion: { [weak self] in
|
|
self?.requestDismiss(saveDraft: false, animated: true, discardDraft: false)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func openCollage(_ collage: MediaEditorCollage, draft: MediaEditorDraft?, mediaPrepared: Bool = false) {
|
|
if draft != nil, collage.isVideo, !mediaPrepared {
|
|
self.node.readyForCollage()
|
|
self.prepareCollageMedia(collage) { [weak self] success in
|
|
guard let self else {
|
|
return
|
|
}
|
|
if success {
|
|
self.openCollage(collage, draft: draft, mediaPrepared: true)
|
|
} else {
|
|
self.requestDismiss(saveDraft: false, animated: true, discardDraft: false)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
let resolutionId = self.collageResolutionId
|
|
Queue.concurrentDefaultQueue().async { [weak self] in
|
|
// Decoding and composing full-size photos must not block the picker transition.
|
|
let subject = mediaEditorCollageSubject(collage)
|
|
Queue.mainQueue().async {
|
|
guard let self, self.collageResolutionId == resolutionId else {
|
|
return
|
|
}
|
|
guard let subject else {
|
|
self.node.readyForCollage()
|
|
self.presentCollageAlert(text: self.context.sharedContext.currentPresentationData.with { $0 }.strings.Login_UnknownError, completion: { [weak self] in
|
|
self?.requestDismiss(saveDraft: false, animated: true, discardDraft: false)
|
|
})
|
|
return
|
|
}
|
|
self.collage = collage
|
|
self.node.setup(subject: subject, privacy: draft?.privacy, values: draft.map { collage.valuesForEditor($0.values, resetVideoTimeline: $0.collage?.mainItemId != collage.mainItemId) }, caption: draft?.caption, isDraft: draft != nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
func presentCollageAlert(text: String, completion: @escaping () -> Void = {}) {
|
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
|
|
Queue.mainQueue().justDispatch { [weak self] in
|
|
guard let self else {
|
|
return
|
|
}
|
|
weak var weakAlert: MediaEditorDraftSaveAlert?
|
|
var finished = false
|
|
let close = {
|
|
guard !finished else {
|
|
return
|
|
}
|
|
finished = true
|
|
weakAlert?.close(completion: completion)
|
|
}
|
|
let alert = MediaEditorDraftSaveAlert(
|
|
configuration: AlertScreen.Configuration(dismissOnOutsideTap: false),
|
|
contentSignal: .single([AnyComponentWithIdentity(id: "text", component: AnyComponent(AlertTextComponent(content: .plain(text))))]),
|
|
actionsSignal: .single([AlertScreen.Action(title: presentationData.strings.Common_OK, type: .default, action: close, autoDismiss: false)]),
|
|
updatedPresentationData: (initial: presentationData, signal: self.context.sharedContext.presentationData |> map { $0.withUpdated(theme: defaultDarkPresentationTheme) })
|
|
)
|
|
weakAlert = alert
|
|
alert.requestClose = {
|
|
close()
|
|
return false
|
|
}
|
|
self.present(alert, in: .window(.root))
|
|
}
|
|
}
|
|
}
|