This commit is contained in:
fmodf 2024-07-10 19:49:36 +02:00
parent 485071162c
commit e564ae5747
10 changed files with 502 additions and 275 deletions

View file

@ -4,20 +4,18 @@ enum SharingAction: Stateable {
case showSharing(Bool) case showSharing(Bool)
case shareLocation(lat: Double, lon: Double) case shareLocation(lat: Double, lon: Double)
case shareContact(jid: String) case shareContact(jid: String)
case shareDocuments([Data]) case shareDocuments([Data])
// case sendAttachment([ShareItem]) case checkCameraAccess
// case sendAttachmentDone case setCameraAccess(Bool)
// case sendAttachmentError(reason: String)
}
// struct ShareItem: Stateable { case checkGalleryAccess
// let id: String case setGalleryAccess(Bool)
// let type: AttachmentType case fetchGallery
// let data: Data case galleryFetched([SharingGalleryItem])
// let thumbnail: Data case thumbnailUpdated(Data, String)
// let string: String
// } case cameraCaptured(media: Data, type: SharingCameraMediaType)
case flushCameraCaptured
}

View file

@ -44,6 +44,22 @@ extension Database {
table.column("type", .integer).notNull() table.column("type", .integer).notNull()
} }
// attachments
try db.create(table: "attachments", options: [.ifNotExists]) { table in
table.column("id", .text).notNull().primaryKey().unique(onConflict: .replace)
}
// attachment items
try db.create(table: "attachment_items", options: [.ifNotExists]) { table in
table.column("id", .text).notNull().primaryKey().unique(onConflict: .replace)
table.belongsTo("attachments", onDelete: .cascade).notNull()
table.column("type", .integer).notNull()
table.column("localPath", .text)
table.column("remotePath", .text)
table.column("localThumbnailPath", .text)
table.column("string", .text)
}
// messages // messages
try db.create(table: "messages", options: [.ifNotExists]) { table in try db.create(table: "messages", options: [.ifNotExists]) { table in
table.column("id", .text).notNull().primaryKey().unique(onConflict: .replace) table.column("id", .text).notNull().primaryKey().unique(onConflict: .replace)
@ -60,22 +76,6 @@ extension Database {
table.column("sentError", .boolean).notNull() table.column("sentError", .boolean).notNull()
table.column("attachment", .text).references("attachments", onDelete: .cascade) table.column("attachment", .text).references("attachments", onDelete: .cascade)
} }
// attachments
try db.create(table: "attachments", options: [.ifNotExists]) { table in
table.column("id", .text).notNull().primaryKey().unique(onConflict: .replace)
}
// attachment items
try db.create(table: "attachment_items", options: [.ifNotExists]) { table in
table.column("id", .text).notNull().primaryKey().unique(onConflict: .replace)
table.belongsTo("attachments", onDelete: .cascade).notNull()
table.column("type", .integer).notNull()
table.column("localPath", .text)
table.column("remotePath", .text)
table.column("localThumbnailPath", .text)
table.column("string", .text)
}
} }
// return migrator // return migrator

View file

