Various fixes

This commit is contained in:
Ilya Laktyushin 2023-08-10 17:34:56 +02:00
parent e7c717867c
commit 5bf4a36d46
6 changed files with 155 additions and 45 deletions

View File

@ -9,6 +9,7 @@ import Display
import TelegramCore
import TelegramPresentationData
import FastBlur
import AccountContext
public struct MediaEditorPlayerState {
public let generationTimestamp: Double
@ -41,6 +42,7 @@ public final class MediaEditor {
}
}
private let context: AccountContext
private let subject: Subject
private var player: AVPlayer?
private var additionalPlayer: AVPlayer?
@ -253,7 +255,8 @@ public final class MediaEditor {
}
}
public init(subject: Subject, values: MediaEditorValues? = nil, hasHistogram: Bool = false) {
public init(context: AccountContext, subject: Subject, values: MediaEditorValues? = nil, hasHistogram: Bool = false) {
self.context = context
self.subject = subject
if let values {
self.values = values
@ -332,6 +335,7 @@ public final class MediaEditor {
print("error")
}
let context = self.context
let textureSource: Signal<(TextureSource, UIImage?, AVPlayer?, AVPlayer?, UIColor, UIColor), NoError>
switch subject {
case let .image(image, _):
@ -340,7 +344,7 @@ public final class MediaEditor {
case let .draft(draft):
if draft.isVideo {
textureSource = Signal { subscriber in
let url = URL(fileURLWithPath: draft.fullPath())
let url = URL(fileURLWithPath: draft.fullPath(engine: context.engine))
let asset = AVURLAsset(url: url)
let playerItem = AVPlayerItem(asset: asset)
@ -372,7 +376,7 @@ public final class MediaEditor {
}
}
} else {
guard let image = UIImage(contentsOfFile: draft.fullPath()) else {
guard let image = UIImage(contentsOfFile: draft.fullPath(engine: context.engine)) else {
return
}
let colors: (UIColor, UIColor)

View File

@ -70,6 +70,7 @@ public final class MediaEditorDraft: Codable, Equatable {
case timestamp
case locationLatitude
case locationLongitude
case expiresOn
}
public let path: String
@ -82,8 +83,9 @@ public final class MediaEditorDraft: Codable, Equatable {
public let privacy: MediaEditorResultPrivacy?
public let timestamp: Int32
public let location: CLLocationCoordinate2D?
public let expiresOn: Int32?
public init(path: String, isVideo: Bool, thumbnail: UIImage, dimensions: PixelDimensions, duration: Double?, values: MediaEditorValues, caption: NSAttributedString, privacy: MediaEditorResultPrivacy?, timestamp: Int32, location: CLLocationCoordinate2D?) {
public init(path: String, isVideo: Bool, thumbnail: UIImage, dimensions: PixelDimensions, duration: Double?, values: MediaEditorValues, caption: NSAttributedString, privacy: MediaEditorResultPrivacy?, timestamp: Int32, location: CLLocationCoordinate2D?, expiresOn: Int32?) {
self.path = path
self.isVideo = isVideo
self.thumbnail = thumbnail
@ -94,6 +96,7 @@ public final class MediaEditorDraft: Codable, Equatable {
self.privacy = privacy
self.timestamp = timestamp
self.location = location
self.expiresOn = expiresOn
}
public init(from decoder: Decoder) throws {
@ -133,6 +136,8 @@ public final class MediaEditorDraft: Codable, Equatable {
} else {
self.location = nil
}
self.expiresOn = try container.decodeIfPresent(Int32.self, forKey: .expiresOn)
}
public func encode(to encoder: Encoder) throws {
@ -170,6 +175,7 @@ public final class MediaEditorDraft: Codable, Equatable {
try container.encodeNil(forKey: .locationLatitude)
try container.encodeNil(forKey: .locationLongitude)
}
try container.encodeIfPresent(self.expiresOn, forKey: .expiresOn)
}
}
@ -207,14 +213,25 @@ public func addStoryDraft(engine: TelegramEngine, item: MediaEditorDraft) {
public func removeStoryDraft(engine: TelegramEngine, path: String, delete: Bool) {
if delete {
try? FileManager.default.removeItem(atPath: fullDraftPath(path))
try? FileManager.default.removeItem(atPath: fullDraftPath(engine: engine, path: path))
}
let itemId = MediaEditorDraftItemId(path.persistentHashValue)
let _ = engine.orderedLists.removeItem(collectionId: ApplicationSpecificOrderedItemListCollectionId.storyDrafts, id: itemId.rawValue).start()
}
public func clearStoryDrafts(engine: TelegramEngine) {
let _ = engine.orderedLists.clear(collectionId: ApplicationSpecificOrderedItemListCollectionId.storyDrafts).start()
let _ = engine.data.get(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: ApplicationSpecificOrderedItemListCollectionId.storyDrafts)).start(next: { items in
for item in items {
if let draft = item.contents.get(MediaEditorDraft.self) {
removeStoryDraft(engine: engine, path: draft.path, delete: true)
}
}
})
}
public func deleteAllStoryDrafts(peerId: EnginePeer.Id) {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/storyDrafts_\(peerId.toInt64())/"
try? FileManager.default.removeItem(atPath: path)
}
public func storyDrafts(engine: TelegramEngine) -> Signal<[MediaEditorDraft], NoError> {
@ -230,12 +247,27 @@ public func storyDrafts(engine: TelegramEngine) -> Signal<[MediaEditorDraft], No
}
}
public func updateStoryDrafts(engine: TelegramEngine) {
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let _ = engine.data.get(
TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: ApplicationSpecificOrderedItemListCollectionId.storyDrafts)
).start(next: { items in
for item in items {
if let draft = item.contents.get(MediaEditorDraft.self) {
if let expiresOn = draft.expiresOn, expiresOn < currentTimestamp {
removeStoryDraft(engine: engine, path: draft.path, delete: true)
}
}
}
})
}
public extension MediaEditorDraft {
func fullPath() -> String {
return fullDraftPath(self.path)
func fullPath(engine: TelegramEngine) -> String {
return fullDraftPath(engine: engine, path: self.path)
}
}
private func fullDraftPath(_ path: String) -> String {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/storyDrafts/" + path
private func fullDraftPath(engine: TelegramEngine, path: String) -> String {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/storyDrafts_\(engine.account.peerId.toInt64())/" + path
}

View File

@ -1886,7 +1886,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
}
let mediaEditor = MediaEditor(subject: subject.editorSubject, values: initialValues, hasHistogram: true)
let mediaEditor = MediaEditor(context: self.context, subject: subject.editorSubject, values: initialValues, hasHistogram: true)
if let initialVideoPosition = self.controller?.initialVideoPosition {
mediaEditor.seek(initialVideoPosition, andPlay: true)
}
@ -3441,6 +3441,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
})
}
updateStorySources(engine: self.context.engine)
updateStoryDrafts(engine: self.context.engine)
}
required public init(coder aDecoder: NSCoder) {
@ -3827,23 +3830,35 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
guard let subject = self.node.subject, let mediaEditor = self.node.mediaEditor else {
return
}
try? FileManager.default.createDirectory(atPath: draftPath(), withIntermediateDirectories: true)
try? FileManager.default.createDirectory(atPath: draftPath(engine: self.context.engine), withIntermediateDirectories: true)
let values = mediaEditor.values
let privacy = self.state.privacy
let caption = self.getCaption()
let duration = mediaEditor.duration ?? 0.0
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
var timestamp: Int32
var location: CLLocationCoordinate2D?
let expiresOn: Int32
if case let .draft(draft, _) = subject {
timestamp = draft.timestamp
location = draft.location
if let _ = id {
expiresOn = draft.expiresOn ?? currentTimestamp + 3600 * 24 * 7
} else {
expiresOn = currentTimestamp + 3600 * 24 * 7
}
} else {
timestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
timestamp = currentTimestamp
if case let .asset(asset) = subject {
location = asset.location?.coordinate
}
if let _ = id {
expiresOn = currentTimestamp + Int32(self.state.privacy.timeout)
} else {
expiresOn = currentTimestamp + 3600 * 24 * 7
}
}
if let resultImage = mediaEditor.resultImage {
@ -3859,8 +3874,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) {
let path = "\(Int64.random(in: .min ... .max)).jpg"
if let data = image.jpegData(compressionQuality: 0.87) {
let draft = MediaEditorDraft(path: path, isVideo: false, thumbnail: thumbnailImage, dimensions: dimensions, duration: nil, values: values, caption: caption, privacy: privacy, timestamp: timestamp, location: location)
try? data.write(to: URL(fileURLWithPath: draft.fullPath()))
let draft = MediaEditorDraft(path: path, isVideo: false, thumbnail: thumbnailImage, dimensions: dimensions, duration: nil, values: values, caption: caption, privacy: privacy, timestamp: timestamp, location: location, expiresOn: expiresOn)
try? data.write(to: URL(fileURLWithPath: draft.fullPath(engine: context.engine)))
if let id {
saveStorySource(engine: context.engine, item: draft, peerId: context.account.peerId, id: id)
} else {
@ -3873,8 +3888,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let saveVideoDraft: (String, PixelDimensions, Double) -> Void = { videoPath, dimensions, duration in
if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) {
let path = "\(Int64.random(in: .min ... .max)).mp4"
let draft = MediaEditorDraft(path: path, isVideo: true, thumbnail: thumbnailImage, dimensions: dimensions, duration: duration, values: values, caption: caption, privacy: privacy, timestamp: timestamp, location: location)
try? FileManager.default.moveItem(atPath: videoPath, toPath: draft.fullPath())
let draft = MediaEditorDraft(path: path, isVideo: true, thumbnail: thumbnailImage, dimensions: dimensions, duration: duration, values: values, caption: caption, privacy: privacy, timestamp: timestamp, location: location, expiresOn: expiresOn)
try? FileManager.default.moveItem(atPath: videoPath, toPath: draft.fullPath(engine: context.engine))
if let id {
saveStorySource(engine: context.engine, item: draft, peerId: context.account.peerId, id: id)
} else {
@ -3906,8 +3921,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
case let .draft(draft, _):
if draft.isVideo {
saveVideoDraft(draft.fullPath(), draft.dimensions, draft.duration ?? 0.0)
} else if let image = UIImage(contentsOfFile: draft.fullPath()) {
saveVideoDraft(draft.fullPath(engine: context.engine), draft.dimensions, draft.duration ?? 0.0)
} else if let image = UIImage(contentsOfFile: draft.fullPath(engine: context.engine)) {
saveImageDraft(image, draft.dimensions)
}
removeStoryDraft(engine: self.context.engine, path: draft.path, delete: false)
@ -3943,6 +3958,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
mediaEditor.invalidate()
self.node.entitiesView.invalidate()
let context = self.context
if let navigationController = self.navigationController as? NavigationController {
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate)
}
@ -4109,15 +4125,16 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
return EmptyDisposable
}
case let .draft(draft, _):
let draftPath = draft.fullPath(engine: context.engine)
if draft.isVideo {
videoResult = .videoFile(path: draft.fullPath())
videoResult = .videoFile(path: draftPath)
if let videoTrimRange = mediaEditor.values.videoTrimRange {
duration = videoTrimRange.upperBound - videoTrimRange.lowerBound
} else {
duration = min(draft.duration ?? 5.0, storyMaxVideoDuration)
}
firstFrame = Signal<(UIImage?, UIImage?), NoError> { subscriber in
let avAsset = AVURLAsset(url: URL(fileURLWithPath: draft.fullPath()))
let avAsset = AVURLAsset(url: URL(fileURLWithPath: draftPath))
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
avAssetGenerator.appliesPreferredTrackTransform = true
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
@ -4131,10 +4148,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
}
} else {
videoResult = .imageFile(path: draft.fullPath())
videoResult = .imageFile(path: draftPath)
duration = 5.0
if let image = UIImage(contentsOfFile: draft.fullPath()) {
if let image = UIImage(contentsOfFile: draftPath) {
firstFrame = .single((image, nil))
} else {
firstFrame = .single((UIImage(), nil))
@ -4228,6 +4245,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
return
}
let context = self.context
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
@ -4292,10 +4311,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
case let .draft(draft, _):
if draft.isVideo {
let asset = AVURLAsset(url: NSURL(fileURLWithPath: draft.fullPath()) as URL)
let asset = AVURLAsset(url: NSURL(fileURLWithPath: draft.fullPath(engine: context.engine)) as URL)
exportSubject = .single(.video(asset))
} else {
if let image = UIImage(contentsOfFile: draft.fullPath()) {
if let image = UIImage(contentsOfFile: draft.fullPath(engine: context.engine)) {
exportSubject = .single(.image(image))
} else {
fatalError()
@ -4768,8 +4787,8 @@ public final class BlurredGradientComponent: Component {
}
}
func draftPath() -> String {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/storyDrafts"
func draftPath(engine: TelegramEngine) -> String {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/storyDrafts_\(engine.account.peerId.toInt64())"
}
func hasFirstResponder(_ view: UIView) -> Bool {

View File

@ -6,24 +6,79 @@ import TelegramUIPreferences
import MediaEditor
import AccountContext
public func saveStorySource(engine: TelegramEngine, item: MediaEditorDraft, peerId: EnginePeer.Id, id: Int64) {
public func updateStorySources(engine: TelegramEngine) {
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let _ = engine.data.get(
TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: ApplicationSpecificOrderedItemListCollectionId.storySources)
).start(next: { items in
for item in items {
let key = EngineDataBuffer(item.id)
let _ = getStorySource(engine: engine, key: key).start(next: { source in
if let source {
if let expiresOn = source.expiresOn, expiresOn < currentTimestamp {
let _ = removeStorySource(engine: engine, key: key, delete: true).start()
}
}
})
}
})
}
private func key(peerId: EnginePeer.Id, id: Int64) -> EngineDataBuffer {
let key = EngineDataBuffer(length: 16)
key.setInt64(0, value: peerId.toInt64())
key.setInt64(8, value: id)
return key
}
private class StorySourceItem: Codable {
}
private func addStorySource(engine: TelegramEngine, key: EngineDataBuffer) {
let _ = engine.orderedLists.addOrMoveToFirstPosition(
collectionId: ApplicationSpecificOrderedItemListCollectionId.storySources,
id: key.toMemoryBuffer(),
item: StorySourceItem(),
removeTailIfCountExceeds: nil
).start()
}
private func removeStorySource(engine: TelegramEngine, peerId: EnginePeer.Id, id: Int64, delete: Bool) -> Signal<Never, NoError> {
let key = key(peerId: peerId, id: id)
return getStorySource(engine: engine, peerId: peerId, id: id)
|> mapToSignal { source in
if let source {
let _ = engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: key).start()
removeStoryDraft(engine: engine, path: source.path, delete: delete)
}
return engine.orderedLists.removeItem(collectionId: ApplicationSpecificOrderedItemListCollectionId.storySources, id: key.toMemoryBuffer())
}
}
private func removeStorySource(engine: TelegramEngine, key: EngineDataBuffer, delete: Bool) -> Signal<Never, NoError> {
return getStorySource(engine: engine, key: key)
|> mapToSignal { source in
if let source {
let _ = engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: key).start()
removeStoryDraft(engine: engine, path: source.path, delete: delete)
}
return engine.orderedLists.removeItem(collectionId: ApplicationSpecificOrderedItemListCollectionId.storySources, id: key.toMemoryBuffer())
}
}
public func saveStorySource(engine: TelegramEngine, item: MediaEditorDraft, peerId: EnginePeer.Id, id: Int64) {
let key = key(peerId: peerId, id: id)
addStorySource(engine: engine, key: key)
let _ = engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: key, item: item).start()
}
public func removeStorySource(engine: TelegramEngine, peerId: EnginePeer.Id, id: Int64) {
let key = EngineDataBuffer(length: 16)
key.setInt64(0, value: peerId.toInt64())
key.setInt64(8, value: id)
let _ = engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: key).start()
public func getStorySource(engine: TelegramEngine, peerId: EnginePeer.Id, id: Int64) -> Signal<MediaEditorDraft?, NoError> {
let key = key(peerId: peerId, id: id)
return getStorySource(engine: engine, key: key)
}
public func getStorySource(engine: TelegramEngine, peerId: EnginePeer.Id, id: Int64) -> Signal<MediaEditorDraft?, NoError> {
let key = EngineDataBuffer(length: 16)
key.setInt64(0, value: peerId.toInt64())
key.setInt64(8, value: id)
private func getStorySource(engine: TelegramEngine, key: EngineDataBuffer) -> Signal<MediaEditorDraft?, NoError> {
return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: key))
|> map { result -> MediaEditorDraft? in
return result?.get(MediaEditorDraft.self)
@ -31,20 +86,16 @@ public func getStorySource(engine: TelegramEngine, peerId: EnginePeer.Id, id: In
}
public func moveStorySource(engine: TelegramEngine, peerId: EnginePeer.Id, from fromId: Int64, to toId: Int64) {
let fromKey = EngineDataBuffer(length: 16)
fromKey.setInt64(0, value: peerId.toInt64())
fromKey.setInt64(8, value: fromId)
let toKey = EngineDataBuffer(length: 16)
toKey.setInt64(0, value: peerId.toInt64())
toKey.setInt64(8, value: toId)
let fromKey = key(peerId: peerId, id: fromId)
let toKey = key(peerId: peerId, id: toId)
let _ = (engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: fromKey))
|> mapToSignal { item -> Signal<Never, NoError> in
if let item = item?.get(MediaEditorDraft.self) {
addStorySource(engine: engine, key: toKey)
return engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: toKey, item: item)
|> then(
engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.storySource, id: fromKey)
removeStorySource(engine: engine, key: fromKey, delete: false)
)
} else {
return .complete()

View File

@ -38,6 +38,7 @@ import PhoneNumberFormat
import AuthorizationUI
import ManagedFile
import DeviceProximity
import MediaEditor
#if canImport(AppCenter)
import AppCenter
@ -1291,6 +1292,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
let _ = (updateIntentsSettingsInteractively(accountManager: accountManager) { current in
var updated = current
for peerId in loggedOutAccountPeerIds {
deleteAllStoryDrafts(peerId: peerId)
if peerId == updated.account {
deleteAllSendMessageIntents()
updated = updated.withUpdatedAccount(nil)

View File

@ -100,6 +100,7 @@ private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {
case settingsSearchRecentItems = 2
case localThemes = 3
case storyDrafts = 4
case storySources = 5
}
public struct ApplicationSpecificOrderedItemListCollectionId {
@ -108,4 +109,5 @@ public struct ApplicationSpecificOrderedItemListCollectionId {
public static let settingsSearchRecentItems = applicationSpecificOrderedItemListCollectionId(ApplicationSpecificOrderedItemListCollectionIdValues.settingsSearchRecentItems.rawValue)
public static let localThemes = applicationSpecificOrderedItemListCollectionId(ApplicationSpecificOrderedItemListCollectionIdValues.localThemes.rawValue)
public static let storyDrafts = applicationSpecificOrderedItemListCollectionId(ApplicationSpecificOrderedItemListCollectionIdValues.storyDrafts.rawValue)
public static let storySources = applicationSpecificOrderedItemListCollectionId(ApplicationSpecificOrderedItemListCollectionIdValues.storySources.rawValue)
}