wip
This commit is contained in:
parent
bb502ba79a
commit
e21d1a1ce9
|
@ -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])
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Photos
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
final class FileProcessing {
|
final class FileProcessing {
|
||||||
|
@ -42,8 +43,71 @@ 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension FileProcessing {
|
||||||
func scaleAndCropImage(_ img: UIImage, _ size: CGSize) -> UIImage? {
|
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
|
||||||
|
@ -64,3 +128,19 @@ func scaleAndCropImage(_ img: UIImage, _ size: CGSize) -> UIImage? {
|
||||||
|
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue