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 attachmentThumbnailCreated(messageId: String, thumbnailName: String)
case copyFileForUploading(messageId: String, fileData: Data, thumbnailData: Data?) 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 checkGalleryAccess
case setGalleryAccess(Bool) case setGalleryAccess(Bool)
case fetchGallery case galleryItemsUpdated(items: [SharingGalleryItem])
case galleryFetched([SharingGalleryItem])
case thumbnailUpdated(data: Data, id: String)
case cameraCaptured(media: Data, type: SharingCameraMediaType) case cameraCaptured(media: Data, type: SharingCameraMediaType)
case flushCameraCaptured case flushCameraCaptured

View file

@ -1,4 +1,5 @@
import Foundation import Foundation
import Photos
import UIKit import UIKit
final class FileProcessing { final class FileProcessing {
@ -42,9 +43,72 @@ final class FileProcessing {
return nil 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
}
} }
func scaleAndCropImage(_ img: UIImage, _ size: CGSize) -> UIImage? { private extension FileProcessing {
func scaleAndCropImage(_ img: UIImage, _ size: CGSize) -> UIImage? {
let aspect = img.size.width / img.size.height let aspect = img.size.width / img.size.height
let targetAspect = size.width / size.height let targetAspect = size.width / size.height
var newWidth: CGFloat var newWidth: CGFloat
@ -63,4 +127,20 @@ func scaleAndCropImage(_ img: UIImage, _ size: CGSize) -> UIImage? {
UIGraphicsEndImageContext() UIGraphicsEndImageContext()
return newImage 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> { func middleware(state _: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
switch action { switch action {
// MARK: - For incomig attachments
case .conversationAction(.messagesUpdated(let messages)): case .conversationAction(.messagesUpdated(let messages)):
return Future { [weak self] promise in return Future { [weak self] promise in
guard let wSelf = self else { guard let wSelf = self else {
@ -62,6 +63,21 @@ final class FileMiddleware {
} }
.eraseToAnyPublisher() .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)): case .fileAction(.copyFileForUploading(let messageId, let data, let thumbnail)):
print("=====") print("=====")
print("copyFileForUploading") print("copyFileForUploading")

View file

@ -53,69 +53,10 @@ final class SharingMiddleware {
} }
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .sharingAction(.fetchGallery): case .fileAction(.itemsFromGalleryFetched(let items)):
return Future<AppAction, Never> { promise in return Just(.sharingAction(.galleryItemsUpdated(items: items)))
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))))
}
.eraseToAnyPublisher() .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 // MARK: - Sharing
case .sharingAction(.shareMedia(let ids)): case .sharingAction(.shareMedia(let ids)):
return Future<AppAction, Never> { promise in return Future<AppAction, Never> { promise in

View file

@ -20,15 +20,9 @@ extension SharingState {
state.cameraCapturedMedia = Data() state.cameraCapturedMedia = Data()
state.cameraCapturedMediaType = .photo state.cameraCapturedMediaType = .photo
case .galleryFetched(let items): case .galleryItemsUpdated(let items):
state.galleryItems = 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: default:
break break
} }

View file

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