This commit is contained in:
fmodf 2024-07-14 15:42:51 +02:00
parent bb502ba79a
commit e21d1a1ce9
7 changed files with 122 additions and 90 deletions

View file

@ -9,4 +9,7 @@ enum FileAction: Stateable {
case attachmentThumbnailCreated(messageId: String, thumbnailName: String)
case copyFileForUploading(messageId: String, fileData: Data, thumbnailData: Data?)
case fetchItemsFromGallery
case itemsFromGalleryFetched(items: [SharingGalleryItem])
}

View file

@ -13,9 +13,7 @@ enum SharingAction: Stateable {
case checkGalleryAccess
case setGalleryAccess(Bool)
case fetchGallery
case galleryFetched([SharingGalleryItem])
case thumbnailUpdated(data: Data, id: String)
case galleryItemsUpdated(items: [SharingGalleryItem])
case cameraCaptured(media: Data, type: SharingCameraMediaType)
case flushCameraCaptured

View file

@ -1,4 +1,5 @@
import Foundation
import Photos
import UIKit
final class FileProcessing {
@ -42,8 +43,71 @@ final class FileProcessing {
return nil
}
}
func fetchGallery() -> [SharingGalleryItem] {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let assets = PHAsset.fetchAssets(with: fetchOptions)
var items: [SharingGalleryItem] = []
assets.enumerateObjects { asset, _, _ in
if asset.mediaType == .image {
items.append(.init(id: asset.localIdentifier, type: .photo))
} else if asset.mediaType == .video {
items.append(.init(id: asset.localIdentifier, type: .video))
}
}
return items
}
func fillGalleryItemsThumbnails(items: [SharingGalleryItem]) -> [SharingGalleryItem] {
var result: [SharingGalleryItem] = []
let ids = items
.filter { $0.thumbnail == nil }
.map { $0.id }
let assets = PHAsset.fetchAssets(withLocalIdentifiers: ids, options: nil)
assets.enumerateObjects { asset, _, _ in
if asset.mediaType == .image {
PHImageManager.default().requestImage(
for: asset,
targetSize: PHImageManagerMaximumSize,
contentMode: .aspectFill,
options: nil
) { image, _ in
image?.scaleAndCropImage(toExampleSize: CGSize(width: Const.galleryGridSize, height: Const.galleryGridSize)) { image in
if let image {
let data = image.jpegData(compressionQuality: 1.0) ?? Data()
result.append(.init(id: asset.localIdentifier, type: .photo, thumbnail: data))
}
}
}
} else if asset.mediaType == .video {
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in
if let avAsset {
let imageGenerator = AVAssetImageGenerator(asset: avAsset)
imageGenerator.appliesPreferredTrackTransform = true
let time = CMTimeMake(value: 1, timescale: 2)
do {
let imageRef = try imageGenerator.copyCGImage(at: time, actualTime: nil)
let thumbnail = UIImage(cgImage: imageRef)
thumbnail.scaleAndCropImage(toExampleSize: CGSize(width: Const.galleryGridSize, height: Const.galleryGridSize)) { image in
if let image {
let data = image.jpegData(compressionQuality: 1.0) ?? Data()
result.append(.init(id: asset.localIdentifier, type: .video, thumbnail: data))
}
}
} catch {
print("Failed to create thumbnail image")
}
}
}
}
}
return result
}
}
private extension FileProcessing {
func scaleAndCropImage(_ img: UIImage, _ size: CGSize) -> UIImage? {
let aspect = img.size.width / img.size.height
let targetAspect = size.width / size.height
@ -64,3 +128,19 @@ func scaleAndCropImage(_ img: UIImage, _ size: CGSize) -> UIImage? {
return newImage
}
func syncEnumrate(_ ids: [String]? = nil) -> [PHAsset] {
var result: [PHAsset] = []
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
if let ids {
fetchOptions.predicate = NSPredicate(format: "localIdentifier IN %@", ids)
}
let assets = PHAsset.fetchAssets(with: fetchOptions)
assets.enumerateObjects { asset, _, _ in
result.append(asset)
}
return result
}
}

View file

@ -8,6 +8,7 @@ final class FileMiddleware {
func middleware(state _: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
switch action {
// MARK: - For incomig attachments
case .conversationAction(.messagesUpdated(let messages)):
return Future { [weak self] promise in
guard let wSelf = self else {
@ -62,6 +63,21 @@ final class FileMiddleware {
}
.eraseToAnyPublisher()
// MARK: - For outgoing sharing
case .fileAction(.fetchItemsFromGallery):
return Future<AppAction, Never> { promise in
let items = FileProcessing.shared.fetchGallery()
promise(.success(.fileAction(.itemsFromGalleryFetched(items: items))))
}
.eraseToAnyPublisher()
case .fileAction(.itemsFromGalleryFetched(let items)):
return Future { promise in
let newItems = FileProcessing.shared.fillGalleryItemsThumbnails(items: items)
promise(.success(.sharingAction(.galleryItemsUpdated(items: newItems))))
}
.eraseToAnyPublisher()
case .fileAction(.copyFileForUploading(let messageId, let data, let thumbnail)):
print("=====")
print("copyFileForUploading")

View file

@ -53,69 +53,10 @@ final class SharingMiddleware {
}
.eraseToAnyPublisher()
case .sharingAction(.fetchGallery):
return Future<AppAction, Never> { promise in
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let assets = PHAsset.fetchAssets(with: fetchOptions)
var items: [SharingGalleryItem] = []
assets.enumerateObjects { asset, _, _ in
if asset.mediaType == .image {
items.append(.init(id: asset.localIdentifier, type: .photo))
} else if asset.mediaType == .video {
items.append(.init(id: asset.localIdentifier, type: .video))
}
}
promise(.success(.sharingAction(.galleryFetched(items))))
}
case .fileAction(.itemsFromGalleryFetched(let items)):
return Just(.sharingAction(.galleryItemsUpdated(items: items)))
.eraseToAnyPublisher()
case .sharingAction(.galleryFetched(let items)):
DispatchQueue.global().async {
let ids = items
.filter { $0.thumbnail == nil }
.map { $0.id }
let assets = PHAsset.fetchAssets(withLocalIdentifiers: ids, options: nil)
assets.enumerateObjects { asset, _, _ in
if asset.mediaType == .image {
PHImageManager.default().requestImage(
for: asset,
targetSize: PHImageManagerMaximumSize,
contentMode: .aspectFill,
options: nil
) { image, _ in
image?.scaleAndCropImage(toExampleSize: CGSize(width: Const.galleryGridSize, height: Const.galleryGridSize)) { image in
if let image {
let data = image.jpegData(compressionQuality: 1.0) ?? Data()
store.dispatch(.sharingAction(.thumbnailUpdated(data: data, id: asset.localIdentifier)))
}
}
}
} else if asset.mediaType == .video {
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in
if let avAsset {
let imageGenerator = AVAssetImageGenerator(asset: avAsset)
imageGenerator.appliesPreferredTrackTransform = true
let time = CMTimeMake(value: 1, timescale: 2)
do {
let imageRef = try imageGenerator.copyCGImage(at: time, actualTime: nil)
let thumbnail = UIImage(cgImage: imageRef)
thumbnail.scaleAndCropImage(toExampleSize: CGSize(width: Const.galleryGridSize, height: Const.galleryGridSize)) { image in
if let image {
let data = image.jpegData(compressionQuality: 1.0) ?? Data()
store.dispatch(.sharingAction(.thumbnailUpdated(data: data, id: asset.localIdentifier)))
}
}
} catch {
print("Failed to create thumbnail image")
}
}
}
}
}
}
return Empty().eraseToAnyPublisher()
// MARK: - Sharing
case .sharingAction(.shareMedia(let ids)):
return Future<AppAction, Never> { promise in

View file

@ -20,15 +20,9 @@ extension SharingState {
state.cameraCapturedMedia = Data()
state.cameraCapturedMediaType = .photo
case .galleryFetched(let items):
case .galleryItemsUpdated(let items):
state.galleryItems = items
case .thumbnailUpdated(let thumbnailData, let id):
guard let index = state.galleryItems.firstIndex(where: { $0.id == id }) else {
return
}
state.galleryItems[index].thumbnail = thumbnailData
default:
break
}

View file

@ -135,7 +135,7 @@ struct SharingMediaPickerView: View {
}
.onChange(of: store.state.sharingState.isGalleryAccessGranted) { granted in
if granted {
store.dispatch(.sharingAction(.fetchGallery))
store.dispatch(.fileAction(.fetchItemsFromGallery))
}
}
}