@ -1,15 +1,354 @@
import AVFoundation
import Combine import Combine
import Foundation import Foundation
import Martin import Photos
import UIKit
final class SharingMiddleware { final class SharingMiddleware {
static let shared = SharingMiddleware() static let shared = SharingMiddleware()
private let gallery = GalleryService()
// swiftlint:disable:next function_body_length
func middleware(state _: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> { func middleware(state _: AppState, action: AppAction) -> AnyPublisher<AppAction, Never> {
switch action { switch action {
case .sharingAction(.checkCameraAccess):
return Future<AppAction, Never> { promise in
let status = AVCaptureDevice.authorizationStatus(for: .video)
switch status {
case .authorized:
promise(.success(.sharingAction(.setCameraAccess(true))))
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { granted in
promise(.success(.sharingAction(.setCameraAccess(granted))))
// DispatchQueue.main.async {
// self.isCameraAccessGranted = granted
// }
}
case .denied, .restricted:
promise(.success(.sharingAction(.setCameraAccess(false))))
@unknown default:
promise(.success(.sharingAction(.setCameraAccess(false))))
}
}
.eraseToAnyPublisher()
case .sharingAction(.checkGalleryAccess):
return Future<AppAction, Never> { promise in
let status = PHPhotoLibrary.authorizationStatus()
switch status {
case .authorized, .limited:
promise(.success(.sharingAction(.setGalleryAccess(true))))
case .notDetermined:
PHPhotoLibrary.requestAuthorization { status in
promise(.success(.sharingAction(.setGalleryAccess(status == .authorized))))
// DispatchQueue.main.async {
// self.isGalleryAccessGranted = status == .authorized
// if self.isGalleryAccessGranted {
// self.fetchGallery()
// }
// }
}
case .denied, .restricted:
promise(.success(.sharingAction(.setGalleryAccess(false))))
@unknown default:
promise(.success(.sharingAction(.setGalleryAccess(false))))
}
}
.eraseToAnyPublisher()
case .sharingAction(.fetchGallery):
return Future<AppAction, Never> { promise in
DispatchQueue.global(qos: .background).async {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let assets = PHAsset.fetchAssets(with: fetchOptions)
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
option.isSynchronous = true
var items: [SharingGalleryItem] = []
let group = DispatchGroup()
assets.enumerateObjects { asset, _, _ in
if asset.mediaType == .image {
group.enter()
manager.requestImage(
for: asset,
targetSize: PHImageManagerMaximumSize,
contentMode: .aspectFill,
options: option
) { image, _ in
if image != nil {
items.append(.init(id: asset.localIdentifier, type: .photo))
group.leave()
}
}
} else if asset.mediaType == .video {
group.enter()
manager.requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in
if avAsset != nil {
items.append(.init(id: asset.localIdentifier, type: .video, duration: asset.duration.minAndSec))
group.leave()
}
}
}
}
group.notify(queue: .main) {
promise(.success(.sharingAction(.galleryFetched(items))))
}
}
}
.eraseToAnyPublisher()
case .sharingAction(.galleryFetched(let items)):
return Future<AppAction, Never> { promise in
DispatchQueue.global(qos: .background).async {
let ids = items
.filter { $0.thumbnail == nil }
.map { $0.id }
let assets = PHAsset.fetchAssets(withLocalIdentifiers: ids, options: nil)
assets.enumerateObjects { asset, _, _ in
let manager = PHImageManager.default()
if asset.mediaType == .image {
manager.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()
DispatchQueue.main.async {
store.dispatch(.sharingAction(.thumbnailUpdated(data, asset.localIdentifier)))
}
}
}
}
} else if asset.mediaType == .video {
manager.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), completion: { image in
if let image {
let data = image.jpegData(compressionQuality: 1.0) ?? Data()
DispatchQueue.main.async {
store.dispatch(.sharingAction(.thumbnailUpdated(data, asset.localIdentifier)))
}
}
})
} catch {
print("Failed to create thumbnail image")
}
}
}
}
}
}
promise(.success(.empty))
}
.eraseToAnyPublisher()
default: default:
return Empty().eraseToAnyPublisher() return Empty().eraseToAnyPublisher()
} }
} }
} }
// private func fetchGallery() {
// let fetchOptions = PHFetchOptions()
// fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
// let assets = PHAsset.fetchAssets(with: fetchOptions)
//
// let manager = PHImageManager.default()
// let option = PHImageRequestOptions()
// option.isSynchronous = true
//
// assets.enumerateObjects { asset, _, _ in
// if asset.mediaType == .image {
// manager.requestImage(
// for: asset,
// targetSize: PHImageManagerMaximumSize,
// contentMode: .aspectFill,
// options: option
// ) { image, _ in
// image?.scaleAndCropImage(toExampleSize: CGSize(width: gridSize, height: gridSize), completion: { image in
// if let image {
// DispatchQueue.main.async {
// self.thumbnails.append(ThumbnailView(id: asset.localIdentifier, image: image, gridSize: gridSize, selected: $selectedMedia))
// }
// }
// })
// }
// } else if asset.mediaType == .video {
// manager.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: gridSize, height: gridSize), completion: { image in
// if let image {
// DispatchQueue.main.async {
// self.thumbnails.append(ThumbnailView(id: asset.localIdentifier, image: image, gridSize: gridSize, selected: $selectedMedia, duration: asset.duration.minAndSec))
// }
// }
// })
// } catch {
// print("Failed to create thumbnail image")
// }
// }
// }
// }
// }
// }
// private func sendGalleryMedia(ids _: [String]) {
// var media: [AttachmentItem] = []
// let dispatchGroup = DispatchGroup()
//
// let asset = PHAsset.fetchAssets(withLocalIdentifiers: ids, options: nil)
// asset.enumerateObjects { asset, _, _ in
// dispatchGroup.enter()
// if asset.mediaType == .image {
// let manager = PHImageManager.default()
// let option = PHImageRequestOptions()
// option.isSynchronous = true
//
// manager.requestImage(
// for: asset,
// targetSize: PHImageManagerMaximumSize,
// contentMode: .aspectFill,
// options: option
// ) { image, _ in
// if let image {
// let data = image.jpegData(compressionQuality: 1.0) ?? Data()
// media.append(.init(type: .image, data: data, string: ""))
// }
// dispatchGroup.leave()
// }
// } else if asset.mediaType == .video {
// let manager = PHImageManager.default()
// let option = PHVideoRequestOptions()
// option.version = .current
// option.deliveryMode = .highQualityFormat
//
// manager.requestAVAsset(forVideo: asset, options: option) { avAsset, _, _ in
// if let avAsset {
// let url = (avAsset as? AVURLAsset)?.url
// let data = try? Data(contentsOf: url ?? URL(fileURLWithPath: ""))
// media.append(.init(type: .movie, data: data ?? Data(), string: ""))
// }
// dispatchGroup.leave()
// }
// }
// }
// dispatchGroup.notify(queue: .main) {
// store.dispatch(.conversationAction(.sendAttachment(.init(
// id: UUID().uuidString,
// items: media
// ))))
// }
// }
// }
//
// private struct ThumbnailView: Identifiable, View {
// let id: String
// let gridSize: CGFloat
// let duration: String?
//
// @State private var image: UIImage
// @State private var ready = false
// @State private var selected = false
// @Binding var selectedMedia: [SelectedMedia]
//
// init(id: String, image: UIImage, gridSize: CGFloat, selected: Binding<[SelectedMedia]>, duration: String? = nil) {
// self.id = id
// self.image = image
// self.gridSize = gridSize
// _selectedMedia = selected
// self.duration = duration
// }
//
// var body: some View {
// if ready {
// ZStack {
// Image(uiImage: image)
// .resizable()
// .aspectRatio(contentMode: .fill)
// .frame(width: gridSize, height: gridSize)
// .clipped()
// if let duration {
// VStack {
// Spacer()
// HStack {
// Spacer()
// Text(duration)
// .foregroundColor(.Material.Text.white)
// .font(.sub1)
// .shadow(color: .black, radius: 2)
// .padding(4)
// }
// }
// }
// if selected {
// VStack {
// HStack {
// Spacer()
// Circle()
// .frame(width: 30, height: 30)
// .shadow(color: .black, radius: 2)
// .foregroundColor(.Material.Shape.white)
// .overlay {
// Image(systemName: "checkmark")
// .foregroundColor(.Material.Elements.active)
// .font(.body3)
// }
// .padding(4)
// }
// Spacer()
// }
// }
// }
// .onTapGesture {
// withAnimation {
// selected.toggle()
// if selected {
// selectedMedia.append(SelectedMedia(id: id))
// } else {
// selectedMedia.removeAll { $0.id == id }
// }
// }
// }
// } else {
// ZStack {
// Rectangle()
// .fill(Color.Material.Background.light)
// .overlay {
// ProgressView()
// }
// .frame(width: gridSize, height: gridSize)
// }
// .onAppear {
// image.scaleAndCropImage(toExampleSize: CGSize(width: gridSize, height: gridSize), completion: { image in
// if let image {
// self.image = image
// ready = true
// }
// })
// }
// }
// }
// }

View file

@ -1,9 +1,34 @@
import Foundation
extension SharingState { extension SharingState {
static func reducer(state: inout SharingState, action: SharingAction) { static func reducer(state: inout SharingState, action: SharingAction) {
switch action { switch action {
case .showSharing(let shown): case .showSharing(let shown):
state.sharingShown = shown state.sharingShown = shown
case .setCameraAccess(let granted):
state.isCameraAccessGranted = granted
case .setGalleryAccess(let granted):
state.isGalleryAccessGranted = granted
case .cameraCaptured(let media, let type):
state.cameraCapturedMedia = media
state.cameraCapturedMediaType = type
case .flushCameraCaptured:
state.cameraCapturedMedia = Data()
state.cameraCapturedMediaType = .photo
case .galleryFetched(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: default:
break break
} }

View file

@ -0,0 +1,5 @@
import Foundation
final class CameraService {
var dumb = false
}

View file

@ -4,7 +4,6 @@ struct ConversationState: Stateable {
var currentMessages: [Message] var currentMessages: [Message]
var replyText: String var replyText: String
var attachmentPickerVisible: Bool
} }
// MARK: Init // MARK: Init
@ -12,6 +11,5 @@ extension ConversationState {
init() { init() {
currentMessages = [] currentMessages = []
replyText = "" replyText = ""
attachmentPickerVisible = false
} }
} }

View file

@ -1,10 +1,38 @@
import Foundation
enum SharingCameraMediaType: Stateable {
case video
case photo
}
struct SharingGalleryItem: Stateable, Identifiable {
var id: String
var type: SharingCameraMediaType
var thumbnail: Data?
var duration: String?
}
struct SharingState: Stateable { struct SharingState: Stateable {
var sharingShown: Bool var sharingShown: Bool
var isCameraAccessGranted: Bool
var isGalleryAccessGranted: Bool
var cameraCapturedMedia: Data
var cameraCapturedMediaType: SharingCameraMediaType
var galleryItems: [SharingGalleryItem]
} }
// MARK: Init // MARK: Init
extension SharingState { extension SharingState {
init() { init() {
sharingShown = false sharingShown = false
isCameraAccessGranted = false
isGalleryAccessGranted = false
cameraCapturedMedia = Data()
cameraCapturedMediaType = .photo
galleryItems = []
} }
} }

View file

@ -1,4 +1,5 @@
import Foundation import Foundation
import UIKit
enum Const { enum Const {
// // Network // // Network
@ -28,6 +29,12 @@ enum Const {
case conversations = "conversations.im" case conversations = "conversations.im"
} }
// Limit for video for sharing
static let videoDurationLimit = 60.0
// Upload/download file folder // Upload/download file folder
static let fileFolder = "ConversationsClassic" static let fileFolder = "ConversationsClassic"
// Grid size for gallery preview (3 in a row)
static let galleryGridSize = UIScreen.main.bounds.width / 3
} }

View file

@ -8,16 +8,11 @@ struct SelectedMedia {
} }
struct AttachmentMediaPickerView: View { struct AttachmentMediaPickerView: View {
@State private var isCameraAccessGranted = AVCaptureDevice.authorizationStatus(for: .video) == .authorized @EnvironmentObject var store: AppStore
@State private var isGalleryAccessGranted = PHPhotoLibrary.authorizationStatus() == .authorized
@State private var thumbnails = [ThumbnailView]()
@State private var selectedMedia = [SelectedMedia]() @State private var selectedMedia = [SelectedMedia]()
@State private var showCameraPicker = false @State private var showCameraPicker = false
let gridSize = UIScreen.main.bounds.width / 3
var body: some View { var body: some View {
let columns = Array(repeating: GridItem(.flexible(), spacing: 0), count: 3) let columns = Array(repeating: GridItem(.flexible(), spacing: 0), count: 3)
@ -26,7 +21,7 @@ struct AttachmentMediaPickerView: View {
ScrollView(showsIndicators: false) { ScrollView(showsIndicators: false) {
LazyVGrid(columns: columns, spacing: 0) { LazyVGrid(columns: columns, spacing: 0) {
// For camera // For camera
if isCameraAccessGranted { if store.state.sharingState.isCameraAccessGranted {
ZStack { ZStack {
CameraView() CameraView()
.aspectRatio(1, contentMode: .fit) .aspectRatio(1, contentMode: .fit)
@ -67,9 +62,9 @@ struct AttachmentMediaPickerView: View {
} }
// For gallery // For gallery
if isGalleryAccessGranted { if store.state.sharingState.isGalleryAccessGranted {
ForEach(thumbnails) { photo in ForEach(store.state.sharingState.galleryItems) { item in
photo GridViewItem(item: item)
} }
} else { } else {
Button { Button {
@ -95,19 +90,12 @@ struct AttachmentMediaPickerView: View {
} }
} }
.fullScreenCover(isPresented: $showCameraPicker) { .fullScreenCover(isPresented: $showCameraPicker) {
// TODO: fix it CameraPicker(sourceType: .camera) { data, type in
// CameraPicker(sourceType: .camera) { data, type in store.dispatch(.sharingAction(.cameraCaptured(media: data, type: type)))
// store.dispatch(.conversationAction(.sendAttachment([.init( showCameraPicker = false
// id: UUID().uuidString, store.dispatch(.sharingAction(.showSharing(false)))
// type: type, }
// data: data, .edgesIgnoringSafeArea(.all)
// thumbnail: Data(),
// string: ""
// )])))
// showCameraPicker = false
// store.dispatch(.conversationAction(.showAttachmentPicker(false)))
// }
// .edgesIgnoringSafeArea(.all)
} }
// Send panel // Send panel
@ -130,190 +118,38 @@ struct AttachmentMediaPickerView: View {
.clipped() .clipped()
.onTapGesture { .onTapGesture {
let ids = selectedMedia.map { $0.id } let ids = selectedMedia.map { $0.id }
sendGalleryMedia(ids: ids) // sendGalleryMedia(ids: ids)
store.dispatch(.sharingAction(.showSharing(false))) store.dispatch(.sharingAction(.showSharing(false)))
} }
} }
.onAppear { .onAppear {
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.2) { store.dispatch(.sharingAction(.checkCameraAccess))
checkCameraAccess() store.dispatch(.sharingAction(.checkGalleryAccess))
checkGalleryAccess() // DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.2) {
} // checkCameraAccess()
} // checkGalleryAccess()
}
private func checkCameraAccess() {
let status = AVCaptureDevice.authorizationStatus(for: .video)
switch status {
case .authorized:
isCameraAccessGranted = true
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { granted in
DispatchQueue.main.async {
self.isCameraAccessGranted = granted
}
}
case .denied, .restricted:
isCameraAccessGranted = false
@unknown default:
isCameraAccessGranted = false
}
}
private func checkGalleryAccess() {
let status = PHPhotoLibrary.authorizationStatus()
switch status {
case .authorized, .limited:
isGalleryAccessGranted = true
fetchGallery()
case .notDetermined:
PHPhotoLibrary.requestAuthorization { status in
DispatchQueue.main.async {
self.isGalleryAccessGranted = status == .authorized
if self.isGalleryAccessGranted {
self.fetchGallery()
}
}
}
case .denied, .restricted:
isGalleryAccessGranted = false
@unknown default:
isGalleryAccessGranted = false
}
}
private func fetchGallery() {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let assets = PHAsset.fetchAssets(with: fetchOptions)
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
option.isSynchronous = true
assets.enumerateObjects { asset, _, _ in
if asset.mediaType == .image {
manager.requestImage(
for: asset,
targetSize: PHImageManagerMaximumSize,
contentMode: .aspectFill,
options: option
) { image, _ in
image?.scaleAndCropImage(toExampleSize: CGSize(width: gridSize, height: gridSize), completion: { image in
if let image {
DispatchQueue.main.async {
self.thumbnails.append(ThumbnailView(id: asset.localIdentifier, image: image, gridSize: gridSize, selected: $selectedMedia))
}
}
})
}
} else if asset.mediaType == .video {
manager.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: gridSize, height: gridSize), completion: { image in
if let image {
DispatchQueue.main.async {
self.thumbnails.append(ThumbnailView(id: asset.localIdentifier, image: image, gridSize: gridSize, selected: $selectedMedia, duration: asset.duration.minAndSec))
}
}
})
} catch {
print("Failed to create thumbnail image")
}
}
}
}
}
}
private func sendGalleryMedia(ids _: [String]) {
// var media: [AttachmentItem] = []
// let dispatchGroup = DispatchGroup()
//
// let asset = PHAsset.fetchAssets(withLocalIdentifiers: ids, options: nil)
// asset.enumerateObjects { asset, _, _ in
// dispatchGroup.enter()
// if asset.mediaType == .image {
// let manager = PHImageManager.default()
// let option = PHImageRequestOptions()
// option.isSynchronous = true
//
// manager.requestImage(
// for: asset,
// targetSize: PHImageManagerMaximumSize,
// contentMode: .aspectFill,
// options: option
// ) { image, _ in
// if let image {
// let data = image.jpegData(compressionQuality: 1.0) ?? Data()
// media.append(.init(type: .image, data: data, string: ""))
// }
// dispatchGroup.leave()
// }
// } else if asset.mediaType == .video {
// let manager = PHImageManager.default()
// let option = PHVideoRequestOptions()
// option.version = .current
// option.deliveryMode = .highQualityFormat
//
// manager.requestAVAsset(forVideo: asset, options: option) { avAsset, _, _ in
// if let avAsset {
// let url = (avAsset as? AVURLAsset)?.url
// let data = try? Data(contentsOf: url ?? URL(fileURLWithPath: ""))
// media.append(.init(type: .movie, data: data ?? Data(), string: ""))
// }
// dispatchGroup.leave()
// }
// }
// }
// dispatchGroup.notify(queue: .main) {
// store.dispatch(.conversationAction(.sendAttachment(.init(
// id: UUID().uuidString,
// items: media
// ))))
// } // }
} }
.onChange(of: store.state.sharingState.isGalleryAccessGranted) { granted in
if granted {
store.dispatch(.sharingAction(.fetchGallery))
}
}
}
} }
private struct ThumbnailView: Identifiable, View { private struct GridViewItem: View {
let id: String let item: SharingGalleryItem
let gridSize: CGFloat
let duration: String?
@State private var image: UIImage
@State private var ready = false
@State private var selected = false
@Binding var selectedMedia: [SelectedMedia]
init(id: String, image: UIImage, gridSize: CGFloat, selected: Binding<[SelectedMedia]>, duration: String? = nil) {
self.id = id
self.image = image
self.gridSize = gridSize
_selectedMedia = selected
self.duration = duration
}
var body: some View { var body: some View {
if ready { if let data = item.thumbnail {
ZStack { ZStack {
Image(uiImage: image) Image(uiImage: UIImage(data: data) ?? UIImage())
.resizable() .resizable()
.aspectRatio(contentMode: .fill) .aspectRatio(contentMode: .fill)
.frame(width: gridSize, height: gridSize) .frame(width: Const.galleryGridSize, height: Const.galleryGridSize)
.clipped() .clipped()
if let duration { if let duration = item.duration {
VStack { VStack {
Spacer() Spacer()
HStack { HStack {
@ -326,34 +162,34 @@ private struct ThumbnailView: Identifiable, View {
} }
} }
} }
if selected { // if selected {
VStack { // VStack {
HStack { // HStack {
Spacer() // Spacer()
Circle() // Circle()
.frame(width: 30, height: 30) // .frame(width: 30, height: 30)
.shadow(color: .black, radius: 2) // .shadow(color: .black, radius: 2)
.foregroundColor(.Material.Shape.white) // .foregroundColor(.Material.Shape.white)
.overlay { // .overlay {
Image(systemName: "checkmark") // Image(systemName: "checkmark")
.foregroundColor(.Material.Elements.active) // .foregroundColor(.Material.Elements.active)
.font(.body3) // .font(.body3)
} // }
.padding(4) // .padding(4)
} // }
Spacer() // Spacer()
} // }
} // }
} }
.onTapGesture { .onTapGesture {
withAnimation { // withAnimation {
selected.toggle() // selected.toggle()
if selected { // if selected {
selectedMedia.append(SelectedMedia(id: id)) // selectedMedia.append(SelectedMedia(id: id))
} else { // } else {
selectedMedia.removeAll { $0.id == id } // selectedMedia.removeAll { $0.id == id }
} // }
} // }
} }
} else { } else {
ZStack { ZStack {
@ -362,15 +198,7 @@ private struct ThumbnailView: Identifiable, View {
.overlay { .overlay {
ProgressView() ProgressView()
} }
.frame(width: gridSize, height: gridSize) .frame(width: Const.galleryGridSize, height: Const.galleryGridSize)
}
.onAppear {
image.scaleAndCropImage(toExampleSize: CGSize(width: gridSize, height: gridSize), completion: { image in
if let image {
self.image = image
ready = true
}
})
} }
} }
} }
@ -411,7 +239,7 @@ struct CameraView: UIViewRepresentable {
struct CameraPicker: UIViewControllerRepresentable { struct CameraPicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType var sourceType: UIImagePickerController.SourceType
var completionHandler: (Data, Data, AttachmentType) -> Void var completionHandler: (Data, SharingCameraMediaType) -> Void
func makeUIViewController(context: Context) -> UIImagePickerController { func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController() let picker = UIImagePickerController()
@ -419,7 +247,7 @@ struct CameraPicker: UIViewControllerRepresentable {
picker.delegate = context.coordinator picker.delegate = context.coordinator
picker.mediaTypes = [UTType.movie.identifier, UTType.image.identifier] picker.mediaTypes = [UTType.movie.identifier, UTType.image.identifier]
picker.videoQuality = .typeHigh picker.videoQuality = .typeHigh
picker.videoMaximumDuration = 60 // 60 sec Limit picker.videoMaximumDuration = Const.videoDurationLimit
picker.view.backgroundColor = .clear picker.view.backgroundColor = .clear
return picker return picker
} }
@ -441,18 +269,17 @@ struct CameraPicker: UIViewControllerRepresentable {
// swiftlint:disable:next force_cast // swiftlint:disable:next force_cast
let mediaType = info[.mediaType] as! String let mediaType = info[.mediaType] as! String
// TODO: fix it if mediaType == UTType.image.identifier {
// if mediaType == UTType.image.identifier { if let image = info[.originalImage] as? UIImage {
// if let image = info[.originalImage] as? UIImage { let data = image.jpegData(compressionQuality: 1.0) ?? Data()
// let data = image.jpegData(compressionQuality: 1.0) ?? Data() parent.completionHandler(data, .photo)
// parent.completionHandler(data, .image) }
// } } else if mediaType == UTType.movie.identifier {
// } else if mediaType == UTType.movie.identifier { if let url = info[.mediaURL] as? URL {
// if let url = info[.mediaURL] as? URL { let data = try? Data(contentsOf: url)
// let data = try? Data(contentsOf: url) parent.completionHandler(data ?? Data(), .video)
// parent.completionHandler(data ?? Data(), .movie) }
// } }
// }
} }
} }
} }

View file

@ -89,7 +89,7 @@ struct ConversationTextInput: View {
} }
} }
.fullScreenCover(isPresented: Binding<Bool>( .fullScreenCover(isPresented: Binding<Bool>(
get: { store.state.conversationsState.attachmentPickerVisible }, get: { store.state.sharingState.sharingShown },
set: { _ in } set: { _ in }
)) { )) {
AttachmentPickerScreen() AttachmentPickerScreen